@tomflow/proflow-platform-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +37 -0
  2. package/conformance.json +1 -0
  3. package/dist/deployment/adapter.d.ts +57 -0
  4. package/dist/deployment/adapter.js +25 -0
  5. package/dist/deployment/descriptor.d.ts +39 -0
  6. package/dist/deployment/descriptor.js +41 -0
  7. package/dist/src/apply/apply.d.ts +24 -0
  8. package/dist/src/apply/apply.js +271 -0
  9. package/dist/src/apply/cleanup.d.ts +12 -0
  10. package/dist/src/apply/cleanup.js +37 -0
  11. package/dist/src/apply/current.d.ts +4 -0
  12. package/dist/src/apply/current.js +73 -0
  13. package/dist/src/apply/driver.d.ts +23 -0
  14. package/dist/src/apply/driver.js +154 -0
  15. package/dist/src/apply/execute.d.ts +36 -0
  16. package/dist/src/apply/execute.js +124 -0
  17. package/dist/src/apply/index.d.ts +8 -0
  18. package/dist/src/apply/index.js +4 -0
  19. package/dist/src/apply/reality.d.ts +20 -0
  20. package/dist/src/apply/reality.js +93 -0
  21. package/dist/src/binding/production-bindings.d.ts +45 -0
  22. package/dist/src/binding/production-bindings.js +102 -0
  23. package/dist/src/cli.d.ts +12 -0
  24. package/dist/src/cli.js +712 -0
  25. package/dist/src/contracts.d.ts +113 -0
  26. package/dist/src/contracts.js +1 -0
  27. package/dist/src/discovery/catalog.d.ts +10 -0
  28. package/dist/src/discovery/catalog.js +76 -0
  29. package/dist/src/discovery/discover.d.ts +19 -0
  30. package/dist/src/discovery/discover.js +124 -0
  31. package/dist/src/discovery/index.d.ts +5 -0
  32. package/dist/src/discovery/index.js +4 -0
  33. package/dist/src/discovery/installed.d.ts +21 -0
  34. package/dist/src/discovery/installed.js +130 -0
  35. package/dist/src/discovery/workspace.d.ts +10 -0
  36. package/dist/src/discovery/workspace.js +155 -0
  37. package/dist/src/docs/docs.d.ts +47 -0
  38. package/dist/src/docs/docs.js +129 -0
  39. package/dist/src/docs/index.d.ts +1 -0
  40. package/dist/src/docs/index.js +1 -0
  41. package/dist/src/doctor/doctor.d.ts +41 -0
  42. package/dist/src/doctor/doctor.js +80 -0
  43. package/dist/src/doctor/index.d.ts +2 -0
  44. package/dist/src/doctor/index.js +1 -0
  45. package/dist/src/errors.d.ts +6 -0
  46. package/dist/src/errors.js +35 -0
  47. package/dist/src/graph/graph.d.ts +23 -0
  48. package/dist/src/graph/graph.js +132 -0
  49. package/dist/src/graph/index.d.ts +2 -0
  50. package/dist/src/graph/index.js +1 -0
  51. package/dist/src/index.d.ts +28 -0
  52. package/dist/src/index.js +18 -0
  53. package/dist/src/install/bootstrap.d.ts +10 -0
  54. package/dist/src/install/bootstrap.js +79 -0
  55. package/dist/src/install/environment.d.ts +29 -0
  56. package/dist/src/install/environment.js +226 -0
  57. package/dist/src/install/index.d.ts +6 -0
  58. package/dist/src/install/index.js +3 -0
  59. package/dist/src/install/install.d.ts +19 -0
  60. package/dist/src/install/install.js +131 -0
  61. package/dist/src/install/package-manager.d.ts +11 -0
  62. package/dist/src/install/package-manager.js +58 -0
  63. package/dist/src/lifecycle/dispatch.d.ts +47 -0
  64. package/dist/src/lifecycle/dispatch.js +142 -0
  65. package/dist/src/lifecycle/index.d.ts +3 -0
  66. package/dist/src/lifecycle/index.js +2 -0
  67. package/dist/src/lifecycle/service-process.d.ts +11 -0
  68. package/dist/src/lifecycle/service-process.js +326 -0
  69. package/dist/src/manifest/index.d.ts +2 -0
  70. package/dist/src/manifest/index.js +1 -0
  71. package/dist/src/manifest/manifest.d.ts +61 -0
  72. package/dist/src/manifest/manifest.js +160 -0
  73. package/dist/src/modules.d.ts +11 -0
  74. package/dist/src/modules.js +55 -0
  75. package/dist/src/observer/deployment-summary.d.ts +31 -0
  76. package/dist/src/observer/deployment-summary.js +34 -0
  77. package/dist/src/paths.d.ts +21 -0
  78. package/dist/src/paths.js +56 -0
  79. package/dist/src/persistence/atomic.d.ts +2 -0
  80. package/dist/src/persistence/atomic.js +16 -0
  81. package/dist/src/persistence/config.d.ts +12 -0
  82. package/dist/src/persistence/config.js +43 -0
  83. package/dist/src/persistence/generated.d.ts +2 -0
  84. package/dist/src/persistence/generated.js +4 -0
  85. package/dist/src/persistence/guards.d.ts +7 -0
  86. package/dist/src/persistence/guards.js +120 -0
  87. package/dist/src/persistence/index.d.ts +6 -0
  88. package/dist/src/persistence/index.js +5 -0
  89. package/dist/src/persistence/plans.d.ts +5 -0
  90. package/dist/src/persistence/plans.js +34 -0
  91. package/dist/src/persistence/state.d.ts +7 -0
  92. package/dist/src/persistence/state.js +34 -0
  93. package/dist/src/persistence/verification.d.ts +5 -0
  94. package/dist/src/persistence/verification.js +47 -0
  95. package/dist/src/planner/assemble.d.ts +16 -0
  96. package/dist/src/planner/assemble.js +107 -0
  97. package/dist/src/planner/check.d.ts +20 -0
  98. package/dist/src/planner/check.js +96 -0
  99. package/dist/src/planner/fingerprint.d.ts +12 -0
  100. package/dist/src/planner/fingerprint.js +164 -0
  101. package/dist/src/planner/index.d.ts +13 -0
  102. package/dist/src/planner/index.js +7 -0
  103. package/dist/src/planner/plan.d.ts +14 -0
  104. package/dist/src/planner/plan.js +156 -0
  105. package/dist/src/planner/repair.d.ts +28 -0
  106. package/dist/src/planner/repair.js +131 -0
  107. package/dist/src/planner/stale.d.ts +7 -0
  108. package/dist/src/planner/stale.js +23 -0
  109. package/dist/src/planner/steps.d.ts +37 -0
  110. package/dist/src/planner/steps.js +165 -0
  111. package/dist/src/planner/target-catalog.d.ts +13 -0
  112. package/dist/src/planner/target-catalog.js +48 -0
  113. package/dist/src/planner/upgrade.d.ts +18 -0
  114. package/dist/src/planner/upgrade.js +105 -0
  115. package/dist/src/preflight/config.d.ts +10 -0
  116. package/dist/src/preflight/config.js +45 -0
  117. package/dist/src/preflight/findings.d.ts +26 -0
  118. package/dist/src/preflight/findings.js +1 -0
  119. package/dist/src/preflight/index.d.ts +7 -0
  120. package/dist/src/preflight/index.js +3 -0
  121. package/dist/src/preflight/preflight.d.ts +8 -0
  122. package/dist/src/preflight/preflight.js +174 -0
  123. package/dist/src/preflight/requirements.d.ts +11 -0
  124. package/dist/src/preflight/requirements.js +224 -0
  125. package/dist/src/ready/index.d.ts +2 -0
  126. package/dist/src/ready/index.js +1 -0
  127. package/dist/src/ready/ready.d.ts +40 -0
  128. package/dist/src/ready/ready.js +201 -0
  129. package/dist/src/registry/index.d.ts +2 -0
  130. package/dist/src/registry/index.js +1 -0
  131. package/dist/src/registry/npm-registry.d.ts +51 -0
  132. package/dist/src/registry/npm-registry.js +341 -0
  133. package/dist/src/security/index.d.ts +3 -0
  134. package/dist/src/security/index.js +2 -0
  135. package/dist/src/security/lock.d.ts +9 -0
  136. package/dist/src/security/lock.js +89 -0
  137. package/dist/src/security/redact.d.ts +6 -0
  138. package/dist/src/security/redact.js +48 -0
  139. package/dist/src/verification/index.d.ts +2 -0
  140. package/dist/src/verification/index.js +1 -0
  141. package/dist/src/verification/verify.d.ts +33 -0
  142. package/dist/src/verification/verify.js +114 -0
  143. package/package.json +49 -0
  144. package/proflow.module.json +45 -0
