@h-rig/cli-surface-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158
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/app/drone-ui.d.ts +0 -11
- package/dist/src/app/drone-ui.js +0 -114
- package/dist/src/commands/_async-ui.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +0 -29
- package/dist/src/commands/_cli-format.js +59 -113
- package/dist/src/commands/_connection-state.d.ts +6 -33
- package/dist/src/commands/_connection-state.js +654 -138
- package/dist/src/commands/_doctor-checks.d.ts +2 -5
- package/dist/src/commands/_doctor-checks.js +10 -9
- package/dist/src/commands/_help-catalog.d.ts +2 -1
- package/dist/src/commands/_help-catalog.js +654 -7
- package/dist/src/commands/_inprocess-services.d.ts +5 -5
- package/dist/src/commands/_inprocess-services.js +1 -1
- package/dist/src/commands/_parsers.js +651 -12
- package/dist/src/commands/_paths.d.ts +0 -2
- package/dist/src/commands/_paths.js +2 -10
- package/dist/src/commands/_pi-install.d.ts +2 -12
- package/dist/src/commands/_pi-install.js +3 -36
- package/dist/src/commands/_policy.js +659 -20
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +675 -24
- package/dist/src/commands/config.d.ts +1 -1
- package/dist/src/commands/config.js +656 -21
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +828 -102
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +658 -12
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +658 -19
- package/dist/src/commands/inbox.d.ts +12 -8
- package/dist/src/commands/inbox.js +741 -22
- package/dist/src/commands/init.d.ts +17 -19
- package/dist/src/commands/init.js +836 -306
- package/dist/src/commands/inspect.d.ts +5 -6
- package/dist/src/commands/inspect.js +754 -42
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/pi.js +655 -16
- package/dist/src/commands/plugin.d.ts +9 -9
- package/dist/src/commands/plugin.js +652 -13
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +655 -16
- package/dist/src/commands/queue.d.ts +1 -1
- package/dist/src/commands/queue.js +871 -12
- package/dist/src/commands/remote-client.d.ts +152 -0
- package/dist/src/commands/remote-client.js +475 -0
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +1100 -29
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +2321 -47
- package/dist/src/commands/run.d.ts +10 -6
- package/dist/src/commands/run.js +830 -50
- package/dist/src/commands/server.d.ts +1 -1
- package/dist/src/commands/server.js +649 -11
- package/dist/src/commands/setup.d.ts +2 -2
- package/dist/src/commands/setup.js +829 -18
- package/dist/src/commands/stats.d.ts +2 -4
- package/dist/src/commands/stats.js +1299 -20
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +648 -9
- package/dist/src/commands/triage.d.ts +2 -3
- package/dist/src/commands/triage.js +657 -11
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands/workspace.js +1280 -15
- package/dist/src/control-plane/agent-binary-build.d.ts +9 -0
- package/dist/src/control-plane/agent-binary-build.js +88 -0
- package/dist/src/control-plane/embedded-native-assets.d.ts +7 -0
- package/dist/src/control-plane/embedded-native-assets.js +6 -0
- package/dist/src/control-plane/guard.d.ts +17 -0
- package/dist/src/control-plane/guard.js +684 -0
- package/dist/src/control-plane/harness-cli.d.ts +12 -0
- package/dist/src/control-plane/harness-cli.js +1623 -0
- package/dist/src/control-plane/native/git-ops.d.ts +67 -0
- package/dist/src/control-plane/native/git-ops.js +1381 -0
- package/dist/src/control-plane/native/github-auth-env.d.ts +1 -0
- package/dist/src/control-plane/native/github-auth-env.js +21 -0
- package/dist/src/control-plane/native/host-git.d.ts +4 -0
- package/dist/src/control-plane/native/host-git.js +51 -0
- package/dist/src/control-plane/priority-queue.d.ts +22 -0
- package/dist/src/control-plane/priority-queue.js +212 -0
- package/dist/src/control-plane/rigfig.d.ts +9 -0
- package/dist/src/control-plane/rigfig.js +70 -0
- package/dist/src/control-plane/scope.d.ts +3 -0
- package/dist/src/control-plane/scope.js +58 -0
- package/dist/src/control-plane/setup-status.d.ts +44 -0
- package/dist/src/control-plane/setup-status.js +164 -0
- package/dist/src/control-plane/task-data.d.ts +2 -0
- package/dist/src/control-plane/task-data.js +12 -0
- package/dist/src/control-plane/workspace-ops.d.ts +79 -0
- package/dist/src/control-plane/workspace-ops.js +639 -0
- package/dist/src/help-catalog-data.d.ts +7 -0
- package/dist/src/help-catalog-data.js +660 -0
- package/dist/src/kernel-dispatch.js +1 -3
- package/dist/src/plugin.js +10072 -30
- package/dist/src/runner.d.ts +7 -9
- package/dist/src/runner.js +750 -30
- package/package.json +12 -13
- package/dist/src/commands/_json-output.d.ts +0 -11
- package/dist/src/commands/_json-output.js +0 -54
- package/dist/src/commands/_pi-frontend.d.ts +0 -35
- package/dist/src/commands/_pi-frontend.js +0 -64
- package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
- package/dist/src/commands/_run-driver-helpers.js +0 -132
- package/dist/src/commands/task-run-driver.d.ts +0 -93
- package/dist/src/commands/task-run-driver.js +0 -136
- package/dist/src/commands/task.d.ts +0 -46
- package/dist/src/commands/task.js +0 -555
- package/dist/src/provider-model.d.ts +0 -34
- package/dist/src/provider-model.js +0 -56
- package/dist/src/rig-config-package-deps.d.ts +0 -10
- package/dist/src/rig-config-package-deps.js +0 -272
- package/dist/src/version.d.ts +0 -8
- package/dist/src/version.js +0 -47
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/workspace-ops.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync,
|
|
6
|
+
closeSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
openSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
writeFileSync
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { join, resolve } from "path";
|
|
15
|
+
import { TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
16
|
+
import { defineCapability } from "@rig/core/capability";
|
|
17
|
+
import { requireInstalledCapability } from "@rig/core/capability-loaders";
|
|
18
|
+
var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
|
|
19
|
+
var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before reading workspace task counts.");
|
|
20
|
+
var ONLINE_REMOTE_HOST_STATUSES = new Set(["ready", "busy", "degraded", "draining"]);
|
|
21
|
+
function asRecord(value) {
|
|
22
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
23
|
+
}
|
|
24
|
+
function asString(value) {
|
|
25
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
26
|
+
}
|
|
27
|
+
function asStringArray(value) {
|
|
28
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim().length > 0 ? [entry.trim()] : []) : [];
|
|
29
|
+
}
|
|
30
|
+
function asStringMap(value) {
|
|
31
|
+
const record = asRecord(value);
|
|
32
|
+
if (!record) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
const entries = Object.entries(record).flatMap(([key, entry]) => {
|
|
36
|
+
if (typeof entry === "string" && entry.trim().length > 0) {
|
|
37
|
+
return [[key, entry]];
|
|
38
|
+
}
|
|
39
|
+
if (typeof entry === "number" || typeof entry === "boolean") {
|
|
40
|
+
return [[key, String(entry)]];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
});
|
|
44
|
+
return Object.fromEntries(entries);
|
|
45
|
+
}
|
|
46
|
+
function parseScalar(raw) {
|
|
47
|
+
const value = raw.trim();
|
|
48
|
+
if (value.length === 0)
|
|
49
|
+
return "";
|
|
50
|
+
if (value === "null")
|
|
51
|
+
return null;
|
|
52
|
+
if (value === "true")
|
|
53
|
+
return true;
|
|
54
|
+
if (value === "false")
|
|
55
|
+
return false;
|
|
56
|
+
if (/^-?\d+$/.test(value))
|
|
57
|
+
return Number.parseInt(value, 10);
|
|
58
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
59
|
+
return Number.parseFloat(value);
|
|
60
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
61
|
+
return value.slice(1, -1);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function parseYamlSubset(source) {
|
|
66
|
+
const lines = source.split(/\r?\n/).map((line) => line.replace(/\t/g, " ")).filter((line) => line.trim().length > 0 && !line.trimStart().startsWith("#"));
|
|
67
|
+
const root = {};
|
|
68
|
+
const stack = [
|
|
69
|
+
{ indent: -1, value: root }
|
|
70
|
+
];
|
|
71
|
+
const ensureContainer = (indent) => {
|
|
72
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
73
|
+
stack.pop();
|
|
74
|
+
}
|
|
75
|
+
return stack[stack.length - 1];
|
|
76
|
+
};
|
|
77
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
78
|
+
const rawLine = lines[index];
|
|
79
|
+
const indent = rawLine.match(/^ */)?.[0].length ?? 0;
|
|
80
|
+
const line = rawLine.trim();
|
|
81
|
+
const parent = ensureContainer(indent);
|
|
82
|
+
if (line.startsWith("- ")) {
|
|
83
|
+
const itemContent = line.slice(2);
|
|
84
|
+
if (!Array.isArray(parent.value)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (itemContent.includes(":")) {
|
|
88
|
+
const [firstKey, ...rest] = itemContent.split(":");
|
|
89
|
+
const valueText2 = rest.join(":").trim();
|
|
90
|
+
const entry = {};
|
|
91
|
+
entry[firstKey.trim()] = valueText2.length > 0 ? parseScalar(valueText2) : "";
|
|
92
|
+
parent.value.push(entry);
|
|
93
|
+
stack.push({ indent, value: entry });
|
|
94
|
+
} else {
|
|
95
|
+
parent.value.push(parseScalar(itemContent));
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const colonIndex = line.indexOf(":");
|
|
100
|
+
if (colonIndex === -1 || !asRecord(parent.value)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const key = line.slice(0, colonIndex).trim();
|
|
104
|
+
const valueText = line.slice(colonIndex + 1).trim();
|
|
105
|
+
const parentRecord = parent.value;
|
|
106
|
+
if (valueText.length > 0) {
|
|
107
|
+
parentRecord[key] = parseScalar(valueText);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const nextLine = lines[index + 1]?.trim() ?? "";
|
|
111
|
+
const container = nextLine.startsWith("- ") ? [] : {};
|
|
112
|
+
parentRecord[key] = container;
|
|
113
|
+
stack.push({ indent, value: container });
|
|
114
|
+
}
|
|
115
|
+
return root;
|
|
116
|
+
}
|
|
117
|
+
function relativePath(rootPath, targetPath) {
|
|
118
|
+
return targetPath.startsWith(rootPath) ? targetPath.slice(rootPath.length + 1) || "." : targetPath;
|
|
119
|
+
}
|
|
120
|
+
function resolveRuntimeWorkspaceRoot(projectRoot) {
|
|
121
|
+
return resolve(process.env.RIG_TASK_WORKSPACE?.trim() || projectRoot);
|
|
122
|
+
}
|
|
123
|
+
function resolveServiceFabricPaths(projectRoot) {
|
|
124
|
+
const workspaceRoot = resolveRuntimeWorkspaceRoot(projectRoot);
|
|
125
|
+
const fabricRoot = resolve(workspaceRoot, ".rig", "service-fabric");
|
|
126
|
+
return {
|
|
127
|
+
workspaceRoot,
|
|
128
|
+
fabricRoot,
|
|
129
|
+
statePath: resolve(fabricRoot, "fabric-state.json"),
|
|
130
|
+
logsDir: resolve(fabricRoot, "logs")
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function issueStatus(status) {
|
|
134
|
+
switch (status) {
|
|
135
|
+
case "ready":
|
|
136
|
+
case "open":
|
|
137
|
+
case "queued":
|
|
138
|
+
case "blocked":
|
|
139
|
+
case "completed":
|
|
140
|
+
case "draft":
|
|
141
|
+
case "cancelled":
|
|
142
|
+
return status;
|
|
143
|
+
case "in_progress":
|
|
144
|
+
case "under_review":
|
|
145
|
+
return "running";
|
|
146
|
+
case "unknown":
|
|
147
|
+
return "unknown";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function collectTaskCounts(projectRoot) {
|
|
151
|
+
const initial = {
|
|
152
|
+
total: 0,
|
|
153
|
+
ready: 0,
|
|
154
|
+
open: 0,
|
|
155
|
+
queued: 0,
|
|
156
|
+
running: 0,
|
|
157
|
+
blocked: 0,
|
|
158
|
+
failed: 0,
|
|
159
|
+
unknown: 0,
|
|
160
|
+
completed: 0,
|
|
161
|
+
draft: 0,
|
|
162
|
+
cancelled: 0
|
|
163
|
+
};
|
|
164
|
+
const snapshot = taskData().readSyncedTrackerState(projectRoot, {}, { allowLocalFallback: true });
|
|
165
|
+
for (const issue of snapshot.issues) {
|
|
166
|
+
if (!issue.id.startsWith("bd-"))
|
|
167
|
+
continue;
|
|
168
|
+
if (issue.issueType === "epic")
|
|
169
|
+
continue;
|
|
170
|
+
initial.total += 1;
|
|
171
|
+
initial[issueStatus(issue.status)] += 1;
|
|
172
|
+
}
|
|
173
|
+
return initial;
|
|
174
|
+
}
|
|
175
|
+
function listManifestDirs(rootPath) {
|
|
176
|
+
const candidates = [join(rootPath, "microservices")];
|
|
177
|
+
const discovered = new Set;
|
|
178
|
+
for (const candidate of candidates) {
|
|
179
|
+
if (!existsSync(candidate))
|
|
180
|
+
continue;
|
|
181
|
+
for (const entry of readdirSync(candidate, { withFileTypes: true })) {
|
|
182
|
+
if (!entry.isDirectory() || entry.name.startsWith("_"))
|
|
183
|
+
continue;
|
|
184
|
+
const serviceDir = join(candidate, entry.name);
|
|
185
|
+
if (existsSync(join(serviceDir, "service.yaml"))) {
|
|
186
|
+
discovered.add(serviceDir);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
191
|
+
}
|
|
192
|
+
function parsePortValue(value) {
|
|
193
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
194
|
+
return Number(value);
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === "string") {
|
|
197
|
+
const parsed = Number(value);
|
|
198
|
+
if (Number.isFinite(parsed)) {
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
function resolveMonorepoWorkspaceRoot(projectRoot) {
|
|
205
|
+
return resolve(process.env.MONOREPO_ROOT?.trim() || projectRoot);
|
|
206
|
+
}
|
|
207
|
+
function resolveLocalWorkingDir(monorepoRoot, serviceDir, rawWorkingDir) {
|
|
208
|
+
if (!rawWorkingDir) {
|
|
209
|
+
return serviceDir;
|
|
210
|
+
}
|
|
211
|
+
if (rawWorkingDir.startsWith("/")) {
|
|
212
|
+
return rawWorkingDir;
|
|
213
|
+
}
|
|
214
|
+
if (rawWorkingDir.startsWith(".")) {
|
|
215
|
+
return resolve(serviceDir, rawWorkingDir);
|
|
216
|
+
}
|
|
217
|
+
return resolve(monorepoRoot, rawWorkingDir);
|
|
218
|
+
}
|
|
219
|
+
function readNativeServicePlans(projectRoot, selectedServices = []) {
|
|
220
|
+
const manifestDirs = listManifestDirs(projectRoot);
|
|
221
|
+
const warnings = [];
|
|
222
|
+
const services = [];
|
|
223
|
+
const monorepoRoot = resolveMonorepoWorkspaceRoot(projectRoot);
|
|
224
|
+
for (const serviceDir of manifestDirs) {
|
|
225
|
+
const manifestPath = join(serviceDir, "service.yaml");
|
|
226
|
+
try {
|
|
227
|
+
const parsed = asRecord(parseYamlSubset(readFileSync(manifestPath, "utf-8")));
|
|
228
|
+
const name = asString(parsed?.name) ?? serviceDir.split("/").at(-1) ?? "service";
|
|
229
|
+
if (selectedServices.length > 0 && !selectedServices.includes(name)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const localDev = asRecord(parsed?.local_dev);
|
|
233
|
+
const runtime = asString(parsed?.runtime) ?? "unknown";
|
|
234
|
+
const healthcheck = asString(parsed?.healthcheck) ?? "/health";
|
|
235
|
+
const routePrefix = asString(localDev?.route_prefix);
|
|
236
|
+
const command = asString(localDev?.command);
|
|
237
|
+
const requestedMode = asString(localDev?.mode);
|
|
238
|
+
const environment = asStringMap(localDev?.env);
|
|
239
|
+
const port = parsePortValue(environment.PORT) || parsePortValue(environment.HTTP_PORT) || parsePortValue(parsed?.port) || 0;
|
|
240
|
+
const workingDir = resolveLocalWorkingDir(monorepoRoot, serviceDir, asString(localDev?.working_dir));
|
|
241
|
+
const readinessPath = asString(localDev?.readiness_path) ?? healthcheck;
|
|
242
|
+
let mode = requestedMode === "process" || requestedMode !== "stub" && command ? "process" : "stub";
|
|
243
|
+
if (requestedMode === "proxy" && !command) {
|
|
244
|
+
warnings.push(`${relativePath(projectRoot, manifestPath)} declares local_dev.mode=proxy without a command; treating it as a stub`);
|
|
245
|
+
mode = "stub";
|
|
246
|
+
}
|
|
247
|
+
if (mode === "process" && !command) {
|
|
248
|
+
warnings.push(`${relativePath(projectRoot, manifestPath)} declares process local_dev mode without a command; treating it as a stub`);
|
|
249
|
+
mode = "stub";
|
|
250
|
+
}
|
|
251
|
+
services.push({
|
|
252
|
+
name,
|
|
253
|
+
relativeRootPath: relativePath(projectRoot, serviceDir),
|
|
254
|
+
absoluteRootPath: serviceDir,
|
|
255
|
+
runtime,
|
|
256
|
+
port,
|
|
257
|
+
healthcheck,
|
|
258
|
+
routePrefix,
|
|
259
|
+
mode,
|
|
260
|
+
command: mode === "process" ? command : null,
|
|
261
|
+
workingDir,
|
|
262
|
+
environment,
|
|
263
|
+
readinessPath
|
|
264
|
+
});
|
|
265
|
+
} catch (error) {
|
|
266
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { services, warnings };
|
|
270
|
+
}
|
|
271
|
+
function readPersistedFabricState(projectRoot) {
|
|
272
|
+
const { statePath } = resolveServiceFabricPaths(projectRoot);
|
|
273
|
+
if (!existsSync(statePath)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function writePersistedFabricState(projectRoot, state) {
|
|
283
|
+
const { fabricRoot, logsDir, statePath } = resolveServiceFabricPaths(projectRoot);
|
|
284
|
+
mkdirSync(fabricRoot, { recursive: true });
|
|
285
|
+
mkdirSync(logsDir, { recursive: true });
|
|
286
|
+
writeFileSync(statePath, `${JSON.stringify(state, null, 2)}
|
|
287
|
+
`, "utf-8");
|
|
288
|
+
}
|
|
289
|
+
function isProcessRunning(pid) {
|
|
290
|
+
if (!pid || pid <= 0) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
process.kill(pid, 0);
|
|
295
|
+
return true;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function stopProcess(pid) {
|
|
301
|
+
if (!pid || pid <= 0) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
process.kill(pid, "SIGTERM");
|
|
306
|
+
} catch {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const deadline = Date.now() + 5000;
|
|
310
|
+
while (Date.now() < deadline) {
|
|
311
|
+
if (!isProcessRunning(pid)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
await Bun.sleep(100);
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
process.kill(pid, "SIGKILL");
|
|
318
|
+
} catch {}
|
|
319
|
+
}
|
|
320
|
+
async function probeReadiness(service, timeoutMs = 1000) {
|
|
321
|
+
if (service.mode === "stub") {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (!isProcessRunning(service.pid) || service.port <= 0) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
const controller = new AbortController;
|
|
328
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
329
|
+
try {
|
|
330
|
+
const response = await fetch(`http://127.0.0.1:${service.port}${service.readinessPath}`, {
|
|
331
|
+
signal: controller.signal
|
|
332
|
+
});
|
|
333
|
+
return response.ok;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
} finally {
|
|
337
|
+
clearTimeout(timeout);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function waitForServiceHealthy(service, timeoutMs = 30000) {
|
|
341
|
+
if (service.mode === "stub") {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
const deadline = Date.now() + timeoutMs;
|
|
345
|
+
while (Date.now() < deadline) {
|
|
346
|
+
if (await probeReadiness(service, 1000)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
if (!isProcessRunning(service.pid)) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
await Bun.sleep(250);
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
function materializeStateFromPlans(projectRoot, plans, persisted) {
|
|
357
|
+
const persistedByName = new Map((persisted?.services ?? []).map((service) => [service.name, service]));
|
|
358
|
+
const { logsDir } = resolveServiceFabricPaths(projectRoot);
|
|
359
|
+
return plans.map((plan) => {
|
|
360
|
+
const previous = persistedByName.get(plan.name);
|
|
361
|
+
return {
|
|
362
|
+
...plan,
|
|
363
|
+
pid: previous?.pid ?? null,
|
|
364
|
+
logPath: previous?.logPath ?? resolve(logsDir, `${plan.name}.log`),
|
|
365
|
+
status: previous?.status ?? "stopped"
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
function summarizeFabricState(projectRoot, services, warnings, updatedAt) {
|
|
370
|
+
const { fabricRoot } = resolveServiceFabricPaths(projectRoot);
|
|
371
|
+
const healthyServiceCount = services.filter((service) => service.status === "healthy").length;
|
|
372
|
+
const status = services.length === 0 ? "empty" : services.every((service) => service.status === "stopped") ? "stopped" : services.every((service) => service.status === "healthy") ? "ready" : services.some((service) => service.status === "booting") ? "booting" : "degraded";
|
|
373
|
+
return {
|
|
374
|
+
updatedAt,
|
|
375
|
+
status,
|
|
376
|
+
serviceCount: services.length,
|
|
377
|
+
healthyServiceCount,
|
|
378
|
+
stateDir: fabricRoot,
|
|
379
|
+
services: services.map((service) => ({
|
|
380
|
+
name: service.name,
|
|
381
|
+
relativeRootPath: service.relativeRootPath,
|
|
382
|
+
absoluteRootPath: service.absoluteRootPath,
|
|
383
|
+
mode: service.mode,
|
|
384
|
+
port: service.port,
|
|
385
|
+
healthcheck: service.healthcheck,
|
|
386
|
+
routePrefix: service.routePrefix,
|
|
387
|
+
command: service.command,
|
|
388
|
+
environment: service.environment,
|
|
389
|
+
status: service.status,
|
|
390
|
+
pid: service.pid,
|
|
391
|
+
logPath: service.logPath
|
|
392
|
+
})),
|
|
393
|
+
warnings
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function readWorkspaceTopology(projectRoot, compiledAt = new Date().toISOString()) {
|
|
397
|
+
const manifestDirs = listManifestDirs(projectRoot);
|
|
398
|
+
const warnings = [];
|
|
399
|
+
const services = [];
|
|
400
|
+
for (const serviceDir of manifestDirs) {
|
|
401
|
+
const manifestPath = join(serviceDir, "service.yaml");
|
|
402
|
+
try {
|
|
403
|
+
const parsed = asRecord(parseYamlSubset(readFileSync(manifestPath, "utf-8")));
|
|
404
|
+
const name = asString(parsed?.name) ?? serviceDir.split("/").at(-1) ?? "service";
|
|
405
|
+
const runtime = asString(parsed?.runtime) ?? "unknown";
|
|
406
|
+
const portValue = typeof parsed?.port === "number" ? parsed.port : typeof parsed?.port === "string" ? Number(parsed.port) : NaN;
|
|
407
|
+
services.push({
|
|
408
|
+
name,
|
|
409
|
+
relativeRootPath: relativePath(projectRoot, serviceDir),
|
|
410
|
+
runtime,
|
|
411
|
+
port: Number.isFinite(portValue) ? Number(portValue) : null,
|
|
412
|
+
healthcheck: asString(parsed?.healthcheck),
|
|
413
|
+
splitReadyChecklist: asStringArray(parsed?.split_ready_checklist)
|
|
414
|
+
});
|
|
415
|
+
} catch (error) {
|
|
416
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
compiledAt,
|
|
421
|
+
status: services.length === 0 ? "empty" : warnings.length > 0 ? "degraded" : "ready",
|
|
422
|
+
manifestCount: manifestDirs.length,
|
|
423
|
+
serviceCount: services.length,
|
|
424
|
+
services,
|
|
425
|
+
warnings
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function discoverRemoteHostManifests(projectRoot) {
|
|
429
|
+
const workspaceRoot = resolveRuntimeWorkspaceRoot(projectRoot);
|
|
430
|
+
return [
|
|
431
|
+
join(projectRoot, "rig.remote-hosts.yaml"),
|
|
432
|
+
resolve(workspaceRoot, ".rig", "remote-hosts.yaml")
|
|
433
|
+
].filter((candidate) => existsSync(candidate));
|
|
434
|
+
}
|
|
435
|
+
function readWorkspaceRemoteFleet(projectRoot, updatedAt = new Date().toISOString()) {
|
|
436
|
+
const manifestPaths = discoverRemoteHostManifests(projectRoot);
|
|
437
|
+
const warnings = [];
|
|
438
|
+
const hostsById = new Map;
|
|
439
|
+
for (const manifestPath of manifestPaths) {
|
|
440
|
+
try {
|
|
441
|
+
const parsed = asRecord(parseYamlSubset(readFileSync(manifestPath, "utf-8")));
|
|
442
|
+
const hosts2 = Array.isArray(parsed?.hosts) ? parsed.hosts : [];
|
|
443
|
+
for (const entry of hosts2) {
|
|
444
|
+
const host = asRecord(entry);
|
|
445
|
+
const id = asString(host?.id);
|
|
446
|
+
const name = asString(host?.name);
|
|
447
|
+
const baseUrl = asString(host?.base_url);
|
|
448
|
+
if (!id || !name || !baseUrl) {
|
|
449
|
+
warnings.push(`Skipped invalid remote host entry in ${relativePath(projectRoot, manifestPath)}`);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
hostsById.set(id, {
|
|
453
|
+
id,
|
|
454
|
+
name,
|
|
455
|
+
baseUrl,
|
|
456
|
+
workspacePath: asString(host?.workspace_path),
|
|
457
|
+
transport: asString(host?.transport) ?? "websocket",
|
|
458
|
+
hostname: asString(host?.hostname),
|
|
459
|
+
region: asString(host?.region),
|
|
460
|
+
labels: asStringArray(host?.labels),
|
|
461
|
+
capabilities: asStringArray(host?.capabilities),
|
|
462
|
+
runtimeAdapters: ["pi"],
|
|
463
|
+
status: asString(host?.status) ?? "offline",
|
|
464
|
+
currentLeaseCount: typeof host?.current_lease_count === "number" ? Number(host.current_lease_count) : 0,
|
|
465
|
+
manifestPath: relativePath(projectRoot, manifestPath)
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
} catch (error) {
|
|
469
|
+
warnings.push(`Failed to parse ${relativePath(projectRoot, manifestPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const hosts = [...hostsById.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
473
|
+
const onlineHostCount = hosts.filter((host) => ONLINE_REMOTE_HOST_STATUSES.has(host.status)).length;
|
|
474
|
+
return {
|
|
475
|
+
updatedAt,
|
|
476
|
+
status: hosts.length === 0 ? "empty" : warnings.length > 0 || hosts.some((host) => host.status === "degraded" || host.status === "quarantined") ? "degraded" : onlineHostCount > 0 ? "ready" : "degraded",
|
|
477
|
+
manifestCount: manifestPaths.length,
|
|
478
|
+
hostCount: hosts.length,
|
|
479
|
+
onlineHostCount,
|
|
480
|
+
hosts,
|
|
481
|
+
warnings
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function readWorkspaceServiceFabric(projectRoot) {
|
|
485
|
+
try {
|
|
486
|
+
const updatedAt = new Date().toISOString();
|
|
487
|
+
const persisted = readPersistedFabricState(projectRoot);
|
|
488
|
+
const plans = readNativeServicePlans(projectRoot);
|
|
489
|
+
const services = materializeStateFromPlans(projectRoot, plans.services, persisted);
|
|
490
|
+
for (const service of services) {
|
|
491
|
+
if (service.status === "stopped") {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (!isProcessRunning(service.pid)) {
|
|
495
|
+
service.pid = null;
|
|
496
|
+
service.status = "stopped";
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
service.status = await probeReadiness(service, 1000) ? "healthy" : "degraded";
|
|
500
|
+
}
|
|
501
|
+
const state = {
|
|
502
|
+
updatedAt,
|
|
503
|
+
rootPath: projectRoot,
|
|
504
|
+
services
|
|
505
|
+
};
|
|
506
|
+
writePersistedFabricState(projectRoot, state);
|
|
507
|
+
return summarizeFabricState(projectRoot, services, plans.warnings, updatedAt);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
return {
|
|
510
|
+
status: "unavailable",
|
|
511
|
+
warnings: [error instanceof Error ? error.message : String(error)]
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async function mutateWorkspaceServiceFabric(projectRoot, command, services = []) {
|
|
516
|
+
const updatedAt = new Date().toISOString();
|
|
517
|
+
const persisted = readPersistedFabricState(projectRoot);
|
|
518
|
+
const plans = readNativeServicePlans(projectRoot, services);
|
|
519
|
+
const stateServices = materializeStateFromPlans(projectRoot, plans.services, persisted);
|
|
520
|
+
if (command === "verify") {
|
|
521
|
+
for (const service of stateServices) {
|
|
522
|
+
if (service.status === "stopped") {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (!isProcessRunning(service.pid)) {
|
|
526
|
+
service.pid = null;
|
|
527
|
+
service.status = "stopped";
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
service.status = await probeReadiness(service, 1000) ? "healthy" : "degraded";
|
|
531
|
+
}
|
|
532
|
+
} else if (command === "up") {
|
|
533
|
+
const { logsDir } = resolveServiceFabricPaths(projectRoot);
|
|
534
|
+
mkdirSync(logsDir, { recursive: true });
|
|
535
|
+
for (const service of stateServices) {
|
|
536
|
+
if (service.mode === "stub") {
|
|
537
|
+
service.pid = null;
|
|
538
|
+
service.status = "healthy";
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (isProcessRunning(service.pid) && await probeReadiness(service, 1000)) {
|
|
542
|
+
service.status = "healthy";
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (isProcessRunning(service.pid)) {
|
|
546
|
+
await stopProcess(service.pid);
|
|
547
|
+
}
|
|
548
|
+
const logPath = resolve(logsDir, `${service.name}.log`);
|
|
549
|
+
service.logPath = logPath;
|
|
550
|
+
appendFileSync(logPath, `
|
|
551
|
+
=== ${updatedAt} :: rig service-fabric up :: ${service.name} ===
|
|
552
|
+
`, "utf-8");
|
|
553
|
+
const logFd = openSync(logPath, "a");
|
|
554
|
+
try {
|
|
555
|
+
const child = spawn(service.command || "true", {
|
|
556
|
+
cwd: service.workingDir,
|
|
557
|
+
env: { ...process.env, ...service.environment },
|
|
558
|
+
shell: true,
|
|
559
|
+
detached: true,
|
|
560
|
+
stdio: ["ignore", logFd, logFd]
|
|
561
|
+
});
|
|
562
|
+
child.unref();
|
|
563
|
+
service.pid = child.pid ?? null;
|
|
564
|
+
service.status = "booting";
|
|
565
|
+
} finally {
|
|
566
|
+
closeSync(logFd);
|
|
567
|
+
}
|
|
568
|
+
service.status = await waitForServiceHealthy(service) ? "healthy" : "degraded";
|
|
569
|
+
if (service.status !== "healthy" && !isProcessRunning(service.pid)) {
|
|
570
|
+
service.pid = null;
|
|
571
|
+
service.status = "failed";
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} else if (command === "down") {
|
|
575
|
+
for (const service of stateServices) {
|
|
576
|
+
await stopProcess(service.pid);
|
|
577
|
+
service.pid = null;
|
|
578
|
+
service.status = "stopped";
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const merged = new Map;
|
|
582
|
+
for (const service of persisted?.services ?? []) {
|
|
583
|
+
merged.set(service.name, service);
|
|
584
|
+
}
|
|
585
|
+
for (const service of stateServices) {
|
|
586
|
+
merged.set(service.name, service);
|
|
587
|
+
}
|
|
588
|
+
const nextState = {
|
|
589
|
+
updatedAt,
|
|
590
|
+
rootPath: projectRoot,
|
|
591
|
+
services: [...merged.values()].sort((left, right) => left.name.localeCompare(right.name))
|
|
592
|
+
};
|
|
593
|
+
writePersistedFabricState(projectRoot, nextState);
|
|
594
|
+
return summarizeFabricState(projectRoot, stateServices, plans.warnings, updatedAt);
|
|
595
|
+
}
|
|
596
|
+
function countRuntimeDirs(projectRoot) {
|
|
597
|
+
const runtimeRoot = resolve(resolveRuntimeWorkspaceRoot(projectRoot), ".rig", "runtime", "agents");
|
|
598
|
+
if (!existsSync(runtimeRoot)) {
|
|
599
|
+
return 0;
|
|
600
|
+
}
|
|
601
|
+
return readdirSync(runtimeRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
|
|
602
|
+
}
|
|
603
|
+
function countArtifactTaskDirs(projectRoot) {
|
|
604
|
+
const artifactRoot = resolve(resolveRuntimeWorkspaceRoot(projectRoot), "artifacts");
|
|
605
|
+
if (!existsSync(artifactRoot)) {
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
return readdirSync(artifactRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "remote-runs").length;
|
|
609
|
+
}
|
|
610
|
+
async function readWorkspaceSummary(projectRoot) {
|
|
611
|
+
const generatedAt = new Date().toISOString();
|
|
612
|
+
const warnings = [];
|
|
613
|
+
const topology = readWorkspaceTopology(projectRoot, generatedAt);
|
|
614
|
+
const remoteFleet = readWorkspaceRemoteFleet(projectRoot, generatedAt);
|
|
615
|
+
const serviceFabric = await readWorkspaceServiceFabric(projectRoot);
|
|
616
|
+
if (serviceFabric?.warnings) {
|
|
617
|
+
warnings.push(...serviceFabric.warnings.map((warning) => String(warning)));
|
|
618
|
+
}
|
|
619
|
+
warnings.push(...topology.warnings, ...remoteFleet.warnings);
|
|
620
|
+
return {
|
|
621
|
+
rootPath: projectRoot,
|
|
622
|
+
taskCounts: collectTaskCounts(projectRoot),
|
|
623
|
+
runtimeCount: countRuntimeDirs(projectRoot),
|
|
624
|
+
artifactTaskCount: countArtifactTaskDirs(projectRoot),
|
|
625
|
+
failedApproachesPresent: existsSync(resolve(resolveRuntimeWorkspaceRoot(projectRoot), ".rig", "state", "failed_approaches.md")),
|
|
626
|
+
topology,
|
|
627
|
+
remoteFleet,
|
|
628
|
+
serviceFabric,
|
|
629
|
+
warnings,
|
|
630
|
+
generatedAt
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
export {
|
|
634
|
+
readWorkspaceTopology,
|
|
635
|
+
readWorkspaceSummary,
|
|
636
|
+
readWorkspaceServiceFabric,
|
|
637
|
+
readWorkspaceRemoteFleet,
|
|
638
|
+
mutateWorkspaceServiceFabric
|
|
639
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HelpCatalog, HelpCommand, HelpGroup, TopLevelSection } from "@rig/contracts";
|
|
2
|
+
export declare function helpCatalog(): HelpCatalog;
|
|
3
|
+
export declare const TOP_LEVEL_SECTIONS: TopLevelSection[];
|
|
4
|
+
export declare const PRIMARY_GROUPS: HelpGroup[];
|
|
5
|
+
export declare const ADVANCED_GROUPS: HelpGroup[];
|
|
6
|
+
export declare const ADVANCED_COMMANDS: HelpCommand[];
|
|
7
|
+
export declare const ALL_GROUPS: HelpGroup[];
|