@procedures/pnpm 0.0.0-stage → 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 (65) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +792 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/index.d.ts +30 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +32 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/module.d.ts +15 -0
  10. package/dist/module.d.ts.map +1 -0
  11. package/dist/module.js +113 -0
  12. package/dist/module.js.map +1 -0
  13. package/dist/procedures/pnpm/add.d.ts +25 -0
  14. package/dist/procedures/pnpm/add.d.ts.map +1 -0
  15. package/dist/procedures/pnpm/add.js +34 -0
  16. package/dist/procedures/pnpm/add.js.map +1 -0
  17. package/dist/procedures/pnpm/index.d.ts +10 -0
  18. package/dist/procedures/pnpm/index.d.ts.map +1 -0
  19. package/dist/procedures/pnpm/index.js +10 -0
  20. package/dist/procedures/pnpm/index.js.map +1 -0
  21. package/dist/procedures/pnpm/install.d.ts +23 -0
  22. package/dist/procedures/pnpm/install.d.ts.map +1 -0
  23. package/dist/procedures/pnpm/install.js +36 -0
  24. package/dist/procedures/pnpm/install.js.map +1 -0
  25. package/dist/procedures/pnpm/link.d.ts +24 -0
  26. package/dist/procedures/pnpm/link.d.ts.map +1 -0
  27. package/dist/procedures/pnpm/link.js +35 -0
  28. package/dist/procedures/pnpm/link.js.map +1 -0
  29. package/dist/procedures/pnpm/remove.d.ts +18 -0
  30. package/dist/procedures/pnpm/remove.d.ts.map +1 -0
  31. package/dist/procedures/pnpm/remove.js +25 -0
  32. package/dist/procedures/pnpm/remove.js.map +1 -0
  33. package/dist/procedures/pnpm/run.d.ts +25 -0
  34. package/dist/procedures/pnpm/run.d.ts.map +1 -0
  35. package/dist/procedures/pnpm/run.js +33 -0
  36. package/dist/procedures/pnpm/run.js.map +1 -0
  37. package/dist/procedures/pnpm/shared.d.ts +25 -0
  38. package/dist/procedures/pnpm/shared.d.ts.map +1 -0
  39. package/dist/procedures/pnpm/shared.js +75 -0
  40. package/dist/procedures/pnpm/shared.js.map +1 -0
  41. package/dist/procedures/pnpm/store.d.ts +23 -0
  42. package/dist/procedures/pnpm/store.d.ts.map +1 -0
  43. package/dist/procedures/pnpm/store.js +85 -0
  44. package/dist/procedures/pnpm/store.js.map +1 -0
  45. package/dist/register.d.ts +9 -0
  46. package/dist/register.d.ts.map +1 -0
  47. package/dist/register.js +16 -0
  48. package/dist/register.js.map +1 -0
  49. package/dist/types.d.ts +71 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +98 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +76 -3
  54. package/src/index.ts +57 -0
  55. package/src/module.ts +142 -0
  56. package/src/procedures/pnpm/add.ts +40 -0
  57. package/src/procedures/pnpm/index.ts +10 -0
  58. package/src/procedures/pnpm/install.ts +43 -0
  59. package/src/procedures/pnpm/link.ts +43 -0
  60. package/src/procedures/pnpm/remove.ts +32 -0
  61. package/src/procedures/pnpm/run.ts +41 -0
  62. package/src/procedures/pnpm/shared.ts +85 -0
  63. package/src/procedures/pnpm/store.ts +97 -0
  64. package/src/register.ts +20 -0
  65. package/src/types.ts +180 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * pnpm.run procedure
