@tomflow/proflow-platform-cli 0.1.0 → 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.
@@ -48,51 +48,55 @@ export async function preflightInstallerEnvironment(options) {
48
48
  findings.push(platformErrorFinding(error, "NPM_UNAVAILABLE"));
49
49
  }
50
50
  }
51
- const packageManager = await readWorkspacePackageManagerSelection(options.workspaceRoot);
51
+ let packageManager;
52
52
  let packageManagerVersion;
53
- if (packageManager === undefined) {
54
- findings.push({
55
- code: "PACKAGE_MANAGER_UNSUPPORTED",
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: "PNPM_REQUIRED",
57
+ code: "PACKAGE_MANAGER_UNAVAILABLE",
64
58
  severity: "action",
65
- message: `workspace declares ${packageManager.declared}; install/enable pnpm before applying package changes`,
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
- const result = (await systemPackageCommandRunner().run("pnpm", ["--version"], options.workspaceRoot)).trim();
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: "PNPM_REQUIRED",
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
- else if (npmVersion !== undefined) {
88
- packageManagerVersion = npmVersion;
89
- findings.push({
90
- code: "PACKAGE_MANAGER_READY",
91
- severity: "info",
92
- message: packageManager.declared === undefined
93
- ? "workspace does not declare packageManager; npm will be used for bootstrap package operations"
94
- : `workspace declares ${packageManager.declared}`,
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 | undefined>;
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
- let declared;
21
- try {
22
- const raw = await readFile(join(workspaceRoot, "package.json"), "utf8");
23
- const parsed = JSON.parse(raw);
24
- if (isRecord(parsed) && typeof parsed.packageManager === "string") {
25
- declared = parsed.packageManager;
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
- catch {
29
- // A Fresh Workspace without package.json uses npm for bootstrap. Invalid
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
- if (declared === undefined)
33
- return { name: "npm" };
34
- const separator = declared.lastIndexOf("@");
35
- const name = separator > 0 ? declared.slice(0, separator) : declared;
36
- if (name !== "npm" && name !== "pnpm")
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;
@@ -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.0",
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.0"
24
+ "@tomflow/proflow-module-contract": "^0.1.1"
25
25
  },
26
26
  "devDependencies": {
27
- "@tomflow/proflow-deployment-conformance": "^0.1.0",
28
- "@tomflow/proflow-module-template": "^0.1.0"
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": [
@@ -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.0",
6
+ "moduleVersion": "0.1.2",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",