@node-cli/run 1.0.4 → 1.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.
package/README.md CHANGED
@@ -26,11 +26,12 @@ Runs a shell command asynchronously and returns both `stdout` and `stderr`. If t
26
26
 
27
27
  ### Arguments
28
28
 
29
- | Argument | Type | Default |
30
- | ------------------- | ------- | ------- |
31
- | command | String | "" |
32
- | options | Object | { } |
33
- | options.ignoreError | Boolean | false |
29
+ | Argument | Type | Default |
30
+ | -------------------- | ------- | ------- |
31
+ | command | String | "" |
32
+ | options | Object | { } |
33
+ | options.ignoreError | Boolean | false |
34
+ | options.streamOutput | Boolean | false |
34
35
 
35
36
  ### Note
36
37
 
@@ -38,18 +39,37 @@ If `ignoreError` is used, the method will not throw but will instead return an o
38
39
 
39
40
  #### Examples
40
41
 
42
+ ##### Basic usage
43
+
44
+ ```js
45
+ import { run } from "@node-cli/run";
46
+ const { stdout, stderr } = await run("npm run build");
47
+ ```
48
+
49
+ ##### Multiple commands
50
+
41
51
  ```js
42
52
  import { run } from "@node-cli/run";
43
- const { stdout, stderr } = await run("npm config ls");
44
53
  const { stdout, stderr } = await run(
45
- "git add -A && git commit -a -m 'First commit'"
54
+ "git add -A && git commit -a -m 'First commit'"
46
55
  );
47
56
  ```
48
57
 
58
+ ##### Stream output
59
+
60
+ ```js
61
+ import { run } from "@node-cli/run";
62
+ await run("npm run build", {
63
+ streamOutput: true
64
+ });
65
+ ```
66
+
67
+ ##### Ignore error
68
+
49
69
  ```js
50
70
  import { run } from "@node-cli/run";
51
71
  const { exitCode, shortMessage } = await runCommand("ls /not-a-folder", {
52
- ignoreError: true,
72
+ ignoreError: true
53
73
  });
54
74
  // -> exitCode is 1 and shortMessage is "Command failed with exit code 1: ls /not-a-folder"
55
75
  ```
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@
3
3
  */
4
4
 
5
5
  export * from "./run";
6
+ export * from "./utilities";
package/dist/run.d.ts CHANGED
@@ -13,6 +13,8 @@ export type RunResult = {
13
13
  exitCode?: number;
14
14
  shortMessage?: string;
15
15
  };
16
- export declare const run: (command: string, options?: {
16
+ export type RunOptions = {
17
17
  ignoreError?: boolean;
18
- }) => Promise<RunResult>;
18
+ streamOutput?: boolean;
19
+ };
20
+ export declare const run: (command: string, options?: RunOptions) => Promise<RunResult>;
package/dist/run.js CHANGED
@@ -1,24 +1,39 @@
1
- import { execaCommand } from "execa";
1
+ import { execa, execaCommand } from "execa";
2
2
  import kleur from "kleur";
