@procedures/pnpm 0.2.3 → 0.3.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 (70) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +38 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/module.d.ts.map +1 -1
  9. package/dist/module.js +111 -0
  10. package/dist/module.js.map +1 -1
  11. package/dist/procedures/pnpm/add.js +2 -2
  12. package/dist/procedures/pnpm/add.js.map +1 -1
  13. package/dist/procedures/pnpm/exec.d.ts +17 -0
  14. package/dist/procedures/pnpm/exec.d.ts.map +1 -0
  15. package/dist/procedures/pnpm/exec.js +19 -0
  16. package/dist/procedures/pnpm/exec.js.map +1 -0
  17. package/dist/procedures/pnpm/index.d.ts +6 -0
  18. package/dist/procedures/pnpm/index.d.ts.map +1 -1
  19. package/dist/procedures/pnpm/index.js +6 -0
  20. package/dist/procedures/pnpm/index.js.map +1 -1
  21. package/dist/procedures/pnpm/install.js +2 -2
  22. package/dist/procedures/pnpm/install.js.map +1 -1
  23. package/dist/procedures/pnpm/list.d.ts +22 -0
  24. package/dist/procedures/pnpm/list.d.ts.map +1 -0
  25. package/dist/procedures/pnpm/list.js +30 -0
  26. package/dist/procedures/pnpm/list.js.map +1 -0
  27. package/dist/procedures/pnpm/outdated.d.ts +19 -0
  28. package/dist/procedures/pnpm/outdated.d.ts.map +1 -0
  29. package/dist/procedures/pnpm/outdated.js +29 -0
  30. package/dist/procedures/pnpm/outdated.js.map +1 -0
  31. package/dist/procedures/pnpm/pack.d.ts +17 -0
  32. package/dist/procedures/pnpm/pack.d.ts.map +1 -0
  33. package/dist/procedures/pnpm/pack.js +24 -0
  34. package/dist/procedures/pnpm/pack.js.map +1 -0
  35. package/dist/procedures/pnpm/remove.js +2 -2
  36. package/dist/procedures/pnpm/remove.js.map +1 -1
  37. package/dist/procedures/pnpm/run.js +2 -2
  38. package/dist/procedures/pnpm/run.js.map +1 -1
  39. package/dist/procedures/pnpm/shared.d.ts +20 -1
  40. package/dist/procedures/pnpm/shared.d.ts.map +1 -1
  41. package/dist/procedures/pnpm/shared.js +33 -2
  42. package/dist/procedures/pnpm/shared.js.map +1 -1
  43. package/dist/procedures/pnpm/update.d.ts +16 -0
  44. package/dist/procedures/pnpm/update.d.ts.map +1 -0
  45. package/dist/procedures/pnpm/update.js +25 -0
  46. package/dist/procedures/pnpm/update.js.map +1 -0
  47. package/dist/procedures/pnpm/why.d.ts +16 -0
  48. package/dist/procedures/pnpm/why.d.ts.map +1 -0
  49. package/dist/procedures/pnpm/why.js +20 -0
  50. package/dist/procedures/pnpm/why.js.map +1 -0
  51. package/dist/types.d.ts +86 -0
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/types.js +104 -0
  54. package/dist/types.js.map +1 -1
  55. package/package.json +4 -4
  56. package/src/index.ts +19 -0
  57. package/src/module.ts +143 -0
  58. package/src/procedures/pnpm/add.ts +2 -2
  59. package/src/procedures/pnpm/exec.ts +22 -0
  60. package/src/procedures/pnpm/index.ts +6 -0
  61. package/src/procedures/pnpm/install.ts +2 -2
  62. package/src/procedures/pnpm/list.ts +31 -0
  63. package/src/procedures/pnpm/outdated.ts +29 -0
  64. package/src/procedures/pnpm/pack.ts +25 -0
  65. package/src/procedures/pnpm/remove.ts +2 -2
  66. package/src/procedures/pnpm/run.ts +2 -2
  67. package/src/procedures/pnpm/shared.ts +44 -3
  68. package/src/procedures/pnpm/update.ts +25 -0
  69. package/src/procedures/pnpm/why.ts +23 -0
  70. package/src/types.ts +211 -0
package/src/module.ts CHANGED
@@ -29,6 +29,38 @@ import {
29
29
  PnpmStorePathOutputSchema,
30
30
  } from "./types.js";
