@nitpicker/core 0.23.1 → 0.25.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.
Files changed (83) hide show
  1. package/README.md +165 -35
  2. package/lib/assert-u32-id.d.ts +15 -0
  3. package/lib/assert-u32-id.js +22 -0
  4. package/lib/check-native-executor-config.d.ts +21 -0
  5. package/lib/check-native-executor-config.js +24 -0
  6. package/lib/compute-alias-keys.d.ts +28 -0
  7. package/lib/compute-alias-keys.js +30 -0
  8. package/lib/compute-body-hash.d.ts +22 -0
  9. package/lib/compute-body-hash.js +25 -0
  10. package/lib/compute-url-sort-ranks.d.ts +28 -0
  11. package/lib/compute-url-sort-ranks.js +49 -0
  12. package/lib/create-isolated-cluster-run.d.ts +35 -0
  13. package/lib/create-isolated-cluster-run.js +52 -0
  14. package/lib/create-template-classification-run.d.ts +49 -0
  15. package/lib/create-template-classification-run.js +386 -0
  16. package/lib/group-alias-candidates.d.ts +40 -0
  17. package/lib/group-alias-candidates.js +104 -0
  18. package/lib/load-native-binding.d.ts +26 -0
  19. package/lib/load-native-binding.js +152 -0
  20. package/lib/match-js-license-comment-signal.d.ts +19 -0
  21. package/lib/match-js-license-comment-signal.js +21 -0
  22. package/lib/match-selector-html.d.ts +41 -0
  23. package/lib/match-selector-html.js +42 -0
  24. package/lib/match-selector-snapshots.d.ts +40 -0
  25. package/lib/match-selector-snapshots.js +57 -0
  26. package/lib/match-structural-technology-signals.d.ts +23 -0
  27. package/lib/match-structural-technology-signals.js +25 -0
  28. package/lib/max-decoded-snapshot-bytes.d.ts +22 -0
  29. package/lib/max-decoded-snapshot-bytes.js +35 -0
  30. package/lib/native-binding.d.ts +9 -0
  31. package/lib/native-binding.js +9 -0
  32. package/lib/pack-stored-snapshots.d.ts +23 -0
  33. package/lib/pack-stored-snapshots.js +53 -0
  34. package/lib/run-with-signal.d.ts +27 -0
  35. package/lib/run-with-signal.js +54 -0
  36. package/lib/search-html-document.d.ts +32 -0
  37. package/lib/search-html-document.js +31 -0
  38. package/lib/search-html-snapshots.d.ts +46 -0
  39. package/lib/search-html-snapshots.js +54 -0
  40. package/lib/template-block-sample-size.d.ts +13 -0
  41. package/lib/template-block-sample-size.js +14 -0
  42. package/lib/template-exact-page-limit.d.ts +18 -0
  43. package/lib/template-exact-page-limit.js +19 -0
  44. package/lib/template-memory-budget.d.ts +16 -0
  45. package/lib/template-memory-budget.js +17 -0
  46. package/lib/to-native-selector-plan.d.ts +16 -0
  47. package/lib/to-native-selector-plan.js +40 -0
  48. package/lib/to-u32-ids.d.ts +14 -0
  49. package/lib/to-u32-ids.js +23 -0
  50. package/lib/types.d.ts +736 -248
  51. package/lib/validate-search-pattern.d.ts +25 -0
  52. package/lib/validate-search-pattern.js +27 -0
  53. package/package.json +83 -16
  54. package/lib/core.d.ts +0 -4
  55. package/lib/core.js +0 -4
  56. package/lib/discover-analyze-plugins.d.ts +0 -14
  57. package/lib/discover-analyze-plugins.js +0 -32
  58. package/lib/get-table-cache-root.d.ts +0 -18
  59. package/lib/get-table-cache-root.js +0 -22
  60. package/lib/hooks/define-plugin.d.ts +0 -68
  61. package/lib/hooks/define-plugin.js +0 -69
  62. package/lib/import-modules.d.ts +0 -24
  63. package/lib/import-modules.js +0 -38
  64. package/lib/load-plugin-settings.d.ts +0 -44
  65. package/lib/load-plugin-settings.js +0 -125
  66. package/lib/nitpicker.d.ts +0 -150
  67. package/lib/nitpicker.js +0 -437
  68. package/lib/page-analysis-worker.d.ts +0 -58
  69. package/lib/page-analysis-worker.js +0 -68
  70. package/lib/read-plugin-labels.d.ts +0 -15
  71. package/lib/read-plugin-labels.js +0 -30
  72. package/lib/table.d.ts +0 -75
  73. package/lib/table.js +0 -132
  74. package/lib/url-event-bus.d.ts +0 -33
  75. package/lib/url-event-bus.js +0 -21
  76. package/lib/worker/runner.d.ts +0 -25
  77. package/lib/worker/runner.js +0 -31
  78. package/lib/worker/types.d.ts +0 -23
  79. package/lib/worker/types.js +0 -1
  80. package/lib/worker/worker-pool.d.ts +0 -82
  81. package/lib/worker/worker-pool.js +0 -278
  82. package/lib/worker/worker.d.ts +0 -34
  83. package/lib/worker/worker.js +0 -108
