first-base 2.0.1 → 3.0.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/.node-version CHANGED
@@ -1 +1 @@
1
- v24.13.0
1
+ v24.14.1
package/.npm-version CHANGED
@@ -1 +1 @@
1
- 11.6.2
1
+ 11.11.0
package/README.md CHANGED
@@ -183,3 +183,8 @@ The only things that changed between 1.x and 2.0 are:
183
183
  - Linux arm64 and x86_64
184
184
  - macOS arm64 and x86_64
185
185
  - Windows arm64 and x86_64
186
+
187
+ ## Upgrading from 2.x
188
+
189
+ - The type definitions for `RunContext.write` and `RunContext.kill` were narrowed slightly. The runtime behavior of those methods didn't change.
190
+ - `RunContext.debug()` is deprecated (but not removed). Use `spawn` option `debug: true` instead.
@@ -0,0 +1 @@
1
+ export declare function findRootDir(startingDir: string): string;
@@ -1,83 +1,51 @@
1
1
  "use strict";
2
-
3
- function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
4
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
- var fs = require("fs");
7
- var _require = require("nice-path"),
8
- Path = _require.Path;
9
- var strongRootDirIndicators = [".git", ".hg"];
10
- var weakRootDirIndicators = ["package-lock.json", ".gitignore", ".hgignore"];
11
- var veryWeakRootDirIndicators = ["package.json", "README.md"];
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findRootDir = findRootDir;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const nice_path_1 = require("nice-path");
9
+ const strongRootDirIndicators = [".git", ".hg"];
10
+ const weakRootDirIndicators = ["package-lock.json", ".gitignore", ".hgignore"];
11
+ const veryWeakRootDirIndicators = ["package.json", "README.md"];
12
12
  function hasFile(dir, filename) {
13
- var fullPath = dir.concat(filename).toString();
14
- try {
15
- return fs.existsSync(fullPath);
16
- } catch (err) {
17
- return false;
18
- }
13
+ const fullPath = dir.concat(filename).toString();
14
+ try {
15
+ return fs_1.default.existsSync(fullPath);
16
+ }
17
+ catch (err) {
18
+ return false;
19
+ }
19
20
  }
