@tomflow/proflow-platform-cli 0.1.16 → 0.1.18

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 (37) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/DOCS.md +27 -0
  3. package/SETUP.md +27 -0
  4. package/dist/deployment/adapter.d.ts +58 -34
  5. package/dist/deployment/adapter.js +28 -12
  6. package/dist/deployment/descriptor.d.ts +5 -16
  7. package/dist/deployment/descriptor.js +10 -21
  8. package/dist/src/cli.js +173 -140
  9. package/dist/src/contracts.d.ts +0 -2
  10. package/dist/src/discovery/discover.d.ts +1 -3
  11. package/dist/src/discovery/discover.js +1 -10
  12. package/dist/src/errors.d.ts +1 -1
  13. package/dist/src/errors.js +0 -2
  14. package/dist/src/index.d.ts +0 -2
  15. package/dist/src/index.js +0 -1
  16. package/dist/src/lifecycle/dispatch.d.ts +4 -11
  17. package/dist/src/lifecycle/dispatch.js +28 -56
  18. package/dist/src/lifecycle/index.d.ts +4 -4
  19. package/dist/src/lifecycle/index.js +2 -2
  20. package/dist/src/lifecycle/thin.d.ts +19 -7
  21. package/dist/src/lifecycle/thin.js +83 -53
  22. package/dist/src/persistence/index.d.ts +0 -2
  23. package/dist/src/persistence/index.js +0 -1
  24. package/package.json +7 -5
  25. package/proflow.module.json +5 -20
  26. package/dist/src/binding/production-bindings.d.ts +0 -37
  27. package/dist/src/binding/production-bindings.js +0 -75
  28. package/dist/src/docs/aggregate.d.ts +0 -18
  29. package/dist/src/docs/aggregate.js +0 -44
  30. package/dist/src/docs/docs.d.ts +0 -16
  31. package/dist/src/docs/docs.js +0 -68
  32. package/dist/src/docs/index.d.ts +0 -2
  33. package/dist/src/docs/index.js +0 -1
  34. package/dist/src/persistence/config.d.ts +0 -6
  35. package/dist/src/persistence/config.js +0 -54
  36. package/dist/src/persistence/guards.d.ts +0 -1
  37. package/dist/src/persistence/guards.js +0 -7
@@ -1,72 +1,44 @@
1
1
  import { moduleOperationResultSchema, } from "@tomflow/proflow-module-contract";
2
2
  import { PlatformError } from "../errors.js";
3
- function isRecord(value) {
4
- return typeof value === "object" && value !== null;
5
- }
6
- function isPrimitiveFn(value) {
7
- return typeof value === "function";
8
- }
9
- function isWrappedResult(value) {
10
- return isRecord(value) && "result" in value;
11
- }
3
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const isCommandFn = (value) => typeof value === "function";
12
5
  function moduleSource(module) {
13
- const source = {
14
- type: module.source.type,
15
- packageName: module.packageName,
16
- };
17
- if (module.source.path !== undefined)
18
- source.path = module.source.path;
19
- return source;
6
+ return module.source.path === undefined
7
+ ? { type: module.source.type, packageName: module.packageName }
8
+ : {
9
+ type: module.source.type,
10
+ packageName: module.packageName,
11
+ path: module.source.path,
12
+ };
20
13
  }
21
14
  function resolveBehaviorAdapter(namespace) {
22
- if (!isRecord(namespace)) {
23
- throw new PlatformError("COMMAND_FAILED", "lifecycle adapter namespace is not an object");
24
- }
25
- const behaviorAdapter = namespace.behaviorAdapter;
26
- if (!isRecord(behaviorAdapter)) {
27
- throw new PlatformError("COMMAND_FAILED", "lifecycle adapter exposes no behaviorAdapter object");
28
- }
29
- return behaviorAdapter;
30
- }
31
- function observedEffectsOf(value) {
32
- if (!Array.isArray(value))
33
- return [];
34
- return value.filter((item) => typeof item === "string");
15
+ if (!isRecord(namespace) || !isRecord(namespace.behaviorAdapter))
16
+ throw new PlatformError("COMMAND_FAILED", "module adapter exposes no behaviorAdapter object");
17
+ return namespace.behaviorAdapter;
35
18
  }
