@tomflow/proflow-platform-cli 0.1.42 → 0.1.44
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/deployment/adapter.d.ts +7 -7
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/cli.js +57 -0
- package/dist/src/lifecycle/thin.js +158 -60
- package/dist/src/persistence/start-owner.d.ts +3 -0
- package/dist/src/persistence/start-owner.js +118 -0
- package/package.json +3 -3
- package/proflow.module.json +1 -1
- package/CHANGELOG.md +0 -85
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
readonly ok: true;
|
|
7
7
|
readonly status: "SUCCEEDED";
|
|
8
8
|
readonly moduleRef: "platform-cli";
|
|
9
|
-
readonly moduleVersion: "0.1.
|
|
9
|
+
readonly moduleVersion: "0.1.44";
|
|
10
10
|
};
|
|
11
11
|
observedEffects: never[];
|
|
12
12
|
}>;
|
|
@@ -16,7 +16,7 @@ export declare const behaviorAdapter: {
|
|
|
16
16
|
readonly ok: true;
|
|
17
17
|
readonly status: "SUCCEEDED";
|
|
18
18
|
readonly moduleRef: "platform-cli";
|
|
19
|
-
readonly moduleVersion: "0.1.
|
|
19
|
+
readonly moduleVersion: "0.1.44";
|
|
20
20
|
};
|
|
21
21
|
observedEffects: never[];
|
|
22
22
|
}>;
|
|
@@ -26,7 +26,7 @@ export declare const behaviorAdapter: {
|
|
|
26
26
|
ok: true;
|
|
27
27
|
status: "SUCCEEDED";
|
|
28
28
|
moduleRef: "platform-cli";
|
|
29
|
-
moduleVersion: "0.1.
|
|
29
|
+
moduleVersion: "0.1.44";
|
|
30
30
|
data: {
|
|
31
31
|
readonly setupStatus: "READY";
|
|
32
32
|
readonly runtimeStatus: "NOT_APPLICABLE";
|
|
@@ -40,7 +40,7 @@ export declare const behaviorAdapter: {
|
|
|
40
40
|
readonly ok: true;
|
|
41
41
|
readonly status: "SUCCEEDED";
|
|
42
42
|
readonly moduleRef: "platform-cli";
|
|
43
|
-
readonly moduleVersion: "0.1.
|
|
43
|
+
readonly moduleVersion: "0.1.44";
|
|
44
44
|
};
|
|
45
45
|
observedEffects: never[];
|
|
46
46
|
}>;
|
|
@@ -50,7 +50,7 @@ export declare const behaviorAdapter: {
|
|
|
50
50
|
ok: true;
|
|
51
51
|
status: "SUCCEEDED";
|
|
52
52
|
moduleRef: "platform-cli";
|
|
53
|
-
moduleVersion: "0.1.
|
|
53
|
+
moduleVersion: "0.1.44";
|
|
54
54
|
data: {
|
|
55
55
|
docs: string;
|
|
56
56
|
};
|
|
@@ -63,7 +63,7 @@ export declare const behaviorAdapter: {
|
|
|
63
63
|
readonly ok: true;
|
|
64
64
|
readonly status: "SUCCEEDED";
|
|
65
65
|
readonly moduleRef: "platform-cli";
|
|
66
|
-
readonly moduleVersion: "0.1.
|
|
66
|
+
readonly moduleVersion: "0.1.44";
|
|
67
67
|
};
|
|
68
68
|
observedEffects: never[];
|
|
69
69
|
}>;
|
|
@@ -73,7 +73,7 @@ export declare const behaviorAdapter: {
|
|
|
73
73
|
readonly ok: true;
|
|
74
74
|
readonly status: "SUCCEEDED";
|
|
75
75
|
readonly moduleRef: "platform-cli";
|
|
76
|
-
readonly moduleVersion: "0.1.
|
|
76
|
+
readonly moduleVersion: "0.1.44";
|
|
77
77
|
};
|
|
78
78
|
observedEffects: never[];
|
|
79
79
|
}>;
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "platform-cli";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-platform-cli";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.44";
|
|
7
7
|
readonly kind: "cli";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "platform-cli",
|
|
5
5
|
packageName: "@tomflow/proflow-platform-cli",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.44",
|
|
7
7
|
kind: "cli",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { observeWorkspaceInstalledVersion, preflightWorkspacePackageManager, rem
|
|
|
11
11
|
import { cleanOwnedPnpmPolicy, observeMinimumReleaseAgeExclude, recordPnpmPolicyOwnership, } from "./install/pnpm-policy.js";
|
|
12
12
|
import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./lifecycle/index.js";
|
|
13
13
|
import { ensureWorkspaceStateIsGitIgnored } from "./persistence/git-isolation.js";
|
|
14
|
+
import { clearStartOwner, registerStartOwner, requestStartOwnerStop, } from "./persistence/start-owner.js";
|
|
14
15
|
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
15
16
|
import { reportProgress } from "./progress.js";
|
|
16
17
|
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
@@ -1148,15 +1149,71 @@ export function renderHumanResult(result, options = {}) {
|
|
|
1148
1149
|
return `${theme.success("✓ 已经卸载")}${result.workspaceRoot ? `\n${theme.muted("Workspace")} ${result.workspaceRoot}` : ""}`;
|
|
1149
1150
|
return `${result.status === "SUCCEEDED" ? theme.success("✓") : theme.failure("✕")} ${labels[result.command] ?? result.command}${result.status === "SUCCEEDED" ? "成功" : "未完成"}${result.workspaceRoot ? `\n${theme.muted("Workspace")} ${result.workspaceRoot}` : ""}`;
|
|
1150
1151
|
}
|
|
1152
|
+
function ownsStartedRuntime(result) {
|
|
1153
|
+
if (result.command !== "start" || !isRecord(result.data))
|
|
1154
|
+
return false;
|
|
1155
|
+
const results = Array.isArray(result.data.results)
|
|
1156
|
+
? result.data.results.filter(isRecord)
|
|
1157
|
+
: [];
|
|
1158
|
+
return results.some((item) => item.command === "start" &&
|
|
1159
|
+
Array.isArray(item.observedEffects) &&
|
|
1160
|
+
item.observedEffects.length > 0 &&
|
|
1161
|
+
isRecord(item.result) &&
|
|
1162
|
+
item.result.status === "SUCCEEDED");
|
|
1163
|
+
}
|
|
1151
1164
|
if (import.meta.main) {
|
|
1152
1165
|
const argv = process.argv.slice(2);
|
|
1153
1166
|
const reporter = createTerminalProgressReporter();
|
|
1167
|
+
let parsedForOwner;
|
|
1168
|
+
let ownerRoot;
|
|
1169
|
+
try {
|
|
1170
|
+
parsedForOwner = parseArgs(argv);
|
|
1171
|
+
if (parsedForOwner.command === "start" || parsedForOwner.command === "stop")
|
|
1172
|
+
ownerRoot = await canonicalWorkspace(process.cwd(), parsedForOwner.workspace);
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
// runCli owns normal usage/workspace error rendering.
|
|
1176
|
+
}
|
|
1177
|
+
if (parsedForOwner?.command === "stop" && ownerRoot) {
|
|
1178
|
+
reportProgress(reporter, {
|
|
1179
|
+
command: "stop",
|
|
1180
|
+
phase: "owner",
|
|
1181
|
+
status: "STARTED",
|
|
1182
|
+
message: "正在请求当前平台运行进程停止",
|
|
1183
|
+
});
|
|
1184
|
+
const requested = await requestStartOwnerStop(ownerRoot);
|
|
1185
|
+
reportProgress(reporter, {
|
|
1186
|
+
command: "stop",
|
|
1187
|
+
phase: "owner",
|
|
1188
|
+
status: requested === "TIMEOUT" ? "WARNING" : "SUCCEEDED",
|
|
1189
|
+
message: requested === "STOPPED"
|
|
1190
|
+
? "平台运行进程已停止"
|
|
1191
|
+
: "未发现需要接管的前台运行进程",
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1154
1194
|
const result = await runCli(argv, {
|
|
1155
1195
|
onProgress: reporter,
|
|
1156
1196
|
...(process.stdin.isTTY && process.stdout.isTTY
|
|
1157
1197
|
? { setupInteraction: createClackSetupInteraction() }
|
|
1158
1198
|
: {}),
|
|
1159
1199
|
});
|
|
1200
|
+
if (parsedForOwner?.command === "start" &&
|
|
1201
|
+
ownerRoot &&
|
|
1202
|
+
result.status === "SUCCEEDED" &&
|
|
1203
|
+
ownsStartedRuntime(result)) {
|
|
1204
|
+
await registerStartOwner(ownerRoot);
|
|
1205
|
+
let stopping = false;
|
|
1206
|
+
const gracefulStop = async () => {
|
|
1207
|
+
if (stopping)
|
|
1208
|
+
return;
|
|
1209
|
+
stopping = true;
|
|
1210
|
+
const stopped = await runCli(["stop", "--workspace", ownerRoot]);
|
|
1211
|
+
await clearStartOwner(ownerRoot);
|
|
1212
|
+
process.exit(stopped.status === "SUCCEEDED" ? 0 : 1);
|
|
1213
|
+
};
|
|
1214
|
+
process.once("SIGTERM", () => void gracefulStop());
|
|
1215
|
+
process.once("SIGINT", () => void gracefulStop());
|
|
1216
|
+
}
|
|
1160
1217
|
reporter.close();
|
|
1161
1218
|
const color = process.stdout.isTTY === true &&
|
|
1162
1219
|
process.env.NO_COLOR === undefined &&
|
|
@@ -222,27 +222,85 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
222
222
|
.map((moduleRef) => byRef.get(moduleRef))
|
|
223
223
|
.filter((module) => module !== undefined);
|
|
224
224
|
let completed = true;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
225
|
+
const temporaryRuntimeRefs = [];
|
|
226
|
+
const temporaryRuntimeSet = new Set();
|
|
227
|
+
const dependencyClosure = (moduleRef) => {
|
|
228
|
+
const closure = new Set();
|
|
229
|
+
const visit = (ref) => {
|
|
230
|
+
for (const dependency of dependenciesByRef.get(ref) ?? []) {
|
|
231
|
+
if (closure.has(dependency))
|
|
232
|
+
continue;
|
|
233
|
+
closure.add(dependency);
|
|
234
|
+
visit(dependency);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
visit(moduleRef);
|
|
238
|
+
return closure;
|
|
239
|
+
};
|
|
240
|
+
const ensureSetupRuntimeDependencies = async (module) => {
|
|
241
|
+
if (module.kind !== "agent-package")
|
|
242
|
+
return true;
|
|
243
|
+
const closure = dependencyClosure(module.moduleRef);
|
|
244
|
+
for (const dependencyRef of graph.order) {
|
|
245
|
+
if (!closure.has(dependencyRef))
|
|
246
|
+
continue;
|
|
247
|
+
const dependency = byRef.get(dependencyRef);
|
|
248
|
+
if (dependency?.kind !== "service")
|
|
249
|
+
continue;
|
|
250
|
+
const status = await dispatchModuleCommand(catalog, dependency, "status", context(workspaceRoot));
|
|
251
|
+
if (!succeeded(status.result)) {
|
|
252
|
+
blockers.push({
|
|
253
|
+
moduleRef: dependencyRef,
|
|
254
|
+
setupStatus: "FAILED",
|
|
255
|
+
reason: status.result.error?.message ?? "临时运行依赖状态检查失败",
|
|
256
|
+
});
|
|
257
|
+
completed = false;
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
261
|
+
if (observed.setupStatus !== "READY") {
|
|
262
|
+
const issue = observed.issues?.find((item) => item.scope === "SETUP");
|
|
263
|
+
blockers.push({
|
|
264
|
+
moduleRef: dependencyRef,
|
|
265
|
+
setupStatus: observed.setupStatus,
|
|
266
|
+
...(issue
|
|
267
|
+
? { reason: issue.message, nextCommand: issue.nextCommand }
|
|
268
|
+
: {}),
|
|
269
|
+
});
|
|
270
|
+
completed = false;
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (observed.runtimeStatus === "RUNNING" ||
|
|
274
|
+
observed.runtimeStatus === "NOT_APPLICABLE")
|
|
275
|
+
continue;
|
|
276
|
+
if (observed.runtimeStatus !== "STOPPED") {
|
|
277
|
+
blockers.push({
|
|
278
|
+
moduleRef: dependencyRef,
|
|
279
|
+
setupStatus: "FAILED",
|
|
280
|
+
reason: `临时运行依赖 ${dependencyRef} 处于 ${observed.runtimeStatus}`,
|
|
281
|
+
});
|
|
282
|
+
completed = false;
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const started = await dispatchModuleCommand(catalog, dependency, "start", context(workspaceRoot));
|
|
286
|
+
if (!succeeded(started.result)) {
|
|
287
|
+
blockers.push({
|
|
288
|
+
moduleRef: dependencyRef,
|
|
289
|
+
setupStatus: "FAILED",
|
|
290
|
+
reason: started.result.error?.message ?? `临时启动 ${dependencyRef} 失败`,
|
|
291
|
+
});
|
|
292
|
+
completed = false;
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (!temporaryRuntimeSet.has(dependencyRef)) {
|
|
296
|
+
temporaryRuntimeSet.add(dependencyRef);
|
|
297
|
+
temporaryRuntimeRefs.push(dependencyRef);
|
|
298
|
+
}
|
|
242
299
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
return true;
|
|
301
|
+
};
|
|
302
|
+
try {
|
|
303
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
246
304
|
reportProgress(reporter, {
|
|
247
305
|
command: "setup",
|
|
248
306
|
phase: "setup",
|
|
@@ -251,53 +309,93 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
251
309
|
current: index + 1,
|
|
252
310
|
total: modulesInOrder.length,
|
|
253
311
|
moduleRef: module.moduleRef,
|
|
254
|
-
status: "
|
|
312
|
+
status: "STARTED",
|
|
255
313
|
message: module.moduleRef,
|
|
256
314
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
? "ACTION_REQUIRED"
|
|
274
|
-
: "FAILED",
|
|
275
|
-
message: module.moduleRef,
|
|
276
|
-
});
|
|
277
|
-
if (!succeeded(setup.result)) {
|
|
278
|
-
completed = false;
|
|
279
|
-
}
|
|
280
|
-
const reconciled = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
281
|
-
if (!succeeded(reconciled.result)) {
|
|
282
|
-
results.push(reconciled);
|
|
283
|
-
completed = false;
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
|
|
287
|
-
if (reconciledStatus.setupStatus !== "READY") {
|
|
288
|
-
const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
|
|
289
|
-
blockers.push({
|
|
315
|
+
const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
316
|
+
if (!succeeded(status.result)) {
|
|
317
|
+
results.push(status);
|
|
318
|
+
completed = false;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
322
|
+
if (observed.setupStatus === "READY" && target === undefined) {
|
|
323
|
+
skipped.push({ moduleRef: module.moduleRef, reason: "READY" });
|
|
324
|
+
reportProgress(reporter, {
|
|
325
|
+
command: "setup",
|
|
326
|
+
phase: "setup",
|
|
327
|
+
kind: "detail",
|
|
328
|
+
retention: "REPLACE",
|
|
329
|
+
current: index + 1,
|
|
330
|
+
total: modulesInOrder.length,
|
|
290
331
|
moduleRef: module.moduleRef,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
? {}
|
|
294
|
-
: { reason: issue.message, nextCommand: issue.nextCommand }),
|
|
332
|
+
status: "SKIPPED",
|
|
333
|
+
message: module.moduleRef,
|
|
295
334
|
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (!(await ensureSetupRuntimeDependencies(module)))
|
|
338
|
+
break;
|
|
339
|
+
await beforeSetup?.(module.moduleRef);
|
|
340
|
+
const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
|
|
341
|
+
results.push(setup);
|
|
342
|
+
reportProgress(reporter, {
|
|
343
|
+
command: "setup",
|
|
344
|
+
phase: "setup",
|
|
345
|
+
kind: "detail",
|
|
346
|
+
retention: "REPLACE",
|
|
347
|
+
current: index + 1,
|
|
348
|
+
total: modulesInOrder.length,
|
|
349
|
+
moduleRef: module.moduleRef,
|
|
350
|
+
status: succeeded(setup.result)
|
|
351
|
+
? "SUCCEEDED"
|
|
352
|
+
: setup.result.status === "ACTION_REQUIRED"
|
|
353
|
+
? "ACTION_REQUIRED"
|
|
354
|
+
: "FAILED",
|
|
355
|
+
message: module.moduleRef,
|
|
356
|
+
});
|
|
357
|
+
if (!succeeded(setup.result)) {
|
|
296
358
|
completed = false;
|
|
297
359
|
}
|
|
360
|
+
const reconciled = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
361
|
+
if (!succeeded(reconciled.result)) {
|
|
362
|
+
results.push(reconciled);
|
|
363
|
+
completed = false;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
|
|
367
|
+
if (reconciledStatus.setupStatus !== "READY") {
|
|
368
|
+
const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
|
|
369
|
+
blockers.push({
|
|
370
|
+
moduleRef: module.moduleRef,
|
|
371
|
+
setupStatus: reconciledStatus.setupStatus,
|
|
372
|
+
...(issue === undefined
|
|
373
|
+
? {}
|
|
374
|
+
: { reason: issue.message, nextCommand: issue.nextCommand }),
|
|
375
|
+
});
|
|
376
|
+
completed = false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (!succeeded(setup.result) || blockers.length > 0)
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
for (const dependencyRef of [...temporaryRuntimeRefs].reverse()) {
|
|
385
|
+
const dependency = byRef.get(dependencyRef);
|
|
386
|
+
if (!dependency)
|
|
387
|
+
continue;
|
|
388
|
+
const stopped = await dispatchModuleCommand(catalog, dependency, "stop", context(workspaceRoot));
|
|
389
|
+
if (!succeeded(stopped.result)) {
|
|
390
|
+
completed = false;
|
|
391
|
+
blockers.push({
|
|
392
|
+
moduleRef: dependencyRef,
|
|
393
|
+
setupStatus: "FAILED",
|
|
394
|
+
reason: stopped.result.error?.message ??
|
|
395
|
+
`临时运行依赖 ${dependencyRef} 清理失败`,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
298
398
|
}
|
|
299
|
-
if (!succeeded(setup.result) || blockers.length > 0)
|
|
300
|
-
break;
|
|
301
399
|
}
|
|
302
400
|
return {
|
|
303
401
|
phase: "setup",
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function registerStartOwner(root: string): Promise<void>;
|
|
2
|
+
export declare function clearStartOwner(root: string, pid?: number): Promise<void>;
|
|
3
|
+
export declare function requestStartOwnerStop(root: string): Promise<"ABSENT" | "STOPPED" | "UNVERIFIED" | "TIMEOUT">;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile, realpath, rm } from "node:fs/promises";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { atomicWrite } from "../paths.js";
|
|
5
|
+
const CONTRACT = "proflow.platform-start-owner.v1";
|
|
6
|
+
const ownerFile = (root) => join(root, ".proflow", "runtime", "platform-cli", "start-owner.json");
|
|
7
|
+
function processAlive(pid) {
|
|
8
|
+
try {
|
|
9
|
+
process.kill(pid, 0);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function commandLine(pid) {
|
|
17
|
+
return new Promise((resolveCommand) => {
|
|
18
|
+
execFile("ps", ["-p", String(pid), "-o", "command="], { timeout: 2_000, encoding: "utf8" }, (error, stdout) => resolveCommand(error ? undefined : String(stdout).trim()));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function parseRecord(value) {
|
|
22
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
23
|
+
return undefined;
|
|
24
|
+
const item = value;
|
|
25
|
+
if (item.contract !== CONTRACT ||
|
|
26
|
+
typeof item.pid !== "number" ||
|
|
27
|
+
!Number.isInteger(item.pid) ||
|
|
28
|
+
item.pid <= 0 ||
|
|
29
|
+
typeof item.workspaceRoot !== "string" ||
|
|
30
|
+
typeof item.processCwd !== "string" ||
|
|
31
|
+
typeof item.argv1 !== "string" ||
|
|
32
|
+
typeof item.entrypointRealpath !== "string" ||
|
|
33
|
+
typeof item.startedAt !== "string" ||
|
|
34
|
+
Number.isNaN(Date.parse(item.startedAt)))
|
|
35
|
+
return undefined;
|
|
36
|
+
return item;
|
|
37
|
+
}
|
|
38
|
+
async function readOwner(root) {
|
|
39
|
+
try {
|
|
40
|
+
return parseRecord(JSON.parse(await readFile(ownerFile(root), "utf8")));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function ownerMatchesProcess(record) {
|
|
47
|
+
if (!processAlive(record.pid))
|
|
48
|
+
return false;
|
|
49
|
+
let observedEntrypoint;
|
|
50
|
+
try {
|
|
51
|
+
observedEntrypoint = await realpath(record.argv1);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (observedEntrypoint !== record.entrypointRealpath)
|
|
57
|
+
return false;
|
|
58
|
+
const observed = await commandLine(record.pid);
|
|
59
|
+
if (!observed)
|
|
60
|
+
return false;
|
|
61
|
+
const relativeArgv = relative(record.processCwd, record.argv1);
|
|
62
|
+
const entrypointMatches = [
|
|
63
|
+
record.argv1,
|
|
64
|
+
relativeArgv,
|
|
65
|
+
`./${relativeArgv}`,
|
|
66
|
+
].some((candidate) => candidate.length > 0 && observed.includes(candidate));
|
|
67
|
+
return entrypointMatches && /(?:^|\s)start(?:\s|$)/.test(observed);
|
|
68
|
+
}
|
|
69
|
+
async function waitForExit(pid, timeoutMs = 60_000) {
|
|
70
|
+
const deadline = Date.now() + timeoutMs;
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
if (!processAlive(pid))
|
|
73
|
+
return true;
|
|
74
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
75
|
+
}
|
|
76
|
+
return !processAlive(pid);
|
|
77
|
+
}
|
|
78
|
+
export async function registerStartOwner(root) {
|
|
79
|
+
const argv1 = process.argv[1];
|
|
80
|
+
if (!argv1)
|
|
81
|
+
throw new Error("platform start entrypoint is unavailable");
|
|
82
|
+
const record = {
|
|
83
|
+
contract: CONTRACT,
|
|
84
|
+
pid: process.pid,
|
|
85
|
+
workspaceRoot: root,
|
|
86
|
+
processCwd: process.cwd(),
|
|
87
|
+
argv1,
|
|
88
|
+
entrypointRealpath: await realpath(argv1),
|
|
89
|
+
startedAt: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
await atomicWrite(ownerFile(root), `${JSON.stringify(record, null, 2)}\n`);
|
|
92
|
+
}
|
|
93
|
+
export async function clearStartOwner(root, pid = process.pid) {
|
|
94
|
+
const current = await readOwner(root);
|
|
95
|
+
if (current?.pid !== pid)
|
|
96
|
+
return;
|
|
97
|
+
await rm(ownerFile(root), { force: true });
|
|
98
|
+
}
|
|
99
|
+
export async function requestStartOwnerStop(root) {
|
|
100
|
+
const record = await readOwner(root);
|
|
101
|
+
if (!record)
|
|
102
|
+
return "ABSENT";
|
|
103
|
+
if (record.workspaceRoot !== root || !(await ownerMatchesProcess(record))) {
|
|
104
|
+
if (!processAlive(record.pid))
|
|
105
|
+
await rm(ownerFile(root), { force: true });
|
|
106
|
+
return "UNVERIFIED";
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
process.kill(record.pid, "SIGTERM");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return "UNVERIFIED";
|
|
113
|
+
}
|
|
114
|
+
if (!(await waitForExit(record.pid)))
|
|
115
|
+
return "TIMEOUT";
|
|
116
|
+
await rm(ownerFile(root), { force: true });
|
|
117
|
+
return "STOPPED";
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"@tomflow/proflow-module-contract": "^0.1.13"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@tomflow/proflow-
|
|
32
|
-
"@tomflow/proflow-
|
|
31
|
+
"@tomflow/proflow-module-template": "^0.1.10",
|
|
32
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.13"
|
|
33
33
|
},
|
|
34
34
|
"description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
|
|
35
35
|
"keywords": [
|
package/proflow.module.json
CHANGED
package/CHANGELOG.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# @tomflow/proflow-platform-cli
|
|
2
|
-
|
|
3
|
-
## 0.1.42
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Repack the Deployment Closeout release from freshly rebuilt artifacts and enforce build + publishability before every canonical publish.
|
|
8
|
-
|
|
9
|
-
## 0.1.41
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- Harden Deployment Closeout: fail closed on partial installs and package-manager conflicts, normalize Git workspace isolation, make Dev Tunnel status fast and resumable without fabricating login failures, and align Browser Extension setup/conformance behavior.
|
|
14
|
-
|
|
15
|
-
## 0.1.40
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- Ignore governed non-Module tool packages during Registry module discovery so Fresh install remains fixed at 23 active modules.
|
|
20
|
-
|
|
21
|
-
## 0.1.39
|
|
22
|
-
|
|
23
|
-
### Patch Changes
|
|
24
|
-
|
|
25
|
-
- Add professional setup orchestration, Provider FAST/THINK input, recoverable managed Dev Tunnel provisioning, Browser installation guidance, and precise status diagnostics.
|
|
26
|
-
|
|
27
|
-
## 0.1.38
|
|
28
|
-
|
|
29
|
-
### Patch Changes
|
|
30
|
-
|
|
31
|
-
- Retire the obsolete Carrier package and Browser standalone management CLI, make Platform setup/status preserve owner and dependency semantics, and enforce read-only status observation across the final Real-3 deployment closeout.
|
|
32
|
-
|
|
33
|
-
## 0.1.37
|
|
34
|
-
|
|
35
|
-
### Patch Changes
|
|
36
|
-
|
|
37
|
-
- Ship the Phase 3 browser, tunnel, model automation, platform readiness, and documentation closure as one verifiable platform snapshot.
|
|
38
|
-
|
|
39
|
-
## 0.1.36
|
|
40
|
-
|
|
41
|
-
### Patch Changes
|
|
42
|
-
|
|
43
|
-
- Distinguish dependency blocking from verified failure, expose Module-owned status diagnostics, remove automatic docs paging, consolidate start checks, add contextual help, and clarify uninstall output.
|
|
44
|
-
|
|
45
|
-
## 0.1.35
|
|
46
|
-
|
|
47
|
-
### Patch Changes
|
|
48
|
-
|
|
49
|
-
- Render retryable package-manager warnings as warnings instead of failed subprocess events.
|
|
50
|
-
|
|
51
|
-
## 0.1.34
|
|
52
|
-
|
|
53
|
-
### Patch Changes
|
|
54
|
-
|
|
55
|
-
- Upgrade the ProFlow terminal experience, stream Registry and package-manager progress, publish structured Chinese Module documentation, and provide numbered package-owned Setup assistants.
|
|
56
|
-
|
|
57
|
-
## 0.1.33
|
|
58
|
-
|
|
59
|
-
### Patch Changes
|
|
60
|
-
|
|
61
|
-
- Keep lifecycle summary totals mutually exclusive when a no-effect external runtime is skipped.
|
|
62
|
-
|
|
63
|
-
## 0.1.32
|
|
64
|
-
|
|
65
|
-
### Patch Changes
|
|
66
|
-
|
|
67
|
-
- Report successful lifecycle calls with no owned stop effect as skipped instead of claiming an external runtime was stopped.
|
|
68
|
-
|
|
69
|
-
## 0.1.31
|
|
70
|
-
|
|
71
|
-
### Patch Changes
|
|
72
|
-
|
|
73
|
-
- Make every setup command directly runnable from a workspace and render setup failures without Node stack traces.
|
|
74
|
-
|
|
75
|
-
## 0.1.30
|
|
76
|
-
|
|
77
|
-
### Patch Changes
|
|
78
|
-
|
|
79
|
-
- Use a stable progress marker in non-interactive logs instead of a frozen spinner frame.
|
|
80
|
-
|
|
81
|
-
## 0.1.29
|
|
82
|
-
|
|
83
|
-
### Patch Changes
|
|
84
|
-
|
|
85
|
-
- Improve terminal progress and localized status output, publish readable Chinese module docs, and add guided setup assistants for external resources.
|