@vlandoss/clibuddy 0.4.1-git-e0772c7.0 → 0.5.1-git-87ebfca.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/dist/index.d.mts +27 -20
- package/dist/index.mjs +86 -50
- package/package.json +3 -3
- package/src/run.ts +3 -2
- package/src/shell/create.ts +3 -44
- package/src/shell/index.ts +1 -0
- package/src/shell/resolve-bin.ts +53 -0
- package/src/shell/shell.ts +51 -40
- package/src/shell/types.ts +10 -7
- package/src/shell/utils.ts +4 -13
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as _$ansis from "ansis";
|
|
2
2
|
import { hasTTY, isCI } from "std-env";
|
|
3
3
|
import { PackageJson } from "pkg-types";
|
|
4
|
-
import
|
|
5
|
-
import { Options, ProcessOutput, Shell as Shell$1 } from "zx";
|
|
4
|
+
import { NonZeroExitError, Output } from "tinyexec";
|
|
6
5
|
import { Project } from "@pnpm/types";
|
|
7
6
|
|
|
8
7
|
//#region src/colors.d.ts
|
|
@@ -48,35 +47,43 @@ declare function run(fn: () => Promise<void>, logger: {
|
|
|
48
47
|
}): Promise<void>;
|
|
49
48
|
//#endregion
|
|
50
49
|
//#region src/shell/types.d.ts
|
|
51
|
-
type
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
type ShellOptions = {
|
|
51
|
+
cwd?: string;
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
verbose?: boolean;
|
|
54
|
+
};
|
|
55
|
+
type RunOptions = ShellOptions & {
|
|
56
|
+
throwOnError?: boolean;
|
|
57
|
+
shell?: boolean;
|
|
58
|
+
stdin?: string;
|
|
59
|
+
display?: string;
|
|
55
60
|
};
|
|
56
61
|
//#endregion
|
|
57
62
|
//#region src/shell/shell.d.ts
|
|
58
63
|
declare class ShellService {
|
|
59
64
|
#private;
|
|
60
|
-
constructor(options
|
|
61
|
-
get options():
|
|
62
|
-
|
|
65
|
+
constructor(options?: ShellOptions);
|
|
66
|
+
get options(): ShellOptions;
|
|
67
|
+
at(cwd: string): ShellService;
|
|
63
68
|
child(options: ShellOptions): ShellService;
|
|
64
|
-
quiet(
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
quiet(): ShellService;
|
|
70
|
+
run(cmd: string, args?: string[], opts?: RunOptions): Promise<Output>;
|
|
71
|
+
runCaptured(cmd: string, args?: string[], opts?: RunOptions): Promise<Output>;
|
|
67
72
|
}
|
|
68
73
|
//#endregion
|
|
69
74
|
//#region src/shell/create.d.ts
|
|
70
75
|
declare const cwd: string;
|
|
71
|
-
declare function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
declare function createShellService(options?: ShellOptions): ShellService;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/shell/resolve-bin.d.ts
|
|
79
|
+
declare function resolveBinPath(pkg: string, options: {
|
|
80
|
+
from: string;
|
|
81
|
+
binPath?: string;
|
|
82
|
+
binName?: string;
|
|
83
|
+
}): string;
|
|
76
84
|
//#endregion
|
|
77
85
|
//#region src/shell/utils.d.ts
|
|
78
|
-
declare function
|
|
79
|
-
declare function getPreferLocal(localBaseBinPath: string | Array<string> | undefined): string[] | undefined;
|
|
86
|
+
declare function isNonZeroExitError(value: unknown): value is NonZeroExitError;
|
|
80
87
|
//#endregion
|
|
81
88
|
//#region src/text.d.ts
|
|
82
89
|
declare const text: {
|
|
@@ -84,4 +91,4 @@ declare const text: {
|
|
|
84
91
|
version: (version: string) => string;
|
|
85
92
|
};
|
|
86
93
|
//#endregion
|
|
87
|
-
export {
|
|
94
|
+
export { NonZeroExitError, Pkg, type Project, RunOptions, ShellOptions, ShellService, colorize, createPkg, createShellService, cwd, dirnameOf, filenameOf, hasTTY, isCI, isNonZeroExitError, palette, resolveBinPath, run, text };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import ansis, { bold, cyan, dim, green, italic, underline } from "ansis";
|
|
2
3
|
import { hasTTY, isCI } from "std-env";
|
|
3
4
|
import path, { dirname } from "node:path";
|
|
@@ -7,7 +8,7 @@ import { readFile } from "node:fs/promises";
|
|
|
7
8
|
import { findPackages } from "@pnpm/fs.find-packages";
|
|
8
9
|
import { readPackageJSON, resolvePackageJSON, writePackageJSON } from "pkg-types";
|
|
9
10
|
import { parse } from "yaml";
|
|
10
|
-
import {
|
|
11
|
+
import { NonZeroExitError, x } from "tinyexec";
|
|
11
12
|
//#region src/colors.ts
|
|
12
13
|
const colorize = (hex) => ansis.hex(hex);
|
|
13
14
|
const palette = {
|
|
@@ -101,12 +102,8 @@ async function createPkg(cwd) {
|
|
|
101
102
|
}
|
|
102
103
|
//#endregion
|
|
103
104
|
//#region src/shell/utils.ts
|
|
104
|
-
function
|
|
105
|
-
return value instanceof
|
|
106
|
-
}
|
|
107
|
-
const getLocalBinPath = (dirPath) => path.join(dirPath, "node_modules", ".bin");
|
|
108
|
-
function getPreferLocal(localBaseBinPath) {
|
|
109
|
-
return !localBaseBinPath ? void 0 : Array.isArray(localBaseBinPath) ? localBaseBinPath.map(getLocalBinPath) : [localBaseBinPath].map(getLocalBinPath);
|
|
105
|
+
function isNonZeroExitError(value) {
|
|
106
|
+
return value instanceof NonZeroExitError;
|
|
110
107
|
}
|
|
111
108
|
//#endregion
|
|
112
109
|
//#region src/run.ts
|
|
@@ -121,85 +118,124 @@ async function run(fn, logger) {
|
|
|
121
118
|
try {
|
|
122
119
|
await fn();
|
|
123
120
|
} catch (error) {
|
|
124
|
-
if (!
|
|
121
|
+
if (!isNonZeroExitError(error)) logger.error(formatError(error));
|
|
125
122
|
process.exit(1);
|
|
126
123
|
}
|
|
127
124
|
}
|
|
128
125
|
//#endregion
|
|
129
126
|
//#region src/shell/shell.ts
|
|
130
127
|
var ShellService = class ShellService {
|
|
131
|
-
#shell;
|
|
132
128
|
#options;
|
|
133
|
-
constructor(options) {
|
|
134
|
-
this.#options = Object.freeze(
|
|
135
|
-
|
|
129
|
+
constructor(options = {}) {
|
|
130
|
+
this.#options = Object.freeze({
|
|
131
|
+
cwd: options.cwd ?? process.cwd(),
|
|
132
|
+
env: options.env,
|
|
133
|
+
verbose: options.verbose ?? true
|
|
134
|
+
});
|
|
136
135
|
}
|
|
137
136
|
get options() {
|
|
138
137
|
return this.#options;
|
|
139
138
|
}
|
|
140
|
-
|
|
141
|
-
return this
|
|
139
|
+
at(cwd) {
|
|
140
|
+
return this.child({ cwd });
|
|
142
141
|
}
|
|
143
142
|
child(options) {
|
|
144
143
|
return new ShellService({
|
|
145
144
|
...this.#options,
|
|
146
|
-
...options
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
quiet(options) {
|
|
150
|
-
return this.child({
|
|
151
145
|
...options,
|
|
152
|
-
|
|
146
|
+
env: options.env ? {
|
|
147
|
+
...this.#options.env,
|
|
148
|
+
...options.env
|
|
149
|
+
} : this.#options.env
|
|
153
150
|
});
|
|
154
151
|
}
|
|
155
|
-
|
|
156
|
-
return this.
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
quiet() {
|
|
153
|
+
return this.child({ verbose: false });
|
|
154
|
+
}
|
|
155
|
+
async run(cmd, args = [], opts = {}) {
|
|
156
|
+
const merged = this.#mergeRunOpts(opts);
|
|
157
|
+
if (merged.verbose) printCmdLine(opts.display ?? cmd, args);
|
|
158
|
+
return x(cmd, args, {
|
|
159
|
+
throwOnError: opts.throwOnError ?? true,
|
|
160
|
+
...opts.stdin !== void 0 && { stdin: opts.stdin },
|
|
161
|
+
nodeOptions: {
|
|
162
|
+
cwd: merged.cwd,
|
|
163
|
+
...merged.env && { env: merged.env },
|
|
164
|
+
stdio: "inherit",
|
|
165
|
+
...opts.shell && { shell: true }
|
|
166
|
+
}
|
|
159
167
|
});
|
|
160
168
|
}
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
...
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
cwd,
|
|
172
|
-
preferLocal
|
|
169
|
+
async runCaptured(cmd, args = [], opts = {}) {
|
|
170
|
+
const merged = this.#mergeRunOpts(opts);
|
|
171
|
+
return x(cmd, args, {
|
|
172
|
+
throwOnError: opts.throwOnError ?? true,
|
|
173
|
+
...opts.stdin !== void 0 && { stdin: opts.stdin },
|
|
174
|
+
nodeOptions: {
|
|
175
|
+
cwd: merged.cwd,
|
|
176
|
+
...merged.env && { env: merged.env },
|
|
177
|
+
...opts.shell && { shell: true }
|
|
178
|
+
}
|
|
173
179
|
});
|
|
174
180
|
}
|
|
181
|
+
#mergeRunOpts(opts) {
|
|
182
|
+
return {
|
|
183
|
+
cwd: opts.cwd ?? this.#options.cwd ?? process.cwd(),
|
|
184
|
+
env: opts.env ? {
|
|
185
|
+
...this.#options.env,
|
|
186
|
+
...opts.env
|
|
187
|
+
} : this.#options.env,
|
|
188
|
+
verbose: opts.verbose ?? this.#options.verbose ?? true
|
|
189
|
+
};
|
|
190
|
+
}
|
|
175
191
|
};
|
|
192
|
+
function printCmdLine(cmd, args) {
|
|
193
|
+
const tail = args.length === 0 ? "" : ` ${args.join(" ")}`;
|
|
194
|
+
process.stderr.write(`${palette.muted("$")} ${palette.highlight(cmd)}${tail}\n`);
|
|
195
|
+
}
|
|
176
196
|
//#endregion
|
|
177
197
|
//#region src/shell/create.ts
|
|
178
198
|
const cwd = fs.realpathSync(process.cwd());
|
|
179
|
-
function quote(arg) {
|
|
180
|
-
if (/^[\w./:=@-]+$/i.test(arg) || arg === "") return arg;
|
|
181
|
-
return arg.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\f/g, "\\f").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\v/g, "\\v").replace(/\0/g, "\\0");
|
|
182
|
-
}
|
|
183
|
-
const isRaw = (arg) => typeof arg === "object" && arg !== null && "stdout" in arg && typeof arg.stdout === "string";
|
|
184
|
-
function defaultQuote(arg) {
|
|
185
|
-
if (typeof arg === "string") return quote(arg);
|
|
186
|
-
if (isRaw(arg)) return arg.stdout;
|
|
187
|
-
throw TypeError(`Unsupported argument type: ${typeof arg}`);
|
|
188
|
-
}
|
|
189
199
|
function createShellService(options = {}) {
|
|
190
200
|
return new ShellService({
|
|
191
|
-
verbose: true,
|
|
192
201
|
cwd,
|
|
193
|
-
preferLocal: getPreferLocal(options.localBaseBinPath),
|
|
194
|
-
quote: defaultQuote,
|
|
195
202
|
...options
|
|
196
203
|
});
|
|
197
204
|
}
|
|
198
205
|
//#endregion
|
|
206
|
+
//#region src/shell/resolve-bin.ts
|
|
207
|
+
function resolveBinPath(pkg, options) {
|
|
208
|
+
const pkgRoot = findPackageRoot(createRequire(options.from), pkg);
|
|
209
|
+
if (options.binPath) return path.join(pkgRoot, options.binPath);
|
|
210
|
+
const bin = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8")).bin;
|
|
211
|
+
if (!bin) throw new Error(`Package ${pkg} has no "bin" field`);
|
|
212
|
+
if (typeof bin === "string") return path.join(pkgRoot, bin);
|
|
213
|
+
const wantName = options.binName ?? pkg.replace(/^@[^/]+\//, "");
|
|
214
|
+
const rel = bin[wantName] ?? Object.values(bin)[0];
|
|
215
|
+
if (!rel) throw new Error(`No bin entry found for ${pkg} (asked for ${wantName})`);
|
|
216
|
+
return path.join(pkgRoot, rel);
|
|
217
|
+
}
|
|
218
|
+
function findPackageRoot(require, pkg) {
|
|
219
|
+
try {
|
|
220
|
+
return path.dirname(require.resolve(`${pkg}/package.json`));
|
|
221
|
+
} catch {}
|
|
222
|
+
const mainPath = require.resolve(pkg);
|
|
223
|
+
let dir = path.dirname(mainPath);
|
|
224
|
+
const fsRoot = path.parse(dir).root;
|
|
225
|
+
while (dir !== fsRoot) {
|
|
226
|
+
const pkgJsonPath = path.join(dir, "package.json");
|
|
227
|
+
if (fs.existsSync(pkgJsonPath)) try {
|
|
228
|
+
if (JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")).name === pkg) return dir;
|
|
229
|
+
} catch {}
|
|
230
|
+
dir = path.dirname(dir);
|
|
231
|
+
}
|
|
232
|
+
throw new Error(`Could not find package root for ${pkg} from ${mainPath}`);
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
199
235
|
//#region src/text.ts
|
|
200
236
|
const text = {
|
|
201
237
|
vland: `${palette.link(palette.primary("https://variable.land"))} 👊`,
|
|
202
238
|
version: (version) => palette.muted(`v${version}`)
|
|
203
239
|
};
|
|
204
240
|
//#endregion
|
|
205
|
-
export { Pkg, ShellService, colorize, createPkg, createShellService, cwd, dirnameOf, filenameOf,
|
|
241
|
+
export { NonZeroExitError, Pkg, ShellService, colorize, createPkg, createShellService, cwd, dirnameOf, filenameOf, hasTTY, isCI, isNonZeroExitError, palette, resolveBinPath, run, text };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vlandoss/clibuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1-git-87ebfca.0",
|
|
4
4
|
"description": "A helper library to create CLIs in Variable Land",
|
|
5
5
|
"homepage": "https://github.com/variableland/dx/tree/main/packages/clibuddy#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"ansis": "4.2.0",
|
|
33
33
|
"pkg-types": "2.3.0",
|
|
34
34
|
"std-env": "3.9.0",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
35
|
+
"tinyexec": "1.1.2",
|
|
36
|
+
"yaml": "2.8.4"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
package/src/run.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNonZeroExitError } from "./shell/utils.ts";
|
|
2
2
|
|
|
3
3
|
function hasMessage(error: unknown): error is { message: string } {
|
|
4
4
|
return (
|
|
@@ -18,7 +18,8 @@ export async function run(fn: () => Promise<void>, logger: { error: (...args: un
|
|
|
18
18
|
try {
|
|
19
19
|
await fn();
|
|
20
20
|
} catch (error) {
|
|
21
|
-
|
|
21
|
+
// The subprocess already streamed its own stderr; don't double-print.
|
|
22
|
+
if (!isNonZeroExitError(error)) {
|
|
22
23
|
logger.error(formatError(error));
|
|
23
24
|
}
|
|
24
25
|
process.exit(1);
|
package/src/shell/create.ts
CHANGED
|
@@ -1,50 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { ShellService } from "./shell.ts";
|
|
3
|
-
import type {
|
|
4
|
-
import { getPreferLocal } from "./utils.ts";
|
|
3
|
+
import type { ShellOptions } from "./types.ts";
|
|
5
4
|
|
|
6
5
|
export const cwd = fs.realpathSync(process.cwd());
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (/^[\w./:=@-]+$/i.test(arg) || arg === "") {
|
|
11
|
-
return arg;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return arg
|
|
15
|
-
.replace(/\\/g, "\\\\")
|
|
16
|
-
.replace(/'/g, "\\'")
|
|
17
|
-
.replace(/\f/g, "\\f")
|
|
18
|
-
.replace(/\n/g, "\\n")
|
|
19
|
-
.replace(/\r/g, "\\r")
|
|
20
|
-
.replace(/\t/g, "\\t")
|
|
21
|
-
.replace(/\v/g, "\\v")
|
|
22
|
-
.replace(/\0/g, "\\0");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const isRaw = (arg: unknown): arg is { stdout: string } =>
|
|
26
|
-
typeof arg === "object" && arg !== null && "stdout" in arg && typeof arg.stdout === "string";
|
|
27
|
-
|
|
28
|
-
function defaultQuote(arg: unknown) {
|
|
29
|
-
if (typeof arg === "string") {
|
|
30
|
-
return quote(arg);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (isRaw(arg)) {
|
|
34
|
-
return arg.stdout;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
throw TypeError(`Unsupported argument type: ${typeof arg}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function createShellService(options: CreateOptions = {}) {
|
|
41
|
-
const preferLocal = getPreferLocal(options.localBaseBinPath);
|
|
42
|
-
|
|
43
|
-
return new ShellService({
|
|
44
|
-
verbose: true,
|
|
45
|
-
cwd,
|
|
46
|
-
preferLocal,
|
|
47
|
-
quote: defaultQuote,
|
|
48
|
-
...options,
|
|
49
|
-
});
|
|
7
|
+
export function createShellService(options: ShellOptions = {}): ShellService {
|
|
8
|
+
return new ShellService({ cwd, ...options });
|
|
50
9
|
}
|
package/src/shell/index.ts
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export function resolveBinPath(pkg: string, options: { from: string; binPath?: string; binName?: string }): string {
|
|
6
|
+
const require = createRequire(options.from);
|
|
7
|
+
const pkgRoot = findPackageRoot(require, pkg);
|
|
8
|
+
|
|
9
|
+
if (options.binPath) {
|
|
10
|
+
return path.join(pkgRoot, options.binPath);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8")) as {
|
|
14
|
+
bin?: string | Record<string, string>;
|
|
15
|
+
};
|
|
16
|
+
const bin = pkgJson.bin;
|
|
17
|
+
if (!bin) throw new Error(`Package ${pkg} has no "bin" field`);
|
|
18
|
+
|
|
19
|
+
if (typeof bin === "string") return path.join(pkgRoot, bin);
|
|
20
|
+
|
|
21
|
+
const wantName = options.binName ?? pkg.replace(/^@[^/]+\//, "");
|
|
22
|
+
const rel = bin[wantName] ?? Object.values(bin)[0];
|
|
23
|
+
if (!rel) throw new Error(`No bin entry found for ${pkg} (asked for ${wantName})`);
|
|
24
|
+
return path.join(pkgRoot, rel);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Two-step lookup tolerates packages that don't expose `./package.json` in
|
|
28
|
+
// their `exports` map (e.g. oxlint) and packages with no `main`/`exports` at
|
|
29
|
+
// all (e.g. @biomejs/biome) — `require.resolve(pkg)` fails for the latter.
|
|
30
|
+
function findPackageRoot(require: NodeJS.Require, pkg: string): string {
|
|
31
|
+
try {
|
|
32
|
+
return path.dirname(require.resolve(`${pkg}/package.json`));
|
|
33
|
+
} catch {
|
|
34
|
+
// fall through to manual walk
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const mainPath = require.resolve(pkg);
|
|
38
|
+
let dir = path.dirname(mainPath);
|
|
39
|
+
const fsRoot = path.parse(dir).root;
|
|
40
|
+
while (dir !== fsRoot) {
|
|
41
|
+
const pkgJsonPath = path.join(dir, "package.json");
|
|
42
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
43
|
+
try {
|
|
44
|
+
const data = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")) as { name?: string };
|
|
45
|
+
if (data.name === pkg) return dir;
|
|
46
|
+
} catch {
|
|
47
|
+
// not a valid package.json — keep walking
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
dir = path.dirname(dir);
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Could not find package root for ${pkg} from ${mainPath}`);
|
|
53
|
+
}
|
package/src/shell/shell.ts
CHANGED
|
@@ -1,65 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { type Output, x as tinyexec } from "tinyexec";
|
|
2
|
+
import { palette } from "../colors.ts";
|
|
3
|
+
import type { RunOptions, ShellOptions } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
export class ShellService {
|
|
6
|
-
#shell: Shell;
|
|
7
6
|
#options: ShellOptions;
|
|
8
7
|
|
|
9
|
-
constructor(options: ShellOptions) {
|
|
10
|
-
this.#options = Object.freeze(
|
|
11
|
-
|
|
8
|
+
constructor(options: ShellOptions = {}) {
|
|
9
|
+
this.#options = Object.freeze({
|
|
10
|
+
cwd: options.cwd ?? process.cwd(),
|
|
11
|
+
env: options.env,
|
|
12
|
+
verbose: options.verbose ?? true,
|
|
13
|
+
});
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
get options() {
|
|
16
|
+
get options(): ShellOptions {
|
|
15
17
|
return this.#options;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
return this
|
|
20
|
+
at(cwd: string): ShellService {
|
|
21
|
+
return this.child({ cwd });
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
child(options: ShellOptions) {
|
|
24
|
+
child(options: ShellOptions): ShellService {
|
|
23
25
|
return new ShellService({
|
|
24
26
|
...this.#options,
|
|
25
27
|
...options,
|
|
28
|
+
env: options.env ? { ...this.#options.env, ...options.env } : this.#options.env,
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
quiet(
|
|
30
|
-
return this.child({
|
|
31
|
-
...options,
|
|
32
|
-
verbose: false,
|
|
33
|
-
});
|
|
32
|
+
quiet(): ShellService {
|
|
33
|
+
return this.child({ verbose: false });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
async run(cmd: string, args: string[] = [], opts: RunOptions = {}): Promise<Output> {
|
|
37
|
+
const merged = this.#mergeRunOpts(opts);
|
|
38
|
+
if (merged.verbose) printCmdLine(opts.display ?? cmd, args);
|
|
39
|
+
return tinyexec(cmd, args, {
|
|
40
|
+
throwOnError: opts.throwOnError ?? true,
|
|
41
|
+
...(opts.stdin !== undefined && { stdin: opts.stdin }),
|
|
42
|
+
nodeOptions: {
|
|
43
|
+
cwd: merged.cwd,
|
|
44
|
+
...(merged.env && { env: merged.env }),
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
...(opts.shell && { shell: true }),
|
|
47
|
+
},
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: [
|
|
54
|
-
...getLocals(this.#options.preferLocal),
|
|
55
|
-
...getLocals(options?.preferLocal),
|
|
56
|
-
...(cwdPreferLocal ? cwdPreferLocal : []),
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
return this.child({
|
|
60
|
-
...options,
|
|
61
|
-
cwd,
|
|
62
|
-
preferLocal,
|
|
51
|
+
async runCaptured(cmd: string, args: string[] = [], opts: RunOptions = {}): Promise<Output> {
|
|
52
|
+
const merged = this.#mergeRunOpts(opts);
|
|
53
|
+
return tinyexec(cmd, args, {
|
|
54
|
+
throwOnError: opts.throwOnError ?? true,
|
|
55
|
+
...(opts.stdin !== undefined && { stdin: opts.stdin }),
|
|
56
|
+
nodeOptions: {
|
|
57
|
+
cwd: merged.cwd,
|
|
58
|
+
...(merged.env && { env: merged.env }),
|
|
59
|
+
...(opts.shell && { shell: true }),
|
|
60
|
+
},
|
|
63
61
|
});
|
|
64
62
|
}
|
|
63
|
+
|
|
64
|
+
#mergeRunOpts(opts: RunOptions) {
|
|
65
|
+
return {
|
|
66
|
+
cwd: opts.cwd ?? this.#options.cwd ?? process.cwd(),
|
|
67
|
+
env: opts.env ? { ...this.#options.env, ...opts.env } : this.#options.env,
|
|
68
|
+
verbose: opts.verbose ?? this.#options.verbose ?? true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printCmdLine(cmd: string, args: string[]): void {
|
|
74
|
+
const tail = args.length === 0 ? "" : ` ${args.join(" ")}`;
|
|
75
|
+
process.stderr.write(`${palette.muted("$")} ${palette.highlight(cmd)}${tail}\n`);
|
|
65
76
|
}
|
package/src/shell/types.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export type ShellOptions = {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
env?: NodeJS.ProcessEnv;
|
|
4
|
+
verbose?: boolean;
|
|
5
|
+
};
|
|
6
6
|
|
|
7
|
-
export type
|
|
8
|
-
|
|
7
|
+
export type RunOptions = ShellOptions & {
|
|
8
|
+
throwOnError?: boolean;
|
|
9
|
+
shell?: boolean;
|
|
10
|
+
stdin?: string;
|
|
11
|
+
display?: string;
|
|
9
12
|
};
|
package/src/shell/utils.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ProcessOutput } from "zx";
|
|
1
|
+
import { NonZeroExitError } from "tinyexec";
|
|
3
2
|
|
|
4
|
-
export
|
|
5
|
-
return value instanceof ProcessOutput;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const getLocalBinPath = (dirPath: string) => path.join(dirPath, "node_modules", ".bin");
|
|
3
|
+
export { NonZeroExitError };
|
|
9
4
|
|
|
10
|
-
export function
|
|
11
|
-
return
|
|
12
|
-
? undefined
|
|
13
|
-
: Array.isArray(localBaseBinPath)
|
|
14
|
-
? localBaseBinPath.map(getLocalBinPath)
|
|
15
|
-
: [localBaseBinPath].map(getLocalBinPath);
|
|
5
|
+
export function isNonZeroExitError(value: unknown): value is NonZeroExitError {
|
|
6
|
+
return value instanceof NonZeroExitError;
|
|
16
7
|
}
|