@osdk/integration-testing 0.0.0 → 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/CHANGELOG.md +16 -0
- package/bin/postinstall.mjs +15 -0
- package/build/browser/cli-service/CliServiceLauncher.js +220 -0
- package/build/browser/cli-service/CliServiceLauncher.js.map +1 -0
- package/build/browser/cli-service/FoundryCliService.js +168 -0
- package/build/browser/cli-service/FoundryCliService.js.map +1 -0
- package/build/browser/cli-service/OntologyServer.js +32 -0
- package/build/browser/cli-service/OntologyServer.js.map +1 -0
- package/build/browser/cli-service/ServiceDiscoverer.js +100 -0
- package/build/browser/cli-service/ServiceDiscoverer.js.map +1 -0
- package/build/browser/cli-service/StatusServer.js +91 -0
- package/build/browser/cli-service/StatusServer.js.map +1 -0
- package/build/browser/createIntegrationClient.js +67 -0
- package/build/browser/createIntegrationClient.js.map +1 -0
- package/build/browser/createIntegrationServer.js +115 -0
- package/build/browser/createIntegrationServer.js.map +1 -0
- package/build/browser/createSeedClient.js +77 -0
- package/build/browser/createSeedClient.js.map +1 -0
- package/build/browser/generated/cli/OntologySeedingService/setSeed.js +28 -0
- package/build/browser/generated/cli/OntologySeedingService/setSeed.js.map +1 -0
- package/build/browser/generated/cli/OntologySeedingService.js +19 -0
- package/build/browser/generated/cli/OntologySeedingService.js.map +1 -0
- package/build/browser/generated/cli/StatusService/getStatus.js +25 -0
- package/build/browser/generated/cli/StatusService/getStatus.js.map +1 -0
- package/build/browser/generated/cli/StatusService/publishStatus.js +26 -0
- package/build/browser/generated/cli/StatusService/publishStatus.js.map +1 -0
- package/build/browser/generated/cli/StatusService.js +20 -0
- package/build/browser/generated/cli/StatusService.js.map +1 -0
- package/build/browser/generated/cli/__components.js +2 -0
- package/build/browser/generated/cli/__components.js.map +1 -0
- package/build/browser/generated/cli/index.js +20 -0
- package/build/browser/generated/cli/index.js.map +1 -0
- package/build/browser/index.js +27 -0
- package/build/browser/index.js.map +1 -0
- package/build/browser/scripts/download.js +92 -0
- package/build/browser/scripts/download.js.map +1 -0
- package/build/browser/scripts/foundry-host.js +59 -0
- package/build/browser/scripts/foundry-host.js.map +1 -0
- package/build/browser/scripts/foundry-token.js +50 -0
- package/build/browser/scripts/foundry-token.js.map +1 -0
- package/build/browser/scripts/gitRemote.js +39 -0
- package/build/browser/scripts/gitRemote.js.map +1 -0
- package/build/browser/scripts/postinstall.js +38 -0
- package/build/browser/scripts/postinstall.js.map +1 -0
- package/build/browser/test/NoSpawnStatusServer.js +33 -0
- package/build/browser/test/NoSpawnStatusServer.js.map +1 -0
- package/build/browser/test/StubService.js +61 -0
- package/build/browser/test/StubService.js.map +1 -0
- package/build/browser/test/emptyOntologyMetadata.js +32 -0
- package/build/browser/test/emptyOntologyMetadata.js.map +1 -0
- package/build/browser/test/stubStatusServer.js +74 -0
- package/build/browser/test/stubStatusServer.js.map +1 -0
- package/build/browser/test/writeDiscoveryFile.js +39 -0
- package/build/browser/test/writeDiscoveryFile.js.map +1 -0
- package/build/browser/types.js +2 -0
- package/build/browser/types.js.map +1 -0
- package/build/browser/utils/empty-ontology-block.js +61 -0
- package/build/browser/utils/empty-ontology-block.js.map +1 -0
- package/build/browser/utils/foundry-cli.js +46 -0
- package/build/browser/utils/foundry-cli.js.map +1 -0
- package/build/browser/utils/process.js +26 -0
- package/build/browser/utils/process.js.map +1 -0
- package/build/browser/utils/service.js +33 -0
- package/build/browser/utils/service.js.map +1 -0
- package/build/cjs/index.cjs +841 -0
- package/build/cjs/index.cjs.map +1 -0
- package/build/cjs/index.d.cts +118 -0
- package/build/esm/cli-service/CliServiceLauncher.js +220 -0
- package/build/esm/cli-service/CliServiceLauncher.js.map +1 -0
- package/build/esm/cli-service/FoundryCliService.js +168 -0
- package/build/esm/cli-service/FoundryCliService.js.map +1 -0
- package/build/esm/cli-service/OntologyServer.js +32 -0
- package/build/esm/cli-service/OntologyServer.js.map +1 -0
- package/build/esm/cli-service/ServiceDiscoverer.js +100 -0
- package/build/esm/cli-service/ServiceDiscoverer.js.map +1 -0
- package/build/esm/cli-service/StatusServer.js +91 -0
- package/build/esm/cli-service/StatusServer.js.map +1 -0
- package/build/esm/createIntegrationClient.js +67 -0
- package/build/esm/createIntegrationClient.js.map +1 -0
- package/build/esm/createIntegrationServer.js +115 -0
- package/build/esm/createIntegrationServer.js.map +1 -0
- package/build/esm/createSeedClient.js +77 -0
- package/build/esm/createSeedClient.js.map +1 -0
- package/build/esm/generated/cli/OntologySeedingService/setSeed.js +28 -0
- package/build/esm/generated/cli/OntologySeedingService/setSeed.js.map +1 -0
- package/build/esm/generated/cli/OntologySeedingService.js +19 -0
- package/build/esm/generated/cli/OntologySeedingService.js.map +1 -0
- package/build/esm/generated/cli/StatusService/getStatus.js +25 -0
- package/build/esm/generated/cli/StatusService/getStatus.js.map +1 -0
- package/build/esm/generated/cli/StatusService/publishStatus.js +26 -0
- package/build/esm/generated/cli/StatusService/publishStatus.js.map +1 -0
- package/build/esm/generated/cli/StatusService.js +20 -0
- package/build/esm/generated/cli/StatusService.js.map +1 -0
- package/build/esm/generated/cli/__components.js +2 -0
- package/build/esm/generated/cli/__components.js.map +1 -0
- package/build/esm/generated/cli/index.js +20 -0
- package/build/esm/generated/cli/index.js.map +1 -0
- package/build/esm/index.js +27 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/scripts/download.js +92 -0
- package/build/esm/scripts/download.js.map +1 -0
- package/build/esm/scripts/foundry-host.js +59 -0
- package/build/esm/scripts/foundry-host.js.map +1 -0
- package/build/esm/scripts/foundry-token.js +50 -0
- package/build/esm/scripts/foundry-token.js.map +1 -0
- package/build/esm/scripts/gitRemote.js +39 -0
- package/build/esm/scripts/gitRemote.js.map +1 -0
- package/build/esm/scripts/postinstall.js +38 -0
- package/build/esm/scripts/postinstall.js.map +1 -0
- package/build/esm/test/NoSpawnStatusServer.js +33 -0
- package/build/esm/test/NoSpawnStatusServer.js.map +1 -0
- package/build/esm/test/StubService.js +61 -0
- package/build/esm/test/StubService.js.map +1 -0
- package/build/esm/test/emptyOntologyMetadata.js +32 -0
- package/build/esm/test/emptyOntologyMetadata.js.map +1 -0
- package/build/esm/test/stubStatusServer.js +74 -0
- package/build/esm/test/stubStatusServer.js.map +1 -0
- package/build/esm/test/writeDiscoveryFile.js +39 -0
- package/build/esm/test/writeDiscoveryFile.js.map +1 -0
- package/build/esm/types.js +2 -0
- package/build/esm/types.js.map +1 -0
- package/build/esm/utils/empty-ontology-block.js +61 -0
- package/build/esm/utils/empty-ontology-block.js.map +1 -0
- package/build/esm/utils/foundry-cli.js +46 -0
- package/build/esm/utils/foundry-cli.js.map +1 -0
- package/build/esm/utils/process.js +26 -0
- package/build/esm/utils/process.js.map +1 -0
- package/build/esm/utils/service.js +33 -0
- package/build/esm/utils/service.js.map +1 -0
- package/build/types/cli-service/CliServiceLauncher.d.ts +36 -0
- package/build/types/cli-service/CliServiceLauncher.d.ts.map +1 -0
- package/build/types/cli-service/FoundryCliService.d.ts +60 -0
- package/build/types/cli-service/FoundryCliService.d.ts.map +1 -0
- package/build/types/cli-service/OntologyServer.d.ts +10 -0
- package/build/types/cli-service/OntologyServer.d.ts.map +1 -0
- package/build/types/cli-service/ServiceDiscoverer.d.ts +22 -0
- package/build/types/cli-service/ServiceDiscoverer.d.ts.map +1 -0
- package/build/types/cli-service/StatusServer.d.ts +17 -0
- package/build/types/cli-service/StatusServer.d.ts.map +1 -0
- package/build/types/createIntegrationClient.d.ts +2 -0
- package/build/types/createIntegrationClient.d.ts.map +1 -0
- package/build/types/createIntegrationServer.d.ts +2 -0
- package/build/types/createIntegrationServer.d.ts.map +1 -0
- package/build/types/createSeedClient.d.ts +9 -0
- package/build/types/createSeedClient.d.ts.map +1 -0
- package/build/types/generated/cli/OntologySeedingService/setSeed.d.ts +9 -0
- package/build/types/generated/cli/OntologySeedingService/setSeed.d.ts.map +1 -0
- package/build/types/generated/cli/OntologySeedingService.d.ts +1 -0
- package/build/types/generated/cli/OntologySeedingService.d.ts.map +1 -0
- package/build/types/generated/cli/StatusService/getStatus.d.ts +6 -0
- package/build/types/generated/cli/StatusService/getStatus.d.ts.map +1 -0
- package/build/types/generated/cli/StatusService/publishStatus.d.ts +7 -0
- package/build/types/generated/cli/StatusService/publishStatus.d.ts.map +1 -0
- package/build/types/generated/cli/StatusService.d.ts +2 -0
- package/build/types/generated/cli/StatusService.d.ts.map +1 -0
- package/build/types/generated/cli/__components.d.ts +189 -0
- package/build/types/generated/cli/__components.d.ts.map +1 -0
- package/build/types/generated/cli/index.d.ts +3 -0
- package/build/types/generated/cli/index.d.ts.map +1 -0
- package/build/types/index.d.ts +10 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/scripts/download.d.ts +6 -0
- package/build/types/scripts/download.d.ts.map +1 -0
- package/build/types/scripts/foundry-host.d.ts +7 -0
- package/build/types/scripts/foundry-host.d.ts.map +1 -0
- package/build/types/scripts/foundry-token.d.ts +5 -0
- package/build/types/scripts/foundry-token.d.ts.map +1 -0
- package/build/types/scripts/gitRemote.d.ts +9 -0
- package/build/types/scripts/gitRemote.d.ts.map +1 -0
- package/build/types/scripts/postinstall.d.ts +1 -0
- package/build/types/scripts/postinstall.d.ts.map +1 -0
- package/build/types/test/NoSpawnStatusServer.d.ts +11 -0
- package/build/types/test/NoSpawnStatusServer.d.ts.map +1 -0
- package/build/types/test/StubService.d.ts +23 -0
- package/build/types/test/StubService.d.ts.map +1 -0
- package/build/types/test/emptyOntologyMetadata.d.ts +3 -0
- package/build/types/test/emptyOntologyMetadata.d.ts.map +1 -0
- package/build/types/test/stubStatusServer.d.ts +14 -0
- package/build/types/test/stubStatusServer.d.ts.map +1 -0
- package/build/types/test/writeDiscoveryFile.d.ts +8 -0
- package/build/types/test/writeDiscoveryFile.d.ts.map +1 -0
- package/build/types/types.d.ts +104 -0
- package/build/types/types.d.ts.map +1 -0
- package/build/types/utils/empty-ontology-block.d.ts +4 -0
- package/build/types/utils/empty-ontology-block.d.ts.map +1 -0
- package/build/types/utils/foundry-cli.d.ts +13 -0
- package/build/types/utils/foundry-cli.d.ts.map +1 -0
- package/build/types/utils/process.d.ts +1 -0
- package/build/types/utils/process.d.ts.map +1 -0
- package/build/types/utils/service.d.ts +4 -0
- package/build/types/utils/service.d.ts.map +1 -0
- package/package.json +93 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { once } from "node:events";
|
|
19
|
+
import invariant from "tiny-invariant";
|
|
20
|
+
const MAX_CAPTURED_STDERR_CHUNKS = 32;
|
|
21
|
+
export const DEFAULT_READY_TIMEOUT_MS = 30_000;
|
|
22
|
+
export const DEFAULT_STOP_GRACE_MS = 5_000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The shared collaborators a service needs to answer questions about itself.
|
|
26
|
+
* Injected by the launcher via {@link FoundryCliService.attach} so services do
|
|
27
|
+
* not have to construct — or know how to reach — either one.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const TERMINAL_STATES = new Set(["FAILED", "STOPPED"]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One Foundry CLI service subprocess.
|
|
34
|
+
*
|
|
35
|
+
* Subclasses supply the `foundry` subcommand to run and, where the default is
|
|
36
|
+
* wrong, how to tell that the service is up.
|
|
37
|
+
*
|
|
38
|
+
* Dependencies are declared but not enforced here: a service names what it needs
|
|
39
|
+
* and {@link CliServiceLauncher} is what orders and awaits them.
|
|
40
|
+
*/
|
|
41
|
+
export class FoundryCliService {
|
|
42
|
+
#foundryCliPath;
|
|
43
|
+
#projectPath;
|
|
44
|
+
#child;
|
|
45
|
+
#context;
|
|
46
|
+
#stderr = [];
|
|
47
|
+
#exit;
|
|
48
|
+
constructor(name, config) {
|
|
49
|
+
this.name = name;
|
|
50
|
+
this.dependencies = config.dependencies ?? [];
|
|
51
|
+
this.#foundryCliPath = config.foundryCliPath ?? "foundry";
|
|
52
|
+
this.#projectPath = config.projectPath;
|
|
53
|
+
}
|
|
54
|
+
getProjectPath() {
|
|
55
|
+
return this.#projectPath;
|
|
56
|
+
}
|
|
57
|
+
getCaCertPath() {
|
|
58
|
+
return this.#context?.discoverer.get(this.name)?.caCertPath ?? undefined;
|
|
59
|
+
}
|
|
60
|
+
getUrl() {
|
|
61
|
+
return this.#context?.discoverer.get(this.name)?.url;
|
|
62
|
+
}
|
|
63
|
+
getExitInfo() {
|
|
64
|
+
if (this.#exit === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const {
|
|
68
|
+
code,
|
|
69
|
+
signal
|
|
70
|
+
} = this.#exit;
|
|
71
|
+
return signal ?? `code ${code}`;
|
|
72
|
+
}
|
|
73
|
+
getCapturedStderr() {
|
|
74
|
+
return this.#stderr.join("").trim();
|
|
75
|
+
}
|
|
76
|
+
getContext() {
|
|
77
|
+
const context = this.#context;
|
|
78
|
+
!(context !== undefined) ? process.env.NODE_ENV !== "production" ? invariant(false, `${this.name} is not registered with a CliServiceLauncher, so it has ` + `no discoverer or status server to consult`) : invariant(false) : void 0;
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
attach(context) {
|
|
82
|
+
this.#context = context;
|
|
83
|
+
}
|
|
84
|
+
async start() {
|
|
85
|
+
if (this.#child !== undefined) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await this.getContext().discoverer.refresh();
|
|
89
|
+
!!(await this.checkHealth()).ready ? process.env.NODE_ENV !== "production" ? invariant(false, `${this.name} is already running; stop it before starting a new one`) : invariant(false) : void 0;
|
|
90
|
+
await this.#spawn();
|
|
91
|
+
}
|
|
92
|
+
async #spawn() {
|
|
93
|
+
this.#exit = undefined;
|
|
94
|
+
this.#stderr = [];
|
|
95
|
+
const child = spawn(this.#foundryCliPath, [...this.getArgs()], {
|
|
96
|
+
cwd: this.#projectPath
|
|
97
|
+
});
|
|
98
|
+
this.#child = child;
|
|
99
|
+
child.stderr.setEncoding("utf-8");
|
|
100
|
+
child.stderr.on("data", chunk => {
|
|
101
|
+
if (this.#stderr.push(chunk) > MAX_CAPTURED_STDERR_CHUNKS) {
|
|
102
|
+
this.#stderr.shift();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.stdout.resume(); // flush, since we don't read from stdout
|
|
106
|
+
child.on("exit", (code, signal) => {
|
|
107
|
+
this.#exit = {
|
|
108
|
+
code,
|
|
109
|
+
signal
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
await new Promise((resolve, reject) => {
|
|
113
|
+
child.once("spawn", resolve);
|
|
114
|
+
child.once("error", error => reject(new Error(`Failed to spawn ${this.name} via ${this.#foundryCliPath}`, {
|
|
115
|
+
cause: error
|
|
116
|
+
})));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async checkHealth() {
|
|
120
|
+
const {
|
|
121
|
+
discoverer,
|
|
122
|
+
statusServer
|
|
123
|
+
} = this.getContext();
|
|
124
|
+
const discovery = discoverer.get(this.name);
|
|
125
|
+
if (typeof discovery === "undefined") {
|
|
126
|
+
return {
|
|
127
|
+
service: this.name,
|
|
128
|
+
state: "UNDISCOVERED",
|
|
129
|
+
ready: false,
|
|
130
|
+
terminal: false
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const status = await statusServer.getServiceStatus(this.name);
|
|
134
|
+
const state = status?.status ?? "DISCOVERED";
|
|
135
|
+
return {
|
|
136
|
+
service: this.name,
|
|
137
|
+
state,
|
|
138
|
+
ready: state === "READY",
|
|
139
|
+
terminal: TERMINAL_STATES.has(state),
|
|
140
|
+
url: discovery.url,
|
|
141
|
+
...(status?.message != null ? {
|
|
142
|
+
message: status.message
|
|
143
|
+
} : {})
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Signal the service and wait for the process to actually be gone.
|
|
149
|
+
*/
|
|
150
|
+
async stop() {
|
|
151
|
+
const child = this.#child;
|
|
152
|
+
this.#child = undefined;
|
|
153
|
+
if (child === undefined || child.exitCode != null || child.signalCode != null) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const exited = once(child, "exit");
|
|
157
|
+
child.kill("SIGINT");
|
|
158
|
+
const escalation = setTimeout(() => {
|
|
159
|
+
child.kill("SIGKILL");
|
|
160
|
+
}, DEFAULT_STOP_GRACE_MS);
|
|
161
|
+
try {
|
|
162
|
+
await exited;
|
|
163
|
+
} finally {
|
|
164
|
+
clearTimeout(escalation);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=FoundryCliService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FoundryCliService.js","names":["spawn","once","invariant","MAX_CAPTURED_STDERR_CHUNKS","DEFAULT_READY_TIMEOUT_MS","DEFAULT_STOP_GRACE_MS","TERMINAL_STATES","Set","FoundryCliService","foundryCliPath","projectPath","child","context","stderr","exit","constructor","name","config","dependencies","getProjectPath","getCaCertPath","discoverer","get","caCertPath","undefined","getUrl","url","getExitInfo","code","signal","getCapturedStderr","join","trim","getContext","process","env","NODE_ENV","attach","start","refresh","checkHealth","ready","#spawn","getArgs","cwd","setEncoding","on","chunk","push","shift","stdout","resume","Promise","resolve","reject","error","Error","cause","statusServer","discovery","service","state","terminal","status","getServiceStatus","has","message","stop","exitCode","signalCode","exited","kill","escalation","setTimeout","clearTimeout"],"sources":["FoundryCliService.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { once } from \"node:events\";\n\nimport invariant from \"tiny-invariant\";\n\nimport type { ServiceLifecycle, ServiceName } from \"../generated/cli/index.js\";\nimport type { ServiceDiscoverer } from \"./ServiceDiscoverer.js\";\nimport type { StatusServer } from \"./StatusServer.js\";\n\nconst MAX_CAPTURED_STDERR_CHUNKS = 32;\n\nexport const DEFAULT_READY_TIMEOUT_MS = 30_000;\n\nexport const DEFAULT_STOP_GRACE_MS = 5_000;\n\nexport type ServiceState = ServiceLifecycle | \"UNDISCOVERED\" | \"DISCOVERED\";\n\nexport interface ServiceHealth {\n service: ServiceName;\n state: ServiceState;\n ready: boolean;\n terminal: boolean; // Whether `state` is terminal, so waiting for readiness is unnecessary.\n url?: string;\n message?: string;\n}\n\n/**\n * The shared collaborators a service needs to answer questions about itself.\n * Injected by the launcher via {@link FoundryCliService.attach} so services do\n * not have to construct — or know how to reach — either one.\n */\nexport interface ServiceContext {\n discoverer: ServiceDiscoverer;\n statusServer: StatusServer;\n}\n\nexport interface FoundryServiceConfig {\n /** Path to the `foundry` binary. */\n foundryCliPath?: string;\n /** Working directory the CLI runs in; the `.palantir/` dir hangs off it. */\n projectPath: string;\n /** Services that must be ready before this one may be started. */\n dependencies?: readonly FoundryCliService[];\n}\n\nconst TERMINAL_STATES: ReadonlySet<ServiceState> = new Set<ServiceState>([\n \"FAILED\",\n \"STOPPED\",\n]);\n\n/**\n * One Foundry CLI service subprocess.\n *\n * Subclasses supply the `foundry` subcommand to run and, where the default is\n * wrong, how to tell that the service is up.\n *\n * Dependencies are declared but not enforced here: a service names what it needs\n * and {@link CliServiceLauncher} is what orders and awaits them.\n */\nexport abstract class FoundryCliService {\n readonly name: ServiceName;\n readonly dependencies: readonly FoundryCliService[];\n\n #foundryCliPath: string;\n #projectPath: string;\n #child: ChildProcessWithoutNullStreams | undefined;\n #context: ServiceContext | undefined;\n #stderr: string[] = [];\n #exit: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n\n protected constructor(name: ServiceName, config: FoundryServiceConfig) {\n this.name = name;\n this.dependencies = config.dependencies ?? [];\n this.#foundryCliPath = config.foundryCliPath ?? \"foundry\";\n this.#projectPath = config.projectPath;\n }\n\n protected abstract getArgs(): readonly string[];\n\n getProjectPath(): string {\n return this.#projectPath;\n }\n\n getCaCertPath(): string | undefined {\n return this.#context?.discoverer.get(this.name)?.caCertPath ?? undefined;\n }\n\n getUrl(): string | undefined {\n return this.#context?.discoverer.get(this.name)?.url;\n }\n\n getExitInfo(): string | undefined {\n if (this.#exit === undefined) {\n return undefined;\n }\n const { code, signal } = this.#exit;\n return signal ?? `code ${code}`;\n }\n\n getCapturedStderr(): string {\n return this.#stderr.join(\"\").trim();\n }\n\n protected getContext(): ServiceContext {\n const context = this.#context;\n invariant(\n context !== undefined,\n `${this.name} is not registered with a CliServiceLauncher, so it has ` +\n `no discoverer or status server to consult`,\n );\n return context;\n }\n\n attach(context: ServiceContext): void {\n this.#context = context;\n }\n\n async start(): Promise<void> {\n if (this.#child !== undefined) {\n return;\n }\n await this.getContext().discoverer.refresh();\n invariant(\n !(await this.checkHealth()).ready,\n `${this.name} is already running; stop it before starting a new one`,\n );\n await this.#spawn();\n }\n\n async #spawn(): Promise<void> {\n this.#exit = undefined;\n this.#stderr = [];\n const child = spawn(this.#foundryCliPath, [...this.getArgs()], {\n cwd: this.#projectPath,\n });\n this.#child = child;\n child.stderr.setEncoding(\"utf-8\");\n child.stderr.on(\"data\", (chunk: string) => {\n if (this.#stderr.push(chunk) > MAX_CAPTURED_STDERR_CHUNKS) {\n this.#stderr.shift();\n }\n });\n child.stdout.resume(); // flush, since we don't read from stdout\n child.on(\"exit\", (code, signal) => {\n this.#exit = { code, signal };\n });\n await new Promise<void>((resolve, reject) => {\n child.once(\"spawn\", resolve);\n child.once(\"error\", (error) =>\n reject(\n new Error(\n `Failed to spawn ${this.name} via ${this.#foundryCliPath}`,\n {\n cause: error,\n },\n ),\n ),\n );\n });\n }\n\n async checkHealth(): Promise<ServiceHealth> {\n const { discoverer, statusServer } = this.getContext();\n const discovery = discoverer.get(this.name);\n if (typeof discovery === \"undefined\") {\n return {\n service: this.name,\n state: \"UNDISCOVERED\",\n ready: false,\n terminal: false,\n };\n }\n const status = await statusServer.getServiceStatus(this.name);\n const state: ServiceState = status?.status ?? \"DISCOVERED\";\n return {\n service: this.name,\n state,\n ready: state === \"READY\",\n terminal: TERMINAL_STATES.has(state),\n url: discovery.url,\n ...(status?.message != null ? { message: status.message } : {}),\n };\n }\n\n /**\n * Signal the service and wait for the process to actually be gone.\n */\n async stop(): Promise<void> {\n const child = this.#child;\n this.#child = undefined;\n if (\n child === undefined ||\n child.exitCode != null ||\n child.signalCode != null\n ) {\n return;\n }\n const exited = once(child, \"exit\");\n child.kill(\"SIGINT\");\n const escalation = setTimeout(() => {\n child.kill(\"SIGKILL\");\n }, DEFAULT_STOP_GRACE_MS);\n try {\n await exited;\n } finally {\n clearTimeout(escalation);\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path, { isAbsolute, resolve } from "node:path";
|
|
18
|
+
import { FoundryCliService } from "./FoundryCliService.js";
|
|
19
|
+
export class OntologyServer extends FoundryCliService {
|
|
20
|
+
#metadataPath;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
super("ONTOLOGY", config);
|
|
23
|
+
this.#metadataPath = isAbsolute(config.metadataPath) ? config.metadataPath : resolve(config.projectPath, config.metadataPath);
|
|
24
|
+
}
|
|
25
|
+
getMetadataPath() {
|
|
26
|
+
return this.#metadataPath;
|
|
27
|
+
}
|
|
28
|
+
getArgs() {
|
|
29
|
+
return ["start", "ontology", "--skip-build", "--metadata", this.#metadataPath, "--discovery-path", path.resolve(this.getProjectPath(), ".palantir")];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=OntologyServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OntologyServer.js","names":["path","isAbsolute","resolve","FoundryCliService","OntologyServer","metadataPath","constructor","config","projectPath","getMetadataPath","getArgs","getProjectPath"],"sources":["OntologyServer.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport path, { isAbsolute, resolve } from \"node:path\";\n\nimport {\n FoundryCliService,\n type FoundryServiceConfig,\n} from \"./FoundryCliService.js\";\n\nexport interface OntologyServerConfig extends FoundryServiceConfig {\n metadataPath: string;\n}\n\nexport class OntologyServer extends FoundryCliService {\n #metadataPath: string;\n\n constructor(config: OntologyServerConfig) {\n super(\"ONTOLOGY\", config);\n this.#metadataPath = isAbsolute(config.metadataPath)\n ? config.metadataPath\n : resolve(config.projectPath, config.metadataPath);\n }\n\n getMetadataPath(): string {\n return this.#metadataPath;\n }\n\n protected override getArgs(): readonly string[] {\n return [\n \"start\",\n \"ontology\",\n \"--skip-build\",\n \"--metadata\",\n this.#metadataPath,\n \"--discovery-path\",\n path.resolve(this.getProjectPath(), \".palantir\"),\n ];\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAOA,IAAI,IAAIC,UAAU,EAAEC,OAAO,QAAQ,WAAW;AAErD,SACEC,iBAAiB,QAEZ,wBAAwB;AAM/B,OAAO,MAAMC,cAAc,SAASD,iBAAiB,CAAC;EACpD,CAACE,YAAY;EAEbC,WAAWA,CAACC,MAA4B,EAAE;IACxC,KAAK,CAAC,UAAU,EAAEA,MAAM,CAAC;IACzB,IAAI,CAAC,CAACF,YAAY,GAAGJ,UAAU,CAACM,MAAM,CAACF,YAAY,CAAC,GAChDE,MAAM,CAACF,YAAY,GACnBH,OAAO,CAACK,MAAM,CAACC,WAAW,EAAED,MAAM,CAACF,YAAY,CAAC;EACtD;EAEAI,eAAeA,CAAA,EAAW;IACxB,OAAO,IAAI,CAAC,CAACJ,YAAY;EAC3B;EAEmBK,OAAOA,CAAA,EAAsB;IAC9C,OAAO,CACL,OAAO,EACP,UAAU,EACV,cAAc,EACd,YAAY,EACZ,IAAI,CAAC,CAACL,YAAY,EAClB,kBAAkB,EAClBL,IAAI,CAACE,OAAO,CAAC,IAAI,CAACS,cAAc,CAAC,CAAC,EAAE,WAAW,CAAC,CACjD;EACH;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs/promises";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import invariant from "tiny-invariant";
|
|
20
|
+
import { isProcessAlive } from "../utils/process.js";
|
|
21
|
+
import { serviceNameFromFileName } from "../utils/service.js";
|
|
22
|
+
/**
|
|
23
|
+
* Reads the discovery files that running Foundry services publish into
|
|
24
|
+
* `basePath`.
|
|
25
|
+
*/
|
|
26
|
+
export class ServiceDiscoverer {
|
|
27
|
+
#basePath;
|
|
28
|
+
#discovered = new Map();
|
|
29
|
+
constructor(args) {
|
|
30
|
+
this.#basePath = args.basePath;
|
|
31
|
+
}
|
|
32
|
+
get discoveryDir() {
|
|
33
|
+
return this.#basePath;
|
|
34
|
+
}
|
|
35
|
+
async start() {
|
|
36
|
+
await fs.mkdir(this.#basePath, {
|
|
37
|
+
recursive: true
|
|
38
|
+
});
|
|
39
|
+
await this.refresh();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Re-read every discovery file in the directory.
|
|
44
|
+
*/
|
|
45
|
+
async refresh() {
|
|
46
|
+
const fileNames = await fs.readdir(this.#basePath);
|
|
47
|
+
const discoveries = await Promise.all(fileNames.map(fileName => this.#read(fileName)));
|
|
48
|
+
return discoveries.filter(discovery => discovery !== undefined);
|
|
49
|
+
}
|
|
50
|
+
get(name) {
|
|
51
|
+
const discovery = this.#discovered.get(name);
|
|
52
|
+
if (typeof discovery === "undefined") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!isProcessAlive(discovery.pid)) {
|
|
56
|
+
this.#discovered.delete(name); // Stale: the publisher died without cleaning up after itself.
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return discovery;
|
|
60
|
+
}
|
|
61
|
+
isDiscovered(name) {
|
|
62
|
+
return this.get(name) !== undefined;
|
|
63
|
+
}
|
|
64
|
+
all() {
|
|
65
|
+
const live = new Map();
|
|
66
|
+
for (const name of this.#discovered.keys()) {
|
|
67
|
+
const discovery = this.get(name);
|
|
68
|
+
if (discovery !== undefined) {
|
|
69
|
+
live.set(name, discovery);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return live;
|
|
73
|
+
}
|
|
74
|
+
stop() {
|
|
75
|
+
this.#discovered.clear();
|
|
76
|
+
}
|
|
77
|
+
async #read(fileName) {
|
|
78
|
+
const serviceName = serviceNameFromFileName(fileName);
|
|
79
|
+
if (typeof serviceName === "undefined") {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
let contents;
|
|
83
|
+
try {
|
|
84
|
+
contents = await fs.readFile(join(this.#basePath, fileName), "utf-8");
|
|
85
|
+
} catch {
|
|
86
|
+
this.#discovered.delete(serviceName);
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(contents);
|
|
92
|
+
} catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
!parsed.url ? process.env.NODE_ENV !== "production" ? invariant(false, `Service URL not found`) : invariant(false) : void 0;
|
|
96
|
+
this.#discovered.set(serviceName, parsed);
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=ServiceDiscoverer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServiceDiscoverer.js","names":["fs","join","invariant","isProcessAlive","serviceNameFromFileName","ServiceDiscoverer","basePath","discovered","Map","constructor","args","discoveryDir","start","mkdir","recursive","refresh","fileNames","readdir","discoveries","Promise","all","map","fileName","read","filter","discovery","undefined","get","name","pid","delete","isDiscovered","live","keys","set","stop","clear","#read","serviceName","contents","readFile","parsed","JSON","parse","url","process","env","NODE_ENV"],"sources":["ServiceDiscoverer.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as fs from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport invariant from \"tiny-invariant\";\n\nimport type {\n ComponentDiscovery,\n ServiceName,\n} from \"../generated/cli/index.js\";\nimport { isProcessAlive } from \"../utils/process.js\";\nimport { serviceNameFromFileName } from \"../utils/service.js\";\n\nexport type ServiceDiscovererConfig = {\n basePath: string;\n};\n\n/**\n * Reads the discovery files that running Foundry services publish into\n * `basePath`.\n */\nexport class ServiceDiscoverer {\n #basePath: string;\n #discovered = new Map<ServiceName, ComponentDiscovery>();\n\n constructor(args: ServiceDiscovererConfig) {\n this.#basePath = args.basePath;\n }\n\n get discoveryDir(): string {\n return this.#basePath;\n }\n\n async start(): Promise<void> {\n await fs.mkdir(this.#basePath, { recursive: true });\n await this.refresh();\n }\n\n /**\n * Re-read every discovery file in the directory.\n */\n async refresh(): Promise<ComponentDiscovery[]> {\n const fileNames = await fs.readdir(this.#basePath);\n const discoveries = await Promise.all(\n fileNames.map((fileName) => this.#read(fileName)),\n );\n return discoveries.filter(\n (discovery): discovery is ComponentDiscovery => discovery !== undefined,\n );\n }\n\n get(name: ServiceName): ComponentDiscovery | undefined {\n const discovery = this.#discovered.get(name);\n if (typeof discovery === \"undefined\") {\n return undefined;\n }\n if (!isProcessAlive(discovery.pid)) {\n this.#discovered.delete(name); // Stale: the publisher died without cleaning up after itself.\n return undefined;\n }\n return discovery;\n }\n\n isDiscovered(name: ServiceName): boolean {\n return this.get(name) !== undefined;\n }\n\n all(): Map<ServiceName, ComponentDiscovery> {\n const live = new Map<ServiceName, ComponentDiscovery>();\n for (const name of this.#discovered.keys()) {\n const discovery = this.get(name);\n if (discovery !== undefined) {\n live.set(name, discovery);\n }\n }\n return live;\n }\n\n stop(): void {\n this.#discovered.clear();\n }\n\n async #read(fileName: string): Promise<ComponentDiscovery | undefined> {\n const serviceName = serviceNameFromFileName(fileName);\n if (typeof serviceName === \"undefined\") {\n return undefined;\n }\n let contents: string;\n try {\n contents = await fs.readFile(join(this.#basePath, fileName), \"utf-8\");\n } catch {\n this.#discovered.delete(serviceName);\n return undefined;\n }\n let parsed: ComponentDiscovery;\n try {\n parsed = JSON.parse(contents) as ComponentDiscovery;\n } catch {\n return undefined;\n }\n invariant(parsed.url, `Service URL not found`);\n this.#discovered.set(serviceName, parsed);\n return parsed;\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import invariant from "tiny-invariant";
|
|
19
|
+
import { StatusService } from "../generated/cli/index.js";
|
|
20
|
+
import { FoundryCliService } from "./FoundryCliService.js";
|
|
21
|
+
const HEALTH_REQUEST_TIMEOUT_MS = 2_000;
|
|
22
|
+
/**
|
|
23
|
+
* The local status server, and the health authority for every other service.
|
|
24
|
+
*/
|
|
25
|
+
export class StatusServer extends FoundryCliService {
|
|
26
|
+
/** Shared by callers that ask while a fetch is already outstanding. */
|
|
27
|
+
#inFlight;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
super("STATUS_SERVER", config);
|
|
30
|
+
}
|
|
31
|
+
getArgs() {
|
|
32
|
+
// force project path to cwd
|
|
33
|
+
return ["start", "status-server", "--discovery-path", path.resolve(this.getProjectPath(), ".palantir")];
|
|
34
|
+
}
|
|
35
|
+
async checkHealth() {
|
|
36
|
+
const discovery = this.getContext().discoverer.get("STATUS_SERVER");
|
|
37
|
+
if (discovery === undefined) {
|
|
38
|
+
return {
|
|
39
|
+
service: "STATUS_SERVER",
|
|
40
|
+
state: "UNDISCOVERED",
|
|
41
|
+
ready: false,
|
|
42
|
+
terminal: false
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const healthy = await this.#isHealthy(discovery.url);
|
|
46
|
+
return {
|
|
47
|
+
service: "STATUS_SERVER",
|
|
48
|
+
state: healthy ? "READY" : "DISCOVERED",
|
|
49
|
+
ready: healthy,
|
|
50
|
+
terminal: false,
|
|
51
|
+
url: discovery.url
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async #isHealthy(baseUrl) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(new URL("/api/health", baseUrl), {
|
|
57
|
+
signal: AbortSignal.timeout(HEALTH_REQUEST_TIMEOUT_MS)
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const body = await response.json();
|
|
63
|
+
return body.status === "healthy";
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The lifecycle snapshot for every service.
|
|
71
|
+
*/
|
|
72
|
+
async getServiceStatuses() {
|
|
73
|
+
const baseUrl = this.getContext().discoverer.get("STATUS_SERVER")?.url;
|
|
74
|
+
!baseUrl ? process.env.NODE_ENV !== "production" ? invariant(false, "Cannot get service status because the status server is not discovered yet") : invariant(false) : void 0;
|
|
75
|
+
this.#inFlight ??= StatusService.getStatus({
|
|
76
|
+
baseUrl,
|
|
77
|
+
servicePath: ""
|
|
78
|
+
}).catch(() => undefined).finally(() => {
|
|
79
|
+
this.#inFlight = undefined;
|
|
80
|
+
});
|
|
81
|
+
const result = await this.#inFlight;
|
|
82
|
+
return result ?? [];
|
|
83
|
+
}
|
|
84
|
+
async getServiceStatus(name) {
|
|
85
|
+
const statuses = await this.getServiceStatuses();
|
|
86
|
+
return statuses.find(({
|
|
87
|
+
service
|
|
88
|
+
}) => service === name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=StatusServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusServer.js","names":["path","invariant","StatusService","FoundryCliService","HEALTH_REQUEST_TIMEOUT_MS","StatusServer","inFlight","constructor","config","getArgs","resolve","getProjectPath","checkHealth","discovery","getContext","discoverer","get","undefined","service","state","ready","terminal","healthy","isHealthy","url","#isHealthy","baseUrl","response","fetch","URL","signal","AbortSignal","timeout","ok","body","json","status","getServiceStatuses","process","env","NODE_ENV","getStatus","servicePath","catch","finally","result","getServiceStatus","name","statuses","find"],"sources":["StatusServer.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport path from \"node:path\";\n\nimport invariant from \"tiny-invariant\";\n\nimport {\n type ServiceName,\n type ServiceStatus,\n StatusService,\n} from \"../generated/cli/index.js\";\nimport {\n FoundryCliService,\n type FoundryServiceConfig,\n type ServiceHealth,\n} from \"./FoundryCliService.js\";\n\nconst HEALTH_REQUEST_TIMEOUT_MS = 2_000;\n\nexport type StatusServerConfig = FoundryServiceConfig;\n\n/**\n * The local status server, and the health authority for every other service.\n */\nexport class StatusServer extends FoundryCliService {\n /** Shared by callers that ask while a fetch is already outstanding. */\n #inFlight: Promise<ServiceStatus[] | undefined> | undefined;\n\n constructor(config: StatusServerConfig) {\n super(\"STATUS_SERVER\", config);\n }\n\n protected override getArgs(): readonly string[] {\n // force project path to cwd\n return [\n \"start\",\n \"status-server\",\n \"--discovery-path\",\n path.resolve(this.getProjectPath(), \".palantir\"),\n ];\n }\n\n override async checkHealth(): Promise<ServiceHealth> {\n const discovery = this.getContext().discoverer.get(\"STATUS_SERVER\");\n if (discovery === undefined) {\n return {\n service: \"STATUS_SERVER\",\n state: \"UNDISCOVERED\",\n ready: false,\n terminal: false,\n };\n }\n const healthy = await this.#isHealthy(discovery.url);\n return {\n service: \"STATUS_SERVER\",\n state: healthy ? \"READY\" : \"DISCOVERED\",\n ready: healthy,\n terminal: false,\n url: discovery.url,\n };\n }\n\n async #isHealthy(baseUrl: string): Promise<boolean> {\n try {\n const response = await fetch(new URL(\"/api/health\", baseUrl), {\n signal: AbortSignal.timeout(HEALTH_REQUEST_TIMEOUT_MS),\n });\n if (!response.ok) {\n return false;\n }\n const body: { status?: unknown } = await response.json();\n return body.status === \"healthy\";\n } catch {\n return false;\n }\n }\n\n /**\n * The lifecycle snapshot for every service.\n */\n async getServiceStatuses(): Promise<ServiceStatus[]> {\n const baseUrl = this.getContext().discoverer.get(\"STATUS_SERVER\")?.url;\n invariant(\n baseUrl,\n \"Cannot get service status because the status server is not discovered yet\",\n );\n this.#inFlight ??= StatusService.getStatus({ baseUrl, servicePath: \"\" })\n .catch(() => undefined)\n .finally(() => {\n this.#inFlight = undefined;\n });\n const result = await this.#inFlight;\n return result ?? [];\n }\n\n async getServiceStatus(\n name: ServiceName,\n ): Promise<ServiceStatus | undefined> {\n const statuses = await this.getServiceStatuses();\n return statuses.find(({ service }) => service === name);\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs/promises";
|
|
18
|
+
import { createClient } from "@osdk/client";
|
|
19
|
+
import { createMockClient } from "@osdk/unit-testing";
|
|
20
|
+
import { fetch as undiciFetch, Agent } from "undici";
|
|
21
|
+
|
|
22
|
+
/** The definition kinds accepted by {@link Client}'s call signatures. */
|
|
23
|
+
|
|
24
|
+
export async function createIntegrationClient(config) {
|
|
25
|
+
const {
|
|
26
|
+
baseUrl,
|
|
27
|
+
metadata,
|
|
28
|
+
caCertPath
|
|
29
|
+
} = config;
|
|
30
|
+
const agent = new Agent({
|
|
31
|
+
connect: caCertPath ? {
|
|
32
|
+
ca: await fs.readFile(caCertPath)
|
|
33
|
+
} : {
|
|
34
|
+
rejectUnauthorized: false
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const realClient = createClient(baseUrl, metadata.ontology.rid, () => Promise.resolve("integration-client-token"), {}, (input, init) => undiciFetch(input, {
|
|
38
|
+
...init,
|
|
39
|
+
dispatcher: agent
|
|
40
|
+
}));
|
|
41
|
+
const mockClient = createMockClient();
|
|
42
|
+
const client = Object.defineProperties(def => {
|
|
43
|
+
switch (def.type) {
|
|
44
|
+
case "query":
|
|
45
|
+
return mockClient(def);
|
|
46
|
+
case "action":
|
|
47
|
+
return realClient(def);
|
|
48
|
+
case "interface":
|
|
49
|
+
return realClient(def);
|
|
50
|
+
case "object":
|
|
51
|
+
return realClient(def);
|
|
52
|
+
case "experiment":
|
|
53
|
+
default:
|
|
54
|
+
return realClient(def);
|
|
55
|
+
}
|
|
56
|
+
}, {
|
|
57
|
+
...Object.getOwnPropertyDescriptors(realClient),
|
|
58
|
+
whenQuery: {
|
|
59
|
+
value: mockClient.whenQuery.bind(undefined)
|
|
60
|
+
},
|
|
61
|
+
clearStubs: {
|
|
62
|
+
value: mockClient.clearStubs.bind(undefined)
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return client;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=createIntegrationClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createIntegrationClient.js","names":["fs","createClient","createMockClient","fetch","undiciFetch","Agent","createIntegrationClient","config","baseUrl","metadata","caCertPath","agent","connect","ca","readFile","rejectUnauthorized","realClient","ontology","rid","Promise","resolve","input","init","dispatcher","mockClient","client","Object","defineProperties","def","type","getOwnPropertyDescriptors","whenQuery","value","bind","undefined","clearStubs"],"sources":["createIntegrationClient.ts"],"sourcesContent":["/*\n * Copyright 2026 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from \"node:fs/promises\";\n\nimport type {\n ActionDefinition,\n InterfaceDefinition,\n ObjectTypeDefinition,\n QueryDefinition,\n} from \"@osdk/api\";\nimport type { Experiment } from \"@osdk/api/unstable\";\nimport { createClient, type Client } from \"@osdk/client\";\nimport { createMockClient } from \"@osdk/unit-testing\";\nimport { fetch as undiciFetch, Agent } from \"undici\";\n\nimport type { IntegrationClient, IntegrationClientConfig } from \"./types.js\";\n\n/** The definition kinds accepted by {@link Client}'s call signatures. */\ntype ClientArg =\n | ObjectTypeDefinition\n | InterfaceDefinition\n | ActionDefinition\n | QueryDefinition\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | Experiment<any>;\n\nexport async function createIntegrationClient(\n config: IntegrationClientConfig,\n): Promise<IntegrationClient> {\n const { baseUrl, metadata, caCertPath } = config;\n const agent = new Agent({\n connect: caCertPath\n ? {\n ca: await fs.readFile(caCertPath),\n }\n : {\n rejectUnauthorized: false,\n },\n });\n const fetchWithCert = ((input, init) =>\n undiciFetch(input, {\n ...init,\n dispatcher: agent,\n })) satisfies typeof undiciFetch as unknown as typeof fetch;\n const realClient = createClient(\n baseUrl,\n metadata.ontology.rid,\n () => Promise.resolve(\"integration-client-token\"),\n {},\n fetchWithCert,\n );\n const mockClient = createMockClient();\n const clientInternal = ((def: ClientArg) => {\n switch (def.type) {\n case \"query\":\n return mockClient(def);\n case \"action\":\n return realClient(def);\n case \"interface\":\n return realClient(def);\n case \"object\":\n return realClient(def);\n case \"experiment\":\n default:\n return realClient(def);\n }\n }) as Client;\n const client = Object.defineProperties<IntegrationClient>(\n clientInternal as IntegrationClient,\n {\n ...Object.getOwnPropertyDescriptors(realClient),\n whenQuery: {\n value: mockClient.whenQuery.bind(undefined),\n },\n clearStubs: {\n value: mockClient.clearStubs.bind(undefined),\n },\n },\n );\n return client;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAOA,EAAE,MAAM,kBAAkB;AASjC,SAASC,YAAY,QAAqB,cAAc;AACxD,SAASC,gBAAgB,QAAQ,oBAAoB;AACrD,SAASC,KAAK,IAAIC,WAAW,EAAEC,KAAK,QAAQ,QAAQ;;AAIpD;;AASA,OAAO,eAAeC,uBAAuBA,CAC3CC,MAA+B,EACH;EAC5B,MAAM;IAAEC,OAAO;IAAEC,QAAQ;IAAEC;EAAW,CAAC,GAAGH,MAAM;EAChD,MAAMI,KAAK,GAAG,IAAIN,KAAK,CAAC;IACtBO,OAAO,EAAEF,UAAU,GACf;MACEG,EAAE,EAAE,MAAMb,EAAE,CAACc,QAAQ,CAACJ,UAAU;IAClC,CAAC,GACD;MACEK,kBAAkB,EAAE;IACtB;EACN,CAAC,CAAC;EAMF,MAAMC,UAAU,GAAGf,YAAY,CAC7BO,OAAO,EACPC,QAAQ,CAACQ,QAAQ,CAACC,GAAG,EACrB,MAAMC,OAAO,CAACC,OAAO,CAAC,0BAA0B,CAAC,EACjD,CAAC,CAAC,EATmB,CAACC,KAAK,EAAEC,IAAI,KACjClB,WAAW,CAACiB,KAAK,EAAE;IACjB,GAAGC,IAAI;IACPC,UAAU,EAAEZ;EACd,CAAC,CAOH,CAAC;EACD,MAAMa,UAAU,GAAGtB,gBAAgB,CAAC,CAAC;EAgBrC,MAAMuB,MAAM,GAAGC,MAAM,CAACC,gBAAgB,CAfbC,GAAc,IAAK;IAC1C,QAAQA,GAAG,CAACC,IAAI;MACd,KAAK,OAAO;QACV,OAAOL,UAAU,CAACI,GAAG,CAAC;MACxB,KAAK,QAAQ;QACX,OAAOZ,UAAU,CAACY,GAAG,CAAC;MACxB,KAAK,WAAW;QACd,OAAOZ,UAAU,CAACY,GAAG,CAAC;MACxB,KAAK,QAAQ;QACX,OAAOZ,UAAU,CAACY,GAAG,CAAC;MACxB,KAAK,YAAY;MACjB;QACE,OAAOZ,UAAU,CAACY,GAAG,CAAC;IAC1B;EACF,CAAC,EAGC;IACE,GAAGF,MAAM,CAACI,yBAAyB,CAACd,UAAU,CAAC;IAC/Ce,SAAS,EAAE;MACTC,KAAK,EAAER,UAAU,CAACO,SAAS,CAACE,IAAI,CAACC,SAAS;IAC5C,CAAC;IACDC,UAAU,EAAE;MACVH,KAAK,EAAER,UAAU,CAACW,UAAU,CAACF,IAAI,CAACC,SAAS;IAC7C;EACF,CACF,CAAC;EACD,OAAOT,MAAM;AACf","ignoreList":[]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs/promises";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { PreviewOntologyIrConverter } from "@osdk/generator-converters.preview";
|
|
20
|
+
import invariant from "tiny-invariant";
|
|
21
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
22
|
+
import { CliServiceLauncher } from "./cli-service/CliServiceLauncher.js";
|
|
23
|
+
import { OntologyServer } from "./cli-service/OntologyServer.js";
|
|
24
|
+
import { createIntegrationClient } from "./createIntegrationClient.js";
|
|
25
|
+
import { createSeedClient } from "./createSeedClient.js";
|
|
26
|
+
import { EMPTY_ONTOLOGY_BLOCK_DATA } from "./utils/empty-ontology-block.js";
|
|
27
|
+
const transformMetadata = metadata => {
|
|
28
|
+
const transformed = PreviewOntologyIrConverter.getPreviewFullMetadataFromBlockData(EMPTY_ONTOLOGY_BLOCK_DATA, metadata);
|
|
29
|
+
return {
|
|
30
|
+
...transformed,
|
|
31
|
+
ontology: metadata.ontology
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const writeMetadata = async args => {
|
|
35
|
+
const {
|
|
36
|
+
metadataPath,
|
|
37
|
+
previewMetadata
|
|
38
|
+
} = args;
|
|
39
|
+
const stringified = JSON.stringify(previewMetadata);
|
|
40
|
+
await fs.writeFile(metadataPath, stringified);
|
|
41
|
+
};
|
|
42
|
+
export async function createIntegrationServer(config) {
|
|
43
|
+
const {
|
|
44
|
+
metadata,
|
|
45
|
+
projectPath = process.cwd(),
|
|
46
|
+
foundryCliPath
|
|
47
|
+
} = config;
|
|
48
|
+
const testPath = await fs.mkdtemp(path.join(projectPath, ".test-run-"));
|
|
49
|
+
const metadataPath = path.resolve(testPath, "ontology-metadata.json");
|
|
50
|
+
const previewMetadata = transformMetadata(metadata);
|
|
51
|
+
const serviceLauncher = new CliServiceLauncher({
|
|
52
|
+
projectDir: testPath,
|
|
53
|
+
foundryCliPath
|
|
54
|
+
});
|
|
55
|
+
// write metadata to metadataPath for foundry cli to read from
|
|
56
|
+
await writeMetadata({
|
|
57
|
+
previewMetadata,
|
|
58
|
+
metadataPath
|
|
59
|
+
});
|
|
60
|
+
const ontology = serviceLauncher.register(new OntologyServer({
|
|
61
|
+
projectPath: testPath,
|
|
62
|
+
metadataPath,
|
|
63
|
+
foundryCliPath
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// stateful cache of client and seed client
|
|
67
|
+
let client;
|
|
68
|
+
let seedClient;
|
|
69
|
+
return {
|
|
70
|
+
getOntologyUrl: () => ontology.getUrl(),
|
|
71
|
+
start: async () => {
|
|
72
|
+
await serviceLauncher.start();
|
|
73
|
+
},
|
|
74
|
+
stop: async () => {
|
|
75
|
+
await serviceLauncher.stop();
|
|
76
|
+
await fs.rm(testPath, {
|
|
77
|
+
recursive: true,
|
|
78
|
+
force: true
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
getSeedClient: async () => {
|
|
82
|
+
if (seedClient !== undefined) return seedClient;
|
|
83
|
+
const baseUrl = ontology.getUrl();
|
|
84
|
+
!baseUrl ? process.env.NODE_ENV !== "production" ? invariant(false, "Ontology server is not ready") : invariant(false) : void 0;
|
|
85
|
+
const caCertPath = ontology.getCaCertPath();
|
|
86
|
+
!caCertPath ? process.env.NODE_ENV !== "production" ? invariant(false, "Ontology server is not ready") : invariant(false) : void 0;
|
|
87
|
+
const agent = new Agent({
|
|
88
|
+
connect: caCertPath ? {
|
|
89
|
+
ca: await fs.readFile(caCertPath)
|
|
90
|
+
} : {
|
|
91
|
+
rejectUnauthorized: false
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return seedClient = createSeedClient({
|
|
95
|
+
baseUrl,
|
|
96
|
+
metadata,
|
|
97
|
+
fetchFn: (input, init) => undiciFetch(input, {
|
|
98
|
+
...init,
|
|
99
|
+
dispatcher: agent
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
getClient: async () => {
|
|
104
|
+
if (client !== undefined) return client;
|
|
105
|
+
const baseUrl = ontology.getUrl();
|
|
106
|
+
!baseUrl ? process.env.NODE_ENV !== "production" ? invariant(false, "Ontology server is not ready") : invariant(false) : void 0;
|
|
107
|
+
return client = await createIntegrationClient({
|
|
108
|
+
baseUrl,
|
|
109
|
+
metadata,
|
|
110
|
+
caCertPath: ontology.getCaCertPath()
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=createIntegrationServer.js.map
|