@react-native-harness/tools 1.0.0 → 1.1.0-rc.2

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.
Files changed (41) hide show
  1. package/dist/__tests__/abort.test.d.ts +2 -0
  2. package/dist/__tests__/abort.test.d.ts.map +1 -0
  3. package/dist/__tests__/abort.test.js +49 -0
  4. package/dist/__tests__/crash-artifacts.test.d.ts +2 -0
  5. package/dist/__tests__/crash-artifacts.test.d.ts.map +1 -0
  6. package/dist/__tests__/crash-artifacts.test.js +77 -0
  7. package/dist/abort.d.ts +2 -0
  8. package/dist/abort.d.ts.map +1 -1
  9. package/dist/abort.js +10 -3
  10. package/dist/crash-artifacts.d.ts +20 -0
  11. package/dist/crash-artifacts.d.ts.map +1 -0
  12. package/dist/crash-artifacts.js +57 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/logger.d.ts +12 -7
  17. package/dist/logger.d.ts.map +1 -1
  18. package/dist/logger.js +50 -43
  19. package/dist/regex.d.ts +2 -0
  20. package/dist/regex.d.ts.map +1 -0
  21. package/dist/regex.js +1 -0
  22. package/dist/spawn.d.ts.map +1 -1
  23. package/dist/spawn.js +35 -16
  24. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  25. package/eslint.config.mjs +4 -1
  26. package/package.json +1 -2
  27. package/src/__tests__/abort.test.ts +71 -0
  28. package/src/__tests__/crash-artifacts.test.ts +94 -0
  29. package/src/abort.ts +15 -3
  30. package/src/crash-artifacts.ts +140 -0
  31. package/src/index.ts +3 -1
  32. package/src/logger.ts +73 -45
  33. package/src/regex.ts +2 -0
  34. package/src/spawn.ts +43 -17
  35. package/tsconfig.lib.json +2 -1
  36. package/dist/runtime.d.ts +0 -10
  37. package/dist/runtime.d.ts.map +0 -1
  38. package/dist/runtime.js +0 -10
  39. package/dist/timeout.d.ts +0 -2
  40. package/dist/timeout.d.ts.map +0 -1
  41. package/dist/timeout.js +0 -16
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=abort.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/abort.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { getTimeoutSignal, raceAbortSignals, withAbortTimeout } from '../abort.js';
3
+ const createAbortError = () => new DOMException('The operation was aborted', 'AbortError');
4
+ const waitForAbort = (signal) => {
5
+ if (signal.aborted) {
6
+ return Promise.resolve(signal.reason);
7
+ }
8
+ return new Promise((resolve) => {
9
+ signal.addEventListener('abort', () => {
10
+ resolve(signal.reason);
11
+ }, { once: true });
12
+ });
13
+ };
14
+ afterEach(() => {
15
+ vi.useRealTimers();
16
+ });
17
+ describe('abort helpers', () => {
18
+ it('aborts timeout signals after the configured duration', async () => {
19
+ vi.useFakeTimers();
20
+ const signal = getTimeoutSignal(1_000);
21
+ const abortPromise = waitForAbort(signal);
22
+ await vi.advanceTimersByTimeAsync(1_000);
23
+ await expect(abortPromise).resolves.toBeInstanceOf(DOMException);
24
+ expect(signal.aborted).toBe(true);
25
+ expect(signal.reason).toBeInstanceOf(DOMException);
26
+ });
27
+ it('races abort signals and preserves the first abort reason', async () => {
28
+ const first = new AbortController();
29
+ const second = new AbortController();
30
+ const signal = raceAbortSignals([first.signal, second.signal]);
31
+ const abortPromise = waitForAbort(signal);
32
+ const secondReason = new Error('second');
33
+ second.abort(secondReason);
34
+ first.abort(new Error('first'));
35
+ await expect(abortPromise).resolves.toBe(secondReason);
36
+ });
37
+ it('combines parent cancellation and timeout behavior', async () => {
38
+ vi.useFakeTimers();
39
+ const controller = new AbortController();
40
+ const signal = withAbortTimeout(controller.signal, 1_000);
41
+ const abortPromise = waitForAbort(signal);
42
+ controller.abort(createAbortError());
43
+ await expect(abortPromise).resolves.toBeInstanceOf(DOMException);
44
+ const timedSignal = withAbortTimeout(new AbortController().signal, 1_000);
45
+ const timedAbortPromise = waitForAbort(timedSignal);
46
+ await vi.advanceTimersByTimeAsync(1_000);
47
+ await expect(timedAbortPromise).resolves.toBeInstanceOf(DOMException);
48
+ });
49
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crash-artifacts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-artifacts.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crash-artifacts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { createCrashArtifactWriter } from '../crash-artifacts.js';
6
+ describe('createCrashArtifactWriter', () => {
7
+ const rootDir = fs.mkdtempSync(path.join(tmpdir(), 'rn-harness-crash-artifacts-'));
8
+ afterEach(() => {
9
+ fs.rmSync(rootDir, { recursive: true, force: true });
10
+ fs.mkdirSync(rootDir, { recursive: true });
11
+ });
12
+ it('uses a shared run timestamp and preserves useful file extensions', () => {
13
+ const sourcePath = path.join(rootDir, 'Harness Playground 01.crash');
14
+ fs.writeFileSync(sourcePath, 'crash data', 'utf8');
15
+ const writer = createCrashArtifactWriter({
16
+ runnerName: 'ios simulator',
17
+ platformId: 'ios',
18
+ rootDir,
19
+ runTimestamp: '2026-03-12T11-35-08-000Z',
20
+ });
21
+ const persistedPath = writer.persistArtifact({
22
+ artifactKind: 'ios-crash-report',
23
+ source: {
24
+ kind: 'file',
25
+ path: sourcePath,
26
+ },
27
+ });
28
+ expect(path.basename(persistedPath)).toBe('2026-03-12T11-35-08-000Z--ios-simulator--ios--ios-crash-report--Harness-Playground-01.crash');
29
+ expect(fs.readFileSync(persistedPath, 'utf8')).toBe('crash data');
30
+ expect(writer.runTimestamp).toBe('2026-03-12T11-35-08-000Z');
31
+ });
32
+ it('creates the artifact directory lazily and writes text artifacts', () => {
33
+ const artifactRoot = path.join(rootDir, '.harness', 'crash-reports');
34
+ const writer = createCrashArtifactWriter({
35
+ runnerName: 'android',
36
+ platformId: 'android',
37
+ rootDir: artifactRoot,
38
+ runTimestamp: '2026-03-12T11-35-08-000Z',
39
+ });
40
+ const persistedPath = writer.persistArtifact({
41
+ artifactKind: 'logcat',
42
+ source: {
43
+ kind: 'text',
44
+ fileName: 'logcat.txt',
45
+ text: '--------- beginning of crash\nRuntimeException: boom\n',
46
+ },
47
+ });
48
+ expect(fs.existsSync(artifactRoot)).toBe(true);
49
+ expect(fs.readFileSync(persistedPath, 'utf8')).toContain('RuntimeException');
50
+ });
51
+ it('deduplicates repeated persistence requests within one run', () => {
52
+ const sourcePath = path.join(rootDir, 'duplicate.crash');
53
+ fs.writeFileSync(sourcePath, 'same crash', 'utf8');
54
+ const writer = createCrashArtifactWriter({
55
+ runnerName: 'ios',
56
+ platformId: 'ios',
57
+ rootDir,
58
+ runTimestamp: '2026-03-12T11-35-08-000Z',
59
+ });
60
+ const firstPath = writer.persistArtifact({
61
+ artifactKind: 'ios-crash-report',
62
+ source: {
63
+ kind: 'file',
64
+ path: sourcePath,
65
+ },
66
+ });
67
+ const secondPath = writer.persistArtifact({
68
+ artifactKind: 'ios-crash-report',
69
+ source: {
70
+ kind: 'file',
71
+ path: sourcePath,
72
+ },
73
+ });
74
+ expect(firstPath).toBe(secondPath);
75
+ expect(fs.readdirSync(rootDir)).toHaveLength(2);
76
+ });
77
+ });
package/dist/abort.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export declare const getTimeoutSignal: (timeout: number) => AbortSignal;
2
+ export declare const raceAbortSignals: (signals: AbortSignal[]) => AbortSignal;
3
+ export declare const withAbortTimeout: (signal: AbortSignal, timeout: number) => AbortSignal;
2
4
  //# sourceMappingURL=abort.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,WAIlD,CAAC"}
1
+ {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,WAElD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,KAAG,WAKzD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,WAAW,EACnB,SAAS,MAAM,KACd,WAEF,CAAC"}
package/dist/abort.js CHANGED
@@ -1,5 +1,12 @@
1
1
  export const getTimeoutSignal = (timeout) => {
2
- const controller = new AbortController();
3
- setTimeout(() => controller.abort(), timeout);
4
- return controller.signal;
2
+ return AbortSignal.timeout(timeout);
3
+ };
4
+ export const raceAbortSignals = (signals) => {
5
+ if (signals.length === 0) {
6
+ return new AbortController().signal;
7
+ }
8
+ return AbortSignal.any(signals);
9
+ };
10
+ export const withAbortTimeout = (signal, timeout) => {
11
+ return raceAbortSignals([signal, getTimeoutSignal(timeout)]);
5
12
  };
@@ -0,0 +1,20 @@
1
+ export declare const createCrashArtifactWriter: ({ runnerName, platformId, rootDir, runTimestamp, }: {
2
+ runnerName: string;
3
+ platformId: string;
4
+ rootDir?: string;
5
+ runTimestamp?: string;
6
+ }) => {
7
+ runTimestamp: string;
8
+ persistArtifact: (options: {
9
+ artifactKind: string;
10
+ source: {
11
+ kind: "file";
12
+ path: string;
13
+ } | {
14
+ kind: "text";
15
+ fileName: string;
16
+ text: string;
17
+ };
18
+ }) => string;
19
+ };
20
+ //# sourceMappingURL=crash-artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-artifacts.d.ts","sourceRoot":"","sources":["../src/crash-artifacts.ts"],"names":[],"mappings":"AA4EA,eAAO,MAAM,yBAAyB,GAAI,oDAKvC;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;+BAK8B;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EACF;YACE,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;SACd,GACD;YACE,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,MAAM,CAAC;YACjB,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;KACP;CAoCJ,CAAC"}
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const DEFAULT_ARTIFACT_ROOT = path.join(process.cwd(), '.harness', 'crash-reports');
4
+ const sanitizePathSegment = (value) => value
5
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
6
+ .replace(/-+/g, '-')
7
+ .replace(/^-|-$/g, '') || 'artifact';
8
+ const formatRunTimestamp = (value) => value.toISOString().replace(/[:.]/g, '-');
9
+ const getTargetFileName = ({ runTimestamp, runnerName, platformId, artifactKind, source, }) => {
10
+ const originalName = source.kind === 'file' ? path.basename(source.path) : source.fileName;
11
+ return [
12
+ sanitizePathSegment(runTimestamp),
13
+ sanitizePathSegment(runnerName),
14
+ sanitizePathSegment(platformId),
15
+ sanitizePathSegment(artifactKind),
16
+ sanitizePathSegment(originalName),
17
+ ].join('--');
18
+ };
19
+ const getDeduplicationKey = ({ platformId, artifactKind, source, }) => {
20
+ if (source.kind === 'file') {
21
+ return `file:${platformId}:${artifactKind}:${path.resolve(source.path)}`;
22
+ }
23
+ return `text:${platformId}:${artifactKind}:${source.fileName}:${source.text}`;
24
+ };
25
+ export const createCrashArtifactWriter = ({ runnerName, platformId, rootDir = DEFAULT_ARTIFACT_ROOT, runTimestamp = formatRunTimestamp(new Date()), }) => {
26
+ const persistedArtifacts = new Map();
27
+ return {
28
+ runTimestamp,
29
+ persistArtifact: (options) => {
30
+ const deduplicationKey = getDeduplicationKey({
31
+ platformId,
32
+ artifactKind: options.artifactKind,
33
+ source: options.source,
34
+ });
35
+ const existingPath = persistedArtifacts.get(deduplicationKey);
36
+ if (existingPath) {
37
+ return existingPath;
38
+ }
39
+ fs.mkdirSync(rootDir, { recursive: true });
40
+ const targetPath = path.join(rootDir, getTargetFileName({
41
+ runTimestamp,
42
+ runnerName,
43
+ platformId,
44
+ artifactKind: options.artifactKind,
45
+ source: options.source,
46
+ }));
47
+ if (options.source.kind === 'file') {
48
+ fs.copyFileSync(options.source.path, targetPath);
49
+ }
50
+ else {
51
+ fs.writeFileSync(targetPath, options.source.text, 'utf8');
52
+ }
53
+ persistedArtifacts.set(deduplicationKey, targetPath);
54
+ return targetPath;
55
+ },
56
+ };
57
+ };
package/dist/index.d.ts CHANGED
@@ -7,4 +7,6 @@ export * from './react-native.js';
7
7
  export * from './error.js';
8
8
  export * from './events.js';
9
9
  export * from './packages.js';
10
+ export * from './crash-artifacts.js';
11
+ export * from './regex.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -7,3 +7,5 @@ export * from './react-native.js';
7
7
  export * from './error.js';
8
8
  export * from './events.js';
9
9
  export * from './packages.js';
10
+ export * from './crash-artifacts.js';
11
+ export * from './regex.js';
package/dist/logger.d.ts CHANGED
@@ -1,11 +1,16 @@
1
- export declare const logger: {
2
- success: (...messages: Array<unknown>) => void;
3
- info: (...messages: Array<unknown>) => void;
4
- warn: (...messages: Array<unknown>) => void;
5
- error: (...messages: Array<unknown>) => void;
6
- debug: (...messages: Array<unknown>) => void;
7
- log: (...messages: Array<unknown>) => void;
1
+ type LoggerMethod = (...messages: Array<unknown>) => void;
2
+ export type HarnessLogger = {
3
+ debug: LoggerMethod;
4
+ info: LoggerMethod;
5
+ warn: LoggerMethod;
6
+ error: LoggerMethod;
7
+ log: LoggerMethod;
8
+ success: LoggerMethod;
9
+ child: (scope: string) => HarnessLogger;
8
10
  setVerbose: (level: boolean) => void;
9
11
  isVerbose: () => boolean;
10
12
  };
13
+ export declare const createLogger: (scope: string) => HarnessLogger;
14
+ export declare const logger: HarnessLogger;
15
+ export {};
11
16
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAwDA,eAAO,MAAM,MAAM;2BA1CW,KAAK,CAAC,OAAO,CAAC;wBAKjB,KAAK,CAAC,OAAO,CAAC;wBAKd,KAAK,CAAC,OAAO,CAAC;yBAKb,KAAK,CAAC,OAAO,CAAC;yBAUd,KAAK,CAAC,OAAO,CAAC;uBALhB,KAAK,CAAC,OAAO,CAAC;wBAcb,OAAO;;CAiBjC,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAKA,KAAK,YAAY,GAAG,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;AAE1D,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,YAAY,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa,CAAC;IACxC,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B,CAAC;AA6EF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,aAChB,CAAC;AAE9B,eAAO,MAAM,MAAM,eAAuB,CAAC"}
package/dist/logger.js CHANGED
@@ -1,38 +1,30 @@
1
1
  import util from 'node:util';
2
- import { log as clackLog } from '@clack/prompts';
3
- import isUnicodeSupported from 'is-unicode-supported';
4
- import { color } from './color.js';
5
- const unicode = isUnicodeSupported();
6
- const unicodeWithFallback = (c, fallback) => unicode ? c : fallback;
7
- const SYMBOL_DEBUG = unicodeWithFallback('●', '•');
8
2
  let verbose = !!process.env.HARNESS_DEBUG;
9
- const success = (...messages) => {
3
+ const BASE_TAG = '[harness]';
4
+ const getTimestamp = () => new Date().toISOString();
5
+ const normalizeScope = (scope) => scope
6
+ .trim()
7
+ .replace(/^\[+|\]+$/g, '')
8
+ .replace(/\]\[/g, '][');
9
+ const formatPrefix = (scopes) => {
10
+ const suffix = scopes.map((scope) => `[${normalizeScope(scope)}]`).join('');
11
+ return `${BASE_TAG}${suffix}`;
12
+ };
13
+ const mapLines = (text, prefix) => text
14
+ .split('\n')
15
+ .map((line) => `${prefix} ${line}`)
16
+ .join('\n');
17
+ const writeLog = (level, scopes, messages) => {
18
+ const method = level === 'warn'
19
+ ? console.warn
20
+ : level === 'error'
21
+ ? console.error
22
+ : level === 'debug'
23
+ ? console.debug
24
+ : console.info;
10
25
  const output = util.format(...messages);
11
- clackLog.success(output);
12
- };
13
- const info = (...messages) => {
14
- const output = util.format(...messages);
15
- clackLog.info(output);
16
- };
17
- const warn = (...messages) => {
18
- const output = util.format(...messages);
19
- clackLog.warn(mapLines(output, color.yellow));
20
- };
21
- const error = (...messages) => {
22
- const output = util.format(...messages);
23
- clackLog.error(mapLines(output, color.red));
24
- };
25
- const log = (...messages) => {
26
- const output = util.format(...messages);
27
- clackLog.step(output);
28
- };
29
- const debug = (...messages) => {
30
- if (verbose) {
31
- const output = util.format(...messages);
32
- clackLog.message(mapLines(output, color.dim), {
33
- symbol: color.dim(SYMBOL_DEBUG),
34
- });
35
- }
26
+ const prefix = `${getTimestamp()} ${formatPrefix(scopes)}`;
27
+ method(mapLines(output, prefix));
36
28
  };
37
29
  const setVerbose = (level) => {
38
30
  verbose = level;
@@ -40,16 +32,31 @@ const setVerbose = (level) => {
40
32
  const isVerbose = () => {
41
33
  return verbose;
42
34
  };
43
- export const logger = {
44
- success,
45
- info,
46
- warn,
47
- error,
48
- debug,
49
- log,
35
+ const createScopedLogger = (scopes = []) => ({
36
+ debug: (...messages) => {
37
+ if (!verbose) {
38
+ return;
39
+ }
40
+ writeLog('debug', scopes, messages);
41
+ },
42
+ info: (...messages) => {
43
+ writeLog('info', scopes, messages);
44
+ },
45
+ warn: (...messages) => {
46
+ writeLog('warn', scopes, messages);
47
+ },
48
+ error: (...messages) => {
49
+ writeLog('error', scopes, messages);
50
+ },
51
+ log: (...messages) => {
52
+ writeLog('log', scopes, messages);
53
+ },
54
+ success: (...messages) => {
55
+ writeLog('success', scopes, messages);
56
+ },
57
+ child: (scope) => createScopedLogger([...scopes, scope]),
50
58
  setVerbose,
51
59
  isVerbose,
52
- };
53
- function mapLines(text, colorFn) {
54
- return text.split('\n').map(colorFn).join('\n');
55
- }
60
+ });
61
+ export const createLogger = (scope) => createScopedLogger([scope]);
62
+ export const logger = createScopedLogger();
@@ -0,0 +1,2 @@
1
+ export declare const escapeRegExp: (value: string) => string;
2
+ //# sourceMappingURL=regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../src/regex.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WACI,CAAC"}
package/dist/regex.js ADDED
@@ -0,0 +1 @@
1
+ export const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -1 +1 @@
1
- {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,OAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGxD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAEnC,eAAO,MAAM,KAAK,GAChB,MAAM,MAAM,EACZ,OAAO,SAAS,MAAM,EAAE,EACxB,UAAU,YAAY,KACrB,UAaF,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAMjH,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,OAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGxD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAGnC,eAAO,MAAM,KAAK,GAChB,MAAM,MAAM,EACZ,OAAO,SAAS,MAAM,EAAE,EACxB,UAAU,YAAY,KACrB,UAcF,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,EAAE,UAAU,YAAY,KAAG,OAAO,CAAC,IAAI,CAMjH,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC"}
package/dist/spawn.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import nanoSpawn, { SubprocessError } from 'nano-spawn';
2
2
  import { logger } from './logger.js';
3
+ const spawnLogger = logger.child('spawn');
3
4
  export const spawn = (file, args, options) => {
4
5
  const defaultStream = 'pipe';
5
6
  const defaultOptions = {
@@ -8,7 +9,8 @@ export const spawn = (file, args, options) => {
8
9
  // Always 'pipe' stderr to handle errors properly down the line
9
10
  stderr: 'pipe',
10
11
  };
11
- logger.debug(`Running: ${file}`, ...(args ?? []));
12
+ const command = [file, ...(args ?? [])].join(' ');
13
+ spawnLogger.debug('running command: %s', command);
12
14
  const childProcess = nanoSpawn(file, args, { ...defaultOptions, ...options });
13
15
  setupChildProcessCleanup(childProcess);
14
16
  return childProcess;
@@ -22,6 +24,35 @@ export const spawnAndForget = async (file, args, options) => {
22
24
  }
23
25
  };
24
26
  export { SubprocessError };
27
+ const activeChildProcesses = new Set();
28
+ let isProcessCleanupInstalled = false;
29
+ const terminateActiveChildren = async () => {
30
+ const children = [...activeChildProcesses];
31
+ await Promise.allSettled(children.map(async (childProcess) => {
32
+ try {
33
+ (await childProcess.nodeChildProcess).kill();
34
+ }
35
+ catch {
36
+ // Ignore cleanup failures while shutting down.
37
+ }
38
+ }));
39
+ };
40
+ const installProcessCleanup = () => {
41
+ if (isProcessCleanupInstalled) {
42
+ return;
43
+ }
44
+ isProcessCleanupInstalled = true;
45
+ const terminate = async () => {
46
+ await terminateActiveChildren();
47
+ process.exit(1);
48
+ };
49
+ process.on('SIGINT', () => {
50
+ void terminate();
51
+ });
52
+ process.on('SIGTERM', () => {
53
+ void terminate();
54
+ });
55
+ };
25
56
  const setupChildProcessCleanup = (childProcess) => {
26
57
  // https://stackoverflow.com/questions/53049939/node-daemon-wont-start-with-process-stdin-setrawmodetrue/53050098#53050098
27
58
  if (process.stdin.isTTY) {
@@ -29,22 +60,10 @@ const setupChildProcessCleanup = (childProcess) => {
29
60
  // which prevents listening for SIGINT and SIGTERM
30
61
  process.stdin.setRawMode(false);
31
62
  }
32
- const terminate = async () => {
33
- try {
34
- (await childProcess.nodeChildProcess).kill();
35
- process.exit(1);
36
- }
37
- catch {
38
- // ignore
39
- }
40
- };
41
- const sigintHandler = () => terminate();
42
- const sigtermHandler = () => terminate();
43
- process.on('SIGINT', sigintHandler);
44
- process.on('SIGTERM', sigtermHandler);
63
+ installProcessCleanup();
64
+ activeChildProcesses.add(childProcess);
45
65
  const cleanup = () => {
46
- process.off('SIGINT', sigintHandler);
47
- process.off('SIGTERM', sigtermHandler);
66
+ activeChildProcesses.delete(childProcess);
48
67
  };
49
68
  childProcess.nodeChildProcess.finally(cleanup);
50
69
  };