@@ -0,0 +1,25 @@
1
+ import type { NativeSearchPattern } from './types.js';
2
+ /**
3
+ * Compiles a `query search-html` pattern and reports whether it matches the
4
+ * empty string.
5
+ *
6
+ * The pattern is a literal, or a regular expression in the syntax of Rust's
7
+ * `regex` crate: backreferences and look-around are not supported, and
8
+ * matching runs in time linear in the document. The addon keeps the last
9
+ * compiled pattern, so a search that follows with the same pattern does not
10
+ * compile it again; this is for refusing a pattern before the archive is
11
+ * opened.
12
+ * @param pattern - The pattern and its flags.
13
+ * @returns Whether the pattern matches the empty string, which `search-html` rejects
14
+ * because it would match at every position of every snapshot.
15
+ * @throws {Error} With code `InvalidArg` and the regex engine's message if the
16
+ * pattern is not valid, uses an unsupported construct, or compiles to more than
17
+ * the engine's size limit.
18
+ * @example
19
+ * ```ts
20
+ * validateSearchPattern({ source: 'a+', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // false
21
+ * validateSearchPattern({ source: 'a*', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // true
22
+ * validateSearchPattern({ source: '(?=a)a', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // throws
23
+ * ```
24
+ */
25
+ export declare function validateSearchPattern(pattern: NativeSearchPattern): boolean;
@@ -0,0 +1,27 @@
1
+ import { nativeBinding } from './native-binding.js';
2
+ /**
3
+ * Compiles a `query search-html` pattern and reports whether it matches the
4
+ * empty string.
5
+ *
6
+ * The pattern is a literal, or a regular expression in the syntax of Rust's
7
+ * `regex` crate: backreferences and look-around are not supported, and
8
+ * matching runs in time linear in the document. The addon keeps the last
9
+ * compiled pattern, so a search that follows with the same pattern does not
10
+ * compile it again; this is for refusing a pattern before the archive is
11
+ * opened.
12
+ * @param pattern - The pattern and its flags.
13
+ * @returns Whether the pattern matches the empty string, which `search-html` rejects
14
+ * because it would match at every position of every snapshot.
15
+ * @throws {Error} With code `InvalidArg` and the regex engine's message if the
16
+ * pattern is not valid, uses an unsupported construct, or compiles to more than
17
+ * the engine's size limit.
18
+ * @example
19
+ * ```ts
20
+ * validateSearchPattern({ source: 'a+', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // false
21
+ * validateSearchPattern({ source: 'a*', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // true
22
+ * validateSearchPattern({ source: '(?=a)a', literal: false, ignoreCase: false, multiLine: false, dotAll: false }); // throws
23
+ * ```
24
+ */
25
+ export function validateSearchPattern(pattern) {
26
+ return nativeBinding.validateSearchPattern(pattern);
27
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nitpicker/core",
3
- "version": "0.23.1",
4
- "description": "Plugin-based page analysis engine for Nitpicker",
3
+ "version": "0.25.0",
4
+ "description": "Native (Rust, Node-API) compute core for Nitpicker",
5
5
  "author": "D-ZERO",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
@@ -20,23 +20,90 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "exports": {
23
- ".": {
24
- "import": "./lib/core.js",
25
- "types": "./lib/core.d.ts"
23
+ "./check-native-executor-config": {
24
+ "import": "./lib/check-native-executor-config.js",
25
+ "types": "./lib/check-native-executor-config.d.ts"
26
+ },
27
+ "./compute-alias-keys": {
28
+ "import": "./lib/compute-alias-keys.js",
29
+ "types": "./lib/compute-alias-keys.d.ts"
30
+ },
31
+ "./compute-body-hash": {
32
+ "import": "./lib/compute-body-hash.js",
33
+ "types": "./lib/compute-body-hash.d.ts"
34
+ },
35
+ "./compute-url-sort-ranks": {
36
+ "import": "./lib/compute-url-sort-ranks.js",
37
+ "types": "./lib/compute-url-sort-ranks.d.ts"
38
+ },
39
+ "./create-isolated-cluster-run": {
40
+ "import": "./lib/create-isolated-cluster-run.js",
41
+ "types": "./lib/create-isolated-cluster-run.d.ts"
42
+ },
43
+ "./create-template-classification-run": {
44
+ "import": "./lib/create-template-classification-run.js",
45
+ "types": "./lib/create-template-classification-run.d.ts"
46
+ },
47
+ "./group-alias-candidates": {
48
+ "import": "./lib/group-alias-candidates.js",
49
+ "types": "./lib/group-alias-candidates.d.ts"
50
+ },
51
+ "./match-js-license-comment-signal": {
52
+ "import": "./lib/match-js-license-comment-signal.js",
53
+ "types": "./lib/match-js-license-comment-signal.d.ts"
54
+ },
55
+ "./match-selector-html": {
56
+ "import": "./lib/match-selector-html.js",
57
+ "types": "./lib/match-selector-html.d.ts"
58
+ },
59
+ "./match-selector-snapshots": {
60
+ "import": "./lib/match-selector-snapshots.js",
61
+ "types": "./lib/match-selector-snapshots.d.ts"
62
+ },
63
+ "./match-structural-technology-signals": {
64
+ "import": "./lib/match-structural-technology-signals.js",
65
+ "types": "./lib/match-structural-technology-signals.d.ts"
66
+ },
67
+ "./max-decoded-snapshot-bytes": {
68
+ "import": "./lib/max-decoded-snapshot-bytes.js",
69
+ "types": "./lib/max-decoded-snapshot-bytes.d.ts"
70
+ },
71
+ "./search-html-document": {
72
+ "import": "./lib/search-html-document.js",
73
+ "types": "./lib/search-html-document.d.ts"
74
+ },
75
+ "./search-html-snapshots": {
76
+ "import": "./lib/search-html-snapshots.js",
77
+ "types": "./lib/search-html-snapshots.d.ts"
78
+ },
79
+ "./validate-search-pattern": {
80
+ "import": "./lib/validate-search-pattern.js",
81
+ "types": "./lib/validate-search-pattern.d.ts"
82
+ },
83
+ "./template-exact-page-limit": {
84
+ "import": "./lib/template-exact-page-limit.js",
85
+ "types": "./lib/template-exact-page-limit.d.ts"
86
+ },
87
+ "./template-memory-budget": {
88
+ "import": "./lib/template-memory-budget.js",
89
+ "types": "./lib/template-memory-budget.d.ts"
90
+ },
91
+ "./template-block-sample-size": {
92
+ "import": "./lib/template-block-sample-size.js",
93
+ "types": "./lib/template-block-sample-size.d.ts"
94
+ },
95
+ "./types": {
96
+ "import": "./lib/types.js",
97
+ "types": "./lib/types.d.ts"
26
98
  }
27
99
  },
28
100
  "scripts": {
29
- "build": "tsc",
30
- "clean": "tsc --build --clean"
101
+ "build": "node scripts/build-native.mjs && tsc",
102
+ "clean": "node scripts/build-native.mjs --clean && tsc --build --clean"
31
103
  },
32
- "dependencies": {
33
- "@d-zero/dealer": "1.14.0",
34
- "@d-zero/shared": "0.23.0",
35
- "@nitpicker/crawler": "0.23.0",
36
- "@nitpicker/types": "0.23.0",
37
- "ansi-colors": "4.1.3",
38
- "cosmiconfig": "10.0.1",
39
- "jsdom": "30.0.1"
104
+ "optionalDependencies": {
105
+ "@nitpicker/core-darwin-arm64": "0.25.0",
106
+ "@nitpicker/core-linux-x64-gnu": "0.25.0"
40
107
  },
41
- "gitHead": "f86ab90a28332dc431324780d8298741739db847"
108
+ "gitHead": "fb258acf112366504c6dbb44b69a25bbc528fa30"
42
109
  }
package/lib/core.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { Nitpicker } from './nitpicker.js';
2
- export { definePlugin } from './hooks/define-plugin.js';
3
- export { readPluginLabels } from './read-plugin-labels.js';
4
- export * from './types.js';
package/lib/core.js DELETED
@@ -1,4 +0,0 @@
1
- export { Nitpicker } from './nitpicker.js';
2
- export { definePlugin } from './hooks/define-plugin.js';
3
- export { readPluginLabels } from './read-plugin-labels.js';
4
- export * from './types.js';
@@ -1,14 +0,0 @@
1
- import type { Plugin } from './types.js';
2
- /**
3
- * Returns the standard set of `@nitpicker/analyze-*` plugins
4
- * as {@link Plugin} entries with default (empty) settings.
5
- *
6
- * This is used as a fallback when no configuration file is found,
7
- * allowing `nitpicker analyze` to work out of the box without
8
- * requiring a `.nitpickerrc` file.
9
- *
10
- * All analyze plugins are treated as standard packages bundled
11
- * with the CLI, so no filesystem scanning is necessary.
12
- * @returns Array of standard analyze plugins with empty settings.
13
- */
14
- export declare function discoverAnalyzePlugins(): Plugin[];
@@ -1,32 +0,0 @@
1
- /**
2
- * Standard analyze plugin module names bundled with the Nitpicker CLI.
3
- *
4
- * These are treated as built-in plugins and always available without
5
- * explicit configuration.
6
- */
7
- const STANDARD_ANALYZE_PLUGINS = [
8
- '@nitpicker/analyze-axe',
9
- '@nitpicker/analyze-markuplint',
10
- '@nitpicker/analyze-search',
11
- '@nitpicker/analyze-textlint',
12
- ];
13
- /**
14
- * Returns the standard set of `@nitpicker/analyze-*` plugins
15
- * as {@link Plugin} entries with default (empty) settings.
16
- *
17
- * This is used as a fallback when no configuration file is found,
18
- * allowing `nitpicker analyze` to work out of the box without
19
- * requiring a `.nitpickerrc` file.
20
- *
21
- * All analyze plugins are treated as standard packages bundled
22
- * with the CLI, so no filesystem scanning is necessary.
23
- * @returns Array of standard analyze plugins with empty settings.
24
- */
25
- export function discoverAnalyzePlugins() {
26
- return STANDARD_ANALYZE_PLUGINS.map((moduleName) => ({
27
- name: moduleName,
28
- module: moduleName,
29
- configFilePath: '',
30
- settings: {},
31
- }));
32
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Resolve the storage location for the analyze command's scratch `table`
3
- * cache (per-page axe results, cleared at the start of every `analyze` run).
4
- *
5
- * Defined as a child of {@link getArchiveCacheRoot} so that
6
- * `NITPICKER_TAR_CACHE_DIR` — which already redirects the tar-extraction
7
- * cache — consistently redirects this cache too, instead of the two caches
8
- * silently splitting across different roots.
9
- * @returns Absolute path to the `table` cache directory. The `Cache` class
10
- * creates a further subdirectory under this path named after its `name`
11
- * argument.
12
- * @example
13
- * ```ts
14
- * const cache = new Cache('nitpicker-axe', getTableCacheRoot());
15
- * await cache.clear();
16
- * ```
17
- */
18
- export declare function getTableCacheRoot(): string;
@@ -1,22 +0,0 @@
1
- import path from 'node:path';
2
- import { getArchiveCacheRoot } from '@nitpicker/crawler';
3
- /**
4
- * Resolve the storage location for the analyze command's scratch `table`
5
- * cache (per-page axe results, cleared at the start of every `analyze` run).
6
- *
7
- * Defined as a child of {@link getArchiveCacheRoot} so that
8
- * `NITPICKER_TAR_CACHE_DIR` — which already redirects the tar-extraction
9
- * cache — consistently redirects this cache too, instead of the two caches
10
- * silently splitting across different roots.
11
- * @returns Absolute path to the `table` cache directory. The `Cache` class
12
- * creates a further subdirectory under this path named after its `name`
13
- * argument.
14
- * @example
15
- * ```ts
16
- * const cache = new Cache('nitpicker-axe', getTableCacheRoot());
17
- * await cache.clear();
18
- * ```
19
- */
20
- export function getTableCacheRoot() {
21
- return path.join(getArchiveCacheRoot(), 'table');
22
- }
@@ -1,68 +0,0 @@
1
- import type { PluginFactory } from '../types.js';
2
- /**
3
- * Identity function that provides full type inference for analyze plugin definitions.
4
- *
5
- * This function does **nothing at runtime** - it returns its argument unchanged.
6
- * Its sole purpose is to give TypeScript enough context to infer the generic
7
- * parameters `O` (options type) and `T` (column key union) from the plugin
8
- * implementation, without requiring explicit type annotations at the call site.
9
- *
10
- * ## Why an identity function?
11
- *
12
- * Without this wrapper, plugin authors would need to manually annotate the
13
- * `PluginFactory` type with both generic parameters:
14
- *
15
- * ```ts
16
- * // Without definePlugin - verbose and error-prone
17
- * const factory: PluginFactory<{ lang: string }, 'score' | 'details'> = (options) => { ... };
18
- * export default factory;
19
- * ```
20
- *
21
- * With `definePlugin`, TypeScript infers everything from the function body:
22
- *
23
- * ```ts
24
- * // With definePlugin - concise and type-safe
25
- * export default definePlugin(async (options: { lang: string }) => {
26
- * return {
27
- * label: 'Custom Analysis',
28
- * headers: { score: 'Score', details: 'Details' },
29
- * async eachPage({ window }) {
30
- * return { page: { score: { value: 100 }, details: { value: 'OK' } } };
31
- * },
32
- * };
33
- * });
34
- * ```
35
- *
36
- * This pattern is sometimes called a "satisfies helper" or "builder pattern"
37
- * and is common in TypeScript libraries that need generic inference from
38
- * function arguments (cf. Zod's `z.object()`, tRPC's `router()`).
39
- * @template O - Shape of the plugin's settings/options from the config file.
40
- * @template T - String literal union of column keys contributed by this plugin.
41
- * @param factory - The plugin factory function to pass through.
42
- * @returns The same function, unchanged.
43
- * @example
44
- * ```ts
45
- * import { definePlugin } from '@nitpicker/core';
46
- *
47
- * type Options = { keywords: string[] };
48
- *
49
- * export default definePlugin(async (options: Options) => {
50
- * return {
51
- * label: 'キーワード検索',
52
- * headers: { found: 'Keywords Found', count: 'Match Count' },
53
- * async eachPage({ html }) {
54
- * const matches = options.keywords.filter(k => html.includes(k));
55
- * return {
56
- * page: {
57
- * found: { value: matches.join(', ') },
58
- * count: { value: matches.length },
59
- * },
60
- * };
61
- * },
62
- * };
63
- * });
64
- * ```
65
- * @see {@link ../types.ts!PluginFactory} for the function signature being wrapped
66
- * @see {@link ../types.ts!AnalyzePlugin} for the returned plugin interface
67
- */
68
- export declare function definePlugin<O, T extends string = string>(factory: PluginFactory<O, T>): PluginFactory<O, T>;
@@ -1,69 +0,0 @@
1
- /**
2
- * Identity function that provides full type inference for analyze plugin definitions.
3
- *
4
- * This function does **nothing at runtime** - it returns its argument unchanged.
5
- * Its sole purpose is to give TypeScript enough context to infer the generic
6
- * parameters `O` (options type) and `T` (column key union) from the plugin
7
- * implementation, without requiring explicit type annotations at the call site.
8
- *
9
- * ## Why an identity function?
10
- *
11
- * Without this wrapper, plugin authors would need to manually annotate the
12
- * `PluginFactory` type with both generic parameters:
13
- *
14
- * ```ts
15
- * // Without definePlugin - verbose and error-prone
16
- * const factory: PluginFactory<{ lang: string }, 'score' | 'details'> = (options) => { ... };
17
- * export default factory;
18
- * ```
19
- *
20
- * With `definePlugin`, TypeScript infers everything from the function body:
21
- *
22
- * ```ts
23
- * // With definePlugin - concise and type-safe
24
- * export default definePlugin(async (options: { lang: string }) => {
25
- * return {
26
- * label: 'Custom Analysis',
27
- * headers: { score: 'Score', details: 'Details' },
28
- * async eachPage({ window }) {
29
- * return { page: { score: { value: 100 }, details: { value: 'OK' } } };
30
- * },
31
- * };
32
- * });
33
- * ```
34
- *
35
- * This pattern is sometimes called a "satisfies helper" or "builder pattern"
36
- * and is common in TypeScript libraries that need generic inference from
37
- * function arguments (cf. Zod's `z.object()`, tRPC's `router()`).
38
- * @template O - Shape of the plugin's settings/options from the config file.
39
- * @template T - String literal union of column keys contributed by this plugin.
40
- * @param factory - The plugin factory function to pass through.
41
- * @returns The same function, unchanged.
42
- * @example
43
- * ```ts
44
- * import { definePlugin } from '@nitpicker/core';
45
- *
46
- * type Options = { keywords: string[] };
47
- *
48
- * export default definePlugin(async (options: Options) => {
49
- * return {
50
- * label: 'キーワード検索',
51
- * headers: { found: 'Keywords Found', count: 'Match Count' },
52
- * async eachPage({ html }) {
53
- * const matches = options.keywords.filter(k => html.includes(k));
54
- * return {
55
- * page: {
56
- * found: { value: matches.join(', ') },
57
- * count: { value: matches.length },
58
- * },
59
- * };
60
- * },
61
- * };
62
- * });
63
- * ```
64
- * @see {@link ../types.ts!PluginFactory} for the function signature being wrapped
65
- * @see {@link ../types.ts!AnalyzePlugin} for the returned plugin interface
66
- */
67
- export function definePlugin(factory) {
68
- return factory;
69
- }
@@ -1,24 +0,0 @@
1
- import type { AnalyzePlugin, Plugin } from './types.js';
2
- /**
3
- * Dynamically imports and initializes all analyze plugin modules.
4
- *
5
- * For each plugin in the list:
6
- * 1. Validates that `plugin.module` starts with `@nitpicker/analyze-`
7
- * 2. Calls `import(plugin.module)` to load the npm package
8
- * 3. Invokes the module's default export (a `PluginFactory` factory)
9
- * with the plugin's `settings`
10
- * 4. Returns the resulting `AnalyzePlugin` instance
11
- *
12
- * All plugins are loaded in parallel via `Promise.all` for performance.
13
- *
14
- * This function is called both in the main thread (for `headers` and `eachUrl`)
15
- * and inside each Worker thread (for `eachPage`). The Worker-side call is
16
- * necessary because plugin modules may not be transferable across threads.
17
- * @param plugins - Array of plugin definitions from the resolved config.
18
- * @returns Array of initialized `AnalyzePlugin` instances, in the same order.
19
- * @throws {Error} If any plugin module name does not start with `@nitpicker/analyze-`.
20
- * @see {@link ./types.ts} for the `PluginFactory` factory function signature
21
- * @see {@link ./page-analysis-worker.ts} for Worker-side usage
22
- * @see {@link ./nitpicker.ts!Nitpicker.analyze} for main-thread usage
23
- */
24
- export declare function importModules(plugins: Plugin[]): Promise<AnalyzePlugin<string>[]>;
@@ -1,38 +0,0 @@
1
- /**
2
- * Allowed prefix for analyze plugin module names.
3
- * Only modules starting with this prefix are permitted to be dynamically imported.
4
- */
5
- const ALLOWED_PREFIX = '@nitpicker/analyze-';
6
- /**
7
- * Dynamically imports and initializes all analyze plugin modules.
8
- *
9
- * For each plugin in the list:
10
- * 1. Validates that `plugin.module` starts with `@nitpicker/analyze-`
11
- * 2. Calls `import(plugin.module)` to load the npm package
12
- * 3. Invokes the module's default export (a `PluginFactory` factory)
13
- * with the plugin's `settings`
14
- * 4. Returns the resulting `AnalyzePlugin` instance
15
- *
16
- * All plugins are loaded in parallel via `Promise.all` for performance.
17
- *
18
- * This function is called both in the main thread (for `headers` and `eachUrl`)
19
- * and inside each Worker thread (for `eachPage`). The Worker-side call is
20
- * necessary because plugin modules may not be transferable across threads.
21
- * @param plugins - Array of plugin definitions from the resolved config.
22
- * @returns Array of initialized `AnalyzePlugin` instances, in the same order.
23
- * @throws {Error} If any plugin module name does not start with `@nitpicker/analyze-`.
24
- * @see {@link ./types.ts} for the `PluginFactory` factory function signature
25
- * @see {@link ./page-analysis-worker.ts} for Worker-side usage
26
- * @see {@link ./nitpicker.ts!Nitpicker.analyze} for main-thread usage
27
- */
28
- export async function importModules(plugins) {
29
- const analyzeMods = await Promise.all(plugins.map(async (plugin) => {
30
- if (!plugin.module.startsWith(ALLOWED_PREFIX)) {
31
- throw new Error(`Unauthorized plugin module: "${plugin.module}". Plugin modules must start with "${ALLOWED_PREFIX}".`);
32
- }
33
- const mod = await import(plugin.module);
34
- const factory = mod.default;
35
- return factory(plugin.settings);
36
- }));
37
- return analyzeMods;
38
- }
@@ -1,44 +0,0 @@
1
- import type { Config, PluginOverrides } from './types.js';
2
- /**
3
- * Loads the analyze plugin configuration from the user's config file.
4
- *
5
- * Uses cosmiconfig to search the filesystem from `process.cwd()` upward
6
- * for a Nitpicker configuration file. The external config format
7
- * (`ConfigJSON` from `@nitpicker/types`) is normalized into the internal
8
- * {@link Config} model:
9
- *
10
- * - `config.plugins.analyze` (object keyed by module name with settings)
11
- * is converted to an ordered `Plugin[]` array
12
- * - Boolean `true` settings are normalized to empty objects `{}`
13
- * - The config file path is attached to each plugin for relative path resolution
14
- * @param defaultConfig - Optional partial config to merge as defaults.
15
- * Plugin lists are concatenated (defaults first, then discovered plugins).
16
- * @param pluginOverrides - CLI-specified plugin setting overrides.
17
- * Values are shallow-merged into the matching plugin's settings,
18
- * with CLI values taking precedence over config-file values.
19
- * @returns Fully resolved {@link Config} with the `analyze` plugin list.
20
- * @example
21
- * ```ts
22
- * // .nitpickerrc.json
23
- * // {
24
- * // "plugins": {
25
- * // "analyze": {
26
- * // "@nitpicker/analyze-axe": { "lang": "ja" },
27
- * // "@nitpicker/analyze-markuplint": true
28
- * // }
29
- * // }
30
- * // }
31
- *
32
- * const config = await loadPluginSettings();
33
- * // config.analyze = [
34
- * // { name: '@nitpicker/analyze-axe', module: '@nitpicker/analyze-axe',
35
- * // configFilePath: '/path/to/.nitpickerrc.json', settings: { lang: 'ja' } },
36
- * // { name: '@nitpicker/analyze-markuplint', module: '@nitpicker/analyze-markuplint',
37
- * // configFilePath: '/path/to/.nitpickerrc.json', settings: {} },
38
- * // ]
39
- * ```
40
- * @see {@link ./types.ts!Config} for the output type
41
- * @see {@link ./types.ts!Plugin} for individual plugin entries
42
- * @see {@link ./types.ts!PluginOverrides} for CLI override support
43
- */
44
- export declare function loadPluginSettings(defaultConfig?: Partial<Config>, pluginOverrides?: PluginOverrides): Promise<Config>;
@@ -1,125 +0,0 @@
1
- import { cosmiconfig } from 'cosmiconfig';
2
- import { discoverAnalyzePlugins } from './discover-analyze-plugins.js';
3
- /**
4
- * The cosmiconfig module name used for config file discovery.
5
- * Searches for: `.nitpickerrc`, `.nitpickerrc.json`, `.nitpickerrc.yaml`,
6
- * `nitpicker.config.js`, `nitpicker.config.cjs`, or a `"nitpicker"` key
7
- * in `package.json`.
8
- */
9
- const MODULE_NAME = 'nitpicker';
10
- /**
11
- * Loads the analyze plugin configuration from the user's config file.
12
- *
13
- * Uses cosmiconfig to search the filesystem from `process.cwd()` upward
14
- * for a Nitpicker configuration file. The external config format
15
- * (`ConfigJSON` from `@nitpicker/types`) is normalized into the internal
16
- * {@link Config} model:
17
- *
18
- * - `config.plugins.analyze` (object keyed by module name with settings)
19
- * is converted to an ordered `Plugin[]` array
20
- * - Boolean `true` settings are normalized to empty objects `{}`
21
- * - The config file path is attached to each plugin for relative path resolution
22
- * @param defaultConfig - Optional partial config to merge as defaults.
23
- * Plugin lists are concatenated (defaults first, then discovered plugins).
24
- * @param pluginOverrides - CLI-specified plugin setting overrides.
25
- * Values are shallow-merged into the matching plugin's settings,
26
- * with CLI values taking precedence over config-file values.
27
- * @returns Fully resolved {@link Config} with the `analyze` plugin list.
28
- * @example
29
- * ```ts
30
- * // .nitpickerrc.json
31
- * // {
32
- * // "plugins": {
33
- * // "analyze": {
34
- * // "@nitpicker/analyze-axe": { "lang": "ja" },
35
- * // "@nitpicker/analyze-markuplint": true
36
- * // }
37
- * // }
38
- * // }
39
- *
40
- * const config = await loadPluginSettings();
41
- * // config.analyze = [
42
- * // { name: '@nitpicker/analyze-axe', module: '@nitpicker/analyze-axe',
43
- * // configFilePath: '/path/to/.nitpickerrc.json', settings: { lang: 'ja' } },
44
- * // { name: '@nitpicker/analyze-markuplint', module: '@nitpicker/analyze-markuplint',
45
- * // configFilePath: '/path/to/.nitpickerrc.json', settings: {} },
46
- * // ]
47
- * ```
48
- * @see {@link ./types.ts!Config} for the output type
49
- * @see {@link ./types.ts!Plugin} for individual plugin entries
50
- * @see {@link ./types.ts!PluginOverrides} for CLI override support
51
- */
52
- export async function loadPluginSettings(defaultConfig = {}, pluginOverrides = {}) {
53
- const explorer = cosmiconfig(MODULE_NAME);
54
- const result = await explorer.search();
55
- if (!result) {
56
- const defaultPlugins = defaultConfig.analyze || [];
57
- const plugins = defaultPlugins.length > 0 ? defaultPlugins : discoverAnalyzePlugins();
58
- return {
59
- analyze: applyPluginOverrides(plugins, pluginOverrides),
60
- };
61
- }
62
- const config = result.config;
63
- const { isEmpty, filepath } = result;
64
- if (!config || isEmpty) {
65
- const defaultPlugins = defaultConfig.analyze || [];
66
- const plugins = defaultPlugins.length > 0 ? defaultPlugins : discoverAnalyzePlugins();
67
- return {
68
- analyze: applyPluginOverrides(plugins, pluginOverrides),
69
- };
70
- }
71
- const analyzePlugins = [];
72
- if (config.plugins && config.plugins.analyze) {
73
- const moduleNames = Object.keys(config.plugins.analyze);
74
- for (const name of moduleNames) {
75
- if (!config.plugins.analyze[name]) {
76
- continue;
77
- }
78
- const settings = config.plugins.analyze[name] === true ? {} : config.plugins.analyze[name];
79
- analyzePlugins.push({
80
- name,
81
- module: name,
82
- configFilePath: filepath,
83
- settings,
84
- });
85
- }
86
- }
87
- const mergedPlugins = [...(defaultConfig.analyze || []), ...analyzePlugins];
88
- const finalPlugins = mergedPlugins.length > 0 ? mergedPlugins : discoverAnalyzePlugins();
89
- return {
90
- analyze: applyPluginOverrides(finalPlugins, pluginOverrides),
91
- };
92
- }
93
- /**
94
- * Applies CLI-specified overrides to plugin settings.
95
- *
96
- * For each plugin in the list, if there is a matching entry in
97
- * `overrides`, the override values are shallow-merged into the
98
- * plugin's existing settings. CLI values take precedence over
99
- * config-file values.
100
- *
101
- * **Limitation**: Merging is shallow (one level). Nested objects in
102
- * the override will replace the entire config-file value, not deep-merge.
103
- * @param plugins - The plugin list to apply overrides to.
104
- * @param overrides - CLI-specified plugin setting overrides.
105
- * @returns A new plugin array with overrides applied.
106
- */
107
- function applyPluginOverrides(plugins, overrides) {
108
- const overrideKeys = Object.keys(overrides);
109
- if (overrideKeys.length === 0) {
110
- return plugins;
111
- }
112
- return plugins.map((plugin) => {
113
- const override = overrides[plugin.module];
114
- if (!override) {
115
- return plugin;
116
- }
117
- return {
118
- ...plugin,
119
- settings: {
120
- ...plugin.settings,
121
- ...override,
122
- },
123
- };
124
- });
125
- }