@arcgis/node-toolkit 5.2.0-next.41 → 5.2.0-next.42

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/dist/file.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ExecSyncOptionsWithStringEncoding, SpawnSyncOptionsWithStringEncoding } from 'node:child_process';
1
+ import { ExecSyncOptionsWithStringEncoding, SpawnOptions, SpawnSyncOptionsWithStringEncoding } from 'node:child_process';
2
2
  /**
3
3
  * Asynchronously check if a file or directory exists.
4
4
  * Using un-promisified version because promises version creates exceptions
@@ -7,16 +7,49 @@ import { ExecSyncOptionsWithStringEncoding, SpawnSyncOptionsWithStringEncoding }
7
7
  export declare const existsAsync: (file: string) => Promise<boolean>;
8
8
  /**
9
9
  * Synchronously execute a shell command and return the output.
10
+ *
11
+ * Prefer {@link sp} for most use cases. {@link sh} goes through a shell,
12
+ * which requires careful escaping and can mis-handle dynamic input with spaces
13
+ * or shell metacharacters.
14
+ *
15
+ * Use {@link sh} only when shell features are intentionally required, such as
16
+ * pipes, redirects, `&&`, or command substitution.
10
17
  */
11
18
  export declare function sh(command: string, options?: Partial<ExecSyncOptionsWithStringEncoding>): string;
12
19
  /**
13
20
  * Asynchronously execute a shell command and return the output.
21
+ *
22
+ * Prefer {@link asyncSp} for most use cases. {@link asyncSh} executes through
23
+ * a shell, so callers must handle shell escaping and quoting correctly.
24
+ *
25
+ * Use {@link asyncSh} only when shell syntax is intentionally needed.
14
26
  */
15
27
  export declare function asyncSh(command: string, options?: Partial<ExecSyncOptionsWithStringEncoding>): Promise<string>;
16
28
  /**
17
29
  * Synchronously execute a command without shell interpolation and return the output.
30
+ *
31
+ * This is usually safer than {@link sh} because arguments are passed as
32
+ * discrete tokens, which avoids most shell escaping issues and command
33
+ * injection pitfalls.
34
+ *
35
+ * Example:
36
+ * `sp("git", ["show", `${baseRef}:pnpm-workspace.yaml`])`
37
+ *
38
+ * Avoid {@link sp} only when you explicitly need shell features.
18
39
  */
19
40
  export declare function sp(command: string, args: string[], options?: Partial<SpawnSyncOptionsWithStringEncoding>): string;
41
+ /**
42
+ * Asynchronously execute a command without shell interpolation and return the output.
43
+ *
44
+ * This is usually safer than {@link asyncSh} because arguments are not parsed
45
+ * by a shell.
46
+ *
47
+ * Example:
48
+ * `await asyncSp("pnpm", ["--filter=@arcgis/map-components", "list", "@arcgis/core", "--json"])`
49
+ *
50
+ * Prefer {@link asyncSp} by default for external command execution.
51
+ */
52
+ export declare function asyncSp(command: string, args: string[], options?: Partial<SpawnOptions>): Promise<string>;
20
53
  /**
21
54
  * Create a file with the specified content if it does not already exist.
22
55
  */
package/dist/file.js CHANGED
@@ -2,7 +2,7 @@ import { access, existsSync } from "node:fs";
2
2
  import { constants, mkdir, writeFile } from "node:fs/promises";
3
3
  import { dirname, resolve, sep, join } from "path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { execSync, exec, spawnSync } from "node:child_process";
5
+ import { execSync, exec, spawnSync, spawn } from "node:child_process";
6
6
  import { styleText } from "node:util";
7
7
  const existsAsync = async (file) => (
8
8
  //#endregion existsAsync
@@ -46,6 +46,36 @@ ${output}`.trim()
46
46
  }
47
47
  return output;
48
48
  }
49
+ async function asyncSp(command, args, options = {}) {
50
+ const child = spawn(command, args, options);
51
+ const stdoutChunks = [];
52
+ const stderrChunks = [];
53
+ child.stdout?.on(
54
+ "data",
55
+ (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk))
56
+ );
57
+ child.stderr?.on(
58
+ "data",
59
+ (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk))
60
+ );
61
+ return await new Promise((resolve2, reject) => {
62
+ child.on("error", (error) => reject(error));
63
+ child.on("close", (code) => {
64
+ const output = `${stdoutChunks.join("")}${stderrChunks.join("")}`.trim();
65
+ const exitCode = code ?? 0;
66
+ if (exitCode !== 0) {
67
+ reject(
68
+ new Error(
69
+ `Command failed with exit code ${String(exitCode)}: ${command} ${args.join(" ")}
70
+ ${output}`.trim()
71
+ )
72
+ );
73
+ return;
74
+ }
75
+ resolve2(output);
76
+ });
77
+ });
78
+ }
49
79
  function makeExecErrorReadable(error) {
50
80
  if (error instanceof Error && error.stack && "output" in error && Array.isArray(error.output) && "status" in error) {
51
81
  const stackIndex = error.stack.indexOf("\n at ");
@@ -105,6 +135,7 @@ async function asyncFindPath(target, startDirectory = process.cwd()) {
105
135
  export {
106
136
  asyncFindPath,
107
137
  asyncSh,
138
+ asyncSp,
108
139
  createFileIfNotExists,
109
140
  existsAsync,
110
141
  findPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcgis/node-toolkit",
3
- "version": "5.2.0-next.41",
3
+ "version": "5.2.0-next.42",
4
4
  "description": "Collection of common internal build-time patterns and utilities for ArcGIS Maps SDK for JavaScript components.",
5
5
  "homepage": "https://developers.arcgis.com/javascript/latest/",
6
6
  "type": "module",