@h-rig/core 0.0.6-alpha.17 → 0.0.6-alpha.170
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/dist/src/agent-role-registry.d.ts +4 -0
- package/dist/src/agent-role-registry.js +27 -0
- package/dist/src/authority-paths.d.ts +15 -0
- package/dist/src/authority-paths.js +80 -0
- package/dist/src/baked-secrets.d.ts +6 -0
- package/dist/src/baked-secrets.js +121 -0
- package/dist/src/build-time-config.d.ts +12 -0
- package/dist/src/build-time-config.js +25 -0
- package/dist/src/build-time-config.macro.d.ts +1 -0
- package/dist/src/capability-loaders.d.ts +51 -0
- package/dist/src/capability-loaders.js +758 -0
- package/dist/src/capability.d.ts +79 -0
- package/dist/src/capability.js +63 -0
- package/dist/src/checkout-root.d.ts +1 -0
- package/dist/src/checkout-root.js +30 -0
- package/dist/src/config-env.d.ts +4 -0
- package/dist/src/config-env.js +23 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +44 -0
- package/dist/src/declarative-config.d.ts +14 -0
- package/dist/src/declarative-config.js +82 -0
- package/dist/src/default-kernel.d.ts +1 -0
- package/dist/src/default-kernel.js +12 -0
- package/dist/src/define-config.d.ts +20 -0
- package/dist/src/define-config.js +28 -15
- package/dist/src/define-plugin.d.ts +13 -0
- package/dist/src/define-plugin.js +4 -43
- package/dist/src/embedded-plugins.d.ts +59 -0
- package/dist/src/embedded-plugins.js +22 -0
- package/dist/src/exec.d.ts +13 -0
- package/dist/src/exec.js +101 -0
- package/dist/src/harness-paths.d.ts +18 -0
- package/dist/src/harness-paths.js +141 -0
- package/dist/src/hook-materializer.d.ts +72 -0
- package/dist/src/hook-materializer.js +281 -0
- package/dist/src/hook-protocol.d.ts +2 -0
- package/dist/src/hook-protocol.js +462 -0
- package/dist/src/hook-runner.d.ts +48 -0
- package/dist/src/hook-runner.js +756 -0
- package/dist/src/hook-runtime.d.ts +52 -0
- package/dist/src/hook-runtime.js +462 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +210 -2499
- package/dist/src/json-files.d.ts +9 -0
- package/dist/src/json-files.js +125 -0
- package/dist/src/kernel-boot.d.ts +2 -0
- package/dist/src/kernel-boot.js +10 -0
- package/dist/src/kernel-entrypoint.d.ts +22 -0
- package/dist/src/kernel-entrypoint.js +548 -0
- package/dist/src/kernel-plugin-abi.d.ts +1 -0
- package/dist/src/kernel-plugin-abi.js +1 -0
- package/dist/src/kernel-resolver.d.ts +2 -0
- package/dist/src/kernel-resolver.js +6 -0
- package/dist/src/layout.d.ts +10 -0
- package/dist/src/layout.js +144 -0
- package/dist/src/load-config.d.ts +2 -0
- package/dist/src/load-config.js +423 -30
- package/dist/src/placement.d.ts +50 -0
- package/dist/src/placement.js +996 -0
- package/dist/src/plugin-host-context.d.ts +66 -0
- package/dist/src/plugin-host-context.js +1292 -0
- package/dist/src/plugin-host-registries.d.ts +31 -0
- package/dist/src/plugin-host-registries.js +79 -0
- package/dist/src/plugin-host.d.ts +77 -0
- package/dist/src/plugin-host.js +127 -63
- package/dist/src/plugin-runtime.d.ts +173 -0
- package/dist/src/profile-ops.d.ts +9 -0
- package/dist/src/profile-ops.js +252 -0
- package/dist/src/project-plugins.d.ts +63 -0
- package/dist/src/project-plugins.js +793 -0
- package/dist/src/remote-config.d.ts +183 -0
- package/dist/src/remote-config.js +574 -0
- package/dist/src/root-resolver.d.ts +5 -0
- package/dist/src/root-resolver.js +69 -0
- package/dist/src/run-provisioning.d.ts +58 -0
- package/dist/src/run-provisioning.js +128 -0
- package/dist/src/runtime-context.d.ts +20 -0
- package/dist/src/runtime-context.js +257 -0
- package/dist/src/runtime-events.d.ts +44 -0
- package/dist/src/runtime-events.js +212 -0
- package/dist/src/runtime-overlay.d.ts +11 -0
- package/dist/src/runtime-overlay.js +71 -0
- package/dist/src/runtime-paths.d.ts +21 -0
- package/dist/src/runtime-paths.js +181 -0
- package/dist/src/runtime-provisioning-env.d.ts +5 -0
- package/dist/src/runtime-provisioning-env.js +217 -0
- package/dist/src/runtime-runner-context.d.ts +12 -0
- package/dist/src/runtime-runner-context.js +1 -0
- package/dist/src/safe-identifiers.d.ts +44 -0
- package/dist/src/safe-identifiers.js +96 -0
- package/dist/src/scope-rules.d.ts +4 -0
- package/dist/src/scope-rules.js +21 -0
- package/dist/src/server-paths.d.ts +26 -0
- package/dist/src/server-paths.js +308 -0
- package/dist/src/setup-version.d.ts +3 -0
- package/dist/src/setup-version.js +14 -0
- package/dist/src/task-record-reader.d.ts +3 -0
- package/dist/src/task-record-reader.js +9 -0
- package/dist/src/validator-registry.d.ts +27 -0
- package/dist/src/validator-registry.js +64 -0
- package/package.json +166 -10
- package/dist/src/engineReadModelReducer.js +0 -1780
- package/dist/src/rig-init-builder.js +0 -57
- package/dist/src/rigSelectors.js +0 -293
- package/dist/src/taskGraph.js +0 -64
- package/dist/src/taskGraphCodes.js +0 -26
- package/dist/src/taskGraphLayout.js +0 -374
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/remote-config.ts
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { homedir, tmpdir } from "os";
|
|
6
|
+
import { dirname as dirname2, join, resolve as resolve2 } from "path";
|
|
7
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
8
|
+
|
|
9
|
+
// packages/core/src/authority-paths.ts
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { dirname, resolve } from "path";
|
|
12
|
+
function normalizeOptionalString(value) {
|
|
13
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
14
|
+
}
|
|
15
|
+
function resolveAuthorityPaths(projectRoot) {
|
|
16
|
+
const normalizedRoot = resolve(projectRoot);
|
|
17
|
+
const stateRoot = resolveAuthorityStateRoot(normalizedRoot);
|
|
18
|
+
const stateDir = resolveAuthorityStateDir(normalizedRoot);
|
|
19
|
+
return {
|
|
20
|
+
projectRoot: normalizedRoot,
|
|
21
|
+
harnessRoot: resolve(normalizedRoot, "rig"),
|
|
22
|
+
runsDir: resolve(stateRoot, "runs"),
|
|
23
|
+
remoteDir: resolve(stateRoot, "remote"),
|
|
24
|
+
stateDir,
|
|
25
|
+
remoteEndpointsPath: resolve(stateRoot, "remote", "endpoints.toml"),
|
|
26
|
+
remoteSecretsPath: resolve(stateDir, "remote-secrets.toml")
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function resolveAuthorityStateRoot(projectRoot) {
|
|
30
|
+
const normalizedRoot = resolve(projectRoot);
|
|
31
|
+
const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
|
|
32
|
+
if (taskWorkspace) {
|
|
33
|
+
return resolve(taskWorkspace, ".rig");
|
|
34
|
+
}
|
|
35
|
+
const stateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
|
|
36
|
+
if (stateDir) {
|
|
37
|
+
return dirname(resolve(stateDir));
|
|
38
|
+
}
|
|
39
|
+
const logsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
|
|
40
|
+
if (logsDir) {
|
|
41
|
+
return dirname(resolve(logsDir));
|
|
42
|
+
}
|
|
43
|
+
const sessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
|
|
44
|
+
if (sessionFile) {
|
|
45
|
+
return dirname(dirname(resolve(sessionFile)));
|
|
46
|
+
}
|
|
47
|
+
const projectStateRoot = resolve(normalizedRoot, ".rig");
|
|
48
|
+
if (existsSync(projectStateRoot)) {
|
|
49
|
+
return projectStateRoot;
|
|
50
|
+
}
|
|
51
|
+
return resolve(normalizedRoot, ".rig");
|
|
52
|
+
}
|
|
53
|
+
function resolveAuthorityStateDir(projectRoot) {
|
|
54
|
+
const explicit = normalizeOptionalString(process.env.RIG_STATE_DIR);
|
|
55
|
+
if (explicit) {
|
|
56
|
+
return resolve(explicit);
|
|
57
|
+
}
|
|
58
|
+
return resolve(resolveAuthorityStateRoot(projectRoot), "state");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// packages/core/src/remote-config.ts
|
|
62
|
+
var DEFAULT_REMOTE_PORT = 7890;
|
|
63
|
+
var BACKBONE_DEFAULTS_KEY = Symbol.for("rig.remote-config.backbone-defaults");
|
|
64
|
+
function backboneDefaultsState() {
|
|
65
|
+
const global = globalThis;
|
|
66
|
+
return global[BACKBONE_DEFAULTS_KEY] ??= {};
|
|
67
|
+
}
|
|
68
|
+
function registerBackboneDefaults(defaults) {
|
|
69
|
+
const relayUrl = normalizeString(defaults.relayUrl);
|
|
70
|
+
const registryBaseUrl = normalizeString(defaults.registryBaseUrl);
|
|
71
|
+
const sshTarget = normalizeString(defaults.sshTarget);
|
|
72
|
+
const state = backboneDefaultsState();
|
|
73
|
+
if (relayUrl)
|
|
74
|
+
state.relayUrl = relayUrl;
|
|
75
|
+
if (registryBaseUrl)
|
|
76
|
+
state.registryBaseUrl = registryBaseUrl;
|
|
77
|
+
if (sshTarget)
|
|
78
|
+
state.sshTarget = sshTarget;
|
|
79
|
+
}
|
|
80
|
+
function resolveRelayUrl(env = process.env) {
|
|
81
|
+
return normalizeString(env.RIG_COLLAB_RELAY) || normalizeString(env.RIG_SPIKE_RELAY) || backboneDefaultsState().relayUrl || "";
|
|
82
|
+
}
|
|
83
|
+
function resolveSshTarget(env = process.env) {
|
|
84
|
+
return normalizeString(env.RIG_SSH_TARGET) || backboneDefaultsState().sshTarget || "";
|
|
85
|
+
}
|
|
86
|
+
var REMOTES_CONFIG_PATH = join(homedir(), ".config", "rig", "remotes.toml");
|
|
87
|
+
|
|
88
|
+
class RemoteCliError extends Error {
|
|
89
|
+
code;
|
|
90
|
+
exitCode;
|
|
91
|
+
details;
|
|
92
|
+
constructor(code, message, exitCode = 1, details) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "RemoteCliError";
|
|
95
|
+
this.code = code;
|
|
96
|
+
this.exitCode = exitCode;
|
|
97
|
+
this.details = details;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function loadRemotesConfig(configPath = REMOTES_CONFIG_PATH) {
|
|
101
|
+
if (!existsSync2(configPath)) {
|
|
102
|
+
return { version: 1, remotes: {} };
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const content = readFileSync(configPath, "utf-8");
|
|
106
|
+
if (!content.trim()) {
|
|
107
|
+
return { version: 1, remotes: {} };
|
|
108
|
+
}
|
|
109
|
+
const parsed = parseToml(content);
|
|
110
|
+
return {
|
|
111
|
+
version: parsed.version ?? 1,
|
|
112
|
+
remotes: parsed.remotes ?? {}
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new RemoteCliError("RIG_REMOTE_CONFIG_PARSE_ERROR", `Failed to parse remotes config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`, 2, { configPath });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function saveRemotesConfig(config, configPath = REMOTES_CONFIG_PATH) {
|
|
119
|
+
mkdirSync(dirname2(configPath), { recursive: true });
|
|
120
|
+
writeFileSync(configPath, `${stringifyToml(config).trimEnd()}
|
|
121
|
+
`, "utf-8");
|
|
122
|
+
try {
|
|
123
|
+
chmodSync(configPath, 384);
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
function readTomlFile(path, fallback) {
|
|
127
|
+
if (!existsSync2(path))
|
|
128
|
+
return fallback;
|
|
129
|
+
const raw = readFileSync(path, "utf8");
|
|
130
|
+
if (!raw.trim())
|
|
131
|
+
return fallback;
|
|
132
|
+
return parseToml(raw);
|
|
133
|
+
}
|
|
134
|
+
function writeTomlFile(path, value) {
|
|
135
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
136
|
+
writeFileSync(path, `${stringifyToml(value).trimEnd()}
|
|
137
|
+
`, "utf8");
|
|
138
|
+
}
|
|
139
|
+
function normalizeStringArray(value) {
|
|
140
|
+
if (!Array.isArray(value))
|
|
141
|
+
return [];
|
|
142
|
+
return value.map((entry) => normalizeString(entry)).filter((entry) => entry !== "");
|
|
143
|
+
}
|
|
144
|
+
function normalizePort(value) {
|
|
145
|
+
const port = typeof value === "number" ? value : Number(value);
|
|
146
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : DEFAULT_REMOTE_PORT;
|
|
147
|
+
}
|
|
148
|
+
function normalizeRegistryBaseUrl(raw) {
|
|
149
|
+
const trimmed = raw.replace(/\/+$/, "");
|
|
150
|
+
if (!trimmed)
|
|
151
|
+
return trimmed;
|
|
152
|
+
return /\/registry$/.test(trimmed) ? trimmed : `${trimmed}/registry`;
|
|
153
|
+
}
|
|
154
|
+
function loadRemoteEndpointsToml(projectRoot) {
|
|
155
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
156
|
+
const parsed = readTomlFile(paths.remoteEndpointsPath, { version: 1, endpoints: {} });
|
|
157
|
+
return {
|
|
158
|
+
version: parsed.version ?? 1,
|
|
159
|
+
endpoints: parsed.endpoints ?? {}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function loadRemoteSecretsToml(projectRoot) {
|
|
163
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
164
|
+
const parsed = readTomlFile(paths.remoteSecretsPath, { version: 1, secrets: {} });
|
|
165
|
+
return {
|
|
166
|
+
version: parsed.version ?? 1,
|
|
167
|
+
secrets: parsed.secrets ?? {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function readJsonRecordFile(path) {
|
|
171
|
+
try {
|
|
172
|
+
if (!existsSync2(path))
|
|
173
|
+
return {};
|
|
174
|
+
const raw = readFileSync(path, "utf8");
|
|
175
|
+
if (!raw.trim())
|
|
176
|
+
return {};
|
|
177
|
+
const parsed = JSON.parse(raw);
|
|
178
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
179
|
+
} catch {
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function coerceString(value) {
|
|
184
|
+
return typeof value === "string" ? value.trim() : "";
|
|
185
|
+
}
|
|
186
|
+
function resolveOwnerNamespaceKey(projectRoot, env = process.env) {
|
|
187
|
+
const fromEnv = normalizeString(env.RIG_GITHUB_NAMESPACE_KEY);
|
|
188
|
+
if (fromEnv)
|
|
189
|
+
return fromEnv;
|
|
190
|
+
const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
|
|
191
|
+
const auth = readJsonRecordFile(resolve2(stateDir, "github-auth.json"));
|
|
192
|
+
const namespaceKey = coerceString(auth.userNamespaceKey);
|
|
193
|
+
if (namespaceKey)
|
|
194
|
+
return namespaceKey;
|
|
195
|
+
const userId = coerceString(auth.userId);
|
|
196
|
+
return userId ? `gh:${userId}` : undefined;
|
|
197
|
+
}
|
|
198
|
+
function forceLocalTransport(env) {
|
|
199
|
+
return env.RIG_FORCE_LOCAL === "1" || env.RIG_FORCE_LOCAL === "true";
|
|
200
|
+
}
|
|
201
|
+
function resolveDispatchTransportPlacement(projectRoot, env = process.env) {
|
|
202
|
+
if (forceLocalTransport(env)) {
|
|
203
|
+
return { kind: "local", alias: "local", sshTarget: null, selected: null };
|
|
204
|
+
}
|
|
205
|
+
const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
|
|
206
|
+
const conn = readJsonRecordFile(resolve2(stateDir, "connection.json"));
|
|
207
|
+
const selectedAlias = normalizeString(env.RIG_REMOTE_ALIAS) || coerceString(conn.selected);
|
|
208
|
+
if (selectedAlias === "local") {
|
|
209
|
+
return { kind: "local", alias: "local", sshTarget: null, selected: null };
|
|
210
|
+
}
|
|
211
|
+
const selected = resolveSelectedRemote(projectRoot, env);
|
|
212
|
+
const sshTarget = selected?.sshTarget ?? resolveSshTarget(env);
|
|
213
|
+
if (!sshTarget) {
|
|
214
|
+
return { kind: "local", alias: "local", sshTarget: null, selected: null };
|
|
215
|
+
}
|
|
216
|
+
return { kind: "remote", alias: selected?.alias ?? sshTarget, sshTarget, selected };
|
|
217
|
+
}
|
|
218
|
+
function resolveSelectedRemote(projectRoot, env = process.env) {
|
|
219
|
+
const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
|
|
220
|
+
const conn = readJsonRecordFile(resolve2(stateDir, "connection.json"));
|
|
221
|
+
const selected = normalizeString(env.RIG_REMOTE_ALIAS) || coerceString(conn.selected);
|
|
222
|
+
if (!selected || selected === "local")
|
|
223
|
+
return null;
|
|
224
|
+
const endpoints = Object.values(loadRemoteEndpointsToml(projectRoot).endpoints ?? {});
|
|
225
|
+
const match = endpoints.find((entry) => normalizeString(entry.alias) === selected);
|
|
226
|
+
if (!match) {
|
|
227
|
+
if (selected === "remote") {
|
|
228
|
+
const sshTarget = resolveSshTarget(env);
|
|
229
|
+
if (!sshTarget)
|
|
230
|
+
return null;
|
|
231
|
+
return {
|
|
232
|
+
alias: "remote",
|
|
233
|
+
host: sshTarget,
|
|
234
|
+
port: 22,
|
|
235
|
+
sshTarget,
|
|
236
|
+
checkout: coerceString(conn.serverProjectRoot) || null,
|
|
237
|
+
registryBaseUrl: null,
|
|
238
|
+
secretRef: null
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const host = normalizeString(match.host) || selected;
|
|
244
|
+
const explicitRegistryUrl = normalizeString(env.RIG_REGISTRY_URL) || normalizeString(env.REGISTRY_URL);
|
|
245
|
+
const registryBaseUrl = explicitRegistryUrl ? normalizeRegistryBaseUrl(explicitRegistryUrl) : null;
|
|
246
|
+
return {
|
|
247
|
+
alias: selected,
|
|
248
|
+
host,
|
|
249
|
+
port: normalizePort(match.port),
|
|
250
|
+
sshTarget: host || normalizeString(match.alias) || coerceString(conn.serverProjectRootAlias),
|
|
251
|
+
checkout: coerceString(conn.serverProjectRoot) || null,
|
|
252
|
+
registryBaseUrl,
|
|
253
|
+
secretRef: normalizeString(match.secret_ref) || null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function resolveRegistryBaseUrl(projectRoot, env = process.env) {
|
|
257
|
+
return normalizeRegistryBaseUrl(normalizeString(env.RIG_REGISTRY_URL) || normalizeString(env.REGISTRY_URL) || resolveSelectedRemote(projectRoot, env)?.registryBaseUrl || backboneDefaultsState().registryBaseUrl || "");
|
|
258
|
+
}
|
|
259
|
+
var rigOmpConfigOverlayPath = null;
|
|
260
|
+
function resolveRigOmpConfigOverlayPath(projectRoot = process.cwd()) {
|
|
261
|
+
if (rigOmpConfigOverlayPath && existsSync2(rigOmpConfigOverlayPath))
|
|
262
|
+
return rigOmpConfigOverlayPath;
|
|
263
|
+
const overlay = [
|
|
264
|
+
`collab.relayUrl: ${resolveRelayUrl()}`,
|
|
265
|
+
`registry.url: ${resolveRegistryBaseUrl(projectRoot)}`,
|
|
266
|
+
"# Cockpit owns the screen: skip OMP's welcome/startup so the drone board renders cleanly.",
|
|
267
|
+
"startup.quiet: true",
|
|
268
|
+
""
|
|
269
|
+
].join(`
|
|
270
|
+
`);
|
|
271
|
+
const base = tmpdir();
|
|
272
|
+
mkdirSync(base, { recursive: true });
|
|
273
|
+
const dir = mkdtempSync(join(base, "rig-config-"));
|
|
274
|
+
const file = join(dir, "rig-default-config.yml");
|
|
275
|
+
writeFileSync(file, overlay);
|
|
276
|
+
rigOmpConfigOverlayPath = file;
|
|
277
|
+
return file;
|
|
278
|
+
}
|
|
279
|
+
function saveRemoteEndpointsToml(projectRoot, payload) {
|
|
280
|
+
writeTomlFile(resolveAuthorityPaths(projectRoot).remoteEndpointsPath, payload);
|
|
281
|
+
}
|
|
282
|
+
function saveRemoteSecretsToml(projectRoot, payload) {
|
|
283
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
284
|
+
writeTomlFile(paths.remoteSecretsPath, payload);
|
|
285
|
+
try {
|
|
286
|
+
chmodSync(paths.remoteSecretsPath, 384);
|
|
287
|
+
} catch {}
|
|
288
|
+
}
|
|
289
|
+
function listAuthorityRemoteEndpoints(projectRoot) {
|
|
290
|
+
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
291
|
+
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
292
|
+
return Object.values(endpoints).map((entry) => {
|
|
293
|
+
const token = secrets[entry.secret_ref] ?? "";
|
|
294
|
+
return {
|
|
295
|
+
id: entry.id,
|
|
296
|
+
alias: entry.alias,
|
|
297
|
+
host: entry.host,
|
|
298
|
+
port: normalizePort(entry.port),
|
|
299
|
+
token,
|
|
300
|
+
autoConnect: Boolean(entry.auto_connect),
|
|
301
|
+
addedAt: entry.created_at,
|
|
302
|
+
updatedAt: entry.updated_at,
|
|
303
|
+
lastConnectedAt: normalizeString(entry.last_connected_at) || null,
|
|
304
|
+
labels: normalizeStringArray(entry.labels),
|
|
305
|
+
capabilities: normalizeStringArray(entry.capabilities),
|
|
306
|
+
transport: normalizeString(entry.transport) || "websocket",
|
|
307
|
+
secretRef: entry.secret_ref
|
|
308
|
+
};
|
|
309
|
+
}).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
310
|
+
}
|
|
311
|
+
function nextRemoteEndpointRecord(input) {
|
|
312
|
+
const timestamp = new Date().toISOString();
|
|
313
|
+
const secretRef = input.existing?.secret_ref ?? randomUUID();
|
|
314
|
+
return {
|
|
315
|
+
record: {
|
|
316
|
+
id: input.endpointId ?? input.existing?.id ?? randomUUID(),
|
|
317
|
+
alias: input.alias,
|
|
318
|
+
host: input.host,
|
|
319
|
+
port: normalizePort(input.port),
|
|
320
|
+
transport: normalizeString(input.transport) || normalizeString(input.existing?.transport) || "websocket",
|
|
321
|
+
auto_connect: input.autoConnect ?? input.existing?.auto_connect ?? false,
|
|
322
|
+
labels: input.labels ?? normalizeStringArray(input.existing?.labels),
|
|
323
|
+
capabilities: input.capabilities ?? normalizeStringArray(input.existing?.capabilities),
|
|
324
|
+
secret_ref: secretRef,
|
|
325
|
+
created_at: input.existing?.created_at ?? timestamp,
|
|
326
|
+
updated_at: timestamp,
|
|
327
|
+
last_connected_at: input.existing?.last_connected_at ?? null
|
|
328
|
+
},
|
|
329
|
+
secretRef
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function upsertAuthorityRemoteEndpoint(projectRoot, input) {
|
|
333
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
334
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
335
|
+
const existing = Object.values(endpointsToml.endpoints ?? {}).find((entry) => entry.alias === input.alias) ?? null;
|
|
336
|
+
const { record, secretRef } = nextRemoteEndpointRecord({
|
|
337
|
+
existing,
|
|
338
|
+
alias: input.alias,
|
|
339
|
+
host: input.host,
|
|
340
|
+
port: input.port,
|
|
341
|
+
token: input.token,
|
|
342
|
+
...input.endpointId !== undefined ? { endpointId: input.endpointId } : {},
|
|
343
|
+
...input.autoConnect !== undefined ? { autoConnect: input.autoConnect } : {},
|
|
344
|
+
...input.transport !== undefined ? { transport: input.transport } : {},
|
|
345
|
+
...input.labels !== undefined ? { labels: input.labels } : {},
|
|
346
|
+
...input.capabilities !== undefined ? { capabilities: input.capabilities } : {}
|
|
347
|
+
});
|
|
348
|
+
endpointsToml.endpoints = {
|
|
349
|
+
...endpointsToml.endpoints ?? {},
|
|
350
|
+
[record.id]: record
|
|
351
|
+
};
|
|
352
|
+
secretsToml.secrets = {
|
|
353
|
+
...secretsToml.secrets ?? {},
|
|
354
|
+
[secretRef]: input.token
|
|
355
|
+
};
|
|
356
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
357
|
+
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
358
|
+
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id);
|
|
359
|
+
}
|
|
360
|
+
function updateAuthorityRemoteEndpoint(projectRoot, input) {
|
|
361
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
362
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
363
|
+
const current = Object.values(endpointsToml.endpoints ?? {}).find((entry) => input.endpointId && entry.id === input.endpointId || input.alias && entry.alias === input.alias);
|
|
364
|
+
if (!current)
|
|
365
|
+
return null;
|
|
366
|
+
const record = {
|
|
367
|
+
...current,
|
|
368
|
+
alias: normalizeString(input.alias) || current.alias,
|
|
369
|
+
host: normalizeString(input.host) || current.host,
|
|
370
|
+
port: input.port !== undefined ? normalizePort(input.port) : current.port,
|
|
371
|
+
auto_connect: input.autoConnect ?? current.auto_connect,
|
|
372
|
+
transport: normalizeString(input.transport) || current.transport,
|
|
373
|
+
labels: input.labels ?? normalizeStringArray(current.labels),
|
|
374
|
+
capabilities: input.capabilities ?? normalizeStringArray(current.capabilities),
|
|
375
|
+
updated_at: new Date().toISOString()
|
|
376
|
+
};
|
|
377
|
+
endpointsToml.endpoints = {
|
|
378
|
+
...endpointsToml.endpoints ?? {},
|
|
379
|
+
[record.id]: record
|
|
380
|
+
};
|
|
381
|
+
if (input.token !== undefined) {
|
|
382
|
+
secretsToml.secrets = {
|
|
383
|
+
...secretsToml.secrets ?? {},
|
|
384
|
+
[record.secret_ref]: input.token
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
388
|
+
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
389
|
+
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id) ?? null;
|
|
390
|
+
}
|
|
391
|
+
function removeAuthorityRemoteEndpoint(projectRoot, endpointIdOrAlias) {
|
|
392
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
393
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
394
|
+
const entry = Object.values(endpointsToml.endpoints ?? {}).find((candidate) => candidate.id === endpointIdOrAlias || candidate.alias === endpointIdOrAlias);
|
|
395
|
+
if (!entry)
|
|
396
|
+
return false;
|
|
397
|
+
const nextEndpoints = { ...endpointsToml.endpoints ?? {} };
|
|
398
|
+
delete nextEndpoints[entry.id];
|
|
399
|
+
const nextSecrets = { ...secretsToml.secrets ?? {} };
|
|
400
|
+
delete nextSecrets[entry.secret_ref];
|
|
401
|
+
saveRemoteEndpointsToml(projectRoot, { version: endpointsToml.version ?? 1, endpoints: nextEndpoints });
|
|
402
|
+
saveRemoteSecretsToml(projectRoot, { version: secretsToml.version ?? 1, secrets: nextSecrets });
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
function markAuthorityRemoteEndpointConnected(projectRoot, endpointId, connectedAt = new Date().toISOString()) {
|
|
406
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
407
|
+
const entry = endpointsToml.endpoints?.[endpointId];
|
|
408
|
+
if (!entry)
|
|
409
|
+
return;
|
|
410
|
+
endpointsToml.endpoints = {
|
|
411
|
+
...endpointsToml.endpoints ?? {},
|
|
412
|
+
[endpointId]: {
|
|
413
|
+
...entry,
|
|
414
|
+
last_connected_at: connectedAt,
|
|
415
|
+
updated_at: connectedAt
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
419
|
+
}
|
|
420
|
+
function importLegacyRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
|
|
421
|
+
if (!existsSync2(legacyPath)) {
|
|
422
|
+
return { imported: 0, skipped: 0, sourcePath: legacyPath };
|
|
423
|
+
}
|
|
424
|
+
const parsed = readTomlFile(legacyPath, { version: 1, remotes: {} });
|
|
425
|
+
let imported = 0;
|
|
426
|
+
let skipped = 0;
|
|
427
|
+
for (const [alias, entry] of Object.entries(parsed.remotes ?? {})) {
|
|
428
|
+
const host = normalizeString(entry.host);
|
|
429
|
+
const token = normalizeString(entry.token);
|
|
430
|
+
if (!host || !token) {
|
|
431
|
+
skipped += 1;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
435
|
+
alias,
|
|
436
|
+
host,
|
|
437
|
+
port: normalizePort(entry.port),
|
|
438
|
+
token
|
|
439
|
+
});
|
|
440
|
+
imported += 1;
|
|
441
|
+
}
|
|
442
|
+
return { imported, skipped, sourcePath: legacyPath };
|
|
443
|
+
}
|
|
444
|
+
function doctorAuthorityRemoteEndpoints(projectRoot) {
|
|
445
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
446
|
+
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
447
|
+
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
448
|
+
const missingSecrets = Object.values(endpoints).filter((entry) => !normalizeString(secrets[entry.secret_ref])).map((entry) => entry.alias);
|
|
449
|
+
const warnings = [];
|
|
450
|
+
if (!existsSync2(paths.remoteEndpointsPath)) {
|
|
451
|
+
warnings.push("Remote endpoint manifest is missing.");
|
|
452
|
+
}
|
|
453
|
+
if (!existsSync2(paths.remoteSecretsPath)) {
|
|
454
|
+
warnings.push("Remote secret store is missing.");
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
manifestPath: paths.remoteEndpointsPath,
|
|
458
|
+
secretsPath: paths.remoteSecretsPath,
|
|
459
|
+
endpointCount: Object.keys(endpoints).length,
|
|
460
|
+
missingSecrets,
|
|
461
|
+
warnings
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function listManagedRemoteEndpoints(configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
465
|
+
if (projectRoot) {
|
|
466
|
+
return listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
|
|
467
|
+
id: entry.id,
|
|
468
|
+
alias: entry.alias,
|
|
469
|
+
host: entry.host,
|
|
470
|
+
port: entry.port,
|
|
471
|
+
token: entry.token,
|
|
472
|
+
addedAt: entry.addedAt,
|
|
473
|
+
lastConnected: entry.lastConnectedAt
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
const config = loadRemotesConfig(configPath);
|
|
477
|
+
return Object.entries(config.remotes ?? {}).map(([alias, entry]) => ({
|
|
478
|
+
id: alias,
|
|
479
|
+
alias,
|
|
480
|
+
host: normalizeString(entry.host) || "",
|
|
481
|
+
port: Number.isFinite(entry.port) ? Number(entry.port) : DEFAULT_REMOTE_PORT,
|
|
482
|
+
token: normalizeString(entry.token) || "",
|
|
483
|
+
addedAt: normalizeString(entry.addedAt) || null,
|
|
484
|
+
lastConnected: normalizeString(entry.lastConnected) || null
|
|
485
|
+
})).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
486
|
+
}
|
|
487
|
+
function upsertManagedRemoteEndpoint(input, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
488
|
+
if (projectRoot) {
|
|
489
|
+
const saved = upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
490
|
+
...input,
|
|
491
|
+
token: input.token ?? ""
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
id: saved.id,
|
|
495
|
+
alias: saved.alias,
|
|
496
|
+
host: saved.host,
|
|
497
|
+
port: saved.port,
|
|
498
|
+
token: saved.token,
|
|
499
|
+
addedAt: saved.addedAt,
|
|
500
|
+
lastConnected: saved.lastConnectedAt
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const config = loadRemotesConfig(configPath);
|
|
504
|
+
const existing = config.remotes?.[input.alias];
|
|
505
|
+
const nextEntry = {
|
|
506
|
+
host: input.host,
|
|
507
|
+
port: input.port,
|
|
508
|
+
...input.token !== undefined ? { token: input.token } : {},
|
|
509
|
+
addedAt: normalizeString(existing?.addedAt) || new Date().toISOString(),
|
|
510
|
+
...normalizeString(existing?.lastConnected) ? { lastConnected: normalizeString(existing?.lastConnected) } : {}
|
|
511
|
+
};
|
|
512
|
+
const nextConfig = {
|
|
513
|
+
version: config.version ?? 1,
|
|
514
|
+
remotes: {
|
|
515
|
+
...config.remotes ?? {},
|
|
516
|
+
[input.alias]: nextEntry
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
saveRemotesConfig(nextConfig, configPath);
|
|
520
|
+
return {
|
|
521
|
+
id: input.alias,
|
|
522
|
+
alias: input.alias,
|
|
523
|
+
host: input.host,
|
|
524
|
+
port: input.port,
|
|
525
|
+
token: input.token ?? "",
|
|
526
|
+
addedAt: nextEntry.addedAt ?? null,
|
|
527
|
+
lastConnected: nextEntry.lastConnected ?? null
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function removeManagedRemoteEndpoint(alias, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
531
|
+
if (projectRoot) {
|
|
532
|
+
return removeAuthorityRemoteEndpoint(projectRoot, alias);
|
|
533
|
+
}
|
|
534
|
+
const config = loadRemotesConfig(configPath);
|
|
535
|
+
if (!config.remotes?.[alias]) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
const nextRemotes = { ...config.remotes ?? {} };
|
|
539
|
+
delete nextRemotes[alias];
|
|
540
|
+
saveRemotesConfig({
|
|
541
|
+
version: config.version ?? 1,
|
|
542
|
+
remotes: nextRemotes
|
|
543
|
+
}, configPath);
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
function normalizeString(value) {
|
|
547
|
+
if (!value) {
|
|
548
|
+
return "";
|
|
549
|
+
}
|
|
550
|
+
return value.trim();
|
|
551
|
+
}
|
|
552
|
+
export {
|
|
553
|
+
upsertManagedRemoteEndpoint,
|
|
554
|
+
updateAuthorityRemoteEndpoint,
|
|
555
|
+
resolveSshTarget,
|
|
556
|
+
resolveSelectedRemote,
|
|
557
|
+
resolveRigOmpConfigOverlayPath,
|
|
558
|
+
resolveRelayUrl,
|
|
559
|
+
resolveRegistryBaseUrl,
|
|
560
|
+
resolveOwnerNamespaceKey,
|
|
561
|
+
resolveDispatchTransportPlacement,
|
|
562
|
+
removeManagedRemoteEndpoint,
|
|
563
|
+
registerBackboneDefaults,
|
|
564
|
+
normalizeString,
|
|
565
|
+
markAuthorityRemoteEndpointConnected,
|
|
566
|
+
loadRemotesConfig,
|
|
567
|
+
listManagedRemoteEndpoints,
|
|
568
|
+
listAuthorityRemoteEndpoints,
|
|
569
|
+
importLegacyRemoteEndpoints,
|
|
570
|
+
doctorAuthorityRemoteEndpoints,
|
|
571
|
+
RemoteCliError,
|
|
572
|
+
REMOTES_CONFIG_PATH,
|
|
573
|
+
DEFAULT_REMOTE_PORT
|
|
574
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/root-resolver.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { dirname, parse, resolve } from "path";
|
|
5
|
+
|
|
6
|
+
// packages/core/src/layout.ts
|
|
7
|
+
import {
|
|
8
|
+
RIG_ARTIFACTS_DIRNAME,
|
|
9
|
+
RIG_DEFINITION_DIRNAME,
|
|
10
|
+
RIG_STATE_DIRNAME
|
|
11
|
+
} from "@rig/contracts";
|
|
12
|
+
|
|
13
|
+
// packages/core/src/root-resolver.ts
|
|
14
|
+
function hasProjectMarker(candidate) {
|
|
15
|
+
return existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME));
|
|
16
|
+
}
|
|
17
|
+
function resolveProjectRoot(options) {
|
|
18
|
+
const cwd = options?.cwd || process.cwd();
|
|
19
|
+
const envRoot = options?.projectRootEnv || process.env.PROJECT_RIG_ROOT || "";
|
|
20
|
+
const fallbackFromDir = options?.fallbackFromDir || cwd;
|
|
21
|
+
if (envRoot && hasProjectMarker(envRoot)) {
|
|
22
|
+
return envRoot;
|
|
23
|
+
}
|
|
24
|
+
const walked = walkUpForRoot(cwd);
|
|
25
|
+
if (walked) {
|
|
26
|
+
return walked;
|
|
27
|
+
}
|
|
28
|
+
const configRoot = readConfiguredRoot();
|
|
29
|
+
if (configRoot && hasProjectMarker(configRoot)) {
|
|
30
|
+
return configRoot;
|
|
31
|
+
}
|
|
32
|
+
let fileDir = resolve(fallbackFromDir);
|
|
33
|
+
for (let i = 0;i < 5; i += 1) {
|
|
34
|
+
if (hasProjectMarker(fileDir)) {
|
|
35
|
+
return fileDir;
|
|
36
|
+
}
|
|
37
|
+
fileDir = dirname(fileDir);
|
|
38
|
+
}
|
|
39
|
+
return cwd;
|
|
40
|
+
}
|
|
41
|
+
function walkUpForRoot(start) {
|
|
42
|
+
let searchDir = resolve(start);
|
|
43
|
+
const root = parse(searchDir).root || "/";
|
|
44
|
+
while (searchDir !== root) {
|
|
45
|
+
if (hasProjectMarker(searchDir)) {
|
|
46
|
+
return searchDir;
|
|
47
|
+
}
|
|
48
|
+
searchDir = dirname(searchDir);
|
|
49
|
+
}
|
|
50
|
+
if (hasProjectMarker(root)) {
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
function readConfiguredRoot() {
|
|
56
|
+
const configPath = resolve(process.env.HOME || "~", ".config", "project-rig", "root");
|
|
57
|
+
if (!existsSync(configPath)) {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const value = readFileSync(configPath, "utf-8").split(/\r?\n/)[0]?.trim() || "";
|
|
62
|
+
return value;
|
|
63
|
+
} catch {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
resolveProjectRoot
|
|
69
|
+
};
|