36
19
  function normalizeInvocation(raw) {
37
- if (isWrappedResult(raw)) {
38
- return {
39
- result: raw.result,
40
- observedEffects: observedEffectsOf(raw.observedEffects),
41
- };
42
- }
43
- return { result: raw, observedEffects: [] };
20
+ if (!isRecord(raw) || !("result" in raw))
21
+ return { result: raw, observedEffects: [] };
22
+ return {
23
+ result: raw.result,
24
+ observedEffects: Array.isArray(raw.observedEffects)
25
+ ? raw.observedEffects.filter((item) => typeof item === "string")
26
+ : [],
27
+ };
44
28
  }
45
- /**
46
- * Dispatches a single lifecycle primitive against one module through its
47
- * public deployment adapter. The descriptor is the source of truth for what is
48
- * supported: a primitive not declared in `lifecycle` is rejected with
49
- * `LIFECYCLE_UNSUPPORTED` rather than faked. The adapter result is always
50
- * runtime-validated against the Module Operation Result schema.
51
- */
52
- export async function dispatchLifecycle(catalog, module, primitive) {
53
- if (!module.lifecycle.includes(primitive)) {
54
- throw new PlatformError("LIFECYCLE_UNSUPPORTED", `module ${module.moduleRef} does not declare lifecycle primitive "${primitive}"`);
55
- }
29
+ export async function dispatchModuleCommand(catalog, module, command, context) {
56
30
  const namespace = await catalog.loadAdapter(moduleSource(module));
57
31
  const adapter = resolveBehaviorAdapter(namespace);
58
- const invoke = adapter[primitive];
59
- if (!isPrimitiveFn(invoke)) {
60
- throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} declares "${primitive}" but its adapter does not implement it`);
61
- }
62
- const { result, observedEffects } = normalizeInvocation(await invoke());
32
+ const invoke = adapter[command];
33
+ if (!isCommandFn(invoke))
34
+ throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} does not implement standard command "${command}"`);
35
+ const { result, observedEffects } = normalizeInvocation(await invoke(context));
63
36
  const parsed = moduleOperationResultSchema.safeParse(result);
64
- if (!parsed.success) {
65
- throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} "${primitive}" returned an invalid result: ${parsed.error.message}`);
66
- }
37
+ if (!parsed.success)
38
+ throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} "${command}" returned an invalid result: ${parsed.error.message}`);
67
39
  return {
68
40
  moduleRef: module.moduleRef,
69
- primitive,
41
+ command,
70
42
  result: parsed.data,
71
43
  observedEffects,
72
44
  };
