@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.
- package/README.md +37 -0
- package/conformance.json +1 -0
- package/dist/deployment/adapter.d.ts +57 -0
- package/dist/deployment/adapter.js +25 -0
- package/dist/deployment/descriptor.d.ts +39 -0
- package/dist/deployment/descriptor.js +41 -0
- package/dist/src/apply/apply.d.ts +24 -0
- package/dist/src/apply/apply.js +271 -0
- package/dist/src/apply/cleanup.d.ts +12 -0
- package/dist/src/apply/cleanup.js +37 -0
- package/dist/src/apply/current.d.ts +4 -0
- package/dist/src/apply/current.js +73 -0
- package/dist/src/apply/driver.d.ts +23 -0
- package/dist/src/apply/driver.js +154 -0
- package/dist/src/apply/execute.d.ts +36 -0
- package/dist/src/apply/execute.js +124 -0
- package/dist/src/apply/index.d.ts +8 -0
- package/dist/src/apply/index.js +4 -0
- package/dist/src/apply/reality.d.ts +20 -0
- package/dist/src/apply/reality.js +93 -0
- package/dist/src/binding/production-bindings.d.ts +45 -0
- package/dist/src/binding/production-bindings.js +102 -0
- package/dist/src/cli.d.ts +12 -0
- package/dist/src/cli.js +712 -0
- package/dist/src/contracts.d.ts +113 -0
- package/dist/src/contracts.js +1 -0
- package/dist/src/discovery/catalog.d.ts +10 -0
- package/dist/src/discovery/catalog.js +76 -0
- package/dist/src/discovery/discover.d.ts +19 -0
- package/dist/src/discovery/discover.js +124 -0
- package/dist/src/discovery/index.d.ts +5 -0
- package/dist/src/discovery/index.js +4 -0
- package/dist/src/discovery/installed.d.ts +21 -0
- package/dist/src/discovery/installed.js +130 -0
- package/dist/src/discovery/workspace.d.ts +10 -0
- package/dist/src/discovery/workspace.js +155 -0
- package/dist/src/docs/docs.d.ts +47 -0
- package/dist/src/docs/docs.js +129 -0
- package/dist/src/docs/index.d.ts +1 -0
- package/dist/src/docs/index.js +1 -0
- package/dist/src/doctor/doctor.d.ts +41 -0
- package/dist/src/doctor/doctor.js +80 -0
- package/dist/src/doctor/index.d.ts +2 -0
- package/dist/src/doctor/index.js +1 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +35 -0
- package/dist/src/graph/graph.d.ts +23 -0
- package/dist/src/graph/graph.js +132 -0
- package/dist/src/graph/index.d.ts +2 -0
- package/dist/src/graph/index.js +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.js +18 -0
- package/dist/src/install/bootstrap.d.ts +10 -0
- package/dist/src/install/bootstrap.js +79 -0
- package/dist/src/install/environment.d.ts +29 -0
- package/dist/src/install/environment.js +226 -0
- package/dist/src/install/index.d.ts +6 -0
- package/dist/src/install/index.js +3 -0
- package/dist/src/install/install.d.ts +19 -0
- package/dist/src/install/install.js +131 -0
- package/dist/src/install/package-manager.d.ts +11 -0
- package/dist/src/install/package-manager.js +58 -0
- package/dist/src/lifecycle/dispatch.d.ts +47 -0
- package/dist/src/lifecycle/dispatch.js +142 -0
- package/dist/src/lifecycle/index.d.ts +3 -0
- package/dist/src/lifecycle/index.js +2 -0
- package/dist/src/lifecycle/service-process.d.ts +11 -0
- package/dist/src/lifecycle/service-process.js +326 -0
- package/dist/src/manifest/index.d.ts +2 -0
- package/dist/src/manifest/index.js +1 -0
- package/dist/src/manifest/manifest.d.ts +61 -0
- package/dist/src/manifest/manifest.js +160 -0
- package/dist/src/modules.d.ts +11 -0
- package/dist/src/modules.js +55 -0
- package/dist/src/observer/deployment-summary.d.ts +31 -0
- package/dist/src/observer/deployment-summary.js +34 -0
- package/dist/src/paths.d.ts +21 -0
- package/dist/src/paths.js +56 -0
- package/dist/src/persistence/atomic.d.ts +2 -0
- package/dist/src/persistence/atomic.js +16 -0
- package/dist/src/persistence/config.d.ts +12 -0
- package/dist/src/persistence/config.js +43 -0
- package/dist/src/persistence/generated.d.ts +2 -0
- package/dist/src/persistence/generated.js +4 -0
- package/dist/src/persistence/guards.d.ts +7 -0
- package/dist/src/persistence/guards.js +120 -0
- package/dist/src/persistence/index.d.ts +6 -0
- package/dist/src/persistence/index.js +5 -0
- package/dist/src/persistence/plans.d.ts +5 -0
- package/dist/src/persistence/plans.js +34 -0
- package/dist/src/persistence/state.d.ts +7 -0
- package/dist/src/persistence/state.js +34 -0
- package/dist/src/persistence/verification.d.ts +5 -0
- package/dist/src/persistence/verification.js +47 -0
- package/dist/src/planner/assemble.d.ts +16 -0
- package/dist/src/planner/assemble.js +107 -0
- package/dist/src/planner/check.d.ts +20 -0
- package/dist/src/planner/check.js +96 -0
- package/dist/src/planner/fingerprint.d.ts +12 -0
- package/dist/src/planner/fingerprint.js +164 -0
- package/dist/src/planner/index.d.ts +13 -0
- package/dist/src/planner/index.js +7 -0
- package/dist/src/planner/plan.d.ts +14 -0
- package/dist/src/planner/plan.js +156 -0
- package/dist/src/planner/repair.d.ts +28 -0
- package/dist/src/planner/repair.js +131 -0
- package/dist/src/planner/stale.d.ts +7 -0
- package/dist/src/planner/stale.js +23 -0
- package/dist/src/planner/steps.d.ts +37 -0
- package/dist/src/planner/steps.js +165 -0
- package/dist/src/planner/target-catalog.d.ts +13 -0
- package/dist/src/planner/target-catalog.js +48 -0
- package/dist/src/planner/upgrade.d.ts +18 -0
- package/dist/src/planner/upgrade.js +105 -0
- package/dist/src/preflight/config.d.ts +10 -0
- package/dist/src/preflight/config.js +45 -0
- package/dist/src/preflight/findings.d.ts +26 -0
- package/dist/src/preflight/findings.js +1 -0
- package/dist/src/preflight/index.d.ts +7 -0
- package/dist/src/preflight/index.js +3 -0
- package/dist/src/preflight/preflight.d.ts +8 -0
- package/dist/src/preflight/preflight.js +174 -0
- package/dist/src/preflight/requirements.d.ts +11 -0
- package/dist/src/preflight/requirements.js +224 -0
- package/dist/src/ready/index.d.ts +2 -0
- package/dist/src/ready/index.js +1 -0
- package/dist/src/ready/ready.d.ts +40 -0
- package/dist/src/ready/ready.js +201 -0
- package/dist/src/registry/index.d.ts +2 -0
- package/dist/src/registry/index.js +1 -0
- package/dist/src/registry/npm-registry.d.ts +51 -0
- package/dist/src/registry/npm-registry.js +341 -0
- package/dist/src/security/index.d.ts +3 -0
- package/dist/src/security/index.js +2 -0
- package/dist/src/security/lock.d.ts +9 -0
- package/dist/src/security/lock.js +89 -0
- package/dist/src/security/redact.d.ts +6 -0
- package/dist/src/security/redact.js +48 -0
- package/dist/src/verification/index.d.ts +2 -0
- package/dist/src/verification/index.js +1 -0
- package/dist/src/verification/verify.d.ts +33 -0
- package/dist/src/verification/verify.js +114 -0
- package/package.json +49 -0
- package/proflow.module.json +45 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ConfigSlot, DeploymentEffect, HumanAction, ModuleDescriptor, ModuleRequirement } from "@tomflow/proflow-module-contract";
|
|
2
|
+
export type DeploymentIntent = "install" | "configure" | "upgrade" | "uninstall" | "repair";
|
|
3
|
+
export interface ModuleTarget {
|
|
4
|
+
moduleRef: string;
|
|
5
|
+
targetVersion?: string;
|
|
6
|
+
config?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolvedModule {
|
|
9
|
+
moduleRef: string;
|
|
10
|
+
packageName: string;
|
|
11
|
+
moduleVersion: string;
|
|
12
|
+
kind: ModuleDescriptor["kind"] | "registry-package";
|
|
13
|
+
installClass: ModuleDescriptor["installClass"];
|
|
14
|
+
identity?: ModuleDescriptor["identity"];
|
|
15
|
+
documentation: ModuleDescriptor["documentation"];
|
|
16
|
+
provides: ModuleDescriptor["provides"];
|
|
17
|
+
requires: ModuleDescriptor["requires"];
|
|
18
|
+
requirements: ModuleRequirement[];
|
|
19
|
+
configSlots: ConfigSlot[];
|
|
20
|
+
lifecycle: string[];
|
|
21
|
+
verification: ModuleDescriptor["verification"];
|
|
22
|
+
effects: DeploymentEffect[];
|
|
23
|
+
source: {
|
|
24
|
+
type: "workspace" | "installed" | "registry";
|
|
25
|
+
path?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface DeploymentStep {
|
|
29
|
+
stepRef: string;
|
|
30
|
+
moduleRef: string;
|
|
31
|
+
kind: "config" | "package" | "lifecycle" | "external-resource" | "human";
|
|
32
|
+
preconditions: string[];
|
|
33
|
+
expectedEffect: string;
|
|
34
|
+
checkStrategy: string;
|
|
35
|
+
executeStrategy?: string;
|
|
36
|
+
postcondition: string;
|
|
37
|
+
}
|
|
38
|
+
export interface VerificationStep {
|
|
39
|
+
moduleRef: string;
|
|
40
|
+
checks: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface DeploymentPlan {
|
|
43
|
+
planRef: string;
|
|
44
|
+
intent: DeploymentIntent;
|
|
45
|
+
moduleTargets: ModuleTarget[];
|
|
46
|
+
resolvedModules: ResolvedModule[];
|
|
47
|
+
targetDescriptors?: ModuleDescriptor[];
|
|
48
|
+
steps: DeploymentStep[];
|
|
49
|
+
effects: DeploymentEffect[];
|
|
50
|
+
humanActions: HumanAction[];
|
|
51
|
+
verification: VerificationStep[];
|
|
52
|
+
fingerprint: string;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
}
|
|
55
|
+
export interface VerificationRecord {
|
|
56
|
+
verificationRef: string;
|
|
57
|
+
moduleRef: string;
|
|
58
|
+
moduleVersion: string;
|
|
59
|
+
resourceIdentity?: string;
|
|
60
|
+
resourceVersion?: string;
|
|
61
|
+
result: "PASS" | "FAIL";
|
|
62
|
+
summary: string;
|
|
63
|
+
evidenceRefs: string[];
|
|
64
|
+
verifiedAt: string;
|
|
65
|
+
}
|
|
66
|
+
export type PlatformState = "READY" | "DEGRADED" | "ACTION_REQUIRED" | "NOT_READY";
|
|
67
|
+
export interface SelectedModuleFact {
|
|
68
|
+
moduleRef: string;
|
|
69
|
+
moduleVersion: string;
|
|
70
|
+
}
|
|
71
|
+
export interface DeploymentState {
|
|
72
|
+
contract: "proflow.deployment-state.v1";
|
|
73
|
+
selectedModules: SelectedModuleFact[];
|
|
74
|
+
lastAppliedPlans: {
|
|
75
|
+
planRef: string;
|
|
76
|
+
intent: DeploymentIntent;
|
|
77
|
+
appliedAt: string;
|
|
78
|
+
}[];
|
|
79
|
+
verificationIndex: {
|
|
80
|
+
moduleRef: string;
|
|
81
|
+
latestRef: string;
|
|
82
|
+
}[];
|
|
83
|
+
pendingActions: PendingActionRecord[];
|
|
84
|
+
updatedAt: string;
|
|
85
|
+
}
|
|
86
|
+
export interface PendingActionRecord {
|
|
87
|
+
planRef: string;
|
|
88
|
+
stepRef: string;
|
|
89
|
+
moduleRef: string;
|
|
90
|
+
action: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
createdAt: string;
|
|
93
|
+
}
|
|
94
|
+
export interface LockRecord {
|
|
95
|
+
pid: number;
|
|
96
|
+
createdAt: string;
|
|
97
|
+
planRef: string;
|
|
98
|
+
workspaceFingerprint: string;
|
|
99
|
+
}
|
|
100
|
+
export type ApplyStepStatus = "SKIP" | "EXECUTED" | "ACTION_REQUIRED" | "FAILED";
|
|
101
|
+
export interface ApplyStepResult {
|
|
102
|
+
stepRef: string;
|
|
103
|
+
moduleRef: string;
|
|
104
|
+
status: ApplyStepStatus;
|
|
105
|
+
message: string;
|
|
106
|
+
}
|
|
107
|
+
export type ApplyOutcome = "COMPLETE" | "ACTION_REQUIRED" | "FAILED" | "BLOCKED";
|
|
108
|
+
export interface ApplyResult {
|
|
109
|
+
planRef: string;
|
|
110
|
+
outcome: ApplyOutcome;
|
|
111
|
+
stepResults: ApplyStepResult[];
|
|
112
|
+
completedAt: string;
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ModuleCatalog, ModuleSource } from "../modules.ts";
|
|
2
|
+
export declare class WorkspaceModuleCatalog implements ModuleCatalog {
|
|
3
|
+
readonly root: string;
|
|
4
|
+
constructor(root?: string);
|
|
5
|
+
sources(): Promise<ModuleSource[]>;
|
|
6
|
+
loadDescriptor(source: ModuleSource): Promise<unknown>;
|
|
7
|
+
loadAdapter(source: ModuleSource): Promise<unknown>;
|
|
8
|
+
private loadModule;
|
|
9
|
+
private resolveUrl;
|
|
10
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 { proflowPackageMetadataSchema } from "@tomflow/proflow-module-contract";
|
|
12
|
+
import { PlatformError } from "../errors.js";
|
|
13
|
+
import { expandPackageDirs, findWorkspaceRoot, hasDeploymentArtifacts, readPackageJson, readWorkspaceGlobs, } from "./workspace.js";
|
|
14
|
+
export class WorkspaceModuleCatalog {
|
|
15
|
+
root;
|
|
16
|
+
constructor(root) {
|
|
17
|
+
this.root = root ?? findWorkspaceRoot();
|
|
18
|
+
}
|
|
19
|
+
async sources() {
|
|
20
|
+
const globs = await readWorkspaceGlobs(this.root);
|
|
21
|
+
const directories = await expandPackageDirs(this.root, globs);
|
|
22
|
+
const sources = [];
|
|
23
|
+
for (const directory of directories) {
|
|
24
|
+
if (!hasDeploymentArtifacts(directory))
|
|
25
|
+
continue;
|
|
26
|
+
const packageJson = await readPackageJson(directory);
|
|
27
|
+
if (packageJson.name === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
if (!packageJson.name.startsWith("@tomflow/proflow-"))
|
|
30
|
+
continue;
|
|
31
|
+
if (!proflowPackageMetadataSchema.safeParse(packageJson.proflow).success)
|
|
32
|
+
continue;
|
|
33
|
+
sources.push({
|
|
34
|
+
type: "workspace",
|
|
35
|
+
packageName: packageJson.name,
|
|
36
|
+
path: directory,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return sources;
|
|
40
|
+
}
|
|
41
|
+
async loadDescriptor(source) {
|
|
42
|
+
const namespace = await this.loadModule(source, "descriptor");
|
|
43
|
+
const descriptor = namespace.descriptor;
|
|
44
|
+
if (descriptor === undefined) {
|
|
45
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `deployment descriptor export missing for ${source.packageName}`);
|
|
46
|
+
}
|
|
47
|
+
return descriptor;
|
|
48
|
+
}
|
|
49
|
+
async loadAdapter(source) {
|
|
50
|
+
return this.loadModule(source, "adapter");
|
|
51
|
+
}
|
|
52
|
+
async loadModule(source, artifact) {
|
|
53
|
+
let url;
|
|
54
|
+
try {
|
|
55
|
+
url = this.resolveUrl(source, artifact);
|
|
56
|
+
const namespace = await import(__rewriteRelativeImportExtension(url));
|
|
57
|
+
return namespace;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `failed to load ${artifact} for ${source.packageName}: ${errorMessage(error)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
resolveUrl(source, artifact) {
|
|
64
|
+
if (source.type === "workspace") {
|
|
65
|
+
if (source.path === undefined) {
|
|
66
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `workspace source missing path for ${source.packageName}`);
|
|
67
|
+
}
|
|
68
|
+
return pathToFileURL(join(source.path, "deployment", `${artifact}.ts`))
|
|
69
|
+
.href;
|
|
70
|
+
}
|
|
71
|
+
return import.meta.resolve(`${source.packageName}/deployment/${artifact}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function errorMessage(error) {
|
|
75
|
+
return error instanceof Error ? error.message : String(error);
|
|
76
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DeploymentAdapterBinding } from "../binding/production-bindings.ts";
|
|
2
|
+
import type { ResolvedModule } from "../contracts.ts";
|
|
3
|
+
import type { ModuleCatalog, ModuleSource } from "../modules.ts";
|
|
4
|
+
export interface DiscoverOptions {
|
|
5
|
+
workspaceRoot?: string;
|
|
6
|
+
catalog?: ModuleCatalog;
|
|
7
|
+
sources?: ModuleSource[];
|
|
8
|
+
}
|
|
9
|
+
export declare class AutoModuleCatalog implements ModuleCatalog {
|
|
10
|
+
private readonly workspace;
|
|
11
|
+
private readonly installed;
|
|
12
|
+
private readonly bindings;
|
|
13
|
+
constructor(root?: string, bindings?: ReadonlyMap<string, DeploymentAdapterBinding>);
|
|
14
|
+
sources(): Promise<ModuleSource[]>;
|
|
15
|
+
loadDescriptor(source: ModuleSource): Promise<unknown>;
|
|
16
|
+
loadAdapter(source: ModuleSource): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
export declare function discoverModules(options?: DiscoverOptions): Promise<ResolvedModule[]>;
|
|
19
|
+
export declare function resolveModules(catalog: Pick<ModuleCatalog, "loadDescriptor">, sources: readonly ModuleSource[]): Promise<ResolvedModule[]>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { parseModuleDescriptor, } from "@tomflow/proflow-module-contract";
|
|
2
|
+
import { PlatformError } from "../errors.js";
|
|
3
|
+
import { WorkspaceModuleCatalog } from "./catalog.js";
|
|
4
|
+
import { InstalledModuleCatalog } from "./installed.js";
|
|
5
|
+
import { readPackageJson } from "./workspace.js";
|
|
6
|
+
// Workspace sources win over installed dependencies on a packageName collision.
|
|
7
|
+
export class AutoModuleCatalog {
|
|
8
|
+
workspace;
|
|
9
|
+
installed;
|
|
10
|
+
bindings;
|
|
11
|
+
constructor(root, bindings) {
|
|
12
|
+
this.workspace = new WorkspaceModuleCatalog(root);
|
|
13
|
+
this.installed = new InstalledModuleCatalog(root);
|
|
14
|
+
this.bindings = bindings ?? new Map();
|
|
15
|
+
}
|
|
16
|
+
async sources() {
|
|
17
|
+
const workspaceSources = await this.workspace.sources();
|
|
18
|
+
const installedSources = await this.installed.sources();
|
|
19
|
+
const merged = [...workspaceSources];
|
|
20
|
+
const seen = new Set(workspaceSources.map((source) => source.packageName));
|
|
21
|
+
for (const source of installedSources) {
|
|
22
|
+
if (seen.has(source.packageName))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(source.packageName);
|
|
25
|
+
merged.push(source);
|
|
26
|
+
}
|
|
27
|
+
return merged;
|
|
28
|
+
}
|
|
29
|
+
async loadDescriptor(source) {
|
|
30
|
+
return source.type === "workspace"
|
|
31
|
+
? this.workspace.loadDescriptor(source)
|
|
32
|
+
: this.installed.loadDescriptor(source);
|
|
33
|
+
}
|
|
34
|
+
async loadAdapter(source) {
|
|
35
|
+
// A production binder may supply a bound adapter for a service module
|
|
36
|
+
// (createBehaviorAdapter(realService)); it wins over the unbound default.
|
|
37
|
+
const binding = this.bindings.get(source.packageName);
|
|
38
|
+
if (binding !== undefined)
|
|
39
|
+
return binding;
|
|
40
|
+
return source.type === "workspace"
|
|
41
|
+
? this.workspace.loadAdapter(source)
|
|
42
|
+
: this.installed.loadAdapter(source);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function discoverModules(options = {}) {
|
|
46
|
+
const catalog = options.catalog ?? new AutoModuleCatalog(options.workspaceRoot);
|
|
47
|
+
const sources = options.sources ?? (await catalog.sources());
|
|
48
|
+
return resolveModules(catalog, sources);
|
|
49
|
+
}
|
|
50
|
+
export async function resolveModules(catalog, sources) {
|
|
51
|
+
const modules = [];
|
|
52
|
+
for (const source of sources) {
|
|
53
|
+
modules.push(await resolveModule(catalog, source));
|
|
54
|
+
}
|
|
55
|
+
enforceIdentityInvariants(modules);
|
|
56
|
+
return modules;
|
|
57
|
+
}
|
|
58
|
+
async function resolveModule(catalog, source) {
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = await catalog.loadDescriptor(source);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof PlatformError)
|
|
65
|
+
throw error;
|
|
66
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `failed to load descriptor for ${source.packageName}: ${errorMessage(error)}`);
|
|
67
|
+
}
|
|
68
|
+
let descriptor;
|
|
69
|
+
try {
|
|
70
|
+
descriptor = parseModuleDescriptor(raw);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `invalid descriptor for ${source.packageName}: ${errorMessage(error)}`);
|
|
74
|
+
}
|
|
75
|
+
if (source.type === "workspace" && source.path !== undefined) {
|
|
76
|
+
const packageJson = await readPackageJson(source.path);
|
|
77
|
+
if (packageJson.version !== undefined &&
|
|
78
|
+
packageJson.version !== descriptor.moduleVersion) {
|
|
79
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `moduleVersion ${descriptor.moduleVersion} does not match package.json version ${packageJson.version} for ${descriptor.moduleRef}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return toResolvedModule(descriptor, source);
|
|
83
|
+
}
|
|
84
|
+
function toResolvedModule(descriptor, source) {
|
|
85
|
+
const resolvedSource = source.path === undefined
|
|
86
|
+
? { type: source.type }
|
|
87
|
+
: { type: source.type, path: source.path };
|
|
88
|
+
return {
|
|
89
|
+
moduleRef: descriptor.moduleRef,
|
|
90
|
+
packageName: descriptor.packageName,
|
|
91
|
+
moduleVersion: descriptor.moduleVersion,
|
|
92
|
+
kind: descriptor.kind,
|
|
93
|
+
installClass: descriptor.installClass,
|
|
94
|
+
identity: descriptor.identity,
|
|
95
|
+
documentation: descriptor.documentation,
|
|
96
|
+
provides: descriptor.provides,
|
|
97
|
+
requires: descriptor.requires,
|
|
98
|
+
requirements: descriptor.requirements,
|
|
99
|
+
configSlots: descriptor.configSlots,
|
|
100
|
+
lifecycle: descriptor.lifecycle.supported,
|
|
101
|
+
verification: descriptor.verification,
|
|
102
|
+
effects: descriptor.effects,
|
|
103
|
+
source: resolvedSource,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function enforceIdentityInvariants(modules) {
|
|
107
|
+
const byRef = new Map();
|
|
108
|
+
const byPackage = new Map();
|
|
109
|
+
for (const module of modules) {
|
|
110
|
+
const existingRef = byRef.get(module.moduleRef);
|
|
111
|
+
if (existingRef !== undefined) {
|
|
112
|
+
throw new PlatformError("DUPLICATE_IDENTITY", `duplicate moduleRef "${module.moduleRef}" (${existingRef} and ${module.packageName})`);
|
|
113
|
+
}
|
|
114
|
+
const existingPackage = byPackage.get(module.packageName);
|
|
115
|
+
if (existingPackage !== undefined) {
|
|
116
|
+
throw new PlatformError("DUPLICATE_IDENTITY", `duplicate packageName "${module.packageName}" (${existingPackage} and ${module.moduleRef})`);
|
|
117
|
+
}
|
|
118
|
+
byRef.set(module.moduleRef, module.packageName);
|
|
119
|
+
byPackage.set(module.packageName, module.moduleRef);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function errorMessage(error) {
|
|
123
|
+
return error instanceof Error ? error.message : String(error);
|
|
124
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { WorkspaceModuleCatalog } from "./catalog.ts";
|
|
2
|
+
export type { DiscoverOptions } from "./discover.ts";
|
|
3
|
+
export { AutoModuleCatalog, discoverModules, resolveModules, } from "./discover.ts";
|
|
4
|
+
export { InstalledModuleCatalog } from "./installed.ts";
|
|
5
|
+
export { expandPackageDirs, findWorkspaceRoot, hasDeploymentArtifacts, readWorkspaceGlobs, } from "./workspace.ts";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { WorkspaceModuleCatalog } from "./catalog.js";
|
|
2
|
+
export { AutoModuleCatalog, discoverModules, resolveModules, } from "./discover.js";
|
|
3
|
+
export { InstalledModuleCatalog } from "./installed.js";
|
|
4
|
+
export { expandPackageDirs, findWorkspaceRoot, hasDeploymentArtifacts, readWorkspaceGlobs, } from "./workspace.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ModuleCatalog, ModuleSource } from "../modules.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Discovers governed modules installed as ordinary npm dependencies of a
|
|
4
|
+
* product workspace. It never scans the global npm cache, queries a registry,
|
|
5
|
+
* or walks `node_modules` — it only inspects the package names explicitly
|
|
6
|
+
* declared in the root `package.json` `dependencies`/`devDependencies`, and
|
|
7
|
+
* only treats a package as a governed Module when both its
|
|
8
|
+
* `deployment/descriptor` and `deployment/adapter` subpath exports actually
|
|
9
|
+
* resolve through Node package resolution.
|
|
10
|
+
*/
|
|
11
|
+
export declare class InstalledModuleCatalog implements ModuleCatalog {
|
|
12
|
+
readonly root: string;
|
|
13
|
+
private readonly require;
|
|
14
|
+
constructor(root?: string);
|
|
15
|
+
sources(): Promise<ModuleSource[]>;
|
|
16
|
+
loadDescriptor(source: ModuleSource): Promise<unknown>;
|
|
17
|
+
loadAdapter(source: ModuleSource): Promise<unknown>;
|
|
18
|
+
private isGovernedModule;
|
|
19
|
+
private canResolve;
|
|
20
|
+
private loadModule;
|
|
21
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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 { readFile } from "node:fs/promises";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
13
|
+
import { proflowPackageMetadataSchema } from "@tomflow/proflow-module-contract";
|
|
14
|
+
import { PlatformError } from "../errors.js";
|
|
15
|
+
import { findWorkspaceRoot } from "./workspace.js";
|
|
16
|
+
async function readRootPackageJson(root) {
|
|
17
|
+
try {
|
|
18
|
+
const raw = await readFile(join(root, "package.json"), "utf8");
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
21
|
+
return {};
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Discovers governed modules installed as ordinary npm dependencies of a
|
|
30
|
+
* product workspace. It never scans the global npm cache, queries a registry,
|
|
31
|
+
* or walks `node_modules` — it only inspects the package names explicitly
|
|
32
|
+
* declared in the root `package.json` `dependencies`/`devDependencies`, and
|
|
33
|
+
* only treats a package as a governed Module when both its
|
|
34
|
+
* `deployment/descriptor` and `deployment/adapter` subpath exports actually
|
|
35
|
+
* resolve through Node package resolution.
|
|
36
|
+
*/
|
|
37
|
+
export class InstalledModuleCatalog {
|
|
38
|
+
root;
|
|
39
|
+
require;
|
|
40
|
+
constructor(root) {
|
|
41
|
+
this.root = root ?? findWorkspaceRoot();
|
|
42
|
+
this.require = createRequire(pathToFileURL(join(this.root, "package.json")));
|
|
43
|
+
}
|
|
44
|
+
async sources() {
|
|
45
|
+
const manifest = await readRootPackageJson(this.root);
|
|
46
|
+
const names = new Set();
|
|
47
|
+
for (const name of Object.keys(manifest.dependencies ?? {})) {
|
|
48
|
+
names.add(name);
|
|
49
|
+
}
|
|
50
|
+
for (const name of Object.keys(manifest.devDependencies ?? {})) {
|
|
51
|
+
names.add(name);
|
|
52
|
+
}
|
|
53
|
+
const sources = [];
|
|
54
|
+
for (const name of [...names].sort()) {
|
|
55
|
+
if (!(await this.isGovernedModule(name)))
|
|
56
|
+
continue;
|
|
57
|
+
sources.push({ type: "installed", packageName: name });
|
|
58
|
+
}
|
|
59
|
+
return sources;
|
|
60
|
+
}
|
|
61
|
+
async loadDescriptor(source) {
|
|
62
|
+
const namespace = await this.loadModule(source, "descriptor");
|
|
63
|
+
const descriptor = namespace.descriptor;
|
|
64
|
+
if (descriptor === undefined) {
|
|
65
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `deployment descriptor export missing for ${source.packageName}`);
|
|
66
|
+
}
|
|
67
|
+
return descriptor;
|
|
68
|
+
}
|
|
69
|
+
async loadAdapter(source) {
|
|
70
|
+
return this.loadModule(source, "adapter");
|
|
71
|
+
}
|
|
72
|
+
async isGovernedModule(packageName) {
|
|
73
|
+
if (!packageName.startsWith("@tomflow/proflow-"))
|
|
74
|
+
return false;
|
|
75
|
+
if (!this.canResolve(`${packageName}/deployment/descriptor`) ||
|
|
76
|
+
!this.canResolve(`${packageName}/deployment/adapter`)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const descriptorPath = this.require.resolve(`${packageName}/deployment/descriptor`);
|
|
80
|
+
const packageJson = await readInstalledPackageJson(descriptorPath, packageName);
|
|
81
|
+
if (packageJson === undefined)
|
|
82
|
+
return false;
|
|
83
|
+
return proflowPackageMetadataSchema.safeParse(packageJson.proflow).success;
|
|
84
|
+
}
|
|
85
|
+
canResolve(specifier) {
|
|
86
|
+
try {
|
|
87
|
+
this.require.resolve(specifier);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async loadModule(source, artifact) {
|
|
95
|
+
try {
|
|
96
|
+
const resolved = this.require.resolve(`${source.packageName}/deployment/${artifact}`);
|
|
97
|
+
return await import(__rewriteRelativeImportExtension(pathToFileURL(resolved).href));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `failed to load ${artifact} for ${source.packageName}: ${errorMessage(error)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function errorMessage(error) {
|
|
105
|
+
return error instanceof Error ? error.message : String(error);
|
|
106
|
+
}
|
|
107
|
+
async function readInstalledPackageJson(resolvedArtifact, packageName) {
|
|
108
|
+
let current = dirname(resolvedArtifact);
|
|
109
|
+
for (;;) {
|
|
110
|
+
try {
|
|
111
|
+
const raw = await readFile(join(current, "package.json"), "utf8");
|
|
112
|
+
const parsed = JSON.parse(raw);
|
|
113
|
+
if (typeof parsed === "object" &&
|
|
114
|
+
parsed !== null &&
|
|
115
|
+
!Array.isArray(parsed) &&
|
|
116
|
+
Reflect.get(parsed, "name") === packageName) {
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Keep walking towards the package root. Export maps commonly hide
|
|
122
|
+
// package.json, so resolving the descriptor and walking upward is the
|
|
123
|
+
// stable local-install boundary.
|
|
124
|
+
}
|
|
125
|
+
const parent = dirname(current);
|
|
126
|
+
if (parent === current)
|
|
127
|
+
return undefined;
|
|
128
|
+
current = parent;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function findWorkspaceRoot(fromUrl?: string): string;
|
|
2
|
+
export declare function readWorkspaceGlobs(root: string): Promise<string[]>;
|
|
3
|
+
export declare function expandPackageDirs(root: string, globs: readonly string[]): Promise<string[]>;
|
|
4
|
+
export declare function hasDeploymentArtifacts(dir: string): boolean;
|
|
5
|
+
export interface PackageJson {
|
|
6
|
+
name?: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
proflow?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare function readPackageJson(dir: string): Promise<PackageJson>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { PlatformError } from "../errors.js";
|
|
6
|
+
const PRUNE_DIRS = new Set([
|
|
7
|
+
"node_modules",
|
|
8
|
+
".git",
|
|
9
|
+
".proflow",
|
|
10
|
+
"dist",
|
|
11
|
+
".omo",
|
|
12
|
+
".codegraph",
|
|
13
|
+
]);
|
|
14
|
+
export function findWorkspaceRoot(fromUrl = import.meta.url) {
|
|
15
|
+
let dir = dirname(fileURLToPath(fromUrl));
|
|
16
|
+
for (;;) {
|
|
17
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml")))
|
|
18
|
+
return dir;
|
|
19
|
+
const parent = dirname(dir);
|
|
20
|
+
if (parent === dir) {
|
|
21
|
+
throw new PlatformError("INVALID_REQUEST", "pnpm-workspace.yaml not found in any parent directory");
|
|
22
|
+
}
|
|
23
|
+
dir = parent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function readWorkspaceGlobs(root) {
|
|
27
|
+
const file = join(root, "pnpm-workspace.yaml");
|
|
28
|
+
let raw;
|
|
29
|
+
try {
|
|
30
|
+
raw = await readFile(file, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const globs = [];
|
|
36
|
+
let inPackages = false;
|
|
37
|
+
for (const line of raw.split("\n")) {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
40
|
+
continue;
|
|
41
|
+
if (/^packages\s*:/.test(trimmed)) {
|
|
42
|
+
inPackages = true;
|
|
43
|
+
const inline = /^packages\s*:\s*\[(.*)\]$/.exec(trimmed);
|
|
44
|
+
if (inline !== null) {
|
|
45
|
+
const body = inline[1] ?? "";
|
|
46
|
+
for (const match of body.matchAll(/"([^"]*)"|'([^']*)'/g)) {
|
|
47
|
+
const value = match[1] ?? match[2];
|
|
48
|
+
if (value !== undefined && value !== "")
|
|
49
|
+
globs.push(value);
|
|
50
|
+
}
|
|
51
|
+
inPackages = false;
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!inPackages)
|
|
56
|
+
continue;
|
|
57
|
+
if (!/^\s/.test(line) && !trimmed.startsWith("-")) {
|
|
58
|
+
inPackages = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const match = /^\s*-\s*(?:"([^"]*)"|'([^']*)'|([^\s#]+))/.exec(line);
|
|
62
|
+
if (match !== null) {
|
|
63
|
+
const value = match[1] ?? match[2] ?? match[3];
|
|
64
|
+
if (value !== undefined)
|
|
65
|
+
globs.push(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return globs;
|
|
69
|
+
}
|
|
70
|
+
function globToRegExp(glob) {
|
|
71
|
+
const segments = glob.split("/");
|
|
72
|
+
let pattern = "^";
|
|
73
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
74
|
+
if (index > 0)
|
|
75
|
+
pattern += "/";
|
|
76
|
+
const segment = segments[index] ?? "";
|
|
77
|
+
if (segment === "**") {
|
|
78
|
+
pattern += ".*";
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
pattern += segment
|
|
82
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
83
|
+
.replace(/\*\*/g, ".*")
|
|
84
|
+
.replace(/\*/g, "[^/]*")
|
|
85
|
+
.replace(/\?/g, "[^/]");
|
|
86
|
+
}
|
|
87
|
+
pattern += "$";
|
|
88
|
+
return new RegExp(pattern);
|
|
89
|
+
}
|
|
90
|
+
function globStaticPrefix(glob) {
|
|
91
|
+
const magic = glob.search(/[*?[\]]/);
|
|
92
|
+
if (magic === -1)
|
|
93
|
+
return glob;
|
|
94
|
+
const prefix = glob.slice(0, magic);
|
|
95
|
+
const lastSlash = prefix.lastIndexOf("/");
|
|
96
|
+
if (lastSlash === -1)
|
|
97
|
+
return ".";
|
|
98
|
+
const base = prefix.slice(0, lastSlash);
|
|
99
|
+
return base === "" ? "/" : base;
|
|
100
|
+
}
|
|
101
|
+
async function walkMatchingDirs(base, root, regex) {
|
|
102
|
+
const found = [];
|
|
103
|
+
const entries = await readdir(base, { withFileTypes: true }).catch(() => []);
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (!entry.isDirectory())
|
|
106
|
+
continue;
|
|
107
|
+
if (PRUNE_DIRS.has(entry.name))
|
|
108
|
+
continue;
|
|
109
|
+
const full = join(base, entry.name);
|
|
110
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
111
|
+
if (regex.test(rel))
|
|
112
|
+
found.push(full);
|
|
113
|
+
found.push(...(await walkMatchingDirs(full, root, regex)));
|
|
114
|
+
}
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
export async function expandPackageDirs(root, globs) {
|
|
118
|
+
const found = new Set();
|
|
119
|
+
for (const rawGlob of globs) {
|
|
120
|
+
const glob = rawGlob.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
121
|
+
if (glob === "")
|
|
122
|
+
continue;
|
|
123
|
+
if (!/[*?[\]]/.test(glob)) {
|
|
124
|
+
const absolute = resolve(root, glob);
|
|
125
|
+
try {
|
|
126
|
+
const info = await stat(absolute);
|
|
127
|
+
if (info.isDirectory())
|
|
128
|
+
found.add(absolute);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// ignore missing literal paths
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const regex = globToRegExp(glob);
|
|
136
|
+
const base = resolve(root, globStaticPrefix(glob));
|
|
137
|
+
for (const dir of await walkMatchingDirs(base, root, regex)) {
|
|
138
|
+
found.add(dir);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [...found].sort();
|
|
142
|
+
}
|
|
143
|
+
export function hasDeploymentArtifacts(dir) {
|
|
144
|
+
return (existsSync(join(dir, "deployment", "descriptor.ts")) &&
|
|
145
|
+
existsSync(join(dir, "deployment", "adapter.ts")));
|
|
146
|
+
}
|
|
147
|
+
export async function readPackageJson(dir) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = await readFile(join(dir, "package.json"), "utf8");
|
|
150
|
+
return JSON.parse(raw);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
}
|