20
21
  function findRootDir(startingDir) {
21
- var start = new Path(startingDir).normalize();
22
- var searchDirs = [start];
23
- var currentPath = start.replaceLast([]);
24
- while (currentPath.segments.length > 0) {
25
- searchDirs.push(currentPath);
26
- currentPath = currentPath.replaceLast([]);
27
- }
28
- for (var _i = 0, _searchDirs = searchDirs; _i < _searchDirs.length; _i++) {
29
- var dir = _searchDirs[_i];
30
- var _iterator = _createForOfIteratorHelper(strongRootDirIndicators),
31
- _step;
32
- try {
33
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
34
- var indicator = _step.value;
35
- if (hasFile(dir, indicator)) {
36
- return dir.toString();
22
+ const start = new nice_path_1.Path(startingDir).normalize();
23
+ const searchDirs = [start];
24
+ let currentPath = start.replaceLast([]);
25
+ while (currentPath.segments.length > 0) {
26
+ searchDirs.push(currentPath);
27
+ currentPath = currentPath.replaceLast([]);
28
+ }
29
+ for (const dir of searchDirs) {
30
+ for (const indicator of strongRootDirIndicators) {
31
+ if (hasFile(dir, indicator)) {
32
+ return dir.toString();
33
+ }
37
34
  }
38
- }
39
- } catch (err) {
40
- _iterator.e(err);
41
- } finally {
42
- _iterator.f();
43
35
  }
44
- }
45
- for (var _i2 = 0, _searchDirs2 = searchDirs; _i2 < _searchDirs2.length; _i2++) {
46
- var _dir = _searchDirs2[_i2];
47
- var _iterator2 = _createForOfIteratorHelper(weakRootDirIndicators),
48
- _step2;
49
- try {
50
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
51
- var _indicator = _step2.value;
52
- if (hasFile(_dir, _indicator)) {
53
- return _dir.toString();
36
+ for (const dir of searchDirs) {
37
+ for (const indicator of weakRootDirIndicators) {
38
+ if (hasFile(dir, indicator)) {
39
+ return dir.toString();
40
+ }
54
41
  }
55
- }
56
- } catch (err) {
57
- _iterator2.e(err);
58
- } finally {
59
- _iterator2.f();
60
42
  }
61
- }
62
- for (var _i3 = 0, _searchDirs3 = searchDirs; _i3 < _searchDirs3.length; _i3++) {
63
- var _dir2 = _searchDirs3[_i3];
64
- var _iterator3 = _createForOfIteratorHelper(veryWeakRootDirIndicators),
65
- _step3;
66
- try {
67
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
68
- var _indicator2 = _step3.value;
69
- if (hasFile(_dir2, _indicator2)) {
70
- return _dir2.toString();
43
+ for (const dir of searchDirs) {
44
+ for (const indicator of veryWeakRootDirIndicators) {
45
+ if (hasFile(dir, indicator)) {
46
+ return dir.toString();
47
+ }
71
48
  }
72
- }
73
- } catch (err) {
74
- _iterator3.e(err);
75
- } finally {
76
- _iterator3.f();
77
49
  }
78
- }
79
- return start.toString();
50
+ return start.toString();
80
51
  }
81
- module.exports = {
82
- findRootDir: findRootDir
83
- };
package/dist/index.d.ts CHANGED
@@ -1,73 +1,44 @@
1
+ import { sanitizers } from "./sanitizers";
1
2
  export type Options = {
2
- cwd?: string;
3
- env?: { [varName: string]: any };
4
- argv0?: string;
5
- detached?: boolean;
6
- uid?: number;
7
- gid?: number;
8
- shell?: boolean | string;
9
- windowsVerbatimArguments?: boolean;
10
- windowsHide?: boolean;
11
- pty?: boolean;
3
+ cwd?: string;
4
+ env?: {
5
+ [varName: string]: string | undefined;
6
+ };
7
+ argv0?: string;
8
+ detached?: boolean;
9
+ uid?: number;
10
+ gid?: number;
11
+ shell?: boolean | string;
12
+ windowsVerbatimArguments?: boolean;
13
+ windowsHide?: boolean;
14
+ pty?: boolean;
15
+ debug?: boolean;
12
16
  };
13
-
14
17
  export type RunContext = {
15
- result: {
16
- stdout: string;
17
- stderr: string;
18
- code: null | number;
19
- error: null | Error;
20
- };
21
- /**
22
- * Same as {@link RunContext.result}, but with {@link sanitizers} run on
23
- * stdout/stderr.
24
- */
25
- cleanResult(): {
26
- stdout: string;
27
- stderr: string;
28
- code: null | number;
29
- error: null | Error;
30
- };
31
- completion: Promise<void>;
32
- debug(): RunContext;
33
- outputContains(value: string | RegExp): Promise<void>;
34
- clearOutputContainsBuffer(): void;
35
- // TODO: Should be string | Buffer, but idk how to use Buffer since they might not be using node types
36
- write(data: any): void;
37
- close(stream: "stdin" | "stdout" | "stderr"): void;
38
- kill(signal?: string): void;
18
+ result: {
19
+ stdout: string;
20
+ stderr: string;
21
+ code: null | number;
22
+ error: null | Error;
23
+ };
24
+ cleanResult(): {
25
+ stdout: string;
26
+ stderr: string;
27
+ code: null | number;
28
+ error: null | Error;
29
+ };
30
+ completion: Promise<void>;
31
+ /** @deprecated pass `debug: true` as an option to {@link spawn} instead. */
32
+ debug(): RunContext;
33
+ outputContains(value: string | RegExp): Promise<void>;
34
+ clearOutputContainsBuffer(): void;
35
+ write(data: string | Buffer): void;
36
+ close(stream: "stdin" | "stdout" | "stderr"): void;
37
+ kill(signal?: NodeJS.Signals): void;
39
38
  };
40
-
41
- export const spawn: ((cmd: string) => RunContext) &
42
- ((cmd: string, args: Array<string>) => RunContext) &
43
- ((cmd: string, options: Options) => RunContext) &
44
- ((cmd: string, args: Array<string>, options: Options) => RunContext);
45
-
46
- /**
47
- * An array of functions that will be run on stdout/stderr when calling
48
- * {@link RunContext.cleanResult}.
49
- *
50
- * By default, it contains 5 functions, which are run in order:
51
- *
52
- * - `stripAnsi`: Removes ANSI control characters
53
- * - `replaceRootDir`: Replaces eg `/home/suchipi/Code/first-base/src/index.js` with `<rootDir>/src/index.js`
54
- * - This function searches upwards for the root dir using a heuristic, and caches results in the {@link Map} `replaceRootDir.cache`.
55
- * - The heuristic is:
56
- * - Look upwards for a folder containing `.git` or `.hg`
57
- * - if none is found, look upwards for a folder containing `package-lock.json`, `.gitignore` or `.hgignore`,
58
- * - if none is found, look upwards for a folder containing `package.json` or `README.md`
59
- * - if none is found, consider the present working directory to be the root dir.
60
- * - `replaceCwd`: Replaces the current working directory with `<cwd>`
61
- * - `collapseStackTrace`: For Node.JS-style stack traces, replaces the long chain of "at ..." lines with a single "at somewhere" line
62
- * - `omitThrowLineNumber`: For Node.JS error source display, removes the line number
63
- *
64
- * You can remove them or replace them or add to them by mutating the `sanitizers` Array.
65
- */
66
- export const sanitizers: Array<(str: string) => string>;
67
-
68
- /**
69
- * All runs that have been spawned which haven't reached completion.
70
- *
71
- * It may be beneficial to clean these up in a test timeout handler or etc.
72
- */
73
- export const allInflightRunContexts: Set<RunContext>;
39
+ declare const allInflightRunContexts: Set<RunContext>;
40
+ declare function spawn(cmd: string): RunContext;
41
+ declare function spawn(cmd: string, args: Array<string>): RunContext;
42
+ declare function spawn(cmd: string, options: Options): RunContext;
43
+ declare function spawn(cmd: string, args: Array<string>, options: Options): RunContext;
44
+ export { spawn, sanitizers, allInflightRunContexts };
package/dist/index.js CHANGED
@@ -1,221 +1,289 @@
1
1
  "use strict";
2
-
3
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
- var normalSpawn = require("child_process").spawn;
5
- var stripAnsi = require("strip-ansi");
6
- var _require = require("./sanitizers"),
7
- sanitizers = _require.sanitizers;
8
- var allInflightRunContexts = new Set();
9
-
10
- // Run a child process and return a "run context" object
11
- // to interact with it. Function signature is the same as
12
- // child_process spawn, except you can pass `pty: true` in
13
- // options to run the process in a psuedo-tty.
14
- var spawn = function spawn(cmd, argsOrOptions, passedOptions) {
15
- var args;
16
- var options;
17
- if (Array.isArray(argsOrOptions)) {
18
- args = argsOrOptions;
19
- } else if (_typeof(argsOrOptions) === "object") {
20
- options = argsOrOptions;
21
- }
22
- if (passedOptions && !options) {
23
- options = passedOptions;
24
- }
25
- if (!args) {
26
- args = [];
27
- }
28
- if (!options) {
29
- options = {};
30
- }
31
- var child;
32
- var stdin;
33
- var stdout;
34
- var stderr;
35
- var unreffable;
36
- var running;
37
- var _debug = false;
38
- var outputContainsBuffer = "";
39
- var pendingOutputContainsRequests = new Set();
40
- var debugLog = function debugLog() {
41
- if (_debug) {
42
- var _console;
43
- (_console = console).log.apply(_console, arguments);
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.allInflightRunContexts = exports.sanitizers = void 0;
7
+ exports.spawn = spawn;
8
+ const child_process_1 = require("child_process");
9
+ const strip_ansi_1 = __importDefault(require("strip-ansi"));
10
+ const sanitizers_1 = require("./sanitizers");
11
+ Object.defineProperty(exports, "sanitizers", { enumerable: true, get: function () { return sanitizers_1.sanitizers; } });
12
+ const allInflightRunContexts = new Set();
13
+ exports.allInflightRunContexts = allInflightRunContexts;
14
+ function spawn(cmd, argsOrOptions, passedOptions) {
15
+ let args;
16
+ let options;
17
+ if (Array.isArray(argsOrOptions)) {
18
+ args = argsOrOptions;
44
19
  }
45
- };
46
- var runContext = {
47
- result: {
48
- // All of the stdout and stderr the process has written so far.
49
- stdout: "",
50
- stderr: "",
51
- // Exit status code, if the process has finished.
52
- code: null,
53
- // if the process errored out, this will be the Error
54
- error: null
55
- },
56
- // Return a version of result which has had the string sanitizers run on it
57
- cleanResult: function cleanResult() {
58
- return Object.assign({}, runContext.result, {
59
- stdout: sanitizers.reduce(function (str, transformFn) {
60
- return transformFn(str);
61
- }, runContext.result.stdout),
62
- stderr: sanitizers.reduce(function (str, transformFn) {
63
- return transformFn(str);
64
- }, runContext.result.stderr)
65
- });
66
- },
67
- // Promise that gets resolved when the child process completes.
68
- completion: null,
69
- debug: function debug() {
70
- _debug = true;
71
- return this;
72
- },
73
- // Returns a Promise that resolves once the child process output
74
- // (combined stdout and stderr) contains the passed string or
75
- // matches the passed RegExp. Ignores ansi control characters.
76
- outputContains: function outputContains(value) {
77
- debugLog("Waiting for output to contain ".concat(JSON.stringify(value), "..."));
78
- return new Promise(function (resolve, reject) {
79
- var request = {
80
- value: value
81
- };
82
- request.resolve = function () {
83
- pendingOutputContainsRequests["delete"](request);
84
- resolve();
85
- };
86
- request.reject = function () {
87
- pendingOutputContainsRequests["delete"](request);
88
- reject();
89
- };
90
- pendingOutputContainsRequests.add(request);
91
- });
92
- },
93
- clearOutputContainsBuffer: function clearOutputContainsBuffer() {
94
- outputContainsBuffer = "";
95
- },
96
- // Call this function to write into stdin.
97
- write: function write(data) {
98
- stdin.write(data);
99
- },
100
- // Call this function to close stdin, stdout, or stderr.
101
- close: function close(stream) {
102
- switch (String(stream).toLowerCase()) {
103
- case "stdin":
104
- {
105
- stdin.end();
106
- break;
107
- }
108
- case "stdout":
109
- {
110
- stdout.end();
111
- break;
112
- }
113
- case "stderr":
114
- {
115
- stderr.end();
116
- break;
117
- }
118
- default:
119
- {
120
- throw new Error("Invalid stream name: '".concat(stream, "'. Valid names are 'stdin', 'stdout', or 'stderr'."));
121
- }
122
- }
123
- },
124
- // Call this function to send a signal to the child process.
125
- // You can pass "SIGTERM", "SIGKILL", etc. Defaults to "SIGINT".
126
- kill: function kill() {
127
- var signal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "SIGINT";
128
- if (running) {
129
- child.kill(signal);
130
- }
131
- if (unreffable) {
132
- unreffable.unref();
133
- }
20
+ else if (typeof argsOrOptions === "object") {
21
+ options = argsOrOptions;
134
22
  }
135
- };
136
- if (options.pty) {
137
- var ptySpawn = require("@lydell/node-pty").spawn;
138
- child = ptySpawn(cmd, args, options);
139
- stdin = child;
140
- stdout = child;
141
- stderr = null; // no way to tell between stdout and stderr with pty
142
- unreffable = child.socket;
143
- } else {
144
- child = normalSpawn(cmd, args, options);
145
- stdin = child.stdin;
146
- stdout = child.stdout;
147
- stderr = child.stderr;
148
- unreffable = child;
149
- }
150
- running = true;
151
- allInflightRunContexts.add(runContext);
152
- var checkForPendingOutputRequestsToResolve = function checkForPendingOutputRequestsToResolve() {
153
- pendingOutputContainsRequests.forEach(function (request) {
154
- if (typeof request.value === "string") {
155
- if (stripAnsi(outputContainsBuffer).indexOf(request.value) != -1) {
156
- request.resolve();
157
- }
158
- } else if (request.value instanceof RegExp) {
159
- if (request.value.test(stripAnsi(outputContainsBuffer))) {
160
- request.resolve();
23
+ if (passedOptions && !options) {
24
+ options = passedOptions;
25
+ }
26
+ if (!args) {
27
+ args = [];
28
+ }
29
+ if (!options) {
30
+ options = {};
31
+ }
32
+ let child;
33
+ let stdin;
34
+ let stdout;
35
+ let stderr;
36
+ let unreffable = null;
37
+ let running;
38
+ let debug = options.debug ?? false;
39
+ let outputContainsBuffer = "";
40
+ let pendingOutputContainsRequests = new Set();
41
+ const disposables = [];
42
+ const debugLog = (...msg) => {
43
+ if (debug) {
44
+ console.log(...msg);
161
45
  }
162
- }
163
- });
164
- };
165
- stdout.setEncoding("utf-8");
166
- var handleStdoutData = function handleStdoutData(data) {
167
- runContext.result.stdout += data;
168
- outputContainsBuffer += data;
169
- debugLog("STDOUT: ".concat(data.toString()));
170
- checkForPendingOutputRequestsToResolve();
171
- };
172
- if (stdout.onData) {
173
- // the pty instance returned by node-pty
174
- // requires attaching handlers differently
175
- stdout.onData(handleStdoutData);
176
- } else {
177
- stdout.on("data", handleStdoutData);
178
- }
179
- if (stderr) {
180
- stderr.setEncoding("utf-8");
181
-
182
- // this is never a pty instance,
183
- // so we don't need to deal with onData here:
184
- stderr.on("data", function (data) {
185
- runContext.result.stderr += data;
186
- outputContainsBuffer += data;
187
- debugLog("STDERR: ".concat(data.toString()));
188
- checkForPendingOutputRequestsToResolve();
189
- });
190
- }
191
- runContext.completion = new Promise(function (resolve) {
192
- var finish = function finish(reason) {
193
- debugLog("Process ".concat(reason));
194
- debugLog(runContext.result);
195
- running = false;
196
- allInflightRunContexts["delete"](runContext);
197
- resolve();
198
- pendingOutputContainsRequests.forEach(function (request) {
199
- request.reject(new Error("Child process ".concat(reason, " before its output contained the requested content: ").concat(request.value)));
200
- });
201
46
  };
202
- child.once("exit", function (code) {
203
- runContext.result.code = code;
204
- finish("exited");
205
- });
206
- child.once("error", function (error) {
207
- if (_typeof(error) === "object" && error !== null && error.code === "EIO") {
208
- // not real; process is about to exit
209
- return;
210
- }
211
- runContext.result.error = error;
212
- finish("errored");
47
+ const runContext = {
48
+ result: {
49
+ // All of the stdout and stderr the process has written so far.
50
+ stdout: "",
51
+ stderr: "",
52
+ // Exit status code, if the process has finished.
53
+ code: null,
54
+ // if the process errored out, this will be the Error
55
+ error: null,
56
+ },
57
+ // Return a version of result which has had the string sanitizers run on it
58
+ cleanResult() {
59
+ return Object.assign({}, runContext.result, {
60
+ stdout: sanitizers_1.sanitizers.reduce((str, transformFn) => transformFn(str), runContext.result.stdout),
61
+ stderr: sanitizers_1.sanitizers.reduce((str, transformFn) => transformFn(str), runContext.result.stderr),
62
+ });
63
+ },
64
+ // Promise that gets resolved when the child process completes.
65
+ // Actual value gets filled in below.
66
+ completion: Promise.resolve(),
67
+ debug() {
68
+ debug = true;
69
+ return this;
70
+ },
71
+ // Returns a Promise that resolves once the child process output
72
+ // (combined stdout and stderr) contains the passed string or
73
+ // matches the passed RegExp. Ignores ansi control characters.
74
+ outputContains(value) {
75
+ debugLog(`Waiting for output to contain ${JSON.stringify(value)}...`);
76
+ return new Promise((resolve, reject) => {
77
+ const request = { value, resolve: undefined, reject: undefined };
78
+ request.resolve = () => {
79
+ pendingOutputContainsRequests.delete(request);
80
+ resolve();
81
+ };
82
+ request.reject = (error) => {
83
+ pendingOutputContainsRequests.delete(request);
84
+ reject(error);
85
+ };
86
+ pendingOutputContainsRequests.add(request);
87
+ });
88
+ },
89
+ clearOutputContainsBuffer() {
90
+ outputContainsBuffer = "";
91
+ },
92
+ // Call this function to write into stdin.
93
+ write(data) {
94
+ stdin.write(data);
95
+ },
96
+ // Call this function to close stdin, stdout, or stderr.
97
+ close(stream) {
98
+ switch (String(stream).toLowerCase()) {
99
+ case "stdin": {
100
+ if ("end" in stdin) {
101
+ stdin.end();
102
+ }
103
+ break;
104
+ }
105
+ case "stdout": {
106
+ if ("destroy" in stdout) {
107
+ stdout.destroy();
108
+ }
109
+ break;
110
+ }
111
+ case "stderr": {
112
+ if (stderr != null && "destroy" in stderr) {
113
+ stderr.destroy();
114
+ }
115
+ break;
116
+ }
117
+ default: {
118
+ throw new Error(`Invalid stream name: '${stream}'. Valid names are 'stdin', 'stdout', or 'stderr'.`);
119
+ }
120
+ }
121
+ },
122
+ // Call this function to send a signal to the child process.
123
+ // You can pass "SIGTERM", "SIGKILL", etc. Defaults to "SIGINT".
124
+ kill(signal = "SIGINT") {
125
+ if (running) {
126
+ child.kill(signal);
127
+ }
128
+ if (unreffable != null) {
129
+ unreffable.unref();
130
+ }
131
+ },
132
+ };
133
+ if (options.pty) {
134
+ debugLog("pty option was true; using node-pty");
135
+ const ptySpawn = require("@lydell/node-pty").spawn;
136
+ const ptyChild = ptySpawn(cmd, args, options);
137
+ child = ptyChild;
138
+ stdin = ptyChild;
139
+ stdout = ptyChild;
140
+ stderr = null; // no way to tell between stdout and stderr with pty
141
+ // no unreffable equivalent on ptyChild
142
+ }
143
+ else {
144
+ debugLog("pty option was NOT true; using child_process");
145
+ const nonPtyChild = (0, child_process_1.spawn)(cmd, args, options);
146
+ child = nonPtyChild;
147
+ stdin = nonPtyChild.stdin;
148
+ stdout = nonPtyChild.stdout;
149
+ stderr = nonPtyChild.stderr;
150
+ unreffable = nonPtyChild;
151
+ }
152
+ running = true;
153
+ allInflightRunContexts.add(runContext);
154
+ if ("on" in child) {
155
+ debugLog("using 'on' method to listen for child spawn event");
156
+ child.on("spawn", () => {
157
+ debugLog("'spawn' event");
158
+ });
159
+ }
160
+ else {
161
+ debugLog("child had no 'on' method, so child spawn event listener wasn't set up");
162
+ }
163
+ const checkForPendingOutputRequestsToResolve = () => {
164
+ pendingOutputContainsRequests.forEach((request) => {
165
+ if (typeof request.value === "string") {
166
+ if ((0, strip_ansi_1.default)(outputContainsBuffer).indexOf(request.value) != -1) {
167
+ request.resolve();
168
+ }
169
+ }
170
+ else if (request.value instanceof RegExp) {
171
+ if (request.value.test((0, strip_ansi_1.default)(outputContainsBuffer))) {
172
+ request.resolve();
173
+ }
174
+ }
175
+ });
176
+ };
177
+ if ("setEncoding" in stdout) {
178
+ debugLog("setting stdout encoding to utf-8");
179
+ stdout.setEncoding("utf-8");
180
+ }
181
+ else {
182
+ debugLog("not setting stdout encoding because the setEncoding method was not present");
183
+ }
184
+ const handleStdoutData = (data) => {
185
+ runContext.result.stdout += data;
186
+ outputContainsBuffer += data;
187
+ debugLog(`STDOUT: ${data.toString()}`);
188
+ checkForPendingOutputRequestsToResolve();
189
+ };
190
+ if ("onData" in stdout) {
191
+ debugLog("using 'onData' method to listen for stdout data event");
192
+ // the pty instance returned by node-pty
193
+ // requires attaching handlers differently
194
+ stdout.onData(handleStdoutData);
195
+ }
196
+ else {
197
+ debugLog("using 'on' method to listen for stdout data event");
198
+ stdout.on("data", handleStdoutData);
199
+ }
200
+ if (stderr) {
201
+ debugLog("setting stderr encoding to utf-8");
202
+ stderr.setEncoding("utf-8");
203
+ // this is never a pty instance,
204
+ // so we don't need to deal with onData here:
205
+ debugLog("using 'on' method to listen for stderr data event");
206
+ stderr.on("data", (data) => {
207
+ runContext.result.stderr += data;
208
+ outputContainsBuffer += data;
209
+ debugLog(`STDERR: ${data.toString()}`);
210
+ checkForPendingOutputRequestsToResolve();
211
+ });
212
+ }
213
+ else {
214
+ debugLog("stderr isn't present (pty mixes stdout and stderr together), so not setting encoding or setting up data event listener for stderr");
215
+ }
216
+ runContext.completion = new Promise((resolve) => {
217
+ let hasFinished = false;
218
+ const finish = (reason) => {
219
+ debugLog("in finish", runContext.result);
220
+ if (hasFinished) {
221
+ debugLog("finish called more than once; ignoring");
222
+ }
223
+ else {
224
+ running = false;
225
+ allInflightRunContexts.delete(runContext);
226
+ resolve();
227
+ for (const request of pendingOutputContainsRequests) {
228
+ request.reject(new Error(`Child process ${reason} before its output contained the requested content: ${request.value}`));
229
+ }
230
+ for (const disposable of disposables) {
231
+ disposable.dispose();
232
+ }
233
+ hasFinished = true;
234
+ }
235
+ };
236
+ if ("on" in child) {
237
+ debugLog("using 'on' method to listen for child close event");
238
+ child.on("close", (code, signal) => {
239
+ debugLog("'close' event", { code, signal });
240
+ if (code != null) {
241
+ runContext.result.code = code;
242
+ }
243
+ });
244
+ }
245
+ else {
246
+ debugLog("child had no 'on' method, so child close event listener wasn't set up");
247
+ }
248
+ if ("onExit" in child) {
249
+ debugLog("using 'onExit' method to listen for child exit event");
250
+ const disposable = child.onExit(({ exitCode, signal }) => {
251
+ debugLog("onExit", { exitCode, signal });
252
+ if (exitCode != null) {
253
+ runContext.result.code = exitCode;
254
+ }
255
+ finish("exited");
256
+ });
257
+ disposables.push(disposable);
258
+ }
259
+ else {
260
+ debugLog("using 'on' method to listen for child exit event");
261
+ child.on("exit", (code) => {
262
+ debugLog("'exit' event", { code });
263
+ if (code != null) {
264
+ runContext.result.code = code;
265
+ }
266
+ finish("exited");
267
+ });
268
+ }
269
+ if ("on" in child) {
270
+ debugLog("using 'on' method to listen for child error event");
271
+ child.on("error", (error) => {
272
+ debugLog("'error' event", { error });
273
+ if (typeof error === "object" &&
274
+ error !== null &&
275
+ error.code === "EIO") {
276
+ // not real; process is about to exit
277
+ debugLog("Ignoring spurious EIO error:", error);
278
+ return;
279
+ }
280
+ runContext.result.error = error;
281
+ finish("errored");
282
+ });
283
+ }
284
+ else {
285
+ debugLog("child had no 'on' method, so child error event listener wasn't set up");
286
+ }
213
287
  });
214
- });
215
- return runContext;
216
- };
217
- module.exports = {
218
- spawn: spawn,
219
- sanitizers: sanitizers,
220
- allInflightRunContexts: allInflightRunContexts
221
- };
288
+ return runContext;
289
+ }
@@ -1,7 +1,7 @@
1
1
  // @flow
2
2
  export type Options = {
3
3
  cwd?: ?string,
4
- env?: ?{ [varName: string]: any },
4
+ env?: ?{ [varName: string]: string | undefined },
5
5
  argv0?: ?string,
6
6
  detached?: ?boolean,
7
7
  uid?: ?number,
@@ -10,6 +10,7 @@ export type Options = {
10
10
  windowsVerbatimArguments?: ?boolean,
11
11
  windowsHide?: ?boolean,
12
12
  pty?: ?boolean,
13
+ debug?: ?boolean,
13
14
  };
14
15
 
15
16
  export type RunContext = {
@@ -0,0 +1 @@
1
+ export declare const sanitizers: Array<(str: string) => string>;
@@ -1,32 +1,39 @@
1
1
  "use strict";
2
-
3
- var stripAnsi = require("strip-ansi");
4
- var _require = require("./find-root-dir"),
5
- findRootDir = _require.findRootDir;
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sanitizers = void 0;
7
+ const strip_ansi_1 = __importDefault(require("strip-ansi"));
8
+ const find_root_dir_1 = require("./find-root-dir");
6
9
  function escapeRegex(str) {
7
- return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&");
10
+ return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&");
8
11
  }
9
- var sanitizers = [stripAnsi, Object.assign(function replaceRootDir(str) {
10
- var here = process.cwd();
11
- var rootDir = replaceRootDir.cache.get(here) || findRootDir(here);
12
- replaceRootDir.cache.set(here, rootDir);
13
- return str.replace(new RegExp(escapeRegex(rootDir), "g"), "<rootDir>");
12
+ const replaceRootDir = Object.assign(function _replaceRootDir(str) {
13
+ const here = process.cwd();
14
+ const rootDir = replaceRootDir.cache.get(here) || (0, find_root_dir_1.findRootDir)(here);
15
+ replaceRootDir.cache.set(here, rootDir);
16
+ return str.replace(new RegExp(escapeRegex(rootDir), "g"), "<rootDir>");
14
17
  }, {
15
- cache: new Map()
16
- }), function replaceCwd(str) {
17
- return str.replace(new RegExp(escapeRegex(process.cwd()), "g"), "<cwd>");
18
- }, function collapseStackTrace(str) {
19
- return str
20
- // replace stack trace lines with a single "at somewhere" line
21
- // explanation of regexp:
22
- // newline, optional ansi escape, zero or more whitespace(s), "at",
23
- // whitespace(s), several non-newline characters... and that whole
24
- // thing can happen more than once
25
- .replaceAll(/(?:\n(?:\x1B\[\d+m)?(\s*)at\s+[^\n]+)+/g, "\n$1at somewhere");
26
- }, function omitThrowLineNumber(str) {
27
- // omit line number from eg. "<rootDir>/dist/match.js:57\n\t\t\t\tthrow"
28
- return str.replaceAll(/(\.js):\d+(\s+throw)/g, "$1$2");
29
- }];
30
- module.exports = {
31
- sanitizers: sanitizers
32
- };
18
+ cache: new Map(),
19
+ });
20
+ exports.sanitizers = [
21
+ strip_ansi_1.default,
22
+ replaceRootDir,
23
+ function replaceCwd(str) {
24
+ return str.replace(new RegExp(escapeRegex(process.cwd()), "g"), "<cwd>");
25
+ },
26
+ function collapseStackTrace(str) {
27
+ return (str
28
+ // replace stack trace lines with a single "at somewhere" line
29
+ // explanation of regexp:
30
+ // newline, optional ansi escape, zero or more whitespace(s), "at",
31
+ // whitespace(s), several non-newline characters... and that whole
32
+ // thing can happen more than once
33
+ .replaceAll(/(?:\n(?:\x1B\[\d+m)?(\s*)at\s+[^\n]+)+/g, "\n$1at somewhere"));
34
+ },
35
+ function omitThrowLineNumber(str) {
36
+ // omit line number from eg. "<rootDir>/dist/match.js:57\n\t\t\t\tthrow"
37
+ return str.replaceAll(/(\.js):\d+(\s+throw)/g, "$1$2");
38
+ },
39
+ ];
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "first-base",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Integration testing for CLI applications",
5
5
  "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/suchipi/first-base.git"
@@ -15,22 +16,13 @@
15
16
  "strip-ansi": "^5.0.0"
16
17
  },
17
18
  "devDependencies": {
18
- "@babel/cli": "^7.28.6",
19
- "@babel/core": "^7.29.0",
20
- "@babel/preset-env": "^7.29.0",
21
- "babel-core": "^7.0.0-bridge.0",
22
- "babel-jest": "^30.2.0",
23
- "eslint": "^8.56.0",
24
- "eslint-config-unobtrusive": "^1.2.5",
25
- "eslint-plugin-import": "^2.29.1",
26
- "jest": "^30.2.0",
27
- "prettier": "^3.8.1"
19
+ "@types/node": "^22.0.0",
20
+ "prettier": "^3.8.1",
21
+ "typescript": "^6.0.2",
22
+ "vitest": "^4.1.2"
28
23
  },
29
24
  "scripts": {
30
- "build": "mkdir -p dist; rm -rf dist/*; babel src --out-dir dist && cp src/index.js.flow dist/ && cp src/index.d.ts dist/",
31
- "test": "jest"
32
- },
33
- "jest": {
34
- "prettierPath": null
25
+ "build": "mkdir -p dist; rm -rf dist/*; tsc && rm -f dist/*.test.js dist/*.test.d.ts && cp src/index.js.flow dist/",
26
+ "test": "vitest run"
35
27
  }
36
28
  }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "include": ["src/**/*.ts"],
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "module": "commonjs",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "esModuleInterop": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "types": ["node"],
14
+
15
+ // need this because @lydell/node-pty/package.json specifies "types" field
16
+ // but its "exports" field doesn't have "types".
17
+ //
18
+ // We can remove this after this is merged: https://github.com/lydell/node-pty/pull/15
19
+ "moduleResolution": "Node10",
20
+ "ignoreDeprecations": "6.0"
21
+ }
22
+ }