@@ -1,4 +1,4 @@
1
- export type { LifecycleDispatchResult } from "./dispatch.ts";
2
- export { dispatchLifecycle } from "./dispatch.ts";
3
- export type { ThinLifecycleResult } from "./thin.ts";
4
- export { observeStatuses, preflightAndStartModules, stopModulesThin, } from "./thin.ts";
1
+ export type { ModuleDispatchResult } from "./dispatch.ts";
2
+ export { dispatchModuleCommand } from "./dispatch.ts";
3
+ export type { ModuleBatchResult } from "./thin.ts";
4
+ export { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./thin.ts";
@@ -1,2 +1,2 @@
1
- export { dispatchLifecycle } from "./dispatch.js";
2
- export { observeStatuses, preflightAndStartModules, stopModulesThin, } from "./thin.js";
1
+ export { dispatchModuleCommand } from "./dispatch.js";
2
+ export { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./thin.js";
@@ -1,11 +1,23 @@
1
+ import { type ModuleSetupStatus } from "@tomflow/proflow-module-contract";
1
2
  import type { ResolvedModule } from "../contracts.ts";
2
3
  import type { ModuleCatalog } from "../modules.ts";
3
- import { type LifecycleDispatchResult } from "./dispatch.ts";
4
- export interface ThinLifecycleResult {
5
- phase: "preflight" | "start" | "stop";
6
- results: LifecycleDispatchResult[];
4
+ import { type ModuleDispatchResult } from "./dispatch.ts";
5
+ export interface ModuleBatchResult {
6
+ phase: "install" | "uninstall" | "setup" | "start" | "stop";
7
+ results: ModuleDispatchResult[];
7
8
  completed: boolean;
9
+ blockedBy?: {
10
+ moduleRef: string;
11
+ setupStatus: ModuleSetupStatus;
12
+ };
8
13
  }
9
- export declare function observeStatuses(catalog: ModuleCatalog, modules: readonly ResolvedModule[]): Promise<LifecycleDispatchResult[]>;
10
- export declare function preflightAndStartModules(catalog: ModuleCatalog, modules: readonly ResolvedModule[]): Promise<ThinLifecycleResult>;
11
- export declare function stopModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[]): Promise<ThinLifecycleResult>;
14
+ export declare function observeStatuses(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleDispatchResult[]>;
15
+ export declare function observeDocs(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleDispatchResult[]>;
16
+ export declare const installModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string) => Promise<ModuleBatchResult>;
17
+ export declare const uninstallModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string) => Promise<ModuleBatchResult>;
18
+ export declare const stopModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string) => Promise<ModuleBatchResult>;
19
+ export declare function setupModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, target?: {
20
+ moduleRef: string;
21
+ input?: unknown;
22
+ }): Promise<ModuleBatchResult>;
23
+ export declare function startModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleBatchResult>;
@@ -1,68 +1,98 @@
1
+ import { moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
2
+ import { PlatformError } from "../errors.js";
1
3
  import { buildDependencyGraph } from "../graph/graph.js";
2
- import { dispatchLifecycle } from "./dispatch.js";
3
- function succeeded(result) {
4
- return result.status === "SUCCEEDED";
4
+ import { dispatchModuleCommand, } from "./dispatch.js";
5
+ const succeeded = (result) => result.status === "SUCCEEDED";
6
+ const context = (workspaceRoot, input) => input === undefined ? { workspaceRoot } : { workspaceRoot, input };
7
+ function ordered(modules, reverse = false) {
8
+ const graph = buildDependencyGraph(modules);
9
+ const refs = reverse ? [...graph.order].reverse() : [...graph.order];
10
+ const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
11
+ return refs
12
+ .map((ref) => byRef.get(ref))
13
+ .filter((item) => item !== undefined);
5
14
  }
6
- async function dispatchIfSupported(catalog, module, primitive) {
7
- if (!module.lifecycle.includes(primitive))
8
- return undefined;
9
- return dispatchLifecycle(catalog, module, primitive);
15
+ export async function observeStatuses(catalog, modules, workspaceRoot) {
16
+ const results = [];
17
+ for (const module of [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef)))
18
+ results.push(await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot)));
19
+ return results;
10
20
  }
11
- export async function observeStatuses(catalog, modules) {
21
+ export async function observeDocs(catalog, modules, workspaceRoot) {
12
22
  const results = [];
13
- for (const module of [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef))) {
14
- if (!module.lifecycle.includes("status"))
15
- continue;
16
- results.push(await dispatchLifecycle(catalog, module, "status"));
17
- }
23
+ for (const module of [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef)))
24
+ results.push(await dispatchModuleCommand(catalog, module, "docs", context(workspaceRoot)));
18
25
  return results;
19
26
  }
