@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.
@@ -0,0 +1,278 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ /**
3
+ * Long-lived pool of Worker threads that share execution across many tasks.
4
+ *
5
+ * Replaces the previous "one worker per page" model where every analyzed page
6
+ * paid the cost of spawning a new V8 isolate, importing JSDOM, importing the
7
+ * plugin module, and tearing it all down. With the pool, each worker is
8
+ * created once at the start of an analyze run and processes tasks via
9
+ * message passing until the pool is terminated.
10
+ *
11
+ * ## Why this matters
12
+ *
13
+ * On a 750-page archive with `CONCURRENCY_LIMIT = 50`, the old model spawned
14
+ * 50 workers nearly simultaneously and repeated this every time a worker
15
+ * finished, producing recurring "boot waves" that drove peak memory above
16
+ * 20GB. The pool model performs N initial boots and then reuses those V8
17
+ * isolates for the rest of the run, eliminating the recurring cost entirely.
18
+ *
19
+ * ## Message protocol (Pool ↔ Worker)
20
+ *
21
+ * | Direction | `type` | Payload |
22
+ * |------------------|---------------|----------------------------------|
23
+ * | Pool → Worker | `'task'` | `{ taskId, data }` |
24
+ * | Pool → Worker | `'shutdown'` | (none) |
25
+ * | Worker → Pool | `'url'` | `{ taskId, url }` (forwarded) |
26
+ * | Worker → Pool | `'result'` | `{ taskId, result, error? }` |
27
+ *
28
+ * Each task carries a unique numeric `taskId` so that responses and URL
29
+ * notifications from a worker can be routed back to the correct caller.
30
+ */
31
+ export class WorkerPool {
32
+ /** FIFO queue of slots ready to accept a task. */
33
+ #idle = [];
34
+ /** Tasks awaiting their result, keyed by taskId. */
35
+ #inFlight = new Map();
36
+ /** Monotonic task identifier source. */
37
+ #nextTaskId = 0;
38
+ /** FIFO queue of pending tasks waiting for an idle slot. */
39
+ #queue = [];
40
+ /** Once `terminate()` has been called, no new tasks may be submitted. */
41
+ #shuttingDown = false;
42
+ /** All worker slots, idle or busy. */
43
+ #slots = [];
44
+ /** Per-task data payload to send when a queued task is dispatched. */
45
+ #taskData = new Map();
46
+ /** Worker entry script path; reused when replacing crashed workers. */
47
+ #workerPath;
48
+ /**
49
+ * Constructs a pool with the given size and worker entry script.
50
+ * @param options - Pool size and worker entry script path.
51
+ */
52
+ constructor(options) {
53
+ const size = Math.max(1, Math.floor(options.size));
54
+ this.#workerPath = options.workerPath;
55
+ for (let i = 0; i < size; i++) {
56
+ this.#spawn();
57
+ }
58
+ }
59
+ /**
60
+ * Submits a task to the pool. Resolves with the worker's `result` payload
61
+ * once a worker has finished processing it. The task may be queued if all
62
+ * workers are currently busy.
63
+ * @template I - Shape of the per-task `initialData` payload.
64
+ * @template R - Expected result type returned by the worker module.
65
+ * @param params - Task payload (file path, batch indices, emitter, data).
66
+ * @returns The worker's result.
67
+ */
68
+ run(params) {
69
+ if (this.#shuttingDown) {
70
+ return Promise.reject(new Error('WorkerPool is shutting down'));
71
+ }
72
+ const taskId = this.#nextTaskId++;
73
+ const data = {
74
+ filePath: params.filePath,
75
+ num: params.num,
76
+ total: params.total,
77
+ ...params.initialData,
78
+ };
79
+ return new Promise((resolve, reject) => {
80
+ const task = {
81
+ taskId,
82
+ emitter: params.emitter,
83
+ resolve: resolve,
84
+ reject,
85
+ };
86
+ this.#taskData.set(taskId, data);
87
+ const slot = this.#idle.shift();
88
+ if (slot) {
89
+ this.#dispatch(slot, task);
90
+ return;
91
+ }
92
+ this.#queue.push(task);
93
+ });
94
+ }
95
+ /**
96
+ * Rejects every queued or in-flight task, sends each worker a `shutdown`
97
+ * message, and awaits its exit. After this call returns, no further
98
+ * submissions are accepted.
99
+ *
100
+ * Pending tasks are rejected synchronously so callers waiting on
101
+ * `pool.run()` Promises do not hang. Workers handle the shutdown message
102
+ * by exiting cleanly; if that fails, {@link #shutdownSlot} forces
103
+ * termination after a 5 second timeout.
104
+ */
105
+ async terminate() {
106
+ this.#shuttingDown = true;
107
+ const pendingError = new Error('WorkerPool terminated before task completed');
108
+ for (const task of this.#queue) {
109
+ task.reject(pendingError);
110
+ this.#taskData.delete(task.taskId);
111
+ }
112
+ this.#queue = [];
113
+ for (const task of this.#inFlight.values()) {
114
+ task.reject(pendingError);
115
+ }
116
+ this.#inFlight.clear();
117
+ await Promise.all(this.#slots.map((slot) => this.#shutdownSlot(slot)));
118
+ this.#slots = [];
119
+ this.#idle = [];
120
+ }
121
+ /**
122
+ * Completes the current task on `slot` and dispatches the next queued task.
123
+ * @param slot
124
+ * @param taskId
125
+ * @param result
126
+ * @param error
127
+ */
128
+ #completeTask(slot, taskId, result, error) {
129
+ const task = this.#inFlight.get(taskId);
130
+ this.#inFlight.delete(taskId);
131
+ slot.current = null;
132
+ if (task) {
133
+ if (error) {
134
+ task.reject(new Error(error));
135
+ }
136
+ else {
137
+ task.resolve(result);
138
+ }
139
+ }
140
+ const next = this.#queue.shift();
141
+ if (next) {
142
+ this.#dispatch(slot, next);
143
+ }
144
+ else {
145
+ this.#idle.push(slot);
146
+ }
147
+ }
148
+ /**
149
+ * Sends a queued task to an idle slot and registers it as in-flight.
150
+ *
151
+ * If `postMessage` throws (e.g. the worker died between handoff and
152
+ * dispatch), the task is rejected immediately and the slot is left out
153
+ * of the idle pool so the worker's `error` event can replace it via
154
+ * {@link #onWorkerCrash}.
155
+ * @param slot - Pool slot that owns the worker.
156
+ * @param task - Pending task to dispatch.
157
+ */
158
+ #dispatch(slot, task) {
159
+ const data = this.#taskData.get(task.taskId);
160
+ try {
161
+ slot.worker.postMessage({ type: 'task', taskId: task.taskId, data });
162
+ }
163
+ catch (error) {
164
+ this.#taskData.delete(task.taskId);
165
+ task.reject(error instanceof Error ? error : new Error(String(error)));
166
+ return;
167
+ }
168
+ slot.current = task;
169
+ this.#inFlight.set(task.taskId, task);
170
+ this.#taskData.delete(task.taskId);
171
+ }
172
+ /**
173
+ * Routes an incoming worker message to the matching pending task.
174
+ * @param slot
175
+ * @param message
176
+ */
177
+ #onMessage(slot, message) {
178
+ if (!message || typeof message !== 'object') {
179
+ return;
180
+ }
181
+ const msg = message;
182
+ if (msg.type === 'url' &&
183
+ typeof msg.taskId === 'number' &&
184
+ typeof msg.url === 'string') {
185
+ const task = this.#inFlight.get(msg.taskId);
186
+ if (task) {
187
+ void task.emitter.emit('url', msg.url);
188
+ }
189
+ return;
190
+ }
191
+ if (msg.type === 'result' && typeof msg.taskId === 'number') {
192
+ this.#completeTask(slot, msg.taskId, msg.result, msg.error);
193
+ return;
194
+ }
195
+ }
196
+ /**
197
+ * Handles an unrecoverable worker failure: rejects the current task,
198
+ * removes the dead slot, and spawns a replacement so the pool keeps its
199
+ * configured size. Queued tasks are picked up by the new slot when ready.
200
+ * @param slot
201
+ * @param error
202
+ */
203
+ #onWorkerCrash(slot, error) {
204
+ const task = slot.current;
205
+ slot.current = null;
206
+ if (task) {
207
+ this.#inFlight.delete(task.taskId);
208
+ task.reject(error instanceof Error ? error : new Error(String(error)));
209
+ }
210
+ const slotIndex = this.#slots.indexOf(slot);
211
+ if (slotIndex !== -1) {
212
+ this.#slots.splice(slotIndex, 1);
213
+ }
214
+ const idleIndex = this.#idle.indexOf(slot);
215
+ if (idleIndex !== -1) {
216
+ this.#idle.splice(idleIndex, 1);
217
+ }
218
+ slot.worker.removeAllListeners();
219
+ void slot.worker.terminate();
220
+ if (this.#shuttingDown) {
221
+ return;
222
+ }
223
+ const replacement = this.#spawn();
224
+ const next = this.#queue.shift();
225
+ if (next) {
226
+ const idleIdx = this.#idle.indexOf(replacement);
227
+ if (idleIdx !== -1) {
228
+ this.#idle.splice(idleIdx, 1);
229
+ }
230
+ this.#dispatch(replacement, next);
231
+ }
232
+ }
233
+ /**
234
+ * Sends a shutdown message and waits for the worker to exit cleanly.
235
+ * @param slot
236
+ */
237
+ #shutdownSlot(slot) {
238
+ return new Promise((resolve) => {
239
+ const finish = () => {
240
+ slot.worker.removeAllListeners();
241
+ resolve();
242
+ };
243
+ slot.worker.once('exit', finish);
244
+ slot.worker.once('error', finish);
245
+ try {
246
+ slot.worker.postMessage({ type: 'shutdown' });
247
+ }
248
+ catch {
249
+ void slot.worker.terminate().finally(finish);
250
+ return;
251
+ }
252
+ // Safety net in case the worker fails to acknowledge the shutdown.
253
+ setTimeout(() => {
254
+ void slot.worker.terminate().finally(finish);
255
+ }, 5000).unref();
256
+ });
257
+ }
258
+ /** Spawns a fresh worker, wires up its event handlers, and adds it to the pool. */
259
+ #spawn() {
260
+ const worker = new Worker(this.#workerPath);
261
+ const slot = { worker, current: null };
262
+ this.#slots.push(slot);
263
+ worker.on('message', (message) => {
264
+ this.#onMessage(slot, message);
265
+ });
266
+ worker.on('error', (error) => {
267
+ this.#onWorkerCrash(slot, error);
268
+ });
269
+ worker.on('messageerror', (error) => {
270
+ this.#onWorkerCrash(slot, error);
271
+ });
272
+ this.#idle.push(slot);
273
+ // Workers should not keep the event loop alive on their own;
274
+ // the pool's terminate() controls their lifetime.
275
+ worker.unref();
276
+ return slot;
277
+ }
278
+ }
@@ -1,26 +1,33 @@
1
1
  /**
2
2
  * Worker thread entry point for plugin execution.
3
3
  *
4
- * This module runs inside a `new Worker(...)` created by {@link ./run-in-worker.ts!runInWorker}.
5
- * It uses a message-based protocol to communicate with the main thread:
4
+ * This module runs inside a `new Worker(...)` created by the
5
+ * {@link ./worker-pool.ts!WorkerPool}. Unlike the previous one-shot model,
6
+ * each worker is long-lived and processes many tasks via a message loop until
7
+ * it receives a `shutdown` message.
6
8
  *
7
- * ## Message protocol (Worker -> Main)
9
+ * ## Message protocol (Worker ↔ Pool)
8
10
  *
9
- * | `type` | Payload | Description |
10
- * |------------|------------------|------------------------------------------------|
11
- * | `'url'` | `{ url: string }` | A URL was discovered during analysis |
12
- * | `'finish'` | `{ result: R }` | Analysis complete, carries the plugin result |
11
+ * | Direction | `type` | Payload |
12
+ * |-----------------|---------------|----------------------------------|
13
+ * | Pool → Worker | `'task'` | `{ taskId, data }` |
14
+ * | Pool → Worker | `'shutdown'` | (none) |
15
+ * | Worker → Pool | `'url'` | `{ taskId, url }` |
16
+ * | Worker → Pool | `'result'` | `{ taskId, result, error? }` |
13
17
  *
14
18
  * ## Lifecycle
15
19
  *
16
- * 1. Reads `workerData` containing the module path + plugin data
17
- * 2. Creates a local {@link ../url-event-bus.ts!UrlEventBus} that forwards `'url'`
18
- * events to the main thread via `parentPort.postMessage`
19
- * 3. Delegates to {@link ./runner.ts!runner} for dynamic import and execution
20
- * 4. Posts the `'finish'` message with the result
20
+ * 1. Listens on `parentPort` for `task` messages.
21
+ * 2. For each task, delegates to {@link ./runner.ts!runner} which dynamically
22
+ * `import()`s the module pointed to by `data.filePath`. Subsequent tasks
23
+ * that target the same module reuse the import cache, so the heavy boot
24
+ * cost is paid once per module per worker.
25
+ * 3. Posts a `result` message back to the pool and waits for the next task.
26
+ * 4. On `shutdown`, exits the process so the V8 isolate is fully released.
21
27
  *
22
- * The main thread terminates this Worker after receiving `'finish'`.
23
- * @see {@link ./run-in-worker.ts!runInWorker} for the main-thread counterpart
28
+ * The `'url'` events emitted by plugins are forwarded with the active
29
+ * `taskId` so the pool can route them back to the correct caller's emitter.
30
+ * @see {@link ./worker-pool.ts!WorkerPool} for the main-thread counterpart
24
31
  * @see {@link ./runner.ts!runner} for the actual module loading logic
25
32
  * @module
26
33
  */