3
+ import { parseCommandString } from "./utilities.js";
3
4
  export const run = async (command, options)=>{
4
5
  const { ignoreError } = {
5
6
  ignoreError: false,
6
7
  ...options
7
8
  };
9
+ const execaOptions = {
10
+ shell: false,
11
+ preferLocal: true
12
+ };
13
+ if (command.includes("&&") || command.includes("&") || command.includes("||") || command.includes("|")) {
14
+ execaOptions.shell = true;
15
+ }
16
+ /* istanbul ignore next */ if (options?.streamOutput) {
17
+ execaOptions.stdout = [
18
+ "pipe",
19
+ "inherit"
20
+ ];
21
+ }
8
22
  try {
9
- const { stdout, stderr } = await execaCommand(command, {
10
- /**
11
- * For some reason, a command with a " or ' in execa.command() will
12
- * fail, but it works if shell is set to true... It would work if
13
- * the execaCommand() API is not used:
14
- * execa("ls", ["-l", "|", "wc"]);
15
- * Same problems with &, &&, | and ||.
16
- */ shell: command.includes('"') || command.includes("'") || command.includes("&&") || command.includes("&") || command.includes("||") || command.includes("|")
17
- });
18
- return {
19
- stderr,
20
- stdout
21
- };
23
+ if (execaOptions.shell) {
24
+ const { stdout, stderr } = await execaCommand(command, execaOptions);
25
+ return {
26
+ stderr,
27
+ stdout
28
+ };
29
+ } else {
30
+ const commandArray = parseCommandString(command);
31
+ const { stdout, stderr } = await execa(commandArray[0], commandArray.slice(1), execaOptions);
32
+ return {
33
+ stderr,
34
+ stdout
35
+ };
36
+ }
22
37
  } catch (error) {
23
38
  if (ignoreError) {
24
39
  return {
package/dist/run.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/run.ts"],"sourcesContent":["import { execaCommand } from \"execa\";\nimport kleur from \"kleur\";\n\n/**\n * Runs a shell command asynchronously and\n * returns both `stdout` and `stderr`.\n * If the command fails to run (invalid command or the commands status is\n * anything but 0), the call will throw an exception. The exception can be\n * ignored if the `options.ignoreError` flag is true.\n *\n * @async\n */\nexport type RunResult = {\n\tstderr?: string | number;\n\tstdout?: string | number;\n\texitCode?: number;\n\tshortMessage?: string;\n};\nexport const run = async (\n\tcommand: string,\n\toptions?: { ignoreError?: boolean },\n): Promise<RunResult> => {\n\tconst { ignoreError } = {\n\t\tignoreError: false,\n\t\t...options,\n\t};\n\ttry {\n\t\tconst { stdout, stderr } = await execaCommand(command, {\n\t\t\t/**\n\t\t\t * For some reason, a command with a \" or ' in execa.command() will\n\t\t\t * fail, but it works if shell is set to true... It would work if\n\t\t\t * the execaCommand() API is not used:\n\t\t\t * execa(\"ls\", [\"-l\", \"|\", \"wc\"]);\n\t\t\t * Same problems with &, &&, | and ||.\n\t\t\t */\n\t\t\tshell:\n\t\t\t\tcommand.includes('\"') ||\n\t\t\t\tcommand.includes(\"'\") ||\n\t\t\t\tcommand.includes(\"&&\") ||\n\t\t\t\tcommand.includes(\"&\") ||\n\t\t\t\tcommand.includes(\"||\") ||\n\t\t\t\tcommand.includes(\"|\"),\n\t\t});\n\n\t\treturn { stderr, stdout };\n\t} catch (error) {\n\t\tif (ignoreError) {\n\t\t\treturn {\n\t\t\t\texitCode: error.exitCode === undefined ? 1 : error.exitCode,\n\t\t\t\tshortMessage: error.shortMessage,\n\t\t\t\tstderr: error.exitCode === undefined ? 1 : error.exitCode,\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(kleur.red(error));\n\t\t}\n\t}\n};\n"],"names":["execaCommand","kleur","run","command","options","ignoreError","stdout","stderr","shell","includes","error","exitCode","undefined","shortMessage","Error","red"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,YAAY,QAAQ,QAAQ;AACrC,OAAOC,WAAW,QAAQ;AAiB1B,OAAO,MAAMC,MAAM,OAClBC,SACAC;IAEA,MAAM,EAAEC,WAAW,EAAE,GAAG;QACvBA,aAAa;QACb,GAAGD,OAAO;IACX;IACA,IAAI;QACH,MAAM,EAAEE,MAAM,EAAEC,MAAM,EAAE,GAAG,MAAMP,aAAaG,SAAS;YACtD;;;;;;IAMC,GACDK,OACCL,QAAQM,QAAQ,CAAC,QACjBN,QAAQM,QAAQ,CAAC,QACjBN,QAAQM,QAAQ,CAAC,SACjBN,QAAQM,QAAQ,CAAC,QACjBN,QAAQM,QAAQ,CAAC,SACjBN,QAAQM,QAAQ,CAAC;QACnB;QAEA,OAAO;YAAEF;YAAQD;QAAO;IACzB,EAAE,OAAOI,OAAO;QACf,IAAIL,aAAa;YAChB,OAAO;gBACNM,UAAUD,MAAMC,QAAQ,KAAKC,YAAY,IAAIF,MAAMC,QAAQ;gBAC3DE,cAAcH,MAAMG,YAAY;gBAChCN,QAAQG,MAAMC,QAAQ,KAAKC,YAAY,IAAIF,MAAMC,QAAQ;YAC1D;QACD,OAAO;YACN,MAAM,IAAIG,MAAMb,MAAMc,GAAG,CAACL;QAC3B;IACD;AACD,EAAE"}
1
+ {"version":3,"sources":["../src/run.ts"],"sourcesContent":["import { execa, execaCommand } from \"execa\";\nimport kleur from \"kleur\";\nimport { parseCommandString } from \"./utilities.js\";\n\n/**\n * Runs a shell command asynchronously and\n * returns both `stdout` and `stderr`.\n * If the command fails to run (invalid command or the commands status is\n * anything but 0), the call will throw an exception. The exception can be\n * ignored if the `options.ignoreError` flag is true.\n *\n * @async\n */\nexport type RunResult = {\n\tstderr?: string | number;\n\tstdout?: string | number;\n\texitCode?: number;\n\tshortMessage?: string;\n};\nexport type RunOptions = {\n\tignoreError?: boolean;\n\tstreamOutput?: boolean;\n};\ntype ExecaOptions = {\n\tpreferLocal: boolean;\n\tshell: boolean;\n\tstdout?: [\"pipe\", \"inherit\"];\n};\n\nexport const run = async (\n\tcommand: string,\n\toptions?: RunOptions,\n): Promise<RunResult> => {\n\tconst { ignoreError } = {\n\t\tignoreError: false,\n\t\t...options,\n\t};\n\tconst execaOptions: ExecaOptions = {\n\t\tshell: false,\n\t\tpreferLocal: true,\n\t};\n\n\tif (\n\t\tcommand.includes(\"&&\") ||\n\t\tcommand.includes(\"&\") ||\n\t\tcommand.includes(\"||\") ||\n\t\tcommand.includes(\"|\")\n\t) {\n\t\texecaOptions.shell = true;\n\t}\n\n\t/* istanbul ignore next */\n\tif (options?.streamOutput) {\n\t\texecaOptions.stdout = [\"pipe\", \"inherit\"];\n\t}\n\n\ttry {\n\t\tif (execaOptions.shell) {\n\t\t\tconst { stdout, stderr } = await execaCommand(command, execaOptions);\n\t\t\treturn { stderr, stdout };\n\t\t} else {\n\t\t\tconst commandArray = parseCommandString(command);\n\t\t\tconst { stdout, stderr } = await execa(\n\t\t\t\tcommandArray[0],\n\t\t\t\tcommandArray.slice(1),\n\t\t\t\texecaOptions,\n\t\t\t);\n\t\t\treturn { stderr, stdout };\n\t\t}\n\t} catch (error) {\n\t\tif (ignoreError) {\n\t\t\treturn {\n\t\t\t\texitCode: error.exitCode === undefined ? 1 : error.exitCode,\n\t\t\t\tshortMessage: error.shortMessage,\n\t\t\t\tstderr: error.exitCode === undefined ? 1 : error.exitCode,\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(kleur.red(error));\n\t\t}\n\t}\n};\n"],"names":["execa","execaCommand","kleur","parseCommandString","run","command","options","ignoreError","execaOptions","shell","preferLocal","includes","streamOutput","stdout","stderr","commandArray","slice","error","exitCode","undefined","shortMessage","Error","red"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,KAAK,EAAEC,YAAY,QAAQ,QAAQ;AAC5C,OAAOC,WAAW,QAAQ;AAC1B,SAASC,kBAAkB,QAAQ,iBAAiB;AA2BpD,OAAO,MAAMC,MAAM,OAClBC,SACAC;IAEA,MAAM,EAAEC,WAAW,EAAE,GAAG;QACvBA,aAAa;QACb,GAAGD,OAAO;IACX;IACA,MAAME,eAA6B;QAClCC,OAAO;QACPC,aAAa;IACd;IAEA,IACCL,QAAQM,QAAQ,CAAC,SACjBN,QAAQM,QAAQ,CAAC,QACjBN,QAAQM,QAAQ,CAAC,SACjBN,QAAQM,QAAQ,CAAC,MAChB;QACDH,aAAaC,KAAK,GAAG;IACtB;IAEA,wBAAwB,GACxB,IAAIH,SAASM,cAAc;QAC1BJ,aAAaK,MAAM,GAAG;YAAC;YAAQ;SAAU;IAC1C;IAEA,IAAI;QACH,IAAIL,aAAaC,KAAK,EAAE;YACvB,MAAM,EAAEI,MAAM,EAAEC,MAAM,EAAE,GAAG,MAAMb,aAAaI,SAASG;YACvD,OAAO;gBAAEM;gBAAQD;YAAO;QACzB,OAAO;YACN,MAAME,eAAeZ,mBAAmBE;YACxC,MAAM,EAAEQ,MAAM,EAAEC,MAAM,EAAE,GAAG,MAAMd,MAChCe,YAAY,CAAC,EAAE,EACfA,aAAaC,KAAK,CAAC,IACnBR;YAED,OAAO;gBAAEM;gBAAQD;YAAO;QACzB;IACD,EAAE,OAAOI,OAAO;QACf,IAAIV,aAAa;YAChB,OAAO;gBACNW,UAAUD,MAAMC,QAAQ,KAAKC,YAAY,IAAIF,MAAMC,QAAQ;gBAC3DE,cAAcH,MAAMG,YAAY;gBAChCN,QAAQG,MAAMC,QAAQ,KAAKC,YAAY,IAAIF,MAAMC,QAAQ;YAC1D;QACD,OAAO;YACN,MAAM,IAAIG,MAAMnB,MAAMoB,GAAG,CAACL;QAC3B;IACD;AACD,EAAE"}
@@ -0,0 +1 @@
1
+ export declare const parseCommandString: (command: string) => any[];
@@ -0,0 +1,21 @@
1
+ const SPACES_REGEXP = / +/g;
2
+ export const parseCommandString = (command)=>{
3
+ const trimmedCommand = command.trim();
4
+ if (trimmedCommand === "") {
5
+ return [];
6
+ }
7
+ const tokens = [];
8
+ for (const token of trimmedCommand.split(SPACES_REGEXP)){
9
+ // Allow spaces to be escaped by a backslash if not meant as a delimiter
10
+ const previousToken = tokens.at(-1);
11
+ /* istanbul ignore next */ if (previousToken && previousToken.endsWith("\\")) {
12
+ // Merge previous token with current one
13
+ tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;
14
+ } else {
15
+ tokens.push(token);
16
+ }
17
+ }
18
+ return tokens;
19
+ };
20
+
21
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utilities.ts"],"sourcesContent":["const SPACES_REGEXP = / +/g;\n\nexport const parseCommandString = (command: string) => {\n\tconst trimmedCommand = command.trim();\n\tif (trimmedCommand === \"\") {\n\t\treturn [];\n\t}\n\tconst tokens = [];\n\tfor (const token of trimmedCommand.split(SPACES_REGEXP)) {\n\t\t// Allow spaces to be escaped by a backslash if not meant as a delimiter\n\t\tconst previousToken = tokens.at(-1);\n\t\t/* istanbul ignore next */\n\t\tif (previousToken && previousToken.endsWith(\"\\\\\")) {\n\t\t\t// Merge previous token with current one\n\t\t\ttokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;\n\t\t} else {\n\t\t\ttokens.push(token);\n\t\t}\n\t}\n\treturn tokens;\n};\n"],"names":["SPACES_REGEXP","parseCommandString","command","trimmedCommand","trim","tokens","token","split","previousToken","at","endsWith","length","slice","push"],"rangeMappings":";;;;;;;;;;;;;;;;;;","mappings":"AAAA,MAAMA,gBAAgB;AAEtB,OAAO,MAAMC,qBAAqB,CAACC;IAClC,MAAMC,iBAAiBD,QAAQE,IAAI;IACnC,IAAID,mBAAmB,IAAI;QAC1B,OAAO,EAAE;IACV;IACA,MAAME,SAAS,EAAE;IACjB,KAAK,MAAMC,SAASH,eAAeI,KAAK,CAACP,eAAgB;QACxD,wEAAwE;QACxE,MAAMQ,gBAAgBH,OAAOI,EAAE,CAAC,CAAC;QACjC,wBAAwB,GACxB,IAAID,iBAAiBA,cAAcE,QAAQ,CAAC,OAAO;YAClD,wCAAwC;YACxCL,MAAM,CAACA,OAAOM,MAAM,GAAG,EAAE,GAAG,CAAC,EAAEH,cAAcI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAEN,MAAM,CAAC;QACrE,OAAO;YACND,OAAOQ,IAAI,CAACP;QACb;IACD;IACA,OAAOD;AACR,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-cli/run",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "description": "A wrapper for child_process for nodejs CLI apps",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "node": ">=16",
14
14
  "dependencies": {
15
- "execa": "8.0.1",
15
+ "execa": "9.1.0",
16
16
  "kleur": "4.1.5"
17
17
  },
18
18
  "scripts": {
@@ -24,10 +24,12 @@
24
24
  "lint": "biome lint src",
25
25
  "test": "cross-env-shell NODE_OPTIONS=--experimental-vm-modules jest",
26
26
  "test:coverage": "npm run test -- --coverage",
27
+ "test:echo": "echo \"testing testing\"",
28
+ "test:watch": "npm run test -- --watch",
27
29
  "watch": "swc --strip-leading-paths --watch --out-dir dist src"
28
30
  },
29
31
  "publishConfig": {
30
32
  "access": "public"
31
33
  },
32
- "gitHead": "d7d79f8f71fb3225e5a214aba3a42a89470d8340"
34
+ "gitHead": "c7a00537e6b35ea13ccbab724b30f45ad0e6a822"
33
35
  }