xsh 1.1.8 → 1.1.9
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 +19 -7
- package/dist/mkcmd.d.ts +2 -2
- package/dist/mkcmd.js +3 -4
- package/docs/reference.md +317 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
[![NPM version][npm-image]][npm-url]
|
|
2
|
-
|
|
3
1
|
# xsh
|
|
4
2
|
|
|
3
|
+
[![License][license-image]][license-url]
|
|
4
|
+
[![build][build-image]][build-url]
|
|
5
|
+
|
|
6
|
+
[![Downloads][downloads-image]][downloads-url]
|
|
7
|
+
|
|
8
|
+
[![npm badge][npm-badge-png]][package-url]
|
|
9
|
+
|
|
10
|
+
- [API reference](https://fynjs.pages.dev/api/modules/xsh) - TypeDoc
|
|
11
|
+
- [Markdown reference](docs/reference.md) - the full API and runtime behavior in one file. Also ships in the package.
|
|
12
|
+
- [GitHub](https://github.com/jchip/fynjs/tree/main/packages/xsh)
|
|
13
|
+
|
|
5
14
|
Some random NodeJS helper functions for shell execution
|
|
6
15
|
|
|
7
16
|
## Install
|
|
@@ -175,8 +184,11 @@ xsh.popd();
|
|
|
175
184
|
`popd` `chdir`s back to the directory most recently saved by `pushd`, returning it. `popd`
|
|
176
185
|
throws if the stack is empty.
|
|
177
186
|
|
|
178
|
-
[
|
|
179
|
-
|
|
180
|
-
[
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
[license-image]: https://img.shields.io/npm/l/xsh.svg
|
|
188
|
+
[license-url]: LICENSE
|
|
189
|
+
[build-image]: https://github.com/jchip/fynjs/actions/workflows/ci.yml/badge.svg
|
|
190
|
+
[build-url]: https://github.com/jchip/fynjs/actions/workflows/ci.yml
|
|
191
|
+
[downloads-image]: https://img.shields.io/npm/dm/xsh.svg
|
|
192
|
+
[downloads-url]: https://npm-stat.com/charts.html?package=xsh
|
|
193
|
+
[npm-badge-png]: https://nodei.co/npm/xsh.png?downloads=true&stars=true
|
|
194
|
+
[package-url]: https://npmjs.com/package/xsh
|
package/dist/mkcmd.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Make a command string:
|
|
3
|
-
*
|
|
2
|
+
* Make a command string: every argument, string or array of strings,
|
|
3
|
+
* contributes its words in order, joined with `" "`.
|
|
4
4
|
*/
|
|
5
5
|
export declare function mkCmd(...args: Array<string | string[]>): string;
|
package/dist/mkcmd.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Make a command string:
|
|
3
|
-
*
|
|
2
|
+
* Make a command string: every argument, string or array of strings,
|
|
3
|
+
* contributes its words in order, joined with `" "`.
|
|
4
4
|
*/
|
|
5
5
|
export function mkCmd(...args) {
|
|
6
|
-
|
|
7
|
-
return Array.isArray(a) ? a.join(" ") : args.join(" ");
|
|
6
|
+
return args.flat().join(" ");
|
|
8
7
|
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# xsh reference
|
|
2
|
+
|
|
3
|
+
`xsh` is a small set of Node.js helpers for shell execution: `exec` (a wrapper over `child_process.exec`), environment variable readers, PATH editing, path scrubbing for CWD and `node_modules`, and a `pushd`/`popd` stack. It has no runtime dependencies. ESM only (`"type": "module"`).
|
|
4
|
+
|
|
5
|
+
## Imports
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import xsh from "xsh";
|
|
9
|
+
import { exec, env, envPath, mkCmd, pathCwd, pathCwdNm, pushd, popd } from "xsh";
|
|
10
|
+
import type { Xsh, ExecOutput, ExecError, ExecCallback, ExecOptions, ExecFragment, ExecArg, ExecResult, EnvContainer } from "xsh";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
CommonJS on a Node version that supports `require(esm)`: `const xsh = require("xsh")` returns the `xsh` object itself (the module has a `"module.exports"` export), not a namespace.
|
|
14
|
+
|
|
15
|
+
Runtime exports: `default` (the `xsh` object), `exec`, `env`, `envPath`, `mkCmd`, `pathCwd`, `pathCwdNm`, `pushd`, `popd`, `"module.exports"` (same object as `default`).
|
|
16
|
+
|
|
17
|
+
Type-only exports: `Xsh`, `ExecOutput`, `ExecError`, `ExecCallback`, `ExecOptions`, `ExecFragment`, `ExecArg`, `ExecResult`, `EnvContainer`.
|
|
18
|
+
|
|
19
|
+
Not exported from the package root: `findEnvKey` and `envKey` (only reachable as members of `envPath`), `escapeRegExp`, `util`, and the individual functions `get`, `getAsBool`, `getAsInt`, `addToFront`, `addToEnd`, `add` (only reachable as members of `env` and `envPath`). There is no `$` export and no synchronous exec. There are no subpaths (only `xsh/package.json`).
|
|
20
|
+
|
|
21
|
+
## `xsh` (default export)
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface Xsh {
|
|
25
|
+
exec: typeof exec;
|
|
26
|
+
env: typeof env;
|
|
27
|
+
envPath: typeof envPath;
|
|
28
|
+
mkCmd: typeof mkCmd;
|
|
29
|
+
pathCwd: typeof pathCwd;
|
|
30
|
+
pathCwdNm: typeof pathCwdNm;
|
|
31
|
+
pushd: typeof pushd;
|
|
32
|
+
popd: typeof popd;
|
|
33
|
+
/** Promise implementation used by exec */
|
|
34
|
+
Promise: PromiseConstructor;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A plain object holding the same functions as the named exports, plus a `Promise` accessor property.
|
|
39
|
+
|
|
40
|
+
- Reading `xsh.Promise` returns the constructor `exec` currently uses. The initial value is the global `Promise`.
|
|
41
|
+
- Assigning `xsh.Promise = P` makes later non-callback `exec` calls build their promise with `new P(...)`. Assigning a falsy value (`null`, `undefined`) restores the global `Promise`.
|
|
42
|
+
- The setting is process wide and read at call time, so it affects every later `exec` call without a callback.
|
|
43
|
+
- The assigned value is not checked. A non-constructor fails when `exec` is next called without a callback.
|
|
44
|
+
- `Promise` is defined on the default object only. The named exports have no `Promise`.
|
|
45
|
+
|
|
46
|
+
## `exec(...args)`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
function exec(...args: Array<ExecFragment | ExecOptions | boolean>): ExecResult;
|
|
50
|
+
function exec(...args: [...ExecArg[], ExecCallback]): ChildProcess;
|
|
51
|
+
|
|
52
|
+
type ExecFragment = string | string[];
|
|
53
|
+
type ExecArg = ExecFragment | ExecOptions | ExecCallback | boolean;
|
|
54
|
+
type ExecCallback = (err: ExecError | null, output: ExecOutput) => void;
|
|
55
|
+
|
|
56
|
+
interface ExecOutput { stdout: string; stderr: string }
|
|
57
|
+
interface ExecError extends Error { output: ExecOutput; code: number }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Runs a shell command with Node's `child_process.exec`. Execution is always asynchronous. There is no synchronous form. The two overloads are the callback form (returns the `ChildProcess`) and the thenable form (no callback, returns an `ExecResult`).
|
|
61
|
+
|
|
62
|
+
### Argument rules
|
|
63
|
+
|
|
64
|
+
Arguments are scanned in order. Each argument is one of:
|
|
65
|
+
|
|
66
|
+
| Argument | Meaning |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| string or array | A command fragment. |
|
|
69
|
+
| function | The callback. Must be the last argument. |
|
|
70
|
+
| `boolean` | Options shorthand for `{ silent: <boolean> }`. |
|
|
71
|
+
| non-null object | The options object. |
|
|
72
|
+
| anything else (`null`, `undefined`, number) | Throws `Error`. |
|
|
73
|
+
|
|
74
|
+
- The command is all fragments joined with a single `" "`. An array fragment is first joined with `" "`. No quoting or escaping is applied. The command string goes to the shell as is.
|
|
75
|
+
- With no options argument, options are `{ silent: false }`.
|
|
76
|
+
- The callback must be the last argument. Otherwise an `AssertionError` is thrown with message `xsh.exec: callback must be the last argument`.
|
|
77
|
+
- The options argument (object or boolean) must be the first, the last, or the second to last argument. Otherwise an `AssertionError` is thrown with message `xsh.exec: options must be the first, last, or second to last argument`. If several options arguments appear, the last one scanned wins.
|
|
78
|
+
- Zero fragments throws `Error("xsh.exec: expects at least one command fragment")`. This also happens for `exec({ cwd: "/tmp" })` or `exec(cb)`.
|
|
79
|
+
- An argument that is `null`, `undefined`, a number or another unsupported type throws `Error("xsh.exec: command fragment must be an array or string")`.
|
|
80
|
+
- These errors are thrown synchronously from `exec`, never as a rejected promise.
|
|
81
|
+
- Array elements are not type checked. `join` stringifies them.
|
|
82
|
+
|
|
83
|
+
### Options
|
|
84
|
+
|
|
85
|
+
`ExecOptions` is `{ silent?: boolean; env?: Record<string, string | undefined>; cwd?: string; [key: string]: unknown }`. Every key other than `silent`, `async` and `fatal` is passed to Node's `child_process.exec` as an option.
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Behavior |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `silent` | `boolean` | `false` | When false, the child's stdout and stderr are piped to `process.stdout` and `process.stderr` while the command runs. When true, nothing is echoed. Output is captured either way. Not passed to Node. |
|
|
90
|
+
| `cwd` | `string` | `process.cwd()` at call time | Working directory of the command. |
|
|
91
|
+
| `env` | `Record<string, string \| undefined>` | `process.env` | Environment of the command. Replaces the default entirely, it is not merged. |
|
|
92
|
+
| `maxBuffer` | `number` | `20 * 1024 * 1024` (20 MiB) | Max bytes buffered per stream. Node's own default is 1 MiB. |
|
|
93
|
+
| `encoding` | `string` | `"utf8"` | Output encoding. |
|
|
94
|
+
| `async`, `fatal` | any | none | Removed from the options and ignored. They are not passed to Node. |
|
|
95
|
+
| any other key | any | Node default | Passed to `child_process.exec` unchanged (for example `timeout`, `shell`, `killSignal`, `signal`, `uid`). |
|
|
96
|
+
|
|
97
|
+
Defaults are applied with `Object.assign`, so a user value overrides the default, including an explicit `undefined`. The type of `encoding` is not restricted by xsh. A non-`utf8` encoding changes what `stdout` and `stderr` are, because they are passed through, while the types still say `string`.
|
|
98
|
+
|
|
99
|
+
### Output capture
|
|
100
|
+
|
|
101
|
+
The `ExecOutput` object is `{ stdout, stderr }`, the strings (or buffers, see `encoding`) Node accumulated. Both streams are captured separately in full, up to `maxBuffer`. Output is captured whether or not `silent` is set. Output is delivered when the process closes. It is not streamed to the callback.
|
|
102
|
+
|
|
103
|
+
With `silent: false`, xsh pipes `child.stdout` and `child.stderr` to the parent streams.
|
|
104
|
+
|
|
105
|
+
### Result and errors
|
|
106
|
+
|
|
107
|
+
The callback form calls `cb(err, output)` once the command finishes.
|
|
108
|
+
|
|
109
|
+
- On exit code 0, `err` is `null`. Note `null`, not `undefined`.
|
|
110
|
+
- Otherwise `err` is a new `Error` with message `shell cmd '<cmd>' exit code <code>`, where `<cmd>` is the joined command string. It has two extra properties: `err.output` (the same `ExecOutput` passed to the callback) and `err.code`.
|
|
111
|
+
- `err.code` is taken from Node's error `code`. For a command that exits non-zero this is the numeric exit code. If Node supplies no code, it is `1`.
|
|
112
|
+
- Node's `code` is not always numeric. For a signal kill it is `null`, and for a `maxBuffer` overrun it is a string such as `"ERR_CHILD_PROCESS_STDIO_MAXBUFFER"`. xsh copies it as is (only `undefined` becomes `1`), so `err.code` can be a non-number despite the `number` type. `null` and strings still count as failure. The `exitCode === 0` check is strict.
|
|
113
|
+
- `output` is always passed, also on error, so failed commands keep their stdout and stderr.
|
|
114
|
+
- The original Node error (with `signal`, `killed`, `cmd`) is not attached. Only `output` and `code` are.
|
|
115
|
+
- A command that fails to spawn the shell is handled by the same path. There is no separate error type.
|
|
116
|
+
|
|
117
|
+
The callback form returns the `ChildProcess` from Node. The callback form does not use or require a Promise.
|
|
118
|
+
|
|
119
|
+
The thenable form returns an `ExecResult`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
interface ExecResult {
|
|
123
|
+
then: <T = ExecOutput, R = never>(
|
|
124
|
+
onFulfill?: ((output: ExecOutput) => T | PromiseLike<T>) | null,
|
|
125
|
+
onReject?: ((err: ExecError) => R | PromiseLike<R>) | null
|
|
126
|
+
) => Promise<T | R>;
|
|
127
|
+
catch: <R = never>(onReject?: ((err: ExecError) => R | PromiseLike<R>) | null) => Promise<ExecOutput | R>;
|
|
128
|
+
promise: Promise<ExecOutput>;
|
|
129
|
+
child: ChildProcess;
|
|
130
|
+
stdout: ChildProcess["stdout"];
|
|
131
|
+
stderr: ChildProcess["stderr"];
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `promise` resolves with `ExecOutput` on exit code 0 and rejects with the `ExecError` otherwise.
|
|
136
|
+
- `then(a, b)` and `catch(a)` forward to `promise.then` and `promise.catch`. The result object is a thenable, so `await exec(...)` yields the `ExecOutput`. There is no `finally` on the result object. Use `exec(...).promise.finally(...)`.
|
|
137
|
+
- `child`, `stdout` and `stderr` are the child process and its streams, set when `exec` returns. `stdout` and `stderr` are `child.stdout` and `child.stderr` captured at that moment.
|
|
138
|
+
- The promise is created with the constructor in `xsh.Promise`. If that setting were falsy at call time, `exec` throws an `AssertionError` (`xsh.exec: No Promise available - see doc on setting one`), but the setter prevents this by falling back to the global `Promise`.
|
|
139
|
+
- The command starts immediately when `exec` is called, not when `then` is called.
|
|
140
|
+
- A rejected `promise` with no handler attached causes the usual unhandled rejection. The result object does not attach a handler for you.
|
|
141
|
+
|
|
142
|
+
### Examples
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const { stdout } = await xsh.exec("echo", ["hello", "world"], true); // runs: echo hello world, silent
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await xsh.exec("exit 3", { silent: true });
|
|
149
|
+
} catch (err) {
|
|
150
|
+
err.code; // 3
|
|
151
|
+
err.output.stderr; // captured stderr
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
xsh.exec("pwd", { cwd: "/tmp" }, (err, { stdout }) => {
|
|
155
|
+
if (err) throw err;
|
|
156
|
+
console.log(stdout);
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## `mkCmd(...args)`
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
function mkCmd(...args: Array<string | string[]>): string
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Builds a command string. If the first argument is an array, only that array is used and it is joined with `" "`. Any further arguments are ignored. Otherwise all arguments are joined with `" "`.
|
|
167
|
+
|
|
168
|
+
- `mkCmd(["echo", "hello"])` and `mkCmd("echo", "hello")` both return `"echo hello"`.
|
|
169
|
+
- `mkCmd("a", ["b", "c"])` returns `"a b,c"`, because a later array is stringified with its default `toString`, not joined with a space. This differs from `exec`, which joins every array fragment with `" "`.
|
|
170
|
+
- `mkCmd()` returns `""`.
|
|
171
|
+
- No quoting or escaping.
|
|
172
|
+
|
|
173
|
+
## `env`
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const env: {
|
|
177
|
+
get(name: string, valWhenUndef?: string): string | undefined;
|
|
178
|
+
getAsBool(name: string, valWhenUndef?: unknown): boolean;
|
|
179
|
+
getAsInt(name: string, valWhenUndef?: unknown): number;
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Readers for `process.env`. They read `process.env` on each call and never write to it.
|
|
184
|
+
|
|
185
|
+
### `env.get(name, valWhenUndef?)`
|
|
186
|
+
|
|
187
|
+
Returns `process.env[name]`. If it is `undefined`, returns `valWhenUndef`. An empty string is a defined value and is returned as is.
|
|
188
|
+
|
|
189
|
+
### `env.getAsBool(name, valWhenUndef?)`
|
|
190
|
+
|
|
191
|
+
- Variable defined: returns `true` when the lower cased value is exactly `"true"`, `"1"`, `"yes"` or `"on"`. Any other value, including `""`, `"0"` and `"false"`, is `false`. There is no trimming.
|
|
192
|
+
- Variable undefined: returns `valWhenUndef` if it is a `boolean`, else `false`.
|
|
193
|
+
|
|
194
|
+
### `env.getAsInt(name, valWhenUndef?)`
|
|
195
|
+
|
|
196
|
+
- Variable defined: converts with unary `+`, so it accepts anything `Number` accepts. `"0x10"` gives `16`, `"1.5"` gives `1.5` (not truncated), `""` gives `0`, and `" 7 "` gives `7`. If the result is not `NaN`, it is returned.
|
|
197
|
+
- Variable undefined or not parseable: returns `valWhenUndef` if it is a `number`, else `NaN`. A `NaN` fallback is itself a number, so it is returned as `NaN` either way.
|
|
198
|
+
- Despite the name, the result is not forced to an integer.
|
|
199
|
+
|
|
200
|
+
## `envPath`
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
type EnvContainer = Record<string, string | undefined>;
|
|
204
|
+
|
|
205
|
+
const envPath: {
|
|
206
|
+
addToFront(p: string | null | undefined, env?: EnvContainer | string): string;
|
|
207
|
+
addToEnd(p?: string | null, env?: EnvContainer | string): string;
|
|
208
|
+
add(p?: string | null, env?: EnvContainer | string): string;
|
|
209
|
+
envKey: string;
|
|
210
|
+
findEnvKey(env: EnvContainer, key?: string): string | undefined;
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Edits a PATH style variable, joined with the platform delimiter (`path.delimiter`: `:` or `;`).
|
|
215
|
+
|
|
216
|
+
Shared rules for `addToFront`, `addToEnd` and `add`:
|
|
217
|
+
|
|
218
|
+
- `env` selects the target. With `undefined` (or any falsy value), the target is `process.env` and it is mutated. With an object, that object is mutated. With a string, a temporary object `{ [envKey]: <string> }` is used, the string itself is not changed, and only the return value carries the result.
|
|
219
|
+
- The variable edited is the one named by `envKey`, not by looking up a key on the custom `env`. `envKey` is fixed when the module loads.
|
|
220
|
+
- All three write the result back to `env[envKey]` and return it, even when nothing changed. When the variable was missing or empty and `p` is empty, they set it to `""` and return `""`.
|
|
221
|
+
- If `p` is not a non-empty string (`null`, `undefined`, `""`), the PATH is not rewritten. The current value is returned as is, untrimmed and with its duplicates and empty segments kept.
|
|
222
|
+
- Otherwise the current value is trimmed, split on the delimiter, and empty segments are dropped (so the written PATH has no empty segments, including no implicit CWD entry).
|
|
223
|
+
- Comparison is exact string equality. There is no path normalization, no case folding on Windows and no trailing slash handling.
|
|
224
|
+
- No filesystem checks. `p` does not need to exist.
|
|
225
|
+
|
|
226
|
+
### `envPath.addToFront(p, env?)`
|
|
227
|
+
|
|
228
|
+
Removes every existing occurrence of `p`, then puts `p` first. Returns the new PATH string.
|
|
229
|
+
|
|
230
|
+
### `envPath.addToEnd(p, env?)`
|
|
231
|
+
|
|
232
|
+
Removes every existing occurrence of `p`, then puts `p` last. Returns the new PATH string.
|
|
233
|
+
|
|
234
|
+
### `envPath.add(p, env?)`
|
|
235
|
+
|
|
236
|
+
Appends `p` only if it is not already present. If present, the order is kept and the rewritten PATH has empty segments removed. If `p` is empty, the value is unchanged. Returns the PATH string. The README text "If it already exists, then it is moved" applies to `addToFront` and `addToEnd` only.
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
envPath.addToFront("/opt/bin"); // mutates process.env.PATH
|
|
240
|
+
envPath.addToEnd("/opt/bin", "/usr/bin:/bin"); // returns "/usr/bin:/bin:/opt/bin"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### `envPath.envKey`
|
|
244
|
+
|
|
245
|
+
`string`. The name of the PATH variable in `process.env`, computed once at module load. It starts as `"Path"` on Windows and `"PATH"` elsewhere. If `process.env` does not have that exact key, the first key that matches `path` case insensitively is used instead. If none is found, the starting name is kept.
|
|
246
|
+
|
|
247
|
+
### `envPath.findEnvKey(env, key?)`
|
|
248
|
+
|
|
249
|
+
Returns `key` if `key` is truthy and is an own property of `env`. Otherwise returns the first key of `env` that equals `"path"` case insensitively. If there is no such key, returns `key` as given, which may be `undefined`. It never throws for a normal object.
|
|
250
|
+
|
|
251
|
+
## `pathCwd`
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const pathCwd: {
|
|
255
|
+
remove(p: string, flags?: string, stripSlash?: boolean): string;
|
|
256
|
+
replace(p: string, str?: string | null | false, flags?: string): string;
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Scrubs the current directory out of a string, such as a stack trace or output line. `process.cwd()` is read on each call. Matching is a literal match (regex metacharacters in the path are escaped). `flags` are `RegExp` flags. Without `"g"`, only the first match is changed.
|
|
261
|
+
|
|
262
|
+
### `pathCwd.remove(p, flags?, stripSlash?)`
|
|
263
|
+
|
|
264
|
+
- With `stripSlash` truthy, first removes `<cwd><sep>` (CWD plus the platform separator).
|
|
265
|
+
- Then, if `p` still contains CWD, removes CWD.
|
|
266
|
+
- Returns `p` as is if CWD is not present.
|
|
267
|
+
- The result keeps any following separator unless `stripSlash` removed it. For example `"/a/b/f.js"` with cwd `/a/b` gives `"/f.js"`, and with `stripSlash` gives `"f.js"`.
|
|
268
|
+
- When `stripSlash` is used without `"g"`, only the first `<cwd><sep>` is removed.
|
|
269
|
+
|
|
270
|
+
### `pathCwd.replace(p, str?, flags?)`
|
|
271
|
+
|
|
272
|
+
Replaces CWD in `p` with `str`. If `str` is not a string (`undefined`, `null`, `false`), `"CWD"` is used. The replacement string is passed to `String.replace`, so `$` patterns in `str` are interpreted.
|
|
273
|
+
|
|
274
|
+
## `pathCwdNm`
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const pathCwdNm: {
|
|
278
|
+
remove(p: string, flags?: string): string;
|
|
279
|
+
replace(p: string, str?: string | null | false, flags?: string): string;
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Like `pathCwd`, but also handles `<cwd>/node_modules` (`path.resolve("node_modules")`). Matching is literal and uses the same `flags` rules.
|
|
284
|
+
|
|
285
|
+
### `pathCwdNm.remove(p, flags?)`
|
|
286
|
+
|
|
287
|
+
Removes `<cwd>/node_modules` from `p`, then calls `pathCwd.remove(p, flags)` (without `stripSlash`) on the result.
|
|
288
|
+
|
|
289
|
+
### `pathCwdNm.replace(p, str?, flags?)`
|
|
290
|
+
|
|
291
|
+
1. Replaces `<cwd>/node_modules` with `str`. If `str` is not a string, the replacement is `path.join("CWD", "~")` (`CWD/~`, or `CWD\~` on Windows).
|
|
292
|
+
2. Replaces the remaining CWD with `"CWD"` (via `pathCwd.replace(p, null, flags)`).
|
|
293
|
+
3. Replaces `path.normalize("/node_modules/")` with `path.normalize("/~/")`, using the same `flags`.
|
|
294
|
+
|
|
295
|
+
Without `"g"`, each step changes only its first match. A custom `str` applies only to step 1. Steps 2 and 3 always use their fixed replacements.
|
|
296
|
+
|
|
297
|
+
## `pushd` and `popd`
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
function pushd(dir: string): string
|
|
301
|
+
function popd(): string
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A directory stack for `process.chdir`. The stack is module state, shared by the whole process.
|
|
305
|
+
|
|
306
|
+
- `pushd(dir)` pushes `process.cwd()` and calls `process.chdir(dir)`. It returns the new `process.cwd()`. If `chdir` throws (for example the directory does not exist), the error propagates, and the old directory has already been pushed, so the stack has an extra entry.
|
|
307
|
+
- `popd()` pops the most recent saved directory, calls `process.chdir` on it and returns it. If the stack is empty, throws an `AssertionError` with message `xsh.popd: directory stack is empty`.
|
|
308
|
+
- The stack is not reset by other `process.chdir` calls.
|
|
309
|
+
- `process.chdir` is not available in Node worker threads, where these calls throw.
|
|
310
|
+
|
|
311
|
+
## README mismatches
|
|
312
|
+
|
|
313
|
+
The README is context only. In these places the code is the authority.
|
|
314
|
+
|
|
315
|
+
- Callback `err` is `null` on success, not `undefined`. The README example `xsh.exec("echo hello", (r) => ...)` passes the first callback argument as the result, but it is `err`. The output is the second argument.
|
|
316
|
+
- The README documents only `Promise`, `mkCmd`, `exec`, `envPath` and `pushd`/`popd`. `env`, `pathCwd` and `pathCwdNm` are exported but not in the README.
|
|
317
|
+
- The README says Node `>= 22.18`. `package.json` `engines` says `^22.22.2 || ^24.15.0 || >=26.0.0`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xsh",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Some random NodeJS helper functions for shell execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"postpack": "publish-util-postpack"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist"
|
|
28
|
+
"dist",
|
|
29
|
+
"docs/reference.md"
|
|
29
30
|
],
|
|
30
31
|
"bugs": {
|
|
31
32
|
"url": "https://github.com/jchip/fynjs/issues"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"url": "git+https://github.com/jchip/fynjs.git",
|
|
36
37
|
"directory": "packages/xsh"
|
|
37
38
|
},
|
|
38
|
-
"homepage": "https://
|
|
39
|
+
"homepage": "https://fynjs.pages.dev/api/modules/xsh",
|
|
39
40
|
"keywords": [
|
|
40
41
|
"shell",
|
|
41
42
|
"exec",
|
|
@@ -47,9 +48,9 @@
|
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/node": "^26.4.1",
|
|
49
50
|
"@vitest/coverage-v8": "^5.0.0",
|
|
50
|
-
"aveazul": "^2.1.
|
|
51
|
-
"publish-util": "^3.1.
|
|
52
|
-
"run-verify": "^2.
|
|
51
|
+
"aveazul": "^2.1.9",
|
|
52
|
+
"publish-util": "^3.1.8",
|
|
53
|
+
"run-verify": "^2.2.0",
|
|
53
54
|
"typescript": "^7.0.2",
|
|
54
55
|
"vite": "^8.2.2",
|
|
55
56
|
"vitest": "^5.0.0"
|