@@ -1,68 +1,108 @@
1
1
  /**
2
2
  * Worker thread entry point for plugin execution.
3
3
  *
4
- * This module runs inside a `new Worker(...)` created by {@link ./run-in-worker.ts!runInWorker}.
5
- * It uses a message-based protocol to communicate with the main thread:
4
+ * This module runs inside a `new Worker(...)` created by the
5
+ * {@link ./worker-pool.ts!WorkerPool}. Unlike the previous one-shot model,
6
+ * each worker is long-lived and processes many tasks via a message loop until
7
+ * it receives a `shutdown` message.
6
8
  *
7
- * ## Message protocol (Worker -> Main)
9
+ * ## Message protocol (Worker ↔ Pool)
8
10
  *
9
- * | `type` | Payload | Description |
10
- * |------------|------------------|------------------------------------------------|
11
- * | `'url'` | `{ url: string }` | A URL was discovered during analysis |
12
- * | `'finish'` | `{ result: R }` | Analysis complete, carries the plugin result |
11
+ * | Direction | `type` | Payload |
12
+ * |-----------------|---------------|----------------------------------|
13
+ * | Pool → Worker | `'task'` | `{ taskId, data }` |
14
+ * | Pool → Worker | `'shutdown'` | (none) |
15
+ * | Worker → Pool | `'url'` | `{ taskId, url }` |
16
+ * | Worker → Pool | `'result'` | `{ taskId, result, error? }` |
13
17
  *
14
18
  * ## Lifecycle
15
19
  *
16
- * 1. Reads `workerData` containing the module path + plugin data
17
- * 2. Creates a local {@link ../url-event-bus.ts!UrlEventBus} that forwards `'url'`
18
- * events to the main thread via `parentPort.postMessage`
19
- * 3. Delegates to {@link ./runner.ts!runner} for dynamic import and execution
20
- * 4. Posts the `'finish'` message with the result
20
+ * 1. Listens on `parentPort` for `task` messages.
21
+ * 2. For each task, delegates to {@link ./runner.ts!runner} which dynamically
22
+ * `import()`s the module pointed to by `data.filePath`. Subsequent tasks
23
+ * that target the same module reuse the import cache, so the heavy boot
24
+ * cost is paid once per module per worker.
25
+ * 3. Posts a `result` message back to the pool and waits for the next task.
26
+ * 4. On `shutdown`, exits the process so the V8 isolate is fully released.
21
27
  *
22
- * The main thread terminates this Worker after receiving `'finish'`.
23
- * @see {@link ./run-in-worker.ts!runInWorker} for the main-thread counterpart
28
+ * The `'url'` events emitted by plugins are forwarded with the active
29
+ * `taskId` so the pool can route them back to the correct caller's emitter.
30
+ * @see {@link ./worker-pool.ts!WorkerPool} for the main-thread counterpart
24
31
  * @see {@link ./runner.ts!runner} for the actual module loading logic
25
32
  * @module
26
33
  */