20
- export async function preflightAndStartModules(catalog, modules) {
21
- const graph = buildDependencyGraph(modules);
22
- const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
23
- const preflight = [];
24
- for (const moduleRef of graph.order) {
25
- const module = byRef.get(moduleRef);
26
- if (module === undefined)
27
+ async function runOrdered(catalog, modules, workspaceRoot, command, reverse) {
28
+ const results = [];
29
+ for (const module of ordered(modules, reverse)) {
30
+ const result = await dispatchModuleCommand(catalog, module, command, context(workspaceRoot));
31
+ results.push(result);
32
+ if (!succeeded(result.result))
33
+ return { phase: command, results, completed: false };
34
+ }
35
+ return { phase: command, results, completed: true };
36
+ }
37
+ export const installModulesThin = (catalog, modules, workspaceRoot) => runOrdered(catalog, modules, workspaceRoot, "install", false);
38
+ export const uninstallModulesThin = (catalog, modules, workspaceRoot) => runOrdered(catalog, modules, workspaceRoot, "uninstall", true);
39
+ export const stopModulesThin = (catalog, modules, workspaceRoot) => runOrdered(catalog, modules, workspaceRoot, "stop", true);
40
+ export async function setupModulesThin(catalog, modules, workspaceRoot, target) {
41
+ const results = [];
42
+ let matched = target === undefined;
43
+ let completed = true;
44
+ for (const module of ordered(modules)) {
45
+ if (target !== undefined && module.moduleRef !== target.moduleRef)
27
46
  continue;
28
- const result = await dispatchIfSupported(catalog, module, "preflight");
29
- if (result === undefined)
47
+ matched = true;
48
+ const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
49
+ if (!succeeded(status.result)) {
50
+ results.push(status);
51
+ completed = false;
52
+ if (target !== undefined)
53
+ break;
30
54
  continue;
31
- preflight.push(result);
32
- if (!succeeded(result.result)) {
33
- return { phase: "preflight", results: preflight, completed: false };
34
55
  }
35
- }
36
- const started = [];
37
- for (const moduleRef of graph.order) {
38
- const module = byRef.get(moduleRef);
39
- if (module === undefined)
56
+ const observed = moduleStatusObservationSchema.parse(status.result.data);
57
+ if (observed.setupStatus === "READY" && target?.input === undefined)
40
58
  continue;
41
- const result = await dispatchIfSupported(catalog, module, "start");
42
- if (result === undefined)
43
- continue;
44
- started.push(result);
45
- if (!succeeded(result.result)) {
46
- return { phase: "start", results: started, completed: false };
59
+ const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
60
+ results.push(setup);
61
+ if (!succeeded(setup.result)) {
62
+ completed = false;
63
+ if (target !== undefined)
64
+ break;
47
65
  }
48
66
  }
49
- return { phase: "start", results: started, completed: true };
67
+ if (!matched)
68
+ throw new PlatformError("INVALID_REQUEST", `setup target module ${target?.moduleRef ?? ""} was not discovered`);
69
+ return { phase: "setup", results, completed };
50
70
  }
51
- export async function stopModulesThin(catalog, modules) {
52
- const order = [...buildDependencyGraph(modules).order].reverse();
53
- const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
54
- const stopped = [];
55
- for (const moduleRef of order) {
56
- const module = byRef.get(moduleRef);
57
- if (module === undefined)
58
- continue;
59
- const result = await dispatchIfSupported(catalog, module, "stop");
60
- if (result === undefined)
61
- continue;
62
- stopped.push(result);
63
- if (!succeeded(result.result)) {
64
- return { phase: "stop", results: stopped, completed: false };
65
- }
71
+ export async function startModulesThin(catalog, modules, workspaceRoot) {
72
+ const results = [];
73
+ for (const module of ordered(modules)) {
74
+ const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
75
+ if (!succeeded(status.result))
76
+ return {
77
+ phase: "start",
78
+ results: [...results, status],
79
+ completed: false,
80
+ };
81
+ const observed = moduleStatusObservationSchema.parse(status.result.data);
82
+ if (observed.setupStatus !== "READY")
83
+ return {
84
+ phase: "start",
85
+ results: [...results, status],
86
+ completed: false,
87
+ blockedBy: {
88
+ moduleRef: module.moduleRef,
89
+ setupStatus: observed.setupStatus,
90
+ },
91
+ };
92
+ const started = await dispatchModuleCommand(catalog, module, "start", context(workspaceRoot));
93
+ results.push(started);
94
+ if (!succeeded(started.result))
95
+ return { phase: "start", results, completed: false };
66
96
  }
67
- return { phase: "stop", results: stopped, completed: true };
97
+ return { phase: "start", results, completed: true };
68
98
  }
@@ -1,4 +1,2 @@
1
- export type { MaterializedConfig } from "./config.ts";
2
- export { loadConfig } from "./config.ts";
3
1
  export type { WorkspaceMetadata } from "./workspace-metadata.ts";
4
2
  export { ensureWorkspaceMetadata } from "./workspace-metadata.ts";
@@ -1,2 +1 @@
1
- export { loadConfig } from "./config.js";
2
1
  export { ensureWorkspaceMetadata } from "./workspace-metadata.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,14 +18,16 @@
18
18
  "dist",
19
19
  "proflow.module.json",
20
20
  "conformance.json",
21
- "README.md"
21
+ "README.md",
22
+ "DOCS.md",
23
+ "SETUP.md"
22
24
  ],
23
25
  "dependencies": {
24
- "@tomflow/proflow-module-contract": "^0.1.3"
26
+ "@tomflow/proflow-module-contract": "^0.1.5"
25
27
  },
26
28
  "devDependencies": {
27
- "@tomflow/proflow-deployment-conformance": "^0.1.3",
28
- "@tomflow/proflow-module-template": "^0.1.3"
29
+ "@tomflow/proflow-module-template": "^0.1.5",
30
+ "@tomflow/proflow-deployment-conformance": "^0.1.5"
29
31
  },
30
32
  "description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
31
33
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-cli",
5
5
  "packageName": "@tomflow/proflow-platform-cli",
6
- "moduleVersion": "0.1.16",
6
+ "moduleVersion": "0.1.18",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",
@@ -21,24 +21,9 @@
21
21
  }
22
22
  ],
23
23
  "configSlots": [],
24
- "lifecycle": {
25
- "supported": ["describe", "preflight", "status", "verify", "doctor"]
26
- },
27
- "verification": {
28
- "checks": [
29
- {
30
- "id": "cli-surface",
31
- "description": "Deterministic command surface parses and dispatches",
32
- "lifecycle": "verify"
33
- }
34
- ]
35
- },
36
24
  "effects": [],
37
- "documentation": [
38
- {
39
- "id": "overview",
40
- "path": "./README.md",
41
- "description": "Platform CLI package overview and command surface"
42
- }
43
- ]
25
+ "documentation": {
26
+ "docs": "DOCS.md",
27
+ "setup": "SETUP.md"
28
+ }
44
29
  }
@@ -1,37 +0,0 @@
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 interface ProductionBindingOptions {
14
- workspaceRoot: string;
15
- modules: readonly ResolvedModule[];
16
- configByModuleRef: ReadonlyMap<string, {
17
- publicValues: Record<string, string>;
18
- secretValues: Record<string, string>;
19
- }>;
20
- importAdapter: (packageName: string, source: ResolvedSource) => Promise<Record<string, unknown>>;
21
- }
22
- export declare function importRawAdapter(packageName: string, source: ResolvedSource, workspaceRoot: string): Promise<Record<string, unknown>>;
23
- /**
24
- * The shipped Platform CLI production binding factory. For every discovered
25
- * module it imports the module's own `deployment/adapter.ts` and, when that
26
- * adapter exposes a `createProductionBinding` factory, invokes it with the
27
- * module's materialized config to obtain a real bound adapter. A module without
28
- * a production factory, or whose materialized config the adapter does not accept,
29
- * is left out of the map. Import/factory exceptions are surfaced with module
30
- * identity instead of being mistaken for ordinary unconfigured state. The
31
- * catalog falls back to the module's unbound default only for explicit undefined
32
- * bindings, which must fail-closed
33
- * (ACTION_REQUIRED / NOT_READY). Platform CLI never invents a service or
34
- * resource reality: it only relays the adapter's own current reality.
35
- */
36
- export declare function buildProductionBindings(options: ProductionBindingOptions): Promise<ReadonlyMap<string, DeploymentAdapterBinding>>;
37
- export {};
@@ -1,75 +0,0 @@
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 { createRequire } from "node:module";
10
- import { join } from "node:path";
11
- import { pathToFileURL } from "node:url";
12
- import { PlatformError } from "../errors.js";
13
- export async function importRawAdapter(packageName, source, workspaceRoot) {
14
- if (source.type === "workspace") {
15
- if (source.path === undefined)
16
- return {};
17
- const url = pathToFileURL(join(source.path, "deployment", "adapter.ts"));
18
- return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
19
- }
20
- const workspaceRequire = createRequire(pathToFileURL(join(workspaceRoot, "package.json")));
21
- const resolved = workspaceRequire.resolve(`${packageName}/deployment/adapter`);
22
- const url = pathToFileURL(resolved);
23
- return (await /* architecture-allow-local-file-url-import */ import(__rewriteRelativeImportExtension(url.href)));
24
- }
25
- /**
26
- * The shipped Platform CLI production binding factory. For every discovered
27
- * module it imports the module's own `deployment/adapter.ts` and, when that
28
- * adapter exposes a `createProductionBinding` factory, invokes it with the
29
- * module's materialized config to obtain a real bound adapter. A module without
30
- * a production factory, or whose materialized config the adapter does not accept,
31
- * is left out of the map. Import/factory exceptions are surfaced with module
32
- * identity instead of being mistaken for ordinary unconfigured state. The
33
- * catalog falls back to the module's unbound default only for explicit undefined
34
- * bindings, which must fail-closed
35
- * (ACTION_REQUIRED / NOT_READY). Platform CLI never invents a service or
36
- * resource reality: it only relays the adapter's own current reality.
37
- */
38
- export async function buildProductionBindings(options) {
39
- const bindings = new Map();
40
- const publicConfigByModuleRef = new Map([...options.configByModuleRef].map(([moduleRef, config]) => [
41
- moduleRef,
42
- { ...config.publicValues },
43
- ]));
44
- for (const module of options.modules) {
45
- try {
46
- const namespace = await options.importAdapter(module.packageName, module.source);
47
- const materialized = options.configByModuleRef.get(module.moduleRef);
48
- const config = materialized === undefined
49
- ? {}
50
- : { ...materialized.publicValues, ...materialized.secretValues };
51
- const factory = namespace
52
- .createProductionBinding;
53
- if (typeof factory !== "function")
54
- continue;
55
- const binding = await factory({
56
- moduleRef: module.moduleRef,
57
- config,
58
- workspaceRoot: options.workspaceRoot,
59
- modules: options.modules,
60
- configByModuleRef: publicConfigByModuleRef,
61
- });
62
- if (binding !== undefined &&
63
- typeof binding === "object" &&
64
- binding !== null &&
65
- typeof binding.behaviorAdapter === "object" &&
66
- binding.behaviorAdapter !== null) {
67
- bindings.set(module.packageName, binding);
68
- }
69
- }
70
- catch (error) {
71
- throw new PlatformError("COMMAND_FAILED", `production binding failed for ${module.moduleRef}: ${error instanceof Error ? error.message : String(error)}`);
72
- }
73
- }
74
- return bindings;
75
- }
@@ -1,18 +0,0 @@
1
- import { type ConfigSlot, type ModuleProvide, type ModuleRequire } from "@tomflow/proflow-module-contract";
2
- import type { ResolvedModule } from "../contracts.ts";
3
- import type { ModuleCatalog } from "../modules.ts";
4
- export interface AggregatedDocument {
5
- id: string;
6
- path: string;
7
- description?: string;
8
- content: string;
9
- }
10
- export interface AggregatedModuleDocs {
11
- moduleRef: string;
12
- version: string;
13
- provides: ModuleProvide[];
14
- requires: ModuleRequire[];
15
- configSlots: ConfigSlot[];
16
- documents: AggregatedDocument[];
17
- }
18
- export declare function aggregateModuleDocs(workspaceRoot: string, catalog: ModuleCatalog, modules: readonly ResolvedModule[]): Promise<AggregatedModuleDocs[]>;
@@ -1,44 +0,0 @@
1
- import { parseModuleDescriptor, } from "@tomflow/proflow-module-contract";
2
- import { readModuleDocument } from "./docs.js";
3
- function sourceOf(module) {
4
- return module.source.path === undefined
5
- ? { type: module.source.type, packageName: module.packageName }
6
- : {
7
- type: module.source.type,
8
- packageName: module.packageName,
9
- path: module.source.path,
10
- };
11
- }
12
- export async function aggregateModuleDocs(workspaceRoot, catalog, modules) {
13
- const output = [];
14
- for (const module of [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef))) {
15
- const source = sourceOf(module);
16
- const descriptor = parseModuleDescriptor(await catalog.loadDescriptor(source));
17
- const documents = [];
18
- for (const entry of descriptor.documentation) {
19
- const document = await readModuleDocument({
20
- workspaceRoot,
21
- source,
22
- descriptor,
23
- documentId: entry.id,
24
- });
25
- documents.push({
26
- id: document.documentId,
27
- path: document.path,
28
- ...(document.description === undefined
29
- ? {}
30
- : { description: document.description }),
31
- content: document.content,
32
- });
33
- }
34
- output.push({
35
- moduleRef: module.moduleRef,
36
- version: module.moduleVersion,
37
- provides: [...descriptor.provides],
38
- requires: [...descriptor.requires],
39
- configSlots: [...descriptor.configSlots],
40
- documents,
41
- });
42
- }
43
- return output;
44
- }
@@ -1,16 +0,0 @@
1
- import type { ModuleDescriptor } from "@tomflow/proflow-module-contract";
2
- import type { ModuleSource } from "../modules.ts";
3
- export interface ModuleDocumentContent {
4
- moduleRef: string;
5
- packageName: string;
6
- documentId: string;
7
- path: string;
8
- description?: string;
9
- content: string;
10
- }
11
- export declare function readModuleDocument(input: {
12
- workspaceRoot: string;
13
- source: ModuleSource;
14
- descriptor: ModuleDescriptor;
15
- documentId: string;
16
- }): Promise<ModuleDocumentContent>;
@@ -1,68 +0,0 @@
1
- import { readFile, realpath, stat } from "node:fs/promises";
2
- import { createRequire } from "node:module";
3
- import { dirname, join, resolve, sep } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { PlatformError } from "../errors.js";
6
- async function packageRootFor(workspaceRoot, source) {
7
- if (source.type === "workspace") {
8
- if (source.path === undefined) {
9
- throw new PlatformError("DESCRIPTOR_INVALID", `workspace source missing path for ${source.packageName}`);
10
- }
11
- return await realpath(source.path);
12
- }
13
- const require = createRequire(pathToFileURL(join(workspaceRoot, "package.json")));
14
- let current;
15
- try {
16
- current = dirname(require.resolve(`${source.packageName}/deployment/descriptor`));
17
- }
18
- catch (error) {
19
- throw new PlatformError("DESCRIPTOR_INVALID", `cannot resolve installed package ${source.packageName}: ${error instanceof Error ? error.message : String(error)}`);
20
- }
21
- for (;;) {
22
- try {
23
- const manifest = JSON.parse(await readFile(join(current, "package.json"), "utf8"));
24
- if (manifest.name === source.packageName)
25
- return await realpath(current);
26
- }
27
- catch {
28
- // Export maps commonly hide package.json; keep walking from a resolved
29
- // package-owned descriptor until the package root is found.
30
- }
31
- const parent = dirname(current);
32
- if (parent === current)
33
- break;
34
- current = parent;
35
- }
36
- throw new PlatformError("DESCRIPTOR_INVALID", `package root not found for ${source.packageName}`);
37
- }
38
- export async function readModuleDocument(input) {
39
- const entry = input.descriptor.documentation.find((candidate) => candidate.id === input.documentId);
40
- if (entry === undefined) {
41
- throw new PlatformError("INVALID_REQUEST", `document "${input.documentId}" is not declared by ${input.descriptor.moduleRef}`);
42
- }
43
- const packageRoot = await packageRootFor(input.workspaceRoot, input.source);
44
- const root = resolve(packageRoot);
45
- const target = resolve(root, entry.path);
46
- if (target === root || !target.startsWith(`${root}${sep}`)) {
47
- throw new PlatformError("DESCRIPTOR_INVALID", `documentation path escapes package root for ${input.descriptor.moduleRef}`);
48
- }
49
- try {
50
- const info = await stat(target);
51
- if (!info.isFile())
52
- throw new Error("not a file");
53
- const content = await readFile(target, "utf8");
54
- return {
55
- moduleRef: input.descriptor.moduleRef,
56
- packageName: input.descriptor.packageName,
57
- documentId: entry.id,
58
- path: entry.path,
59
- ...(entry.description === undefined
60
- ? {}
61
- : { description: entry.description }),
62
- content,
63
- };
64
- }
65
- catch (error) {
66
- throw new PlatformError("DESCRIPTOR_INVALID", `cannot read document ${entry.id} for ${input.descriptor.moduleRef}: ${error instanceof Error ? error.message : String(error)}`);
67
- }
68
- }
@@ -1,2 +0,0 @@
1
- export type { AggregatedDocument, AggregatedModuleDocs, } from "./aggregate.ts";
2
- export { aggregateModuleDocs } from "./aggregate.ts";