@tomflow/proflow-platform-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +24 -3
- package/dist/deployment/adapter.d.ts +5 -5
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/apply/driver.d.ts +1 -0
- package/dist/src/apply/driver.js +40 -12
- package/dist/src/binding/global-binding.d.ts +69 -0
- package/dist/src/binding/global-binding.js +349 -0
- package/dist/src/binding/production-bindings.d.ts +1 -1
- package/dist/src/binding/production-bindings.js +5 -3
- package/dist/src/cli.d.ts +12 -1
- package/dist/src/cli.js +453 -31
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +12 -0
- package/dist/src/install/environment.d.ts +2 -1
- package/dist/src/install/environment.js +33 -29
- package/dist/src/install/package-manager.d.ts +4 -2
- package/dist/src/install/package-manager.js +73 -17
- package/dist/src/planner/plan.d.ts +1 -0
- package/dist/src/planner/plan.js +1 -1
- package/package.json +4 -4
- package/proflow.module.json +1 -1
package/dist/src/errors.js
CHANGED
|
@@ -11,6 +11,18 @@ export const platformErrorCodes = [
|
|
|
11
11
|
"PLAN_NOT_FOUND",
|
|
12
12
|
"PLAN_INVALID",
|
|
13
13
|
"WORKSPACE_LOCKED",
|
|
14
|
+
"WORKSPACE_NOT_FOUND",
|
|
15
|
+
"WORKSPACE_NOT_WRITABLE",
|
|
16
|
+
"WORKSPACE_NOT_BOUND",
|
|
17
|
+
"WORKSPACE_ALREADY_BOUND",
|
|
18
|
+
"BOUND_WORKSPACE_MISSING",
|
|
19
|
+
"WORKSPACE_INSTANCE_INVALID",
|
|
20
|
+
"GLOBAL_BINDING_INVALID",
|
|
21
|
+
"GLOBAL_BINDING_LOCKED",
|
|
22
|
+
"GLOBAL_OPERATION_LOCKED",
|
|
23
|
+
"PACKAGE_MANAGER_UNSUPPORTED",
|
|
24
|
+
"PACKAGE_MANAGER_CONFLICT",
|
|
25
|
+
"PACKAGE_MANAGER_UNAVAILABLE",
|
|
14
26
|
"LIFECYCLE_UNSUPPORTED",
|
|
15
27
|
"SECRET_LEAK",
|
|
16
28
|
"APPLY_FAILED",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type NpmCommandRunner } from "../registry/index.ts";
|
|
2
|
-
import { type SupportedWorkspacePackageManager } from "./package-manager.ts";
|
|
2
|
+
import { type PackageManagerSelectionSource, type SupportedWorkspacePackageManager } from "./package-manager.ts";
|
|
3
3
|
export declare const PLATFORM_INSTALL_NODE_RANGE = ">=24.19.0";
|
|
4
4
|
export type InstallerPreflightStatus = "READY" | "ACTION_REQUIRED" | "BLOCKED";
|
|
5
5
|
export type InstallerFindingSeverity = "info" | "action" | "error";
|
|
@@ -17,6 +17,7 @@ export interface InstallerEnvironmentPreflightResult {
|
|
|
17
17
|
npmVersion?: string;
|
|
18
18
|
packageManager?: {
|
|
19
19
|
name: SupportedWorkspacePackageManager;
|
|
20
|
+
source: PackageManagerSelectionSource;
|
|
20
21
|
declared?: string;
|
|
21
22
|
version?: string;
|
|
22
23
|
};
|
|
@@ -48,51 +48,55 @@ export async function preflightInstallerEnvironment(options) {
|
|
|
48
48
|
findings.push(platformErrorFinding(error, "NPM_UNAVAILABLE"));
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
let packageManager;
|
|
52
52
|
let packageManagerVersion;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
severity: "action",
|
|
57
|
-
message: `unsupported packageManager declaration: ${workspace.packageManager ?? "<unknown>"}`,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
else if (packageManager.name === "pnpm") {
|
|
61
|
-
if (!findExecutable("pnpm")) {
|
|
53
|
+
try {
|
|
54
|
+
packageManager = await readWorkspacePackageManagerSelection(options.workspaceRoot);
|
|
55
|
+
if (!findExecutable(packageManager.name)) {
|
|
62
56
|
findings.push({
|
|
63
|
-
code: "
|
|
57
|
+
code: "PACKAGE_MANAGER_UNAVAILABLE",
|
|
64
58
|
severity: "action",
|
|
65
|
-
message: `workspace
|
|
59
|
+
message: `workspace selects ${packageManager.name}, but that executable is not available on PATH`,
|
|
66
60
|
});
|
|
67
61
|
}
|
|
62
|
+
else if (packageManager.name === "npm" && npmVersion !== undefined) {
|
|
63
|
+
packageManagerVersion = npmVersion;
|
|
64
|
+
}
|
|
68
65
|
else {
|
|
69
66
|
try {
|
|
70
|
-
|
|
71
|
-
packageManagerVersion = result;
|
|
72
|
-
findings.push({
|
|
73
|
-
code: "PNPM_READY",
|
|
74
|
-
severity: "info",
|
|
75
|
-
message: `pnpm ${result} is available for this workspace`,
|
|
76
|
-
});
|
|
67
|
+
packageManagerVersion = (await systemPackageCommandRunner().run(packageManager.name, ["--version"], options.workspaceRoot)).trim();
|
|
77
68
|
}
|
|
78
69
|
catch (error) {
|
|
79
70
|
findings.push({
|
|
80
|
-
code: "
|
|
71
|
+
code: "PACKAGE_MANAGER_UNAVAILABLE",
|
|
81
72
|
severity: "action",
|
|
82
73
|
message: error instanceof Error ? error.message : String(error),
|
|
83
74
|
});
|
|
84
75
|
}
|
|
85
76
|
}
|
|
77
|
+
if (packageManagerVersion !== undefined) {
|
|
78
|
+
findings.push({
|
|
79
|
+
code: "PACKAGE_MANAGER_READY",
|
|
80
|
+
severity: "info",
|
|
81
|
+
message: `${packageManager.name} ${packageManagerVersion} is selected via ${packageManager.source}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
86
84
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof PlatformError) {
|
|
87
|
+
findings.push({
|
|
88
|
+
code: error.code,
|
|
89
|
+
severity: error.code === "PACKAGE_MANAGER_CONFLICT" ? "error" : "action",
|
|
90
|
+
message: error.message,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
findings.push({
|
|
95
|
+
code: "PACKAGE_MANAGER_UNAVAILABLE",
|
|
96
|
+
severity: "action",
|
|
97
|
+
message: error instanceof Error ? error.message : String(error),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
96
100
|
}
|
|
97
101
|
let registry;
|
|
98
102
|
if (npmVersion !== undefined) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
export type SupportedWorkspacePackageManager = "npm" | "pnpm";
|
|
1
|
+
export type SupportedWorkspacePackageManager = "npm" | "yarn" | "pnpm";
|
|
2
|
+
export type PackageManagerSelectionSource = "declared" | "lockfile" | "bootstrap-default";
|
|
2
3
|
export interface WorkspacePackageManagerSelection {
|
|
3
4
|
name: SupportedWorkspacePackageManager;
|
|
5
|
+
source: PackageManagerSelectionSource;
|
|
4
6
|
declared?: string;
|
|
5
7
|
}
|
|
6
8
|
export interface PackageCommandRunner {
|
|
7
9
|
run(command: string, args: readonly string[], cwd: string): Promise<string>;
|
|
8
10
|
}
|
|
9
11
|
export declare function systemPackageCommandRunner(): PackageCommandRunner;
|
|
10
|
-
export declare function readWorkspacePackageManagerSelection(workspaceRoot: string): Promise<WorkspacePackageManagerSelection
|
|
12
|
+
export declare function readWorkspacePackageManagerSelection(workspaceRoot: string): Promise<WorkspacePackageManagerSelection>;
|
|
11
13
|
export declare function findExecutable(command: string): boolean;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { accessSync, constants } from "node:fs";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { access, readFile } from "node:fs/promises";
|
|
4
4
|
import { delimiter, join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { PlatformError } from "../errors.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
export function systemPackageCommandRunner() {
|
|
8
9
|
return {
|
|
@@ -17,25 +18,27 @@ export function systemPackageCommandRunner() {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
export async function readWorkspacePackageManagerSelection(workspaceRoot) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
|
|
21
|
+
const declared = await readDeclaredPackageManager(workspaceRoot);
|
|
22
|
+
const lockManagers = await detectLockfileManagers(workspaceRoot);
|
|
23
|
+
if (declared !== undefined) {
|
|
24
|
+
const name = packageManagerName(declared);
|
|
25
|
+
if (name === undefined) {
|
|
26
|
+
throw new PlatformError("PACKAGE_MANAGER_UNSUPPORTED", `unsupported packageManager declaration: ${declared}; expected npm, yarn, or pnpm`);
|
|
27
|
+
}
|
|
28
|
+
const conflicts = [...lockManagers].filter((manager) => manager !== name);
|
|
29
|
+
if (conflicts.length > 0) {
|
|
30
|
+
throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `packageManager declares ${declared}, but conflicting lockfiles indicate ${conflicts.join(", ")}`);
|
|
26
31
|
}
|
|
32
|
+
return { name, source: "declared", declared };
|
|
27
33
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// package.json is rejected earlier by installer preflight.
|
|
34
|
+
if (lockManagers.size > 1) {
|
|
35
|
+
throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `multiple package-manager lockfiles are present: ${[...lockManagers].sort().join(", ")}`);
|
|
31
36
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return undefined;
|
|
38
|
-
return { name, declared };
|
|
37
|
+
const [fromLockfile] = lockManagers;
|
|
38
|
+
if (fromLockfile !== undefined) {
|
|
39
|
+
return { name: fromLockfile, source: "lockfile" };
|
|
40
|
+
}
|
|
41
|
+
return { name: "npm", source: "bootstrap-default" };
|
|
39
42
|
}
|
|
40
43
|
export function findExecutable(command) {
|
|
41
44
|
const path = process.env.PATH ?? "";
|
|
@@ -53,6 +56,59 @@ export function findExecutable(command) {
|
|
|
53
56
|
}
|
|
54
57
|
return false;
|
|
55
58
|
}
|
|
59
|
+
async function readDeclaredPackageManager(workspaceRoot) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = await readFile(join(workspaceRoot, "package.json"), "utf8");
|
|
62
|
+
const parsed = JSON.parse(raw);
|
|
63
|
+
if (!isRecord(parsed)) {
|
|
64
|
+
throw new PlatformError("PACKAGE_MANAGER_CONFLICT", "workspace package.json root must be an object");
|
|
65
|
+
}
|
|
66
|
+
return typeof parsed.packageManager === "string"
|
|
67
|
+
? parsed.packageManager
|
|
68
|
+
: undefined;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isMissingFile(error))
|
|
72
|
+
return undefined;
|
|
73
|
+
if (error instanceof PlatformError)
|
|
74
|
+
throw error;
|
|
75
|
+
throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `cannot inspect workspace package.json: ${errorMessage(error)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function detectLockfileManagers(workspaceRoot) {
|
|
79
|
+
const managers = new Set();
|
|
80
|
+
if ((await pathExists(join(workspaceRoot, "package-lock.json"))) ||
|
|
81
|
+
(await pathExists(join(workspaceRoot, "npm-shrinkwrap.json")))) {
|
|
82
|
+
managers.add("npm");
|
|
83
|
+
}
|
|
84
|
+
if (await pathExists(join(workspaceRoot, "yarn.lock")))
|
|
85
|
+
managers.add("yarn");
|
|
86
|
+
if (await pathExists(join(workspaceRoot, "pnpm-lock.yaml")))
|
|
87
|
+
managers.add("pnpm");
|
|
88
|
+
return managers;
|
|
89
|
+
}
|
|
90
|
+
function packageManagerName(declared) {
|
|
91
|
+
const separator = declared.lastIndexOf("@");
|
|
92
|
+
const name = separator > 0 ? declared.slice(0, separator) : declared;
|
|
93
|
+
return name === "npm" || name === "yarn" || name === "pnpm"
|
|
94
|
+
? name
|
|
95
|
+
: undefined;
|
|
96
|
+
}
|
|
97
|
+
async function pathExists(path) {
|
|
98
|
+
try {
|
|
99
|
+
await access(path);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
56
106
|
function isRecord(value) {
|
|
57
107
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
108
|
}
|
|
109
|
+
function isMissingFile(error) {
|
|
110
|
+
return isRecord(error) && error.code === "ENOENT";
|
|
111
|
+
}
|
|
112
|
+
function errorMessage(error) {
|
|
113
|
+
return error instanceof Error ? error.message : String(error);
|
|
114
|
+
}
|
|
@@ -9,6 +9,7 @@ export interface PlanInput {
|
|
|
9
9
|
currentDescriptors?: readonly ModuleDescriptor[];
|
|
10
10
|
targetDescriptors?: readonly ModuleDescriptor[];
|
|
11
11
|
facts?: readonly RepairFact[];
|
|
12
|
+
uninstallScope?: "module" | "platform-instance";
|
|
12
13
|
now?: Date;
|
|
13
14
|
}
|
|
14
15
|
export declare function planDeployment(input: PlanInput): DeploymentPlan;
|
package/dist/src/planner/plan.js
CHANGED
|
@@ -128,7 +128,7 @@ function planUninstall(input) {
|
|
|
128
128
|
throw new PlatformError("INVALID_REQUEST", "uninstall requires modules");
|
|
129
129
|
}
|
|
130
130
|
const core = modules.find((module) => module.installClass === "core");
|
|
131
|
-
if (core !== undefined) {
|
|
131
|
+
if (core !== undefined && input.uninstallScope !== "platform-instance") {
|
|
132
132
|
throw new PlatformError("CORE_PACKAGE_REQUIRED", `core module ${core.moduleRef} cannot be individually uninstalled`);
|
|
133
133
|
}
|
|
134
134
|
const graph = buildDependencyGraph(modules);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@tomflow/proflow-module-contract": "^0.1.
|
|
24
|
+
"@tomflow/proflow-module-contract": "^0.1.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@tomflow/proflow-
|
|
28
|
-
"@tomflow/proflow-
|
|
27
|
+
"@tomflow/proflow-module-template": "^0.1.2",
|
|
28
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.2"
|
|
29
29
|
},
|
|
30
30
|
"description": "Deterministic platform-level deployment discovery, planning, lifecycle and verification CLI.",
|
|
31
31
|
"keywords": [
|
package/proflow.module.json
CHANGED