@tomflow/proflow-platform-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +24 -3
- package/dist/deployment/adapter.d.ts +5 -5
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/apply/driver.d.ts +1 -0
- package/dist/src/apply/driver.js +40 -12
- package/dist/src/binding/global-binding.d.ts +69 -0
- package/dist/src/binding/global-binding.js +349 -0
- package/dist/src/binding/production-bindings.d.ts +1 -1
- package/dist/src/binding/production-bindings.js +5 -3
- package/dist/src/cli.d.ts +12 -1
- package/dist/src/cli.js +453 -31
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +12 -0
- package/dist/src/install/environment.d.ts +2 -1
- package/dist/src/install/environment.js +33 -29
- package/dist/src/install/package-manager.d.ts +4 -2
- package/dist/src/install/package-manager.js +73 -17
- package/dist/src/planner/plan.d.ts +1 -0
- package/dist/src/planner/plan.js +1 -1
- package/package.json +4 -4
- package/proflow.module.json +1 -1
|
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { createRequire } from "node:module";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
import { pathToFileURL } from "node:url";
|
|
11
12
|
import { managedServiceStatus, restartManagedService, startManagedService, stopManagedService, } from "../lifecycle/service-process.js";
|
|
@@ -21,15 +22,16 @@ function managedServiceAdapter(workspaceRoot, module, serviceProcess, probeAdapt
|
|
|
21
22
|
uninstall: () => stopManagedService(paths, module),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
|
-
export async function importRawAdapter(packageName, source) {
|
|
25
|
+
export async function importRawAdapter(packageName, source, workspaceRoot) {
|
|
25
26
|
if (source.type === "workspace") {
|
|
26
27
|
if (source.path === undefined)
|
|
27
28
|
return {};
|
|
28
29
|
const url = pathToFileURL(join(source.path, "deployment", "adapter.ts"));
|
|
29
30
|
return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
|
|
30
31
|
}
|
|
31
|
-
const
|
|
32
|
-
const
|
|
32
|
+
const workspaceRequire = createRequire(pathToFileURL(join(workspaceRoot, "package.json")));
|
|
33
|
+
const resolved = workspaceRequire.resolve(`${packageName}/deployment/adapter`);
|
|
34
|
+
const url = pathToFileURL(resolved);
|
|
33
35
|
return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
|
|
34
36
|
}
|
|
35
37
|
/**
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type GlobalWorkspaceBinding } from "./binding/global-binding.ts";
|
|
2
3
|
export type CliStatus = "SUCCEEDED" | "ACTION_REQUIRED" | "BLOCKED" | "FAILED";
|
|
4
|
+
export interface CliWorkspaceSummary {
|
|
5
|
+
boundWorkspace: string;
|
|
6
|
+
workspaceInstanceId: string;
|
|
7
|
+
bindingState: GlobalWorkspaceBinding["state"];
|
|
8
|
+
}
|
|
3
9
|
export interface CliOutcome {
|
|
4
10
|
command: string;
|
|
5
11
|
status: CliStatus;
|
|
6
12
|
data?: unknown;
|
|
13
|
+
workspace?: CliWorkspaceSummary;
|
|
7
14
|
error?: {
|
|
8
15
|
code: string;
|
|
9
16
|
message: string;
|
|
10
17
|
};
|
|
11
18
|
}
|
|
12
|
-
export
|
|
19
|
+
export interface CliRuntimeOptions {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
globalRoot?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<string>;
|
package/dist/src/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, rm } from "node:fs/promises";
|
|
3
3
|
import { parseModuleDescriptor, } from "@tomflow/proflow-module-contract";
|
|
4
4
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
5
5
|
import { applyPlan } from "./apply/apply.js";
|
|
6
6
|
import { rebuildCurrentAssumptions } from "./apply/current.js";
|
|
7
7
|
import { createWorkspacePackageManagerDriver } from "./apply/driver.js";
|
|
8
|
+
import { acquireGlobalOperationLock, canonicalizeWorkspace, claimWorkspaceBinding, clearGlobalBinding, forgetMissingWorkspaceBinding, loadGlobalBinding, observeBoundWorkspace, requireBoundWorkspace, updateGlobalBindingState, } from "./binding/global-binding.js";
|
|
8
9
|
import { buildProductionBindings, importRawAdapter, } from "./binding/production-bindings.js";
|
|
9
10
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
10
11
|
import { describeModule, readModuleDocument } from "./docs/docs.js";
|
|
@@ -52,6 +53,7 @@ function parseArgs(argv) {
|
|
|
52
53
|
workspace: undefined,
|
|
53
54
|
intent: undefined,
|
|
54
55
|
configFile: undefined,
|
|
56
|
+
forget: false,
|
|
55
57
|
positional,
|
|
56
58
|
};
|
|
57
59
|
for (let index = 1; index < argv.length; index += 1) {
|
|
@@ -59,18 +61,22 @@ function parseArgs(argv) {
|
|
|
59
61
|
if (token === undefined)
|
|
60
62
|
continue;
|
|
61
63
|
if (token === "--workspace") {
|
|
62
|
-
parsed.workspace = argv
|
|
64
|
+
parsed.workspace = requiredOptionValue(argv, index, token);
|
|
63
65
|
index += 1;
|
|
64
66
|
}
|
|
65
67
|
else if (token === "--intent") {
|
|
66
|
-
parsed.intent = argv
|
|
68
|
+
parsed.intent = requiredOptionValue(argv, index, token);
|
|
67
69
|
index += 1;
|
|
68
70
|
}
|
|
69
71
|
else if (token === "--config") {
|
|
70
|
-
parsed.configFile = argv
|
|
72
|
+
parsed.configFile = requiredOptionValue(argv, index, token);
|
|
71
73
|
index += 1;
|
|
72
74
|
}
|
|
75
|
+
else if (token === "--forget") {
|
|
76
|
+
parsed.forget = true;
|
|
77
|
+
}
|
|
73
78
|
else if (token.startsWith("-")) {
|
|
79
|
+
throw new PlatformError("INVALID_REQUEST", `unknown option: ${token}`);
|
|
74
80
|
}
|
|
75
81
|
else {
|
|
76
82
|
positional.push(token);
|
|
@@ -78,12 +84,29 @@ function parseArgs(argv) {
|
|
|
78
84
|
}
|
|
79
85
|
return parsed;
|
|
80
86
|
}
|
|
87
|
+
function requiredOptionValue(argv, index, option) {
|
|
88
|
+
const value = argv[index + 1];
|
|
89
|
+
if (value === undefined || value.startsWith("-")) {
|
|
90
|
+
throw new PlatformError("INVALID_REQUEST", `${option} requires a value`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
81
94
|
function outcome(command, status, data) {
|
|
82
95
|
const result = { command, status };
|
|
83
96
|
if (data !== undefined)
|
|
84
97
|
result.data = data;
|
|
85
98
|
return result;
|
|
86
99
|
}
|
|
100
|
+
function workspaceSummary(binding) {
|
|
101
|
+
return {
|
|
102
|
+
boundWorkspace: binding.workspaceRealPath,
|
|
103
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
104
|
+
bindingState: binding.state,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function withWorkspace(result, binding) {
|
|
108
|
+
return { ...result, workspace: workspaceSummary(binding) };
|
|
109
|
+
}
|
|
87
110
|
function aggregateStatus(statuses) {
|
|
88
111
|
if (statuses.includes("FAILED"))
|
|
89
112
|
return "FAILED";
|
|
@@ -166,8 +189,7 @@ async function loadConfigFile(path) {
|
|
|
166
189
|
}
|
|
167
190
|
return result;
|
|
168
191
|
}
|
|
169
|
-
async function buildContext(
|
|
170
|
-
const root = workspace ?? process.cwd();
|
|
192
|
+
async function buildContext(root) {
|
|
171
193
|
const paths = workspacePaths(root);
|
|
172
194
|
const catalog = await buildCatalogWithProductionBindings(root, paths);
|
|
173
195
|
return { catalog, paths };
|
|
@@ -196,7 +218,7 @@ async function buildCatalogWithProductionBindings(root, paths) {
|
|
|
196
218
|
workspaceRoot: root,
|
|
197
219
|
modules,
|
|
198
220
|
configByModuleRef,
|
|
199
|
-
importAdapter: (packageName, source) => importRawAdapter(packageName, source),
|
|
221
|
+
importAdapter: (packageName, source) => importRawAdapter(packageName, source, root),
|
|
200
222
|
});
|
|
201
223
|
return new AutoModuleCatalog(root, bindings);
|
|
202
224
|
}
|
|
@@ -493,7 +515,7 @@ async function handleManagedMutation(ctx, args, intent) {
|
|
|
493
515
|
// Package mutations change Workspace package reality. Rebuild the catalog
|
|
494
516
|
// before reporting the managed set so the result can never be sourced from
|
|
495
517
|
// the pre-mutation catalog.
|
|
496
|
-
const refreshed = await buildContext(
|
|
518
|
+
const refreshed = await buildContext(ctx.paths.root);
|
|
497
519
|
const managed = await handleModules(refreshed, {
|
|
498
520
|
...args,
|
|
499
521
|
command: "modules",
|
|
@@ -506,6 +528,301 @@ async function handleManagedMutation(ctx, args, intent) {
|
|
|
506
528
|
managedModules: managed.data,
|
|
507
529
|
});
|
|
508
530
|
}
|
|
531
|
+
async function resolveRequestedInstallWorkspace(args, runtime) {
|
|
532
|
+
const requested = args.workspace ?? runtime.cwd ?? process.cwd();
|
|
533
|
+
return (await canonicalizeWorkspace(requested)).workspaceRealPath;
|
|
534
|
+
}
|
|
535
|
+
async function handleInstallPlanBeforeBinding(args, runtime) {
|
|
536
|
+
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
537
|
+
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
538
|
+
if (current !== undefined &&
|
|
539
|
+
current.workspaceRealPath !== requestedWorkspace) {
|
|
540
|
+
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${current.workspaceRealPath}; uninstall it before planning an install for ${requestedWorkspace}`);
|
|
541
|
+
}
|
|
542
|
+
const ctx = await buildContext(current?.workspaceRealPath ?? requestedWorkspace);
|
|
543
|
+
const result = await handlePlan(ctx, args);
|
|
544
|
+
return current === undefined ? result : withWorkspace(result, current);
|
|
545
|
+
}
|
|
546
|
+
async function handleApplyWithGlobalBinding(args, runtime) {
|
|
547
|
+
const planRef = args.positional[0];
|
|
548
|
+
if (planRef === undefined) {
|
|
549
|
+
throw new PlatformError("INVALID_REQUEST", "apply requires <planRef>");
|
|
550
|
+
}
|
|
551
|
+
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
552
|
+
let binding = await loadGlobalBinding(runtime.globalRoot);
|
|
553
|
+
try {
|
|
554
|
+
let ctx;
|
|
555
|
+
if (binding === undefined) {
|
|
556
|
+
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
557
|
+
ctx = await buildContext(requestedWorkspace);
|
|
558
|
+
const plan = await loadPlan(ctx.paths, planRef);
|
|
559
|
+
if (plan === undefined) {
|
|
560
|
+
throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
|
|
561
|
+
}
|
|
562
|
+
if (plan.intent !== "install") {
|
|
563
|
+
throw new PlatformError("WORKSPACE_NOT_BOUND", "only an install plan may establish the first global Workspace binding");
|
|
564
|
+
}
|
|
565
|
+
binding = (await claimWorkspaceBinding({
|
|
566
|
+
workspace: requestedWorkspace,
|
|
567
|
+
globalRoot: runtime.globalRoot,
|
|
568
|
+
})).binding;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
if (args.workspace !== undefined) {
|
|
572
|
+
const requested = await canonicalizeWorkspace(args.workspace);
|
|
573
|
+
if (requested.workspaceRealPath !== binding.workspaceRealPath) {
|
|
574
|
+
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `command targets ${requested.workspaceRealPath}, but the global Platform Instance is bound to ${binding.workspaceRealPath}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
ctx = await buildContext(binding.workspaceRealPath);
|
|
578
|
+
}
|
|
579
|
+
const plan = await loadPlan(ctx.paths, planRef);
|
|
580
|
+
if (plan === undefined) {
|
|
581
|
+
throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
|
|
582
|
+
}
|
|
583
|
+
const controlsPlatformState = plan.intent === "install" && binding.state !== "INSTALLED";
|
|
584
|
+
if (binding.state === "UNINSTALLING") {
|
|
585
|
+
throw new PlatformError("GLOBAL_OPERATION_LOCKED", "the bound Platform Instance is marked UNINSTALLING; finish recovery before apply");
|
|
586
|
+
}
|
|
587
|
+
if (controlsPlatformState && binding.state !== "INSTALLING") {
|
|
588
|
+
binding = await updateGlobalBindingState({
|
|
589
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
590
|
+
state: "INSTALLING",
|
|
591
|
+
globalRoot: runtime.globalRoot,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
const result = await handleApply(ctx, args);
|
|
596
|
+
if (controlsPlatformState) {
|
|
597
|
+
if (result.status === "SUCCEEDED") {
|
|
598
|
+
binding = await updateGlobalBindingState({
|
|
599
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
600
|
+
state: "INSTALLED",
|
|
601
|
+
globalRoot: runtime.globalRoot,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
else if (result.status === "FAILED" || result.status === "BLOCKED") {
|
|
605
|
+
binding = await updateGlobalBindingState({
|
|
606
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
607
|
+
state: "BROKEN",
|
|
608
|
+
globalRoot: runtime.globalRoot,
|
|
609
|
+
failure: {
|
|
610
|
+
code: result.error?.code ?? result.status,
|
|
611
|
+
message: result.error?.message ??
|
|
612
|
+
"install apply did not reach a successful postcondition",
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return withWorkspace(result, binding);
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
if (controlsPlatformState) {
|
|
621
|
+
binding = await updateGlobalBindingState({
|
|
622
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
623
|
+
state: "BROKEN",
|
|
624
|
+
globalRoot: runtime.globalRoot,
|
|
625
|
+
failure: {
|
|
626
|
+
code: error instanceof PlatformError ? error.code : "COMMAND_FAILED",
|
|
627
|
+
message: error instanceof Error ? error.message : String(error),
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
finally {
|
|
635
|
+
await operationLock.release();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function handleInstallWithGlobalBinding(args, runtime) {
|
|
639
|
+
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
640
|
+
try {
|
|
641
|
+
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
642
|
+
const claimed = await claimWorkspaceBinding({
|
|
643
|
+
workspace: requestedWorkspace,
|
|
644
|
+
globalRoot: runtime.globalRoot,
|
|
645
|
+
});
|
|
646
|
+
const initialBinding = claimed.binding;
|
|
647
|
+
if (claimed.alreadyBound &&
|
|
648
|
+
initialBinding.state === "INSTALLED" &&
|
|
649
|
+
args.positional[0] === undefined) {
|
|
650
|
+
return withWorkspace(outcome("install", "SUCCEEDED", {
|
|
651
|
+
alreadyInstalled: true,
|
|
652
|
+
changed: false,
|
|
653
|
+
boundWorkspace: initialBinding.workspaceRealPath,
|
|
654
|
+
}), initialBinding);
|
|
655
|
+
}
|
|
656
|
+
if (initialBinding.state === "UNINSTALLING") {
|
|
657
|
+
throw new PlatformError("GLOBAL_OPERATION_LOCKED", "the bound Platform Instance is marked UNINSTALLING; finish recovery before installing");
|
|
658
|
+
}
|
|
659
|
+
let activeBinding = initialBinding;
|
|
660
|
+
const controlsPlatformState = !claimed.alreadyBound || initialBinding.state !== "INSTALLED";
|
|
661
|
+
if (controlsPlatformState && initialBinding.state !== "INSTALLING") {
|
|
662
|
+
activeBinding = await updateGlobalBindingState({
|
|
663
|
+
workspaceInstanceId: initialBinding.workspaceInstanceId,
|
|
664
|
+
state: "INSTALLING",
|
|
665
|
+
globalRoot: runtime.globalRoot,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
const ctx = await buildContext(activeBinding.workspaceRealPath);
|
|
670
|
+
const result = await handleManagedMutation(ctx, args, "install");
|
|
671
|
+
if (controlsPlatformState) {
|
|
672
|
+
if (result.status === "SUCCEEDED") {
|
|
673
|
+
activeBinding = await updateGlobalBindingState({
|
|
674
|
+
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
675
|
+
state: "INSTALLED",
|
|
676
|
+
globalRoot: runtime.globalRoot,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
else if (result.status === "FAILED" || result.status === "BLOCKED") {
|
|
680
|
+
activeBinding = await updateGlobalBindingState({
|
|
681
|
+
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
682
|
+
state: "BROKEN",
|
|
683
|
+
globalRoot: runtime.globalRoot,
|
|
684
|
+
failure: {
|
|
685
|
+
code: result.error?.code ?? result.status,
|
|
686
|
+
message: result.error?.message ??
|
|
687
|
+
"Platform install did not reach a successful postcondition",
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return withWorkspace(result, activeBinding);
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
if (controlsPlatformState) {
|
|
696
|
+
activeBinding = await updateGlobalBindingState({
|
|
697
|
+
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
698
|
+
state: "BROKEN",
|
|
699
|
+
globalRoot: runtime.globalRoot,
|
|
700
|
+
failure: {
|
|
701
|
+
code: error instanceof PlatformError ? error.code : "COMMAND_FAILED",
|
|
702
|
+
message: error instanceof Error ? error.message : String(error),
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
throw error;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
await operationLock.release();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async function resolveBoundContext(runtime, requestedWorkspace) {
|
|
714
|
+
const binding = await requireBoundWorkspace(runtime.globalRoot);
|
|
715
|
+
if (requestedWorkspace !== undefined) {
|
|
716
|
+
const requested = await canonicalizeWorkspace(requestedWorkspace);
|
|
717
|
+
if (requested.workspaceRealPath !== binding.workspaceRealPath) {
|
|
718
|
+
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `command targets ${requested.workspaceRealPath}, but the global Platform Instance is bound to ${binding.workspaceRealPath}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
ctx: await buildContext(binding.workspaceRealPath),
|
|
723
|
+
binding,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
async function handleBoundMutatingCommand(args, runtime) {
|
|
727
|
+
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
728
|
+
try {
|
|
729
|
+
const { ctx, binding } = await resolveBoundContext(runtime, args.workspace);
|
|
730
|
+
if (binding.state === "UNINSTALLING") {
|
|
731
|
+
throw new PlatformError("GLOBAL_OPERATION_LOCKED", "the bound Platform Instance is marked UNINSTALLING; finish uninstall recovery before another mutation");
|
|
732
|
+
}
|
|
733
|
+
let result;
|
|
734
|
+
if (args.command === "upgrade" || args.command === "uninstall") {
|
|
735
|
+
result = await handleManagedMutation(ctx, args, args.command);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
result = await handleLifecycle(ctx, args, args.command);
|
|
739
|
+
}
|
|
740
|
+
return withWorkspace(result, binding);
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
await operationLock.release();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function handlePlatformInstanceUninstall(args, runtime) {
|
|
747
|
+
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
748
|
+
let binding;
|
|
749
|
+
try {
|
|
750
|
+
binding = await requireBoundWorkspace(runtime.globalRoot);
|
|
751
|
+
if (args.workspace !== undefined) {
|
|
752
|
+
const requested = await canonicalizeWorkspace(args.workspace);
|
|
753
|
+
if (requested.workspaceRealPath !== binding.workspaceRealPath) {
|
|
754
|
+
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `uninstall targets ${requested.workspaceRealPath}, but the global Platform Instance is bound to ${binding.workspaceRealPath}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
binding = await updateGlobalBindingState({
|
|
758
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
759
|
+
state: "UNINSTALLING",
|
|
760
|
+
globalRoot: runtime.globalRoot,
|
|
761
|
+
});
|
|
762
|
+
const ctx = await buildContext(binding.workspaceRealPath);
|
|
763
|
+
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
764
|
+
let removedModules = [];
|
|
765
|
+
if (modules.length > 0) {
|
|
766
|
+
const plan = planDeployment({
|
|
767
|
+
intent: "uninstall",
|
|
768
|
+
modules,
|
|
769
|
+
uninstallScope: "platform-instance",
|
|
770
|
+
});
|
|
771
|
+
await savePlan(ctx.paths, plan);
|
|
772
|
+
const current = await rebuildCurrentAssumptions(ctx.catalog, plan);
|
|
773
|
+
const applied = await applyPlan({
|
|
774
|
+
paths: ctx.paths,
|
|
775
|
+
planRef: plan.planRef,
|
|
776
|
+
catalog: ctx.catalog,
|
|
777
|
+
current,
|
|
778
|
+
driver: createWorkspacePackageManagerDriver({
|
|
779
|
+
workspaceRoot: ctx.paths.root,
|
|
780
|
+
}),
|
|
781
|
+
});
|
|
782
|
+
if (applied.outcome !== "COMPLETE") {
|
|
783
|
+
throw new PlatformError("UNINSTALL_FAILED", `Platform Instance uninstall stopped with ${applied.outcome}`);
|
|
784
|
+
}
|
|
785
|
+
removedModules = modules.map((module) => module.moduleRef);
|
|
786
|
+
}
|
|
787
|
+
const refreshed = await buildContext(binding.workspaceRealPath);
|
|
788
|
+
const remaining = await discoverModules({ catalog: refreshed.catalog });
|
|
789
|
+
if (remaining.length > 0) {
|
|
790
|
+
throw new PlatformError("UNINSTALL_FAILED", `Platform Instance uninstall left managed modules: ${remaining.map((module) => module.moduleRef).join(", ")}`);
|
|
791
|
+
}
|
|
792
|
+
// Deployment-owned plans/state/verification/instance identity must not leak
|
|
793
|
+
// into a future install of the same directory. Business/domain data outside
|
|
794
|
+
// `.proflow/deployment` is intentionally preserved.
|
|
795
|
+
await rm(ctx.paths.deployment, { recursive: true, force: true });
|
|
796
|
+
const completed = binding;
|
|
797
|
+
await clearGlobalBinding({
|
|
798
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
799
|
+
globalRoot: runtime.globalRoot,
|
|
800
|
+
});
|
|
801
|
+
binding = undefined;
|
|
802
|
+
return outcome("uninstall", "SUCCEEDED", {
|
|
803
|
+
uninstalledWorkspace: completed.workspaceRealPath,
|
|
804
|
+
removedModules,
|
|
805
|
+
bindingCleared: true,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
if (binding !== undefined) {
|
|
810
|
+
await updateGlobalBindingState({
|
|
811
|
+
workspaceInstanceId: binding.workspaceInstanceId,
|
|
812
|
+
state: "BROKEN",
|
|
813
|
+
globalRoot: runtime.globalRoot,
|
|
814
|
+
failure: {
|
|
815
|
+
code: error instanceof PlatformError ? error.code : "UNINSTALL_FAILED",
|
|
816
|
+
message: error instanceof Error ? error.message : String(error),
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
throw error;
|
|
821
|
+
}
|
|
822
|
+
finally {
|
|
823
|
+
await operationLock.release();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
509
826
|
async function handleLifecycle(ctx, args, primitive) {
|
|
510
827
|
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
511
828
|
const selected = selectModules(modules, args.positional[0]);
|
|
@@ -591,8 +908,14 @@ async function handleManifest(ctx, args) {
|
|
|
591
908
|
return outcome("manifest", "FAILED", manifest);
|
|
592
909
|
}
|
|
593
910
|
}
|
|
594
|
-
async function dispatchCommand(argv) {
|
|
595
|
-
|
|
911
|
+
async function dispatchCommand(argv, runtime = {}) {
|
|
912
|
+
let args;
|
|
913
|
+
try {
|
|
914
|
+
args = parseArgs(argv);
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
return failure(argv[0] ?? "", error);
|
|
918
|
+
}
|
|
596
919
|
if (!COMMANDS.includes(args.command)) {
|
|
597
920
|
return {
|
|
598
921
|
command: args.command,
|
|
@@ -604,47 +927,143 @@ async function dispatchCommand(argv) {
|
|
|
604
927
|
};
|
|
605
928
|
}
|
|
606
929
|
try {
|
|
607
|
-
const
|
|
608
|
-
if (args.command === "search")
|
|
930
|
+
const cwd = runtime.cwd ?? process.cwd();
|
|
931
|
+
if (args.command === "search") {
|
|
932
|
+
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
933
|
+
const root = args.workspace ?? current?.workspaceRealPath ?? cwd;
|
|
609
934
|
return await handleSearch(root, args);
|
|
935
|
+
}
|
|
610
936
|
if (args.command === "preflight" && args.intent === "install") {
|
|
937
|
+
const root = await resolveRequestedInstallWorkspace(args, runtime);
|
|
938
|
+
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
939
|
+
if (current !== undefined && current.workspaceRealPath !== root) {
|
|
940
|
+
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${current.workspaceRealPath}; uninstall it before installing ${root}`);
|
|
941
|
+
}
|
|
611
942
|
return await handleInstallerPreflight(root);
|
|
612
943
|
}
|
|
613
|
-
|
|
944
|
+
if (args.command === "plan") {
|
|
945
|
+
if (args.intent === "install") {
|
|
946
|
+
return await handleInstallPlanBeforeBinding(args, runtime);
|
|
947
|
+
}
|
|
948
|
+
if (args.intent === undefined ||
|
|
949
|
+
!["configure", "upgrade", "uninstall", "repair"].includes(args.intent)) {
|
|
950
|
+
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
951
|
+
const root = args.workspace ??
|
|
952
|
+
current?.workspaceRealPath ??
|
|
953
|
+
runtime.cwd ??
|
|
954
|
+
process.cwd();
|
|
955
|
+
return await handlePlan(await buildContext(root), args);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (args.command === "apply") {
|
|
959
|
+
return await handleApplyWithGlobalBinding(args, runtime);
|
|
960
|
+
}
|
|
961
|
+
if (args.command === "install") {
|
|
962
|
+
return await handleInstallWithGlobalBinding(args, runtime);
|
|
963
|
+
}
|
|
964
|
+
if (args.command === "status") {
|
|
965
|
+
const observation = await observeBoundWorkspace(runtime.globalRoot);
|
|
966
|
+
if (observation === undefined) {
|
|
967
|
+
return outcome("status", "SUCCEEDED", {
|
|
968
|
+
installed: false,
|
|
969
|
+
bindingState: "UNBOUND",
|
|
970
|
+
boundWorkspace: null,
|
|
971
|
+
nextAction: "Run platform install [--workspace <path>]",
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (!observation.workspaceExists) {
|
|
975
|
+
return withWorkspace(outcome("status", "BLOCKED", {
|
|
976
|
+
installed: false,
|
|
977
|
+
code: "BOUND_WORKSPACE_MISSING",
|
|
978
|
+
boundWorkspace: observation.binding.workspaceRealPath,
|
|
979
|
+
nextAction: "Restore the Workspace or run platform uninstall --forget to clear only the stale binding",
|
|
980
|
+
}), observation.binding);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (args.command === "uninstall" && args.forget) {
|
|
984
|
+
if (args.positional.length > 0 || args.workspace !== undefined) {
|
|
985
|
+
throw new PlatformError("INVALID_REQUEST", "platform uninstall --forget only clears the current stale global binding; do not combine it with a module/package or --workspace");
|
|
986
|
+
}
|
|
987
|
+
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
988
|
+
try {
|
|
989
|
+
const forgotten = await forgetMissingWorkspaceBinding({
|
|
990
|
+
globalRoot: runtime.globalRoot,
|
|
991
|
+
});
|
|
992
|
+
return outcome("uninstall", "SUCCEEDED", {
|
|
993
|
+
forgottenWorkspace: forgotten.workspaceRealPath,
|
|
994
|
+
bindingCleared: true,
|
|
995
|
+
resourcesCleaned: false,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
finally {
|
|
999
|
+
await operationLock.release();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (args.command === "uninstall" && args.positional.length === 0) {
|
|
1003
|
+
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
1004
|
+
if (current === undefined) {
|
|
1005
|
+
return outcome("uninstall", "SUCCEEDED", {
|
|
1006
|
+
alreadyUninstalled: true,
|
|
1007
|
+
bindingCleared: true,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
return await handlePlatformInstanceUninstall(args, runtime);
|
|
1011
|
+
}
|
|
1012
|
+
if (args.command === "upgrade" ||
|
|
1013
|
+
(args.command === "uninstall" && args.positional.length > 0) ||
|
|
1014
|
+
args.command === "start" ||
|
|
1015
|
+
args.command === "stop" ||
|
|
1016
|
+
args.command === "restart") {
|
|
1017
|
+
return await handleBoundMutatingCommand(args, runtime);
|
|
1018
|
+
}
|
|
1019
|
+
const { ctx, binding } = await resolveBoundContext(runtime, args.workspace);
|
|
1020
|
+
let result;
|
|
614
1021
|
switch (args.command) {
|
|
615
1022
|
case "search":
|
|
616
|
-
|
|
1023
|
+
case "install":
|
|
1024
|
+
throw new PlatformError("COMMAND_FAILED", "unreachable command routing");
|
|
617
1025
|
case "modules":
|
|
618
|
-
|
|
1026
|
+
result = await handleModules(ctx, args);
|
|
1027
|
+
break;
|
|
619
1028
|
case "docs":
|
|
620
|
-
|
|
621
|
-
|
|
1029
|
+
result = await handleDocs(ctx, args);
|
|
1030
|
+
break;
|
|
622
1031
|
case "uninstall":
|
|
623
1032
|
case "upgrade":
|
|
624
|
-
return await handleManagedMutation(ctx, args, args.command);
|
|
625
|
-
case "preflight":
|
|
626
|
-
return await handlePreflight(ctx, args);
|
|
627
|
-
case "plan":
|
|
628
|
-
return await handlePlan(ctx, args);
|
|
629
|
-
case "apply":
|
|
630
|
-
return await handleApply(ctx, args);
|
|
631
1033
|
case "start":
|
|
632
1034
|
case "stop":
|
|
633
1035
|
case "restart":
|
|
1036
|
+
throw new PlatformError("COMMAND_FAILED", "unreachable mutating command routing");
|
|
1037
|
+
case "preflight":
|
|
1038
|
+
result = await handlePreflight(ctx, args);
|
|
1039
|
+
break;
|
|
1040
|
+
case "plan":
|
|
1041
|
+
result = await handlePlan(ctx, args);
|
|
1042
|
+
break;
|
|
1043
|
+
case "apply":
|
|
1044
|
+
result = await handleApply(ctx, args);
|
|
1045
|
+
break;
|
|
634
1046
|
case "status":
|
|
635
|
-
|
|
1047
|
+
result = await handleLifecycle(ctx, args, "status");
|
|
1048
|
+
break;
|
|
636
1049
|
case "verify":
|
|
637
|
-
|
|
1050
|
+
result = await handleVerify(ctx, args);
|
|
1051
|
+
break;
|
|
638
1052
|
case "doctor":
|
|
639
|
-
|
|
1053
|
+
result = await handleDoctor(ctx, args);
|
|
1054
|
+
break;
|
|
640
1055
|
case "manifest":
|
|
641
|
-
|
|
1056
|
+
result = await handleManifest(ctx, args);
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
if (result === undefined) {
|
|
1060
|
+
throw new PlatformError("COMMAND_FAILED", "unreachable command routing");
|
|
642
1061
|
}
|
|
1062
|
+
return withWorkspace(result, binding);
|
|
643
1063
|
}
|
|
644
1064
|
catch (error) {
|
|
645
1065
|
return failure(args.command, error);
|
|
646
1066
|
}
|
|
647
|
-
return failure(args.command, new PlatformError("COMMAND_FAILED", "unreachable"));
|
|
648
1067
|
}
|
|
649
1068
|
function toMachineResult(outcomeResult) {
|
|
650
1069
|
const ok = outcomeResult.status === "SUCCEEDED";
|
|
@@ -654,6 +1073,9 @@ function toMachineResult(outcomeResult) {
|
|
|
654
1073
|
status: outcomeResult.status,
|
|
655
1074
|
moduleRef: MODULE_REF,
|
|
656
1075
|
moduleVersion: MODULE_VERSION,
|
|
1076
|
+
...(outcomeResult.workspace !== undefined
|
|
1077
|
+
? { workspace: outcomeResult.workspace }
|
|
1078
|
+
: {}),
|
|
657
1079
|
...(outcomeResult.data !== undefined ? { data: outcomeResult.data } : {}),
|
|
658
1080
|
...(outcomeResult.status === "ACTION_REQUIRED"
|
|
659
1081
|
? {
|
|
@@ -674,7 +1096,7 @@ function toMachineResult(outcomeResult) {
|
|
|
674
1096
|
: {}),
|
|
675
1097
|
});
|
|
676
1098
|
}
|
|
677
|
-
export async function runCli(argv) {
|
|
1099
|
+
export async function runCli(argv, runtime = {}) {
|
|
678
1100
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
679
1101
|
return JSON.stringify({
|
|
680
1102
|
contract: "deployment.result.v1",
|
|
@@ -698,7 +1120,7 @@ export async function runCli(argv) {
|
|
|
698
1120
|
moduleVersion: MODULE_VERSION,
|
|
699
1121
|
});
|
|
700
1122
|
}
|
|
701
|
-
return toMachineResult(await dispatchCommand(filtered));
|
|
1123
|
+
return toMachineResult(await dispatchCommand(filtered, runtime));
|
|
702
1124
|
}
|
|
703
1125
|
if (import.meta.main) {
|
|
704
1126
|
const output = await runCli(process.argv.slice(2));
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "CONFIG_MISSING", "SECRET_REF_INVALID", "PLAN_STALE", "PLAN_NOT_FOUND", "PLAN_INVALID", "WORKSPACE_LOCKED", "LIFECYCLE_UNSUPPORTED", "SECRET_LEAK", "APPLY_FAILED", "UPGRADE_FAILED", "CORE_PACKAGE_REQUIRED", "UNINSTALL_FAILED", "VERIFY_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "COMMAND_FAILED"];
|
|
1
|
+
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "CONFIG_MISSING", "SECRET_REF_INVALID", "PLAN_STALE", "PLAN_NOT_FOUND", "PLAN_INVALID", "WORKSPACE_LOCKED", "WORKSPACE_NOT_FOUND", "WORKSPACE_NOT_WRITABLE", "WORKSPACE_NOT_BOUND", "WORKSPACE_ALREADY_BOUND", "BOUND_WORKSPACE_MISSING", "WORKSPACE_INSTANCE_INVALID", "GLOBAL_BINDING_INVALID", "GLOBAL_BINDING_LOCKED", "GLOBAL_OPERATION_LOCKED", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "LIFECYCLE_UNSUPPORTED", "SECRET_LEAK", "APPLY_FAILED", "UPGRADE_FAILED", "CORE_PACKAGE_REQUIRED", "UNINSTALL_FAILED", "VERIFY_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "COMMAND_FAILED"];
|
|
2
2
|
export type PlatformErrorCode = (typeof platformErrorCodes)[number];
|
|
3
3
|
export declare class PlatformError extends Error {
|
|
4
4
|
readonly code: PlatformErrorCode;
|