3
+ *
4
+ * Run package scripts using pnpm.
5
+ */
6
+
7
+ import type { ProcedureContext } from "@procedures/client";
8
+ import { pnpmArg, runPnpm } from "./shared.js";
9
+ import type { PnpmRunInput, PnpmCommandOutput } from "../../types.js";
10
+
11
+ /**
12
+ * Run a package script using pnpm
13
+ *
14
+ * @example
15
+ * // Run build script
16
+ * await client.call(["pnpm", "run"], {
17
+ * script: "build",
18
+ * });
19
+ *
20
+ * @example
21
+ * // Run test with arguments
22
+ * await client.call(["pnpm", "run"], {
23
+ * script: "test",
24
+ * args: ["--watch"],
25
+ * });
26
+ */
27
+ export async function pnpmRun(
28
+ input: PnpmRunInput,
29
+ ctx: ProcedureContext
30
+ ): Promise<PnpmCommandOutput> {
31
+ {
32
+ const args: string[] = ["run", pnpmArg("script", input.script)];
33
+
34
+ // Add additional arguments
35
+ if (input.args && input.args.length > 0) {
36
+ args.push("--", ...input.args);
37
+ }
38
+
39
+ return runPnpm(args, input, ctx);
40
+ }
41
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The way every pnpm procedure runs pnpm: an argument list through `shell.run`, no shell.
3
+ *
4
+ * Before, each procedure joined its arguments into one string for `shell.exec`, which runs a
5
+ * shell, so a package name such as `x & calc` ran a second command (deep dive WRP-5).
6
+ *
7
+ * On Windows, `pnpm` is often a `.cmd` file, and Node runs a `.cmd` file only through a shell.
8
+ * So the helper finds the real program: a `pnpm.exe` (the standalone pnpm), or the `pnpm.cjs`
9
+ * script that the `.cmd` file starts, run with Node.
10
+ */
11
+
12
+ import { existsSync } from "node:fs";
13
+ import { delimiter, join } from "node:path";
14
+ import type { ProcedureContext } from "@procedures/client";
15
+ import type { PnpmCommandOutput } from "../../types.js";
16
+
17
+ /** A value that pnpm reads as an argument (a package, a script, a path): it must not start with "-". */
18
+ export function pnpmArg(name: string, value: string): string {
19
+ if (value.startsWith("-")) {
20
+ throw new Error(`Invalid ${name} (starts with "-"): ${value}`);
21
+ }
22
+ return value;
23
+ }
24
+
25
+ /** The program and the first arguments that start pnpm without a shell. */
26
+ export function resolvePnpm(): { command: string; prefix: string[] } {
27
+ // When pnpm runs this process, npm_execpath is its script
28
+ const execPath = process.env["npm_execpath"];
29
+ if (execPath && /pnpm\.c?js$/.test(execPath) && existsSync(execPath)) {
30
+ return { command: process.execPath, prefix: [execPath] };
31
+ }
32
+ if (process.platform !== "win32") {
33
+ return { command: "pnpm", prefix: [] };
34
+ }
35
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
36
+ if (!dir) continue;
37
+ if (existsSync(join(dir, "pnpm.exe"))) {
38
+ return { command: join(dir, "pnpm.exe"), prefix: [] };
39
+ }
40
+ const script = join(dir, "node_modules", "pnpm", "bin", "pnpm.cjs");
41
+ if (existsSync(script)) {
42
+ return { command: process.execPath, prefix: [script] };
43
+ }
44
+ }
45
+ throw new Error("pnpm was not found: install pnpm, or run this from a pnpm script");
46
+ }
47
+
48
+ /** This function runs `pnpm <args>` and gives its result. It does not throw for a failed command. */
49
+ export async function runPnpm(
50
+ args: string[],
51
+ input: { cwd?: string | undefined; timeout?: number | undefined },
52
+ ctx: ProcedureContext
53
+ ): Promise<PnpmCommandOutput> {
54
+ const startTime = Date.now();
55
+ try {
56
+ const pnpm = resolvePnpm();
57
+ const shellInput: { command: string; args: string[]; cwd?: string; timeout?: number } = {
58
+ command: pnpm.command,
59
+ args: [...pnpm.prefix, ...args],
60
+ };
61
+ if (input.cwd !== undefined) shellInput.cwd = input.cwd;
62
+ if (input.timeout !== undefined) shellInput.timeout = input.timeout;
63
+
64
+ const result = await ctx.client.call<
65
+ typeof shellInput,
66
+ { exitCode: number; stdout: string; stderr: string }
67
+ >(["shell", "run"], shellInput);
68
+
69
+ return {
70
+ exitCode: result.exitCode,
71
+ stdout: result.stdout,
72
+ stderr: result.stderr,
73
+ success: result.exitCode === 0,
74
+ duration: Date.now() - startTime,
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ exitCode: 1,
79
+ stdout: "",
80
+ stderr: error instanceof Error ? error.message : String(error),
81
+ success: false,
82
+ duration: Date.now() - startTime,
83
+ };
84
+ }
85
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * pnpm.store.path procedure
3
+ *
4
+ * Get the pnpm store path for snapshot/restore operations.
5
+ * Cross-platform support for Windows, macOS, and Linux.
6
+ */
7
+
8
+ import { existsSync } from "node:fs";
9
+ import { execSync } from "node:child_process";
10
+ import type { PnpmStorePathInput, PnpmStorePathOutput } from "../../types.js";
11
+
12
+ /**
13
+ * Get pnpm store path
14
+ *
15
+ * Uses `pnpm store path` command to get the actual store location.
16
+ * Falls back to platform-specific defaults if command fails.
17
+ *
18
+ * Store locations by platform:
19
+ * - Windows: %LocalAppData%\pnpm\store or %AppData%\pnpm\store
20
+ * - macOS/Linux: ~/.local/share/pnpm/store or ~/.pnpm-store
21
+ *
22
+ * @example
23
+ * const result = await client.call(["pnpm", "store", "path"], {});
24
+ * console.log(result.path); // "C:\Users\user\AppData\Local\pnpm\store\v3"
25
+ */
26
+ export async function pnpmStorePath(input: PnpmStorePathInput): Promise<PnpmStorePathOutput> {
27
+ const platform = process.platform;
28
+ let storePath = "";
29
+
30
+ try {
31
+ // Try to get store path from pnpm directly
32
+ const opts: { cwd?: string; encoding: "utf8" } = { encoding: "utf8" };
33
+ if (input.cwd) {
34
+ opts.cwd = input.cwd;
35
+ }
36
+
37
+ storePath = execSync("pnpm store path", opts).trim();
38
+ } catch {
39
+ // Fallback to platform-specific defaults
40
+ storePath = getDefaultStorePath(platform);
41
+ }
42
+
43
+ return {
44
+ path: storePath,
45
+ exists: existsSync(storePath),
46
+ platform,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Get default pnpm store path based on platform
52
+ */
53
+ function getDefaultStorePath(platform: string): string {
54
+ if (platform === "win32") {
55
+ // Windows: Check LocalAppData first, then AppData
56
+ const localAppData = process.env["LOCALAPPDATA"];
57
+ const appData = process.env["APPDATA"];
58
+
59
+ if (localAppData) {
60
+ const localPath = `${localAppData}\\pnpm\\store\\v3`;
61
+ if (existsSync(localPath)) {
62
+ return localPath;
63
+ }
64
+ }
65
+
66
+ if (appData) {
67
+ const appDataPath = `${appData}\\pnpm\\store\\v3`;
68
+ if (existsSync(appDataPath)) {
69
+ return appDataPath;
70
+ }
71
+ }
72
+
73
+ // Return default LocalAppData path even if it doesn't exist
74
+ return localAppData
75
+ ? `${localAppData}\\pnpm\\store\\v3`
76
+ : `${process.env["USERPROFILE"]}\\AppData\\Local\\pnpm\\store\\v3`;
77
+ }
78
+
79
+ // macOS/Linux
80
+ const home = process.env["HOME"] || "";
81
+
82
+ // Check XDG data home first
83
+ const xdgDataHome = process.env["XDG_DATA_HOME"] || `${home}/.local/share`;
84
+ const xdgPath = `${xdgDataHome}/pnpm/store/v3`;
85
+ if (existsSync(xdgPath)) {
86
+ return xdgPath;
87
+ }
88
+
89
+ // Check legacy ~/.pnpm-store
90
+ const legacyPath = `${home}/.pnpm-store/v3`;
91
+ if (existsSync(legacyPath)) {
92
+ return legacyPath;
93
+ }
94
+
95
+ // Return XDG path as default
96
+ return xdgPath;
97
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/pnpm in the global registry
3
+ * (DECISIONS-2026-10, "Every package root registers its procedures"). The procedures as data
4
+ * are in `module.ts`, which registers nothing: a host with its own registry uses that one.
5
+ */
6
+
7
+ // Import shell dependency to ensure shell.exec is registered
8
+ import "@procedures/shell";
9
+
10
+ import { registerProcedures } from "@procedures/client";
11
+ import { pnpmModule } from "./module.js";
12
+
13
+ export * from "./module.js";
14
+
15
+ export function registerPnpmProcedures(): void {
16
+ registerProcedures(pnpmModule.procedures);
17
+ }
18
+
19
+ // Auto-register when this module is loaded
20
+ registerPnpmProcedures();
package/src/types.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Type definitions for client-pnpm procedures
3
+ *
4
+ * client-pnpm wraps pnpm commands as procedures using client-shell.
5
+ */
6
+
7
+ import { z } from "zod";
8
+
9
+ // =============================================================================
10
+ // Shared Output Type
11
+ // =============================================================================
12
+
13
+ export interface PnpmCommandOutput {
14
+ /** Exit code of the command */
15
+ exitCode: number;
16
+ /** Standard output */
17
+ stdout: string;
18
+ /** Standard error */
19
+ stderr: string;
20
+ /** Whether command succeeded (exit code 0) */
21
+ success: boolean;
22
+ /** Duration in milliseconds */
23
+ duration: number;
24
+ }
25
+
26
+ // =============================================================================
27
+ // pnpm.install Types
28
+ // =============================================================================
29
+
30
+ export const PnpmInstallInputSchema: z.ZodObject<{
31
+ packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
+ dev: z.ZodOptional<z.ZodBoolean>;
33
+ frozen: z.ZodOptional<z.ZodBoolean>;
34
+ cwd: z.ZodOptional<z.ZodString>;
35
+ timeout: z.ZodOptional<z.ZodNumber>;
36
+ }> = z.object({
37
+ /** Packages to install. If not provided, installs from package.json */
38
+ packages: z.array(z.string()).optional().describe("Packages to install. If not provided, installs from package.json"),
39
+ /** Save as dev dependency */
40
+ dev: z.boolean().optional().describe("Save as dev dependency"),
41
+ /** Use frozen lockfile (--frozen-lockfile) */
42
+ frozen: z.boolean().optional().describe("Use frozen lockfile (--frozen-lockfile)"),
43
+ /** Working directory */
44
+ cwd: z.string().optional().describe("Working directory"),
45
+ /** Timeout in milliseconds */
46
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
47
+ });
48
+
49
+ export type PnpmInstallInput = z.infer<typeof PnpmInstallInputSchema>;
50
+
51
+ // =============================================================================
52
+ // pnpm.add Types
53
+ // =============================================================================
54
+
55
+ export const PnpmAddInputSchema: z.ZodObject<{
56
+ packages: z.ZodArray<z.ZodString>;
57
+ dev: z.ZodOptional<z.ZodBoolean>;
58
+ global: z.ZodOptional<z.ZodBoolean>;
59
+ cwd: z.ZodOptional<z.ZodString>;
60
+ timeout: z.ZodOptional<z.ZodNumber>;
61
+ }> = z.object({
62
+ /** Packages to add */
63
+ packages: z.array(z.string()).describe("Packages to add"),
64
+ /** Save as dev dependency */
65
+ dev: z.boolean().optional().describe("Save as dev dependency"),
66
+ /** Install globally */
67
+ global: z.boolean().optional().describe("Install globally"),
68
+ /** Working directory */
69
+ cwd: z.string().optional().describe("Working directory"),
70
+ /** Timeout in milliseconds */
71
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
72
+ });
73
+
74
+ export type PnpmAddInput = z.infer<typeof PnpmAddInputSchema>;
75
+
76
+ // =============================================================================
77
+ // pnpm.remove Types
78
+ // =============================================================================
79
+
80
+ export const PnpmRemoveInputSchema: z.ZodObject<{
81
+ packages: z.ZodArray<z.ZodString>;
82
+ global: z.ZodOptional<z.ZodBoolean>;
83
+ cwd: z.ZodOptional<z.ZodString>;
84
+ timeout: z.ZodOptional<z.ZodNumber>;
85
+ }> = z.object({
86
+ /** Packages to remove */
87
+ packages: z.array(z.string()).describe("Packages to remove"),
88
+ /** Remove from global */
89
+ global: z.boolean().optional().describe("Remove from global"),
90
+ /** Working directory */
91
+ cwd: z.string().optional().describe("Working directory"),
92
+ /** Timeout in milliseconds */
93
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
94
+ });
95
+
96
+ export type PnpmRemoveInput = z.infer<typeof PnpmRemoveInputSchema>;
97
+
98
+ // =============================================================================
99
+ // pnpm.link Types
100
+ // =============================================================================
101
+
102
+ export const PnpmLinkInputSchema: z.ZodObject<{
103
+ path: z.ZodOptional<z.ZodString>;
104
+ global: z.ZodOptional<z.ZodBoolean>;
105
+ cwd: z.ZodOptional<z.ZodString>;
106
+ timeout: z.ZodOptional<z.ZodNumber>;
107
+ }> = z.object({
108
+ /** Path to package to link. If not provided, links current directory globally */
109
+ path: z.string().optional().describe("Path to package to link. If not provided, links current directory globally"),
110
+ /** Link globally */
111
+ global: z.boolean().optional().describe("Link globally"),
112
+ /** Working directory */
113
+ cwd: z.string().optional().describe("Working directory"),
114
+ /** Timeout in milliseconds */
115
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
116
+ });
117
+
118
+ export type PnpmLinkInput = z.infer<typeof PnpmLinkInputSchema>;
119
+
120
+ // =============================================================================
121
+ // pnpm.run Types
122
+ // =============================================================================
123
+
124
+ export const PnpmRunInputSchema: z.ZodObject<{
125
+ script: z.ZodString;
126
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
127
+ cwd: z.ZodOptional<z.ZodString>;
128
+ timeout: z.ZodOptional<z.ZodNumber>;
129
+ }> = z.object({
130
+ /** Script name to run */
131
+ script: z.string().describe("Script name to run"),
132
+ /** Additional arguments to pass to the script */
133
+ args: z.array(z.string()).optional().describe("Additional arguments to pass to the script"),
134
+ /** Working directory */
135
+ cwd: z.string().optional().describe("Working directory"),
136
+ /** Timeout in milliseconds */
137
+ timeout: z.number().optional().describe("Timeout in milliseconds"),
138
+ });
139
+
140
+ export type PnpmRunInput = z.infer<typeof PnpmRunInputSchema>;
141
+
142
+ // =============================================================================
143
+ // pnpm.store.path Types - Get pnpm store path for snapshot/restore
144
+ // =============================================================================
145
+
146
+ export const PnpmStorePathInputSchema: z.ZodObject<{
147
+ cwd: z.ZodOptional<z.ZodString>;
148
+ }> = z.object({
149
+ /** Working directory (affects store path on some configurations) */
150
+ cwd: z.string().optional().describe("Working directory (affects store path on some configurations)"),
151
+ });
152
+
153
+ export type PnpmStorePathInput = z.infer<typeof PnpmStorePathInputSchema>;
154
+
155
+ export interface PnpmStorePathOutput {
156
+ /** Absolute path to the pnpm store */
157
+ path: string;
158
+ /** Whether store exists */
159
+ exists: boolean;
160
+ /** Platform (win32, darwin, linux) */
161
+ platform: string;
162
+ }
163
+
164
+ // Output schemas (roadmap 2.1). Loose objects: a field that a schema does not name stays in the
165
+ // result (a strict schema would remove it when the output is validated).
166
+
167
+ /** The result of a pnpm command: add, install, remove, link and run give it. */
168
+ export const PnpmCommandOutputSchema: z.ZodType<PnpmCommandOutput> = z.looseObject({
169
+ exitCode: z.number().describe("The exit code of pnpm (0 is success)"),
170
+ stdout: z.string().describe("The standard output of pnpm"),
171
+ stderr: z.string().describe("The standard error of pnpm"),
172
+ success: z.boolean().describe("True when the exit code is 0"),
173
+ duration: z.number().describe("The time of the command, in milliseconds"),
174
+ });
175
+
176
+ export const PnpmStorePathOutputSchema: z.ZodType<PnpmStorePathOutput> = z.looseObject({
177
+ path: z.string().describe("The absolute path of the pnpm store"),
178
+ exists: z.boolean().describe("True when the store folder exists"),
179
+ platform: z.string().describe("The platform (win32, darwin, linux)"),
180
+ });