27
- import { parentPort, workerData } from 'node:worker_threads';
34
+ import { parentPort } from 'node:worker_threads';
28
35
  import { UrlEventBus } from '../url-event-bus.js';
29
36
  import { runner } from './runner.js';
30
- const data = workerData;
37
+ /** Identifier for the task currently being processed; used to tag forwarded `'url'` events. */
38
+ let currentTaskId = null;
39
+ /** Long-lived event bus that forwards `'url'` events from plugins to the pool. */
31
40
  const emitter = new UrlEventBus();
32
- /**
33
- * Forward URL discovery events from the plugin to the main thread.
34
- * The main thread's {@link ../url-event-bus.ts!UrlEventBus} re-emits these
35
- * so that the orchestrator can track discovered URLs.
36
- */
37
41
  emitter.on('url', (url) => {
38
42
  if (!parentPort) {
39
43
  throw new Error('Use in worker thread');
40
44
  }
45
+ if (currentTaskId === null) {
46
+ return;
47
+ }
41
48
  parentPort.postMessage({
42
49
  type: 'url',
50
+ taskId: currentTaskId,
43
51
  url,
44
52
  });
45
53
  });
46
- try {
47
- const result = await runner(data, emitter);
48
- if (!parentPort) {
49
- throw new Error('Use in worker thread');
50
- }
51
- parentPort.postMessage({
52
- type: 'finish',
53
- result,
54
- });
54
+ if (!parentPort) {
55
+ throw new Error('Use in worker thread');
55
56
  }
56
- catch (error) {
57
- if (!parentPort) {
58
- throw new Error('Use in worker thread');
57
+ parentPort.on('message', (message) => {
58
+ if (!message || typeof message !== 'object') {
59
+ return;
60
+ }
61
+ const msg = message;
62
+ if (msg.type === 'shutdown') {
63
+ // Defer to the next tick so any in-flight `postMessage` from the most
64
+ // recent task has a chance to flush before the worker exits.
65
+ setImmediate(() => process.exit(0));
66
+ return;
67
+ }
68
+ if (msg.type === 'task' && typeof msg.taskId === 'number' && msg.data) {
69
+ void runTask(msg.taskId, msg.data);
70
+ }
71
+ });
72
+ /**
73
+ * Executes a single task and posts the result back to the pool.
74
+ *
75
+ * Errors are caught and reported via the `error` field on the `result` message
76
+ * so the pool can reject the originating Promise without the worker crashing.
77
+ * @param taskId - Identifier echoed back so the pool can match the response.
78
+ * @param data - Payload from the pool, including `filePath` consumed by `runner()`.
79
+ */
80
+ async function runTask(taskId, data) {
81
+ currentTaskId = taskId;
82
+ try {
83
+ const result = await runner(data, emitter);
84
+ if (!parentPort) {
85
+ throw new Error('Use in worker thread');
86
+ }
87
+ parentPort.postMessage({
88
+ type: 'result',
89
+ taskId,
90
+ result,
91
+ });
92
+ }
93
+ catch (error) {
94
+ if (!parentPort) {
95
+ throw new Error('Use in worker thread');
96
+ }
97
+ const message = error instanceof Error ? error.message : String(error);
98
+ parentPort.postMessage({
99
+ type: 'result',
100
+ taskId,
101
+ result: null,
102
+ error: message,
103
+ });
104
+ }
105
+ finally {
106
+ currentTaskId = null;
59
107
  }
60
- const message = error instanceof Error ? error.message : String(error);
61
- // eslint-disable-next-line no-console
62
- console.error(`[worker] Fatal error: ${message}`);
63
- parentPort.postMessage({
64
- type: 'finish',
65
- result: null,
66
- error: message,
67
- });
68
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitpicker/core",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Plugin-based page analysis engine for Nitpicker",
5
5
  "author": "D-ZERO",
6
6
  "license": "Apache-2.0",
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "@d-zero/dealer": "1.7.0",
31
31
  "@d-zero/shared": "0.20.1",
32
- "@nitpicker/crawler": "0.6.3",
33
- "@nitpicker/types": "0.6.3",
32
+ "@nitpicker/crawler": "0.7.0",
33
+ "@nitpicker/types": "0.7.0",
34
34
  "ansi-colors": "4.1.3",
35
35
  "cosmiconfig": "9.0.0",
36
36
  "jsdom": "28.1.0"
37
37
  },
38
- "gitHead": "840d4c33a2d0ea9469d511b4f74a69f7fb38ee2b"
38
+ "gitHead": "3b4c33adf8af69a46a85faf8c04eddb5ab563972"
39
39
  }
@@ -1,12 +0,0 @@
1
- /**
2
- * Walks up from the current module's directory to locate the parent
3
- * `@nitpicker` directory in the file system.
4
- *
5
- * This is used by plugin discovery and label resolution to find
6
- * where `@nitpicker/*` packages are installed on disk. The traversal
7
- * starts from `import.meta.url` of the calling module (i.e. this file,
8
- * which lives inside `@nitpicker/core`) and walks up until it finds
9
- * a directory named `@nitpicker`.
10
- * @returns Absolute path to the `@nitpicker` directory, or `null` if not found.
11
- */
12
- export declare function findNitpickerModulesDir(): string | null;
@@ -1,23 +0,0 @@
1
- import path from 'node:path';
2
- /**
3
- * Walks up from the current module's directory to locate the parent
4
- * `@nitpicker` directory in the file system.
5
- *
6
- * This is used by plugin discovery and label resolution to find
7
- * where `@nitpicker/*` packages are installed on disk. The traversal
8
- * starts from `import.meta.url` of the calling module (i.e. this file,
9
- * which lives inside `@nitpicker/core`) and walks up until it finds
10
- * a directory named `@nitpicker`.
11
- * @returns Absolute path to the `@nitpicker` directory, or `null` if not found.
12
- */
13
- export function findNitpickerModulesDir() {
14
- const __filename = new URL(import.meta.url).pathname;
15
- let dir = path.dirname(__filename);
16
- while (dir !== path.dirname(dir)) {
17
- if (path.basename(dir) === '@nitpicker') {
18
- return dir;
19
- }
20
- dir = path.dirname(dir);
21
- }
22
- return null;
23
- }
@@ -1,9 +0,0 @@
1
- import type { StartAction } from '../types.js';
2
- declare const _default: {
3
- audit: {
4
- start: import("typescript-fsa").ActionCreator<StartAction>;
5
- log: import("typescript-fsa").ActionCreator<string>;
6
- finish: import("typescript-fsa").ActionCreator<void>;
7
- };
8
- };
9
- export default _default;
@@ -1,9 +0,0 @@
1
- import { actionCreatorFactory } from 'typescript-fsa';
2
- const auditActionCreator = actionCreatorFactory('@@hook/audit');
3
- export default {
4
- audit: {
5
- start: auditActionCreator('START'),
6
- log: auditActionCreator('LOG'),
7
- finish: auditActionCreator('FINISH'),
8
- },
9
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,34 +0,0 @@
1
- // Run on child_process
2
- import { Archive } from '@nitpicker/crawler';
3
- import { isType } from 'typescript-fsa';
4
- import actions from './actions.js';
5
- const crateTable = (headers, data, options) => ({
6
- headers,
7
- data,
8
- options,
9
- });
10
- process.on('message', async (action) => {
11
- if (isType(action, actions.audit.start)) {
12
- const mod = await import(action.payload.hookModulePath);
13
- const pluginHook = mod.default;
14
- const hook = await pluginHook(action.payload.settings || {}, action.payload.configFilePath);
15
- if (hook.audit) {
16
- const archive = await Archive.connect(action.payload.archiveTempDir, action.payload.hookModuleName);
17
- const pluginReport = await hook.audit(archive, (log) => {
18
- if (process.send) {
19
- process.send(actions.audit.log(log));
20
- }
21
- }, action.payload.configFilePath, crateTable);
22
- if (pluginReport) {
23
- const data = {
24
- name: action.payload.hookModuleName,
25
- ...pluginReport,
26
- };
27
- await archive.setData('reports', data);
28
- }
29
- }
30
- if (process.send) {
31
- process.send(actions.audit.finish());
32
- }
33
- }
34
- });
@@ -1 +0,0 @@
1
- export { definePlugin } from './define-plugin.js';
@@ -1 +0,0 @@
1
- export { definePlugin } from './define-plugin.js';
@@ -1,10 +0,0 @@
1
- import type { StartAction } from '../types.js';
2
- import type { ProcessLogger } from '@nitpicker/types';
3
- import actions from './actions.js';
4
- /**
5
- *
6
- * @param phase
7
- * @param startActionParam
8
- * @param log
9
- */
10
- export declare function runner(phase: keyof typeof actions, startActionParam: StartAction, log: ProcessLogger): Promise<void>;
@@ -1,32 +0,0 @@
1
- import childProcess from 'node:child_process';
2
- import path from 'node:path';
3
- import { isType } from 'typescript-fsa';
4
- import actions from './actions.js';
5
- const __filename = new URL(import.meta.url).pathname;
6
- const __dirname = path.dirname(__filename);
7
- /**
8
- *
9
- * @param phase
10
- * @param startActionParam
11
- * @param log
12
- */
13
- export function runner(phase, startActionParam, log) {
14
- log('Fork process');
15
- const child = childProcess.fork(path.join(__dirname, 'child-process'));
16
- const actionOnPhase = actions[phase];
17
- const session = new Promise((resolve) => {
18
- child.on('message', (action) => {
19
- if (isType(action, actionOnPhase.log)) {
20
- log(action.payload);
21
- }
22
- if (isType(action, actionOnPhase.finish)) {
23
- child.disconnect();
24
- log('Finished');
25
- resolve();
26
- }
27
- });
28
- });
29
- child.send(actionOnPhase.start(startActionParam));
30
- log('Started');
31
- return session;
32
- }
package/lib/index.d.ts DELETED
@@ -1,5 +0,0 @@
1
- export { Nitpicker } from './nitpicker.js';
2
- export { definePlugin } from './hooks/index.js';
3
- export { readPluginLabels } from './read-plugin-labels.js';
4
- export { runInWorker } from './worker/run-in-worker.js';
5
- export * from './types.js';
package/lib/index.js DELETED
@@ -1,5 +0,0 @@
1
- export { Nitpicker } from './nitpicker.js';
2
- export { definePlugin } from './hooks/index.js';
3
- export { readPluginLabels } from './read-plugin-labels.js';
4
- export { runInWorker } from './worker/run-in-worker.js';
5
- export * from './types.js';