31
31
  import type { ProcedureContext } from "@procedures/client";
32
+ import { pnpmExec } from "./procedures/pnpm/exec.js";
33
+ import { pnpmList } from "./procedures/pnpm/list.js";
34
+ import { pnpmWhy } from "./procedures/pnpm/why.js";
35
+ import { pnpmOutdated } from "./procedures/pnpm/outdated.js";
36
+ import { pnpmUpdate } from "./procedures/pnpm/update.js";
37
+ import { pnpmPack } from "./procedures/pnpm/pack.js";
38
+ import {
39
+ PnpmExecInputSchema,
40
+ PnpmExecOutputSchema,
41
+ type PnpmExecInput,
42
+ type PnpmExecOutput,
43
+ PnpmListInputSchema,
44
+ PnpmListOutputSchema,
45
+ type PnpmListInput,
46
+ type PnpmListOutput,
47
+ PnpmWhyInputSchema,
48
+ PnpmWhyOutputSchema,
49
+ type PnpmWhyInput,
50
+ type PnpmWhyOutput,
51
+ PnpmOutdatedInputSchema,
52
+ PnpmOutdatedOutputSchema,
53
+ type PnpmOutdatedInput,
54
+ type PnpmOutdatedOutput,
55
+ PnpmUpdateInputSchema,
56
+ PnpmUpdateOutputSchema,
57
+ type PnpmUpdateInput,
58
+ type PnpmUpdateOutput,
59
+ PnpmPackInputSchema,
60
+ PnpmPackOutputSchema,
61
+ type PnpmPackInput,
62
+ type PnpmPackOutput,
63
+ } from "./types.js";
32
64
 
33
65
  // =============================================================================
34
66
  // Procedures
@@ -40,6 +72,7 @@ const pnpmInstallProcedure = createProcedure()
40
72
  .output(zodAdapter<PnpmCommandOutput>(PnpmCommandOutputSchema))