@@ -0,0 +1,154 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { PlatformError } from "../errors.js";
6
+ import { readWorkspacePackageManagerSelection, systemPackageCommandRunner, } from "../install/package-manager.js";
7
+ import { atomicWrite } from "../paths.js";
8
+ /**
9
+ * Monorepo/test seam retained for source-resident planning. Production CLI must
10
+ * inject createWorkspacePackageManagerDriver(); it must never rely on this
11
+ * no-op driver to claim a product Workspace package mutation succeeded.
12
+ */
13
+ export function workspaceResidentDriver() {
14
+ return {
15
+ async observeInstalledVersion(module) {
16
+ return module.moduleVersion;
17
+ },
18
+ async install() { },
19
+ async upgrade() { },
20
+ async remove() { },
21
+ };
22
+ }
23
+ export function createWorkspacePackageManagerDriver(options) {
24
+ const runner = options.runner ?? systemPackageCommandRunner();
25
+ return {
26
+ observeInstalledVersion: (module) => observeInstalledVersion(options.workspaceRoot, module.packageName),
27
+ install: async (module) => {
28
+ await ensureWorkspacePackageJson(options.workspaceRoot);
29
+ const manager = await requirePackageManager(options.workspaceRoot);
30
+ await mutatePackage(runner, manager, options.workspaceRoot, "install", `${module.packageName}@${module.moduleVersion}`);
31
+ },
32
+ upgrade: async (module) => {
33
+ await ensureWorkspacePackageJson(options.workspaceRoot);
34
+ const manager = await requirePackageManager(options.workspaceRoot);
35
+ await mutatePackage(runner, manager, options.workspaceRoot, "upgrade", `${module.packageName}@${module.moduleVersion}`);
36
+ },
37
+ remove: async (module) => {
38
+ const manager = await requirePackageManager(options.workspaceRoot);
39
+ await mutatePackage(runner, manager, options.workspaceRoot, "remove", module.packageName);
40
+ },
41
+ };
42
+ }
43
+ async function mutatePackage(runner, manager, workspaceRoot, operation, packageSpec) {
44
+ const args = packageManagerArgs(manager.name, operation, packageSpec);
45
+ try {
46
+ await runner.run(manager.name, args, workspaceRoot);
47
+ }
48
+ catch (error) {
49
+ throw new PlatformError(operation === "upgrade" ? "UPGRADE_FAILED" : "APPLY_FAILED", `${manager.name} ${operation} failed for ${packageSpec}: ${errorMessage(error)}`);
50
+ }
51
+ }
52
+ function packageManagerArgs(manager, operation, packageSpec) {
53
+ if (manager === "pnpm") {
54
+ return operation === "remove"
55
+ ? ["remove", "--ignore-scripts", packageSpec]
56
+ : ["add", "--save-exact", "--ignore-scripts", packageSpec];
57
+ }
58
+ return operation === "remove"
59
+ ? ["uninstall", "--ignore-scripts", packageSpec]
60
+ : ["install", "--save-exact", "--ignore-scripts", packageSpec];
61
+ }
62
+ async function requirePackageManager(workspaceRoot) {
63
+ const manager = await readWorkspacePackageManagerSelection(workspaceRoot);
64
+ if (manager === undefined) {
65
+ throw new PlatformError("APPLY_FAILED", "workspace packageManager is not supported; expected npm or pnpm");
66
+ }
67
+ return manager;
68
+ }
69
+ async function ensureWorkspacePackageJson(workspaceRoot) {
70
+ const path = join(workspaceRoot, "package.json");
71
+ try {
72
+ const raw = await readFile(path, "utf8");
73
+ const parsed = JSON.parse(raw);
74
+ if (typeof parsed !== "object" ||
75
+ parsed === null ||
76
+ Array.isArray(parsed)) {
77
+ throw new Error("package.json root must be an object");
78
+ }
79
+ return;
80
+ }
81
+ catch (error) {
82
+ if (!isMissingFile(error)) {
83
+ throw new PlatformError("APPLY_FAILED", `workspace package.json is invalid: ${errorMessage(error)}`);
84
+ }
85
+ }
86
+ await atomicWrite(path, `${JSON.stringify({ private: true }, null, 2)}\n`);
87
+ }
88
+ async function observeInstalledVersion(workspaceRoot, packageName) {
89
+ const manifest = await readWorkspaceManifest(workspaceRoot);
90
+ if (manifest === undefined ||
91
+ (!hasOwn(manifest.dependencies, packageName) &&
92
+ !hasOwn(manifest.devDependencies, packageName))) {
93
+ return undefined;
94
+ }
95
+ const require = createRequire(pathToFileURL(join(workspaceRoot, "package.json")));
96
+ let entry;
97
+ try {
98
+ entry = require.resolve(packageName);
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ let current = dirname(entry);
104
+ for (;;) {
105
+ try {
106
+ const raw = await readFile(join(current, "package.json"), "utf8");
107
+ const parsed = JSON.parse(raw);
108
+ if (typeof parsed === "object" &&
109
+ parsed !== null &&
110
+ !Array.isArray(parsed) &&
111
+ Reflect.get(parsed, "name") === packageName &&
112
+ typeof Reflect.get(parsed, "version") === "string") {
113
+ return Reflect.get(parsed, "version");
114
+ }
115
+ }
116
+ catch {
117
+ // keep walking to the package root
118
+ }
119
+ const parent = dirname(current);
120
+ if (parent === current)
121
+ return undefined;
122
+ current = parent;
123
+ }
124
+ }
125
+ async function readWorkspaceManifest(workspaceRoot) {
126
+ try {
127
+ const raw = await readFile(join(workspaceRoot, "package.json"), "utf8");
128
+ const parsed = JSON.parse(raw);
129
+ return typeof parsed === "object" &&
130
+ parsed !== null &&
131
+ !Array.isArray(parsed)
132
+ ? parsed
133
+ : undefined;
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ function hasOwn(record, key) {
140
+ return record !== undefined && Object.hasOwn(record, key);
141
+ }
142
+ function isMissingFile(error) {
143
+ return (typeof error === "object" &&
144
+ error !== null &&
145
+ Reflect.get(error, "code") === "ENOENT");
146
+ }
147
+ function errorMessage(error) {
148
+ if (typeof error === "object" && error !== null) {
149
+ const stderr = Reflect.get(error, "stderr");
150
+ if (typeof stderr === "string" && stderr.trim() !== "")
151
+ return stderr.trim();
152
+ }
153
+ return error instanceof Error ? error.message : String(error);
154
+ }
@@ -0,0 +1,36 @@
1
+ import type { HumanAction } from "@tomflow/proflow-module-contract";
2
+ import type { DeploymentPlan, DeploymentStep } from "../contracts.ts";
3
+ import type { ModuleCatalog } from "../modules.ts";
4
+ import type { WorkspacePaths } from "../paths.ts";
5
+ import type { PackageManagerDriver } from "./driver.ts";
6
+ export interface ExecuteDeps {
7
+ paths: WorkspacePaths;
8
+ catalog: ModuleCatalog;
9
+ driver: PackageManagerDriver;
10
+ }
11
+ /**
12
+ * Structured result of executing one non-human step. A lifecycle adapter's
13
+ * `ModuleOperationResult` is preserved verbatim: ACTION_REQUIRED / BLOCKED /
14
+ * FAILED are returned as values, never collapsed into a thrown APPLY_FAILED.
15
+ * Only genuine boundary violations (missing module, unsupported strategy,
16
+ * malformed adapter result) still throw.
17
+ */
18
+ export type ExecuteStepOutcome = {
19
+ kind: "SUCCEEDED";
20
+ } | {
21
+ kind: "ACTION_REQUIRED";
22
+ actionRequired: HumanAction;
23
+ } | {
24
+ kind: "BLOCKED";
25
+ reason: string;
26
+ } | {
27
+ kind: "FAILED";
28
+ reason: string;
29
+ };
30
+ /**
31
+ * Executes a single non-human step by kind + execute strategy, returning a
32
+ * structured outcome instead of throwing for ACTION_REQUIRED / BLOCKED / FAILED
33
+ * lifecycle results. Package/config mutations still throw on genuine driver or
34
+ * boundary errors; those are not lifecycle outcomes and must not be swallowed.
35
+ */
36
+ export declare function executeStep(deps: ExecuteDeps, step: DeploymentStep, plan: DeploymentPlan): Promise<ExecuteStepOutcome>;
@@ -0,0 +1,124 @@
1
+ import { PlatformError } from "../errors.js";
2
+ import { dispatchLifecycle } from "../lifecycle/index.js";
3
+ import { materializeConfig } from "../persistence/index.js";
4
+ import { ExecuteStrategy } from "../planner/index.js";
5
+ import { cleanupRemovableFilesystemEffects } from "./cleanup.js";
6
+ function moduleOf(plan, step) {
7
+ return plan.resolvedModules.find((module) => module.moduleRef === step.moduleRef);
8
+ }
9
+ function configForStep(plan, step, module) {
10
+ const target = plan.moduleTargets.find((entry) => entry.moduleRef === step.moduleRef);
11
+ if (target?.config === undefined)
12
+ return undefined;
13
+ const secretRefs = module.configSlots
14
+ .filter((slot) => slot.type === "secretRef")
15
+ .map((slot) => slot.key);
16
+ return {
17
+ moduleRef: step.moduleRef,
18
+ values: target.config,
19
+ secretRefs,
20
+ };
21
+ }
22
+ function lifecyclePrimitive(step) {
23
+ switch (step.executeStrategy) {
24
+ case ExecuteStrategy.lifecycleStart:
25
+ return "start";
26
+ case ExecuteStrategy.lifecycleStop:
27
+ return "stop";
28
+ case ExecuteStrategy.lifecycleRestart:
29
+ return "restart";
30
+ case ExecuteStrategy.lifecycleUninstall:
31
+ return "uninstall";
32
+ case ExecuteStrategy.lifecycleMigrate:
33
+ return "migrate";
34
+ default:
35
+ throw new PlatformError("APPLY_FAILED", `unsupported lifecycle strategy ${step.executeStrategy ?? "<none>"}`);
36
+ }
37
+ }
38
+ function externalResourcePrimitive(step) {
39
+ if (step.executeStrategy === ExecuteStrategy.externalResourceConfigure) {
40
+ return "start";
41
+ }
42
+ throw new PlatformError("APPLY_FAILED", `unsupported external-resource strategy ${step.executeStrategy ?? "<none>"}`);
43
+ }
44
+ function requiredActionRequired(result) {
45
+ const actionRequired = result.actionRequired;
46
+ if (actionRequired === undefined) {
47
+ throw new PlatformError("APPLY_FAILED", `module ${result.moduleRef} reported ACTION_REQUIRED without a recoverable action`);
48
+ }
49
+ return actionRequired;
50
+ }
51
+ function operationOutcome(result) {
52
+ switch (result.status) {
53
+ case "SUCCEEDED":
54
+ return { kind: "SUCCEEDED" };
55
+ case "ACTION_REQUIRED":
56
+ return {
57
+ kind: "ACTION_REQUIRED",
58
+ actionRequired: requiredActionRequired(result),
59
+ };
60
+ case "BLOCKED":
61
+ return {
62
+ kind: "BLOCKED",
63
+ reason: `module ${result.moduleRef} is blocked`,
64
+ };
65
+ case "FAILED":
66
+ return {
67
+ kind: "FAILED",
68
+ reason: result.error?.message ?? `module ${result.moduleRef} failed`,
69
+ };
70
+ }
71
+ }
72
+ /**
73
+ * Executes a single non-human step by kind + execute strategy, returning a
74
+ * structured outcome instead of throwing for ACTION_REQUIRED / BLOCKED / FAILED
75
+ * lifecycle results. Package/config mutations still throw on genuine driver or
76
+ * boundary errors; those are not lifecycle outcomes and must not be swallowed.
77
+ */
78
+ export async function executeStep(deps, step, plan) {
79
+ const module = moduleOf(plan, step);
80
+ if (module === undefined) {
81
+ throw new PlatformError("APPLY_FAILED", `module ${step.moduleRef} is not in the plan`);
82
+ }
83
+ switch (step.kind) {
84
+ case "package": {
85
+ if (step.executeStrategy === ExecuteStrategy.packageRemove) {
86
+ await deps.driver.remove(module);
87
+ }
88
+ else if (step.executeStrategy === ExecuteStrategy.packageUpgrade) {
89
+ await deps.driver.upgrade(module);
90
+ }
91
+ else {
92
+ await deps.driver.install(module);
93
+ }
94
+ return { kind: "SUCCEEDED" };
95
+ }
96
+ case "config": {
97
+ const config = configForStep(plan, step, module);
98
+ if (config === undefined) {
99
+ throw new PlatformError("APPLY_FAILED", `module ${step.moduleRef} has no config target to materialize`);
100
+ }
101
+ await materializeConfig(deps.paths, config);
102
+ return { kind: "SUCCEEDED" };
103
+ }
104
+ case "lifecycle": {
105
+ const primitive = lifecyclePrimitive(step);
106
+ const dispatched = await dispatchLifecycle(deps.catalog, module, primitive);
107
+ const operation = operationOutcome(dispatched.result);
108
+ if (primitive === "uninstall" && operation.kind === "SUCCEEDED") {
109
+ await cleanupRemovableFilesystemEffects({
110
+ workspaceRoot: deps.paths.root,
111
+ effects: module.effects,
112
+ });
113
+ }
114
+ return operation;
115
+ }
116
+ case "external-resource": {
117
+ const primitive = externalResourcePrimitive(step);
118
+ const dispatched = await dispatchLifecycle(deps.catalog, module, primitive);
119
+ return operationOutcome(dispatched.result);
120
+ }
121
+ case "human":
122
+ throw new PlatformError("APPLY_FAILED", `human step ${step.stepRef} must be routed to ACTION_REQUIRED, not executed`);
123
+ }
124
+ }
@@ -0,0 +1,8 @@
1
+ export type { ApplyContext } from "./apply.ts";
2
+ export { applyPlan } from "./apply.ts";
3
+ export type { PackageManagerDriver } from "./driver.ts";
4
+ export { createWorkspacePackageManagerDriver } from "./driver.ts";
5
+ export type { ExecuteDeps, ExecuteStepOutcome } from "./execute.ts";
6
+ export { executeStep } from "./execute.ts";
7
+ export type { RealityObserver, RealityObserverDeps } from "./reality.ts";
8
+ export { createRealityObserver } from "./reality.ts";
@@ -0,0 +1,4 @@
1
+ export { applyPlan } from "./apply.js";
2
+ export { createWorkspacePackageManagerDriver } from "./driver.js";
3
+ export { executeStep } from "./execute.js";
4
+ export { createRealityObserver } from "./reality.js";
@@ -0,0 +1,20 @@
1
+ import type { DeploymentPlan, DeploymentStep } from "../contracts.ts";
2
+ import type { ModuleCatalog } from "../modules.ts";
3
+ import type { WorkspacePaths } from "../paths.ts";
4
+ import type { StepReality } from "../planner/index.ts";
5
+ import type { PackageManagerDriver } from "./driver.ts";
6
+ /**
7
+ * Observes the current reality of a single step. The apply loop consults this
8
+ * before every decision so that persisted history can never impersonate current
9
+ * reality; a step that is no longer satisfied is re-executed, never faked as
10
+ * skipped.
11
+ */
12
+ export interface RealityObserver {
13
+ observe(step: DeploymentStep, plan: DeploymentPlan): Promise<StepReality | undefined>;
14
+ }
15
+ export interface RealityObserverDeps {
16
+ paths: WorkspacePaths;
17
+ catalog: ModuleCatalog;
18
+ driver: PackageManagerDriver;
19
+ }
20
+ export declare function createRealityObserver(deps: RealityObserverDeps): RealityObserver;
@@ -0,0 +1,93 @@
1
+ import { dispatchLifecycle } from "../lifecycle/index.js";
2
+ import { loadConfig } from "../persistence/index.js";
3
+ export function createRealityObserver(deps) {
4
+ return {
5
+ observe(step, plan) {
6
+ return observeStep(deps, step, plan);
7
+ },
8
+ };
9
+ }
10
+ function moduleOf(plan, step) {
11
+ return plan.resolvedModules.find((module) => module.moduleRef === step.moduleRef);
12
+ }
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ function isStringMap(value) {
17
+ if (!isRecord(value))
18
+ return false;
19
+ return Object.values(value).every((item) => typeof item === "string");
20
+ }
21
+ /**
22
+ * Runtime-validates an adapter `status` data payload into a `StepReality` at the
23
+ * boundary. Only recognized fields survive; anything else is ignored so an
24
+ * arbitrary payload can never be mistaken for observed reality. A service
25
+ * `status` payload carrying `state: "RUNNING" | "STOPPED"` maps onto the
26
+ * boolean `processRunning` reality.
27
+ */
28
+ function parseStatusReality(value) {
29
+ if (!isRecord(value))
30
+ return {};
31
+ const reality = {};
32
+ if (typeof value.processRunning === "boolean") {
33
+ reality.processRunning = value.processRunning;
34
+ }
35
+ else if (value.state === "RUNNING") {
36
+ reality.processRunning = true;
37
+ }
38
+ else if (value.state === "STOPPED") {
39
+ reality.processRunning = false;
40
+ }
41
+ if (typeof value.resourceConfigured === "boolean") {
42
+ reality.resourceConfigured = value.resourceConfigured;
43
+ }
44
+ if (typeof value.humanActionVerified === "boolean") {
45
+ reality.humanActionVerified = value.humanActionVerified;
46
+ }
47
+ if (typeof value.migrated === "boolean") {
48
+ reality.migrated = value.migrated;
49
+ }
50
+ if (typeof value.installedVersion === "string") {
51
+ reality.installedVersion = value.installedVersion;
52
+ }
53
+ if (isStringMap(value.configValues)) {
54
+ reality.configValues = value.configValues;
55
+ }
56
+ return reality;
57
+ }
58
+ async function observeViaStatus(catalog, module) {
59
+ if (!module.lifecycle.includes("status"))
60
+ return {};
61
+ try {
62
+ const dispatched = await dispatchLifecycle(catalog, module, "status");
63
+ return parseStatusReality(dispatched.result.data);
64
+ }
65
+ catch {
66
+ // Observation failed (transport/timeout/malformed): UNKNOWN, never a
67
+ // fabricated NOT_SATISFIED that would replay a lifecycle/external effect.
68
+ return undefined;
69
+ }
70
+ }
71
+ async function observeStep(deps, step, plan) {
72
+ const module = moduleOf(plan, step);
73
+ if (module === undefined)
74
+ return {};
75
+ switch (step.kind) {
76
+ case "package": {
77
+ const installedVersion = await deps.driver.observeInstalledVersion(module);
78
+ return installedVersion === undefined ? {} : { installedVersion };
79
+ }
80
+ case "config": {
81
+ const config = await loadConfig(deps.paths, step.moduleRef);
82
+ if (config === undefined)
83
+ return {};
84
+ return {
85
+ configValues: { ...config.publicValues, ...config.secretValues },
86
+ };
87
+ }
88
+ case "lifecycle":
89
+ case "external-resource":
90
+ case "human":
91
+ return observeViaStatus(deps.catalog, module);
92
+ }
93
+ }
@@ -0,0 +1,45 @@
1
+ import type { ResolvedModule } from "../contracts.ts";
2
+ type ResolvedSource = ResolvedModule["source"];
3
+ export interface DeploymentAdapterBinding {
4
+ behaviorAdapter: Record<string, unknown>;
5
+ }
6
+ export type ProductionBindingFactory = (input: {
7
+ moduleRef: string;
8
+ config: Record<string, string>;
9
+ workspaceRoot: string;
10
+ modules: readonly ResolvedModule[];
11
+ configByModuleRef: ReadonlyMap<string, Record<string, string>>;
12
+ }) => Promise<DeploymentAdapterBinding | undefined> | DeploymentAdapterBinding | undefined;
13
+ export type ServiceProcessBindingFactory = (input: {
14
+ moduleRef: string;
15
+ config: Record<string, string>;
16
+ workspaceRoot: string;
17
+ modules: readonly ResolvedModule[];
18
+ configByModuleRef: ReadonlyMap<string, Record<string, string>>;
19
+ }) => Promise<{
20
+ serviceProcess: unknown;
21
+ behaviorAdapter?: Record<string, unknown>;
22
+ } | undefined> | {
23
+ serviceProcess: unknown;
24
+ behaviorAdapter?: Record<string, unknown>;
25
+ } | undefined;
26
+ export interface ProductionBindingOptions {
27
+ workspaceRoot: string;
28
+ modules: readonly ResolvedModule[];
29
+ configByModuleRef: ReadonlyMap<string, Record<string, string>>;
30
+ importAdapter: (packageName: string, source: ResolvedSource) => Promise<Record<string, unknown>>;
31
+ }
32
+ export declare function importRawAdapter(packageName: string, source: ResolvedSource): Promise<Record<string, unknown>>;
33
+ /**
34
+ * The shipped Platform CLI production binding factory. For every discovered
35
+ * module it imports the module's own `deployment/adapter.ts` and, when that
36
+ * adapter exposes a `createProductionBinding` factory, invokes it with the
37
+ * module's materialized config to obtain a real bound adapter. A module without
38
+ * a production factory — or whose import/factory fails, or which has no
39
+ * materialized config the adapter accepts — is left out of the map, so the
40
+ * catalog falls back to the module's unbound default, which must fail-closed
41
+ * (ACTION_REQUIRED / NOT_READY). Platform CLI never invents a service or
42
+ * resource reality: it only relays the adapter's own current reality.
43
+ */
44
+ export declare function buildProductionBindings(options: ProductionBindingOptions): Promise<ReadonlyMap<string, DeploymentAdapterBinding>>;
45
+ export {};
@@ -0,0 +1,102 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { join } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+ import { managedServiceStatus, restartManagedService, startManagedService, stopManagedService, } from "../lifecycle/service-process.js";
12
+ import { workspacePaths } from "../paths.js";
13
+ function managedServiceAdapter(workspaceRoot, module, serviceProcess, probeAdapter) {
14
+ const paths = workspacePaths(workspaceRoot);
15
+ return {
16
+ ...probeAdapter,
17
+ status: () => managedServiceStatus(paths, module),
18
+ start: () => startManagedService(paths, module, serviceProcess),
19
+ stop: () => stopManagedService(paths, module),
20
+ restart: () => restartManagedService(paths, module, serviceProcess),
21
+ uninstall: () => stopManagedService(paths, module),
22
+ };
23
+ }
24
+ export async function importRawAdapter(packageName, source) {
25
+ if (source.type === "workspace") {
26
+ if (source.path === undefined)
27
+ return {};
28
+ const url = pathToFileURL(join(source.path, "deployment", "adapter.ts"));
29
+ return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
30
+ }
31
+ const resolved = import.meta.resolve(`${packageName}/deployment/adapter`);
32
+ const url = new URL(resolved);
33
+ return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
34
+ }
35
+ /**
36
+ * The shipped Platform CLI production binding factory. For every discovered
37
+ * module it imports the module's own `deployment/adapter.ts` and, when that
38
+ * adapter exposes a `createProductionBinding` factory, invokes it with the
39
+ * module's materialized config to obtain a real bound adapter. A module without
40
+ * a production factory — or whose import/factory fails, or which has no
41
+ * materialized config the adapter accepts — is left out of the map, so the
42
+ * catalog falls back to the module's unbound default, which must fail-closed
43
+ * (ACTION_REQUIRED / NOT_READY). Platform CLI never invents a service or
44
+ * resource reality: it only relays the adapter's own current reality.
45
+ */
46
+ export async function buildProductionBindings(options) {
47
+ const bindings = new Map();
48
+ for (const module of options.modules) {
49
+ try {
50
+ const namespace = await options.importAdapter(module.packageName, module.source);
51
+ const config = options.configByModuleRef.get(module.moduleRef) ?? {};
52
+ const serviceFactory = namespace.createServiceProcessBinding;
53
+ if (module.kind === "service" && typeof serviceFactory === "function") {
54
+ const processBinding = await serviceFactory({
55
+ moduleRef: module.moduleRef,
56
+ config,
57
+ workspaceRoot: options.workspaceRoot,
58
+ modules: options.modules,
59
+ configByModuleRef: options.configByModuleRef,
60
+ });
61
+ if (processBinding !== undefined) {
62
+ const defaultProbe = namespace
63
+ .behaviorAdapter;
64
+ const probeAdapter = processBinding.behaviorAdapter ??
65
+ (typeof defaultProbe === "object" && defaultProbe !== null
66
+ ? defaultProbe
67
+ : {});
68
+ bindings.set(module.packageName, {
69
+ behaviorAdapter: managedServiceAdapter(options.workspaceRoot, module, processBinding.serviceProcess, probeAdapter),
70
+ });
71
+ }
72
+ // A formal service package that exposes the process seam must never fall
73
+ // back to legacy in-memory production binding. Missing config remains
74
+ // unbound and therefore fails closed.
75
+ continue;
76
+ }
77
+ const factory = namespace
78
+ .createProductionBinding;
79
+ if (typeof factory !== "function")
80
+ continue;
81
+ const binding = await factory({
82
+ moduleRef: module.moduleRef,
83
+ config,
84
+ workspaceRoot: options.workspaceRoot,
85
+ modules: options.modules,
86
+ configByModuleRef: options.configByModuleRef,
87
+ });
88
+ if (binding !== undefined &&
89
+ typeof binding === "object" &&
90
+ binding !== null &&
91
+ typeof binding.behaviorAdapter === "object" &&
92
+ binding.behaviorAdapter !== null) {
93
+ bindings.set(module.packageName, binding);
94
+ }
95
+ }
96
+ catch {
97
+ // Import/factory failure for one module must not break the rest of the
98
+ // CLI; that module simply remains unbound and fails closed when used.
99
+ }
100
+ }
101
+ return bindings;
102
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ export type CliStatus = "SUCCEEDED" | "ACTION_REQUIRED" | "BLOCKED" | "FAILED";
3
+ export interface CliOutcome {
4
+ command: string;
5
+ status: CliStatus;
6
+ data?: unknown;
7
+ error?: {
8
+ code: string;
9
+ message: string;
10
+ };
11
+ }
12
+ export declare function runCli(argv: readonly string[]): Promise<string>;