@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.
- package/dist/__tests__/abort.test.d.ts +2 -0
- package/dist/__tests__/abort.test.d.ts.map +1 -0
- package/dist/__tests__/abort.test.js +49 -0
- package/dist/__tests__/crash-artifacts.test.d.ts +2 -0
- package/dist/__tests__/crash-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/crash-artifacts.test.js +77 -0
- package/dist/abort.d.ts +2 -0
- package/dist/abort.d.ts.map +1 -1
- package/dist/abort.js +10 -3
- package/dist/crash-artifacts.d.ts +20 -0
- package/dist/crash-artifacts.d.ts.map +1 -0
- package/dist/crash-artifacts.js +57 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +12 -7
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +50 -43
- package/dist/regex.d.ts +2 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +1 -0
- package/dist/spawn.d.ts.map +1 -1
- package/dist/spawn.js +35 -16
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/eslint.config.mjs +4 -1
- package/package.json +1 -2
- package/src/__tests__/abort.test.ts +71 -0
- package/src/__tests__/crash-artifacts.test.ts +94 -0
- package/src/abort.ts +15 -3
- package/src/crash-artifacts.ts +140 -0
- package/src/index.ts +3 -1
- package/src/logger.ts +73 -45
- package/src/regex.ts +2 -0
- package/src/spawn.ts +43 -17
- package/tsconfig.lib.json +2 -1
- package/dist/runtime.d.ts +0 -10
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -10
- package/dist/timeout.d.ts +0 -2
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js +0 -16
|
@@ -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 @@
|
|
|
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
|
package/dist/abort.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
package/dist/index.d.ts.map
CHANGED
|
@@ -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
package/dist/logger.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
log:
|
|
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
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
60
|
+
});
|
|
61
|
+
export const createLogger = (scope) => createScopedLogger([scope]);
|
|
62
|
+
export const logger = createScopedLogger();
|
package/dist/regex.d.ts
ADDED
|
@@ -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, '\\$&');
|
package/dist/spawn.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
47
|
-
process.off('SIGTERM', sigtermHandler);
|
|
66
|
+
activeChildProcesses.delete(childProcess);
|
|
48
67
|
};
|
|
49
68
|
childProcess.nodeChildProcess.finally(cleanup);
|
|
50
69
|
};
|