41
73
  .meta({
42
74
  description: "Install packages using pnpm",
75
+ calls: ["shell.run"],
43
76
  // pnpm reads the npm registry, rewrites node_modules and the lockfile, and runs the
44
77
  // lifecycle scripts and the pnpmfile of the project
45
78
  effects: { reads: ["fs", "network"], writes: ["fs", "process"] },
@@ -57,6 +90,7 @@ const pnpmAddProcedure = createProcedure()
57
90
  .output(zodAdapter<PnpmCommandOutput>(PnpmCommandOutputSchema))
58
91
  .meta({
59
92
  description: "Add packages using pnpm",
93
+ calls: ["shell.run"],
60
94
  effects: { reads: ["fs", "network"], writes: ["fs", "process"] },
61
95
  args: ["packages"],
62
96
  shorts: { cwd: "C", dev: "D", global: "g" },
@@ -73,6 +107,7 @@ const pnpmRemoveProcedure = createProcedure()
73
107
  .output(zodAdapter<PnpmCommandOutput>(PnpmCommandOutputSchema))
74
108
  .meta({
75
109
  description: "Remove packages using pnpm",
110
+ calls: ["shell.run"],
76
111
  // pnpm resolves the remaining dependencies again, so it can read the npm registry
77
112
  effects: { reads: ["fs", "network"], writes: ["fs", "process"] },
78
113
  args: ["packages"],
@@ -90,6 +125,7 @@ const pnpmLinkProcedure = createProcedure()
90
125
  .output(zodAdapter<PnpmCommandOutput>(PnpmCommandOutputSchema))
91
126
  .meta({
92
127
  description: "Link packages using pnpm",
128
+ calls: ["shell.run"],
93
129
  effects: { reads: ["fs", "network"], writes: ["fs", "process"] },
94
130
  shorts: { cwd: "C", global: "g" },
95
131
  output: "json",
@@ -105,6 +141,7 @@ const pnpmRunProcedure = createProcedure()
105
141
  .output(zodAdapter<PnpmCommandOutput>(PnpmCommandOutputSchema))
106
142
  .meta({
107
143
  description: "Run package scripts using pnpm",
144
+ calls: ["shell.run"],
108
145
  // A package script is code of the project: it can do anything
109
146
  effects: { reads: ["fs"], writes: ["process"] },
110
147
  args: ["script"],
@@ -133,6 +170,106 @@ const pnpmStorePathProcedure = createProcedure()
133
170
  })
134
171
  .build();
135
172
 
173
+ // =============================================================================
174
+ // Roadmap R41: exec, list, why, outdated, update, pack
175
+ // =============================================================================
176
+
177
+ const pnpmExecProcedure = createProcedure()
178
+ .path(["pnpm", "exec"])
179
+ .input(zodAdapter<PnpmExecInput>(PnpmExecInputSchema))
180
+ .output(zodAdapter<PnpmExecOutput>(PnpmExecOutputSchema))
181
+ .meta({
182
+ description: "Run a program of the dependencies (node_modules/.bin) with pnpm exec",
183
+ calls: ["shell.run"],
184
+ // The program is any program of the dependencies: it can do anything, as pnpm.run
185
+ effects: { reads: ["fs"], writes: ["process"] },
186
+ args: ["command", "args"],
187
+ shorts: { cwd: "C", recursive: "r" },
188
+ output: "json",
189
+ })
190
+ .handler(async (input: PnpmExecInput, ctx: ProcedureContext): Promise<PnpmExecOutput> => pnpmExec(input, ctx))
191
+ .build();
192
+
193
+ const pnpmListProcedure = createProcedure()
194
+ .path(["pnpm", "list"])
195
+ .input(zodAdapter<PnpmListInput>(PnpmListInputSchema))
196
+ .output(zodAdapter<PnpmListOutput>(PnpmListOutputSchema))
197
+ .meta({
198
+ description: "List the installed packages: data is the JSON of pnpm list --json",
199
+ calls: ["shell.run"],
200
+ // It reads package.json, the lockfile and node_modules
201
+ effects: { reads: ["fs"], writes: [] },
202
+ args: ["packages"],
203
+ shorts: { cwd: "C", depth: "d", recursive: "r" },
204
+ output: "json",
205
+ })
206
+ .handler(async (input: PnpmListInput, ctx: ProcedureContext): Promise<PnpmListOutput> => pnpmList(input, ctx))
207
+ .build();
208
+
209
+ const pnpmWhyProcedure = createProcedure()
210
+ .path(["pnpm", "why"])
211
+ .input(zodAdapter<PnpmWhyInput>(PnpmWhyInputSchema))
212
+ .output(zodAdapter<PnpmWhyOutput>(PnpmWhyOutputSchema))
213
+ .meta({
214
+ description: "Show the packages that depend on a package: data is the JSON of pnpm why --json",
215
+ calls: ["shell.run"],
216
+ effects: { reads: ["fs"], writes: [] },
217
+ args: ["packages"],
218
+ shorts: { cwd: "C", depth: "d", recursive: "r" },
219
+ output: "json",
220
+ })
221
+ .handler(async (input: PnpmWhyInput, ctx: ProcedureContext): Promise<PnpmWhyOutput> => pnpmWhy(input, ctx))
222
+ .build();
223
+
224
+ const pnpmOutdatedProcedure = createProcedure()
225
+ .path(["pnpm", "outdated"])
226
+ .input(zodAdapter<PnpmOutdatedInput>(PnpmOutdatedInputSchema))
227
+ .output(zodAdapter<PnpmOutdatedOutput>(PnpmOutdatedOutputSchema))
228
+ .meta({
229
+ description: "List the outdated packages: data is the JSON of pnpm outdated",
230
+ calls: ["shell.run"],
231
+ // pnpm reads the versions of the npm registry
232
+ effects: { reads: ["fs", "network"], writes: [] },
233
+ args: ["packages"],
234
+ shorts: { cwd: "C", recursive: "r" },
235
+ output: "json",
236
+ })
237
+ .handler(async (input: PnpmOutdatedInput, ctx: ProcedureContext): Promise<PnpmOutdatedOutput> => pnpmOutdated(input, ctx))
238
+ .build();
239
+
240
+ const pnpmUpdateProcedure = createProcedure()
241
+ .path(["pnpm", "update"])
242
+ .input(zodAdapter<PnpmUpdateInput>(PnpmUpdateInputSchema))
243
+ .output(zodAdapter<PnpmUpdateOutput>(PnpmUpdateOutputSchema))
244
+ .meta({
245
+ description: "Update packages to their newest versions in the ranges, or with latest to the latest versions",
246
+ calls: ["shell.run"],
247
+ // As pnpm.install: the registry, node_modules, the lockfile and the lifecycle scripts
248
+ effects: { reads: ["fs", "network"], writes: ["fs", "process"] },
249
+ args: ["packages"],
250
+ shorts: { cwd: "C", latest: "L", recursive: "r" },
251
+ output: "json",
252
+ })
253
+ .handler(async (input: PnpmUpdateInput, ctx: ProcedureContext): Promise<PnpmUpdateOutput> => pnpmUpdate(input, ctx))
254
+ .build();
255
+
256
+ const pnpmPackProcedure = createProcedure()
257
+ .path(["pnpm", "pack"])
258
+ .input(zodAdapter<PnpmPackInput>(PnpmPackInputSchema))
259
+ .output(zodAdapter<PnpmPackOutput>(PnpmPackOutputSchema))
260
+ .meta({
261
+ description: "Make a tarball of the package: data gives its file name and files",
262
+ calls: ["shell.run"],
263
+ // It writes the tarball (it replaces a tarball of the same name), and pnpm runs the prepack,
264
+ // prepare and postpack scripts of the package
265
+ effects: { reads: ["fs"], writes: ["fs", "process"] },
266
+ args: [],
267
+ shorts: { cwd: "C", destination: "d", dryRun: "n" },
268
+ output: "json",
269
+ })
270
+ .handler(async (input: PnpmPackInput, ctx: ProcedureContext): Promise<PnpmPackOutput> => pnpmPack(input, ctx))
271
+ .build();
272
+
136
273
  // =============================================================================
137
274
  // Registration
138
275
  // =============================================================================
@@ -148,6 +285,12 @@ export const pnpmModule: ProcedureModule = defineModule("@procedures/pnpm", [
148
285
  pnpmLinkProcedure,
149
286
  pnpmRunProcedure,
150
287
  pnpmStorePathProcedure,
288
+ pnpmExecProcedure,
289
+ pnpmListProcedure,
290
+ pnpmWhyProcedure,
291
+ pnpmOutdatedProcedure,
292
+ pnpmUpdateProcedure,
293
+ pnpmPackProcedure,
151
294
  ], [shellModule]);
152
295
 
153
296
  export default pnpmModule;
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ProcedureContext } from "@procedures/client";
8
- import { pnpmArg, runPnpm } from "./shared.js";
8
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
9
9
  import type { PnpmAddInput, PnpmCommandOutput } from "../../types.js";
10
10
 
11
11
  /**
@@ -29,7 +29,7 @@ export async function pnpmAdd(
29
29
  ctx: ProcedureContext
30
30
  ): Promise<PnpmCommandOutput> {
31
31
  {
32
- const args: string[] = ["add", ...input.packages.map((p) => pnpmArg("package", p))];
32
+ const args: string[] = [...selectionArgs(input), "add", ...input.packages.map((p) => pnpmArg("package", p))];
33
33
 
34
34
  // Add flags
35
35
  if (input.dev) args.push("--save-dev");
@@ -0,0 +1,22 @@
1
+ /**
2
+ * pnpm.exec procedure
3
+ *
4
+ * Run a program of the dependencies with `pnpm exec` (roadmap R41). pnpm finds the program in
5
+ * `node_modules/.bin`. No shell runs it: "--" ends the options of pnpm, and each argument goes
6
+ * to the program as it is.
7
+ */
8
+
9
+ import type { ProcedureContext } from "@procedures/client";
10
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
11
+ import type { PnpmExecInput, PnpmExecOutput } from "../../types.js";
12
+
13
+ /**
14
+ * Run a program of the dependencies with pnpm exec
15
+ *
16
+ * @example
17
+ * await client.call(["pnpm", "exec"], { command: "tsc", args: ["--noEmit"], filter: ["@scope/app"] });
18
+ */
19
+ export async function pnpmExec(input: PnpmExecInput, ctx: ProcedureContext): Promise<PnpmExecOutput> {
20
+ const args = [...selectionArgs(input), "exec", "--", pnpmArg("command", input.command), ...(input.args ?? [])];
21
+ return runPnpm(args, input, ctx);
22
+ }
@@ -8,3 +8,9 @@ export { pnpmRemove } from "./remove.js";
8
8
  export { pnpmLink } from "./link.js";
9
9
  export { pnpmRun } from "./run.js";
10
10
  export { pnpmStorePath } from "./store.js";
11
+ export { pnpmExec } from "./exec.js";
12
+ export { pnpmList } from "./list.js";
13
+ export { pnpmWhy } from "./why.js";
14
+ export { pnpmOutdated } from "./outdated.js";
15
+ export { pnpmUpdate } from "./update.js";
16
+ export { pnpmPack } from "./pack.js";
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ProcedureContext } from "@procedures/client";
8
- import { pnpmArg, runPnpm } from "./shared.js";
8
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
9
9
  import type { PnpmInstallInput, PnpmCommandOutput } from "../../types.js";
10
10
 
11
11
  /**
@@ -27,7 +27,7 @@ export async function pnpmInstall(
27
27
  ctx: ProcedureContext
28
28
  ): Promise<PnpmCommandOutput> {
29
29
  {
30
- const args: string[] = ["install"];
30
+ const args: string[] = [...selectionArgs(input), "install"];
31
31
 
32
32
  // Add packages if specified
33
33
  if (input.packages && input.packages.length > 0) {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * pnpm.list procedure
3
+ *
4
+ * List the installed packages as JSON (roadmap R41). `data` is the parsed output of
5
+ * `pnpm list --json`: one entry for each project.
6
+ */
7
+
8
+ import type { ProcedureContext } from "@procedures/client";
9
+ import { pnpmArg, runPnpmJson, selectionArgs } from "./shared.js";
10
+ import type { PnpmListInput, PnpmListOutput } from "../../types.js";
11
+
12
+ /** The options of list and why: the depth and the kinds of dependencies. */
13
+ export function treeOptions(input: { depth?: number | undefined; prod?: boolean | undefined; dev?: boolean | undefined }): string[] {
14
+ const args: string[] = [];
15
+ if (input.depth !== undefined) args.push("--depth", String(input.depth));
16
+ if (input.prod) args.push("--prod");
17
+ if (input.dev) args.push("--dev");
18
+ return args;
19
+ }
20
+
21
+ /**
22
+ * List the installed packages as JSON
23
+ *
24
+ * @example
25
+ * const { data } = await client.call(["pnpm", "list"], { depth: 0, recursive: true });
26
+ */
27
+ export async function pnpmList(input: PnpmListInput, ctx: ProcedureContext): Promise<PnpmListOutput> {
28
+ const args = [...selectionArgs(input), "list", "--json", ...treeOptions(input)];
29
+ args.push(...(input.packages ?? []).map((name) => pnpmArg("package", name)));
30
+ return runPnpmJson(args, input, ctx);
31
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * pnpm.outdated procedure
3
+ *
4
+ * List the outdated packages as JSON (roadmap R41). pnpm reads the npm registry. `data` is the
5
+ * parsed output of `pnpm outdated --format json`.
6
+ *
7
+ * pnpm exits with 1 when a package is outdated. That is an answer, not a failure: when the output
8
+ * is JSON, the result has `success: true`.
9
+ */
10
+
11
+ import type { ProcedureContext } from "@procedures/client";
12
+ import { pnpmArg, runPnpmJson, selectionArgs } from "./shared.js";
13
+ import type { PnpmOutdatedInput, PnpmOutdatedOutput } from "../../types.js";
14
+
15
+ /**
16
+ * List the outdated packages as JSON
17
+ *
18
+ * @example
19
+ * const { data } = await client.call(["pnpm", "outdated"], { recursive: true });
20
+ */
21
+ export async function pnpmOutdated(input: PnpmOutdatedInput, ctx: ProcedureContext): Promise<PnpmOutdatedOutput> {
22
+ const args = [...selectionArgs(input), "outdated", "--format", "json"];
23
+ if (input.prod) args.push("--prod");
24
+ if (input.dev) args.push("--dev");
25
+ args.push(...(input.packages ?? []).map((name) => pnpmArg("package", name)));
26
+ const result = await runPnpmJson(args, input, ctx);
27
+ if (result.exitCode === 1 && result.data !== undefined) result.success = true;
28
+ return result;
29
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * pnpm.pack procedure
3
+ *
4
+ * Make a tarball of the package (roadmap R41). `data` is the parsed output of
5
+ * `pnpm pack --json`: the name, the version, the file name and the files of the tarball. With
6
+ * `dryRun`, pnpm writes no tarball.
7
+ */
8
+
9
+ import type { ProcedureContext } from "@procedures/client";
10
+ import { runPnpmJson } from "./shared.js";
11
+ import type { PnpmPackInput, PnpmPackOutput } from "../../types.js";
12
+
13
+ /**
14
+ * Make a tarball of the package
15
+ *
16
+ * @example
17
+ * const { data } = await client.call(["pnpm", "pack"], { cwd: "packages/app", dryRun: true });
18
+ */
19
+ export async function pnpmPack(input: PnpmPackInput, ctx: ProcedureContext): Promise<PnpmPackOutput> {
20
+ const args = ["pack", "--json"];
21
+ if (input.dryRun) args.push("--dry-run");
22
+ // With "=", a folder that starts with "-" stays the value of the option
23
+ if (input.destination !== undefined) args.push(`--pack-destination=${input.destination}`);
24
+ return runPnpmJson(args, input, ctx);
25
+ }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ProcedureContext } from "@procedures/client";
8
- import { pnpmArg, runPnpm } from "./shared.js";
8
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
9
9
  import type { PnpmRemoveInput, PnpmCommandOutput } from "../../types.js";
10
10
 
11
11
  /**
@@ -22,7 +22,7 @@ export async function pnpmRemove(
22
22
  ctx: ProcedureContext
23
23
  ): Promise<PnpmCommandOutput> {
24
24
  {
25
- const args: string[] = ["remove", ...input.packages.map((p) => pnpmArg("package", p))];
25
+ const args: string[] = [...selectionArgs(input), "remove", ...input.packages.map((p) => pnpmArg("package", p))];
26
26
 
27
27
  // Add flags
28
28
  if (input.global) args.push("--global");
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ProcedureContext } from "@procedures/client";
8
- import { pnpmArg, runPnpm } from "./shared.js";
8
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
9
9
  import type { PnpmRunInput, PnpmCommandOutput } from "../../types.js";
10
10
 
11
11
  /**
@@ -29,7 +29,7 @@ export async function pnpmRun(
29
29
  ctx: ProcedureContext
30
30
  ): Promise<PnpmCommandOutput> {
31
31
  {
32
- const args: string[] = ["run", pnpmArg("script", input.script)];
32
+ const args: string[] = [...selectionArgs(input), "run", pnpmArg("script", input.script)];
33
33
 
34
34
  // Add additional arguments
35
35
  if (input.args && input.args.length > 0) {
@@ -12,7 +12,7 @@
12
12
  import { existsSync } from "node:fs";
13
13
  import { delimiter, join } from "node:path";
14
14
  import type { ProcedureContext } from "@procedures/client";
15
- import type { PnpmCommandOutput } from "../../types.js";
15
+ import type { PnpmCommandOutput, PnpmJsonOutput } from "../../types.js";
16
16
 
17
17
  /** A value that pnpm reads as an argument (a package, a script, a path): it must not start with "-". */
18
18
  export function pnpmArg(name: string, value: string): string {
@@ -22,13 +22,33 @@ export function pnpmArg(name: string, value: string): string {
22
22
  return value;
23
23
  }
24
24
 
25
+ /** The selection of the workspace packages of a command (roadmap R41). */
26
+ export interface PnpmSelection {
27
+ filter?: string[] | undefined;
28
+ recursive?: boolean | undefined;
29
+ }
30
+
31
+ /**
32
+ * The selection options, before the command: `--filter=<selector>` for each selector, and
33
+ * `--recursive`. With "=", pnpm reads each selector as the value of `--filter`, also when it
34
+ * starts with "-" or "!".
35
+ */
36
+ export function selectionArgs(input: PnpmSelection): string[] {
37
+ const args = (input.filter ?? []).map((selector) => `--filter=${selector}`);
38
+ if (input.recursive) args.push("--recursive");
39
+ return args;
40
+ }
41
+
25
42
  /** The program and the first arguments that start pnpm without a shell. */
26
43
  export function resolvePnpm(): { command: string; prefix: string[] } {
27
- // When pnpm runs this process, npm_execpath is its script
44
+ // When pnpm runs this process, npm_execpath is its script, or its executable from pnpm 12 on
28
45
  const execPath = process.env["npm_execpath"];
29
46
  if (execPath && /pnpm\.c?js$/.test(execPath) && existsSync(execPath)) {
30
47
  return { command: process.execPath, prefix: [execPath] };
31
48
  }
49
+ if (execPath && /(^|[\\/])pnpm(\.exe)?$/i.test(execPath) && existsSync(execPath)) {
50
+ return { command: execPath, prefix: [] };
51
+ }
32
52
  if (process.platform !== "win32") {
33
53
  return { command: "pnpm", prefix: [] };
34
54
  }
@@ -42,7 +62,7 @@ export function resolvePnpm(): { command: string; prefix: string[] } {
42
62
  return { command: process.execPath, prefix: [script] };
43
63
  }
44
64
  }
45
- throw new Error("pnpm was not found: install pnpm, or run this from a pnpm script");
65
+ throw new Error(`pnpm was not found: install pnpm, or run this from a pnpm script (npm_execpath: ${execPath ?? "not set"})`);
46
66
  }
47
67
 
48
68
  /** This function runs `pnpm <args>` and gives its result. It does not throw for a failed command. */
@@ -83,3 +103,24 @@ export async function runPnpm(
83
103
  };
84
104
  }
85
105
  }
106
+
107
+ /**
108
+ * This function runs `pnpm <args>` for a command that writes JSON, and gives `data`: the parsed
109
+ * standard output. Output that is not JSON gives no `data`. It does not throw.
110
+ */
111
+ export async function runPnpmJson(
112
+ args: string[],
113
+ input: { cwd?: string | undefined; timeout?: number | undefined },
114
+ ctx: ProcedureContext
115
+ ): Promise<PnpmJsonOutput> {
116
+ const result: PnpmJsonOutput = await runPnpm(args, input, ctx);
117
+ const text = result.stdout.trim();
118
+ if (text.startsWith("{") || text.startsWith("[")) {
119
+ try {
120
+ result.data = JSON.parse(text);
121
+ } catch {
122
+ // Not JSON: stdout has the text
123
+ }
124
+ }
125
+ return result;
126
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * pnpm.update procedure
3
+ *
4
+ * Update packages (roadmap R41). Without `latest`, pnpm keeps each version in the range of
5
+ * package.json. With `latest`, it changes the ranges too.
6
+ */
7
+
8
+ import type { ProcedureContext } from "@procedures/client";
9
+ import { pnpmArg, runPnpm, selectionArgs } from "./shared.js";
10
+ import type { PnpmUpdateInput, PnpmUpdateOutput } from "../../types.js";
11
+
12
+ /**
13
+ * Update packages to their newest versions in the ranges
14
+ *
15
+ * @example
16
+ * await client.call(["pnpm", "update"], { packages: ["vitest"], latest: true, filter: ["@scope/app"] });
17
+ */
18
+ export async function pnpmUpdate(input: PnpmUpdateInput, ctx: ProcedureContext): Promise<PnpmUpdateOutput> {
19
+ const args = [...selectionArgs(input), "update"];
20
+ if (input.latest) args.push("--latest");
21
+ if (input.prod) args.push("--prod");
22
+ if (input.dev) args.push("--dev");
23
+ args.push(...(input.packages ?? []).map((name) => pnpmArg("package", name)));
24
+ return runPnpm(args, input, ctx);
25
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * pnpm.why procedure
3
+ *
4
+ * Show why a package is installed, as JSON (roadmap R41). `data` is the parsed output of
5
+ * `pnpm why --json`: the packages that depend on it.
6
+ */
7
+
8
+ import type { ProcedureContext } from "@procedures/client";
9
+ import { pnpmArg, runPnpmJson, selectionArgs } from "./shared.js";
10
+ import { treeOptions } from "./list.js";
11
+ import type { PnpmWhyInput, PnpmWhyOutput } from "../../types.js";
12
+
13
+ /**
14
+ * Show why a package is installed, as JSON
15
+ *
16
+ * @example
17
+ * const { data } = await client.call(["pnpm", "why"], { packages: ["zod"] });
18
+ */
19
+ export async function pnpmWhy(input: PnpmWhyInput, ctx: ProcedureContext): Promise<PnpmWhyOutput> {
20
+ const args = [...selectionArgs(input), "why", "--json", ...treeOptions(input)];
21
+ args.push(...input.packages.map((name) => pnpmArg("package", name)));
22
+ return runPnpmJson(args, input, ctx);
23
+ }