@nitpicker/core 0.6.4 → 0.7.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/README.md +39 -1
- package/lib/core.d.ts +0 -1
- package/lib/core.js +0 -1
- package/lib/nitpicker.d.ts +18 -15
- package/lib/nitpicker.js +113 -87
- package/lib/page-analysis-worker.d.ts +7 -5
- package/lib/page-analysis-worker.js +4 -3
- package/lib/types.d.ts +12 -1
- package/lib/url-event-bus.d.ts +7 -6
- package/lib/url-event-bus.js +7 -6
- package/lib/worker/runner.d.ts +2 -2
- package/lib/worker/runner.js +2 -2
- package/lib/worker/types.d.ts +3 -3
- package/lib/worker/worker-pool.d.ts +82 -0
- package/lib/worker/worker-pool.js +278 -0
- package/lib/worker/worker.d.ts +21 -14
- package/lib/worker/worker.js +81 -41
- package/package.json +4 -4
- package/lib/find-nitpicker-modules-dir.d.ts +0 -12
- package/lib/find-nitpicker-modules-dir.js +0 -23
- package/lib/hooks/actions.d.ts +0 -9
- package/lib/hooks/actions.js +0 -9
- package/lib/hooks/child-process.d.ts +0 -1
- package/lib/hooks/child-process.js +0 -34
- package/lib/hooks/index.d.ts +0 -1
- package/lib/hooks/index.js +0 -1
- package/lib/hooks/runner.d.ts +0 -10
- package/lib/hooks/runner.js +0 -32
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -5
- package/lib/utils.d.ts +0 -36
- package/lib/utils.js +0 -43
- package/lib/worker/run-in-worker.d.ts +0 -51
- package/lib/worker/run-in-worker.js +0 -125
package/lib/utils.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
type SequentialEachChunkCallback<R, T> = (item: T, i: number) => Promise<R>;
|
|
2
|
-
/**
|
|
3
|
-
* Sequential processing each chunk
|
|
4
|
-
*
|
|
5
|
-
* 1. Split an array
|
|
6
|
-
* ┌───────── array ────────┐
|
|
7
|
-
* chunk1 │ chunk2 | chunk3
|
|
8
|
-
* ├item ├item ├item
|
|
9
|
-
* ├item ├item ├item
|
|
10
|
-
* └item └item └item
|
|
11
|
-
*
|
|
12
|
-
* 2. Runs parallel and sequential
|
|
13
|
-
* chunk1 ┬ callback(item) ┐
|
|
14
|
-
* │ ├ callback(item) │ Promise.all
|
|
15
|
-
* │ └ callback(item) ┘
|
|
16
|
-
* │
|
|
17
|
-
* (Sequential)
|
|
18
|
-
* │
|
|
19
|
-
* chunk2 ┬ callback(item) ┐
|
|
20
|
-
* │ ├ callback(item) │ Promise.all
|
|
21
|
-
* │ └ callback(item) ┘
|
|
22
|
-
* │
|
|
23
|
-
* (Sequential)
|
|
24
|
-
* │
|
|
25
|
-
* chunk3 ┬ callback(item) ┐
|
|
26
|
-
* │ ├ callback(item) │ Promise.all
|
|
27
|
-
* │ └ callback(item) ┘
|
|
28
|
-
* │
|
|
29
|
-
* (Done)
|
|
30
|
-
* @param array
|
|
31
|
-
* @param split
|
|
32
|
-
* @param callback
|
|
33
|
-
* @returns
|
|
34
|
-
*/
|
|
35
|
-
export declare function sequentialEachChunk<R, T>(array: T[], split: number, callback: SequentialEachChunkCallback<R, T>): Promise<R[]>;
|
|
36
|
-
export {};
|
package/lib/utils.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sequential processing each chunk
|
|
3
|
-
*
|
|
4
|
-
* 1. Split an array
|
|
5
|
-
* ┌───────── array ────────┐
|
|
6
|
-
* chunk1 │ chunk2 | chunk3
|
|
7
|
-
* ├item ├item ├item
|
|
8
|
-
* ├item ├item ├item
|
|
9
|
-
* └item └item └item
|
|
10
|
-
*
|
|
11
|
-
* 2. Runs parallel and sequential
|
|
12
|
-
* chunk1 ┬ callback(item) ┐
|
|
13
|
-
* │ ├ callback(item) │ Promise.all
|
|
14
|
-
* │ └ callback(item) ┘
|
|
15
|
-
* │
|
|
16
|
-
* (Sequential)
|
|
17
|
-
* │
|
|
18
|
-
* chunk2 ┬ callback(item) ┐
|
|
19
|
-
* │ ├ callback(item) │ Promise.all
|
|
20
|
-
* │ └ callback(item) ┘
|
|
21
|
-
* │
|
|
22
|
-
* (Sequential)
|
|
23
|
-
* │
|
|
24
|
-
* chunk3 ┬ callback(item) ┐
|
|
25
|
-
* │ ├ callback(item) │ Promise.all
|
|
26
|
-
* │ └ callback(item) ┘
|
|
27
|
-
* │
|
|
28
|
-
* (Done)
|
|
29
|
-
* @param array
|
|
30
|
-
* @param split
|
|
31
|
-
* @param callback
|
|
32
|
-
* @returns
|
|
33
|
-
*/
|
|
34
|
-
export async function sequentialEachChunk(array, split, callback) {
|
|
35
|
-
array = [...array];
|
|
36
|
-
const result = [];
|
|
37
|
-
do {
|
|
38
|
-
const chunk = array.splice(0, split);
|
|
39
|
-
const segRes = await Promise.all(chunk.map((item, i) => callback(item, i + result.length)));
|
|
40
|
-
result.push(...segRes);
|
|
41
|
-
} while (array.length > 0);
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { UrlEventBus } from '../url-event-bus.js';
|
|
2
|
-
/**
|
|
3
|
-
* Parameters for {@link runInWorker}.
|
|
4
|
-
* @template I - Shape of the additional data merged into `workerData`.
|
|
5
|
-
*/
|
|
6
|
-
export interface RunInWorkerParams<I extends Record<string, unknown>> {
|
|
7
|
-
/** Absolute path to the module to execute in the Worker. */
|
|
8
|
-
readonly filePath: string;
|
|
9
|
-
/** Zero-based index of the current item (for progress display). */
|
|
10
|
-
readonly num: number;
|
|
11
|
-
/** Total number of items in the batch. */
|
|
12
|
-
readonly total: number;
|
|
13
|
-
/** URL event bus; `'url'` messages from the Worker are re-emitted here. */
|
|
14
|
-
readonly emitter: UrlEventBus;
|
|
15
|
-
/** Plugin-specific data to pass to the Worker module. */
|
|
16
|
-
readonly initialData: I;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Spawns a Worker thread to execute a plugin module and returns its result.
|
|
20
|
-
*
|
|
21
|
-
* This is the bridge between the main thread's `deal()` parallelism and the
|
|
22
|
-
* per-page Worker execution. Each call creates a new Worker, passes the
|
|
23
|
-
* initial data via `workerData`, and listens for messages until the Worker
|
|
24
|
-
* signals completion.
|
|
25
|
-
*
|
|
26
|
-
* ## Why Worker threads?
|
|
27
|
-
*
|
|
28
|
-
* DOM-heavy plugins (JSDOM + axe-core, markuplint, etc.) allocate significant
|
|
29
|
-
* memory per page. Running them in Workers ensures:
|
|
30
|
-
* - **Memory isolation**: JSDOM windows are fully GC'd when the Worker exits
|
|
31
|
-
* - **Crash containment**: A plugin segfault/OOM kills only the Worker, not the process
|
|
32
|
-
* - **Signal handling**: Graceful cleanup on SIGABRT, SIGQUIT, and other signals
|
|
33
|
-
*
|
|
34
|
-
* ## Message protocol
|
|
35
|
-
*
|
|
36
|
-
* The Worker sends two types of messages:
|
|
37
|
-
* - `{ type: 'url', url: string }` - URL discovery notification, forwarded to the emitter
|
|
38
|
-
* - `{ type: 'finish', result: R }` - Execution complete, resolves the Promise
|
|
39
|
-
*
|
|
40
|
-
* ## Fallback mode
|
|
41
|
-
*
|
|
42
|
-
* When `useWorker` is `false`, execution delegates directly to
|
|
43
|
-
* {@link ./runner.ts!runner} in the main thread.
|
|
44
|
-
* @template I - Shape of the additional data merged into `workerData`.
|
|
45
|
-
* @template R - Return type expected from the Worker module.
|
|
46
|
-
* @param params - Parameters containing file path, progress info, emitter, and data.
|
|
47
|
-
* @returns The result produced by the Worker module's default export.
|
|
48
|
-
* @see {@link ./worker.ts} for the Worker-side entry point
|
|
49
|
-
* @see {@link ./runner.ts!runner} for the direct (non-Worker) execution path
|
|
50
|
-
*/
|
|
51
|
-
export declare function runInWorker<I extends Record<string, unknown>, R>(params: RunInWorkerParams<I>): Promise<R>;
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { Worker } from 'node:worker_threads';
|
|
3
|
-
import { runner } from './runner.js';
|
|
4
|
-
const __filename = new URL(import.meta.url).pathname;
|
|
5
|
-
const __dirname = path.dirname(__filename);
|
|
6
|
-
/** Resolved path to the compiled Worker thread entry point ({@link ./worker.ts}). */
|
|
7
|
-
const workerPath = path.resolve(__dirname, 'worker.js');
|
|
8
|
-
/**
|
|
9
|
-
* Feature flag controlling whether plugin execution uses Worker threads.
|
|
10
|
-
* When `true` (default), each plugin invocation runs in an isolated Worker,
|
|
11
|
-
* providing memory isolation and crash protection. When `false`, the runner
|
|
12
|
-
* executes directly in the main thread (useful for debugging).
|
|
13
|
-
*/
|
|
14
|
-
const useWorker = true;
|
|
15
|
-
/**
|
|
16
|
-
* Spawns a Worker thread to execute a plugin module and returns its result.
|
|
17
|
-
*
|
|
18
|
-
* This is the bridge between the main thread's `deal()` parallelism and the
|
|
19
|
-
* per-page Worker execution. Each call creates a new Worker, passes the
|
|
20
|
-
* initial data via `workerData`, and listens for messages until the Worker
|
|
21
|
-
* signals completion.
|
|
22
|
-
*
|
|
23
|
-
* ## Why Worker threads?
|
|
24
|
-
*
|
|
25
|
-
* DOM-heavy plugins (JSDOM + axe-core, markuplint, etc.) allocate significant
|
|
26
|
-
* memory per page. Running them in Workers ensures:
|
|
27
|
-
* - **Memory isolation**: JSDOM windows are fully GC'd when the Worker exits
|
|
28
|
-
* - **Crash containment**: A plugin segfault/OOM kills only the Worker, not the process
|
|
29
|
-
* - **Signal handling**: Graceful cleanup on SIGABRT, SIGQUIT, and other signals
|
|
30
|
-
*
|
|
31
|
-
* ## Message protocol
|
|
32
|
-
*
|
|
33
|
-
* The Worker sends two types of messages:
|
|
34
|
-
* - `{ type: 'url', url: string }` - URL discovery notification, forwarded to the emitter
|
|
35
|
-
* - `{ type: 'finish', result: R }` - Execution complete, resolves the Promise
|
|
36
|
-
*
|
|
37
|
-
* ## Fallback mode
|
|
38
|
-
*
|
|
39
|
-
* When `useWorker` is `false`, execution delegates directly to
|
|
40
|
-
* {@link ./runner.ts!runner} in the main thread.
|
|
41
|
-
* @template I - Shape of the additional data merged into `workerData`.
|
|
42
|
-
* @template R - Return type expected from the Worker module.
|
|
43
|
-
* @param params - Parameters containing file path, progress info, emitter, and data.
|
|
44
|
-
* @returns The result produced by the Worker module's default export.
|
|
45
|
-
* @see {@link ./worker.ts} for the Worker-side entry point
|
|
46
|
-
* @see {@link ./runner.ts!runner} for the direct (non-Worker) execution path
|
|
47
|
-
*/
|
|
48
|
-
export function runInWorker(params) {
|
|
49
|
-
const { filePath, num, total, emitter, initialData } = params;
|
|
50
|
-
if (useWorker) {
|
|
51
|
-
const worker = new Worker(workerPath, {
|
|
52
|
-
workerData: {
|
|
53
|
-
filePath,
|
|
54
|
-
num,
|
|
55
|
-
total,
|
|
56
|
-
...initialData,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
return new Promise((resolve, reject) => {
|
|
60
|
-
const killWorker = async (sig) => {
|
|
61
|
-
await worker.terminate();
|
|
62
|
-
worker.unref();
|
|
63
|
-
worker.removeAllListeners();
|
|
64
|
-
process.removeListener('SIGABRT', killWorker);
|
|
65
|
-
process.removeListener('SIGLOST', killWorker);
|
|
66
|
-
process.removeListener('SIGQUIT', killWorker);
|
|
67
|
-
process.removeListener('disconnect', killWorker);
|
|
68
|
-
process.removeListener('exit', killWorker);
|
|
69
|
-
process.removeListener('uncaughtException', killWorker);
|
|
70
|
-
process.removeListener('uncaughtExceptionMonitor', killWorker);
|
|
71
|
-
process.removeListener('unhandledRejection', killWorker);
|
|
72
|
-
// eslint-disable-next-line no-console
|
|
73
|
-
console.log(`Kill Worker cause: %O`, sig);
|
|
74
|
-
reject(`SIG: ${sig}`);
|
|
75
|
-
};
|
|
76
|
-
// Changed from old issue
|
|
77
|
-
// @see https://github.com/nodejs/node-v0.x-archive/issues/6339
|
|
78
|
-
// process.once('SIGKILL', killWorker);
|
|
79
|
-
// process.once('SIGSTOP', killWorker);
|
|
80
|
-
process.once('SIGABRT', killWorker);
|
|
81
|
-
process.once('SIGLOST', killWorker);
|
|
82
|
-
process.once('SIGQUIT', killWorker);
|
|
83
|
-
process.once('disconnect', killWorker);
|
|
84
|
-
process.once('exit', killWorker);
|
|
85
|
-
process.once('uncaughtException', killWorker);
|
|
86
|
-
process.once('uncaughtExceptionMonitor', killWorker);
|
|
87
|
-
process.once('unhandledRejection', killWorker);
|
|
88
|
-
worker.once('error', killWorker);
|
|
89
|
-
worker.once('messageerror', killWorker);
|
|
90
|
-
worker.on('message', async (message) => {
|
|
91
|
-
if (!message) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (message.type === 'url') {
|
|
95
|
-
void emitter.emit('url', message.url);
|
|
96
|
-
}
|
|
97
|
-
if (message.type === 'finish') {
|
|
98
|
-
await worker.terminate();
|
|
99
|
-
worker.removeAllListeners();
|
|
100
|
-
worker.unref();
|
|
101
|
-
process.removeListener('SIGABRT', killWorker);
|
|
102
|
-
process.removeListener('SIGLOST', killWorker);
|
|
103
|
-
process.removeListener('SIGQUIT', killWorker);
|
|
104
|
-
process.removeListener('disconnect', killWorker);
|
|
105
|
-
process.removeListener('exit', killWorker);
|
|
106
|
-
process.removeListener('uncaughtException', killWorker);
|
|
107
|
-
process.removeListener('uncaughtExceptionMonitor', killWorker);
|
|
108
|
-
process.removeListener('unhandledRejection', killWorker);
|
|
109
|
-
if (message.error) {
|
|
110
|
-
reject(new Error(message.error));
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
resolve(message.result);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return runner({
|
|
120
|
-
filePath,
|
|
121
|
-
num,
|
|
122
|
-
total,
|
|
123
|
-
...initialData,
|
|
124
|
-
}, emitter);
|
|
125
|
-
}
|