license-checker-plugin 1.0.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/dist/LicensePluginCore.d.ts +84 -0
  4. package/dist/LicensePluginCore.js +148 -0
  5. package/dist/LicenseWebpackPlugin.d.ts +11 -0
  6. package/dist/LicenseWebpackPlugin.js +53 -0
  7. package/dist/Recorder.d.ts +14 -0
  8. package/dist/Recorder.js +46 -0
  9. package/dist/ViteLicensePlugin.d.ts +16 -0
  10. package/dist/ViteLicensePlugin.js +54 -0
  11. package/dist/checker/BuiltInLicenseChecker.d.ts +27 -0
  12. package/dist/checker/BuiltInLicenseChecker.js +311 -0
  13. package/dist/checker/LicenseCache.d.ts +9 -0
  14. package/dist/checker/LicenseCache.js +22 -0
  15. package/dist/checker/LicenseDatabase.d.ts +8 -0
  16. package/dist/checker/LicenseDatabase.js +52 -0
  17. package/dist/formatter/Formatter.d.ts +4 -0
  18. package/dist/formatter/Formatter.js +2 -0
  19. package/dist/formatter/HtmlFormatter.d.ts +6 -0
  20. package/dist/formatter/HtmlFormatter.js +53 -0
  21. package/dist/formatter/JsonFormatter.d.ts +5 -0
  22. package/dist/formatter/JsonFormatter.js +17 -0
  23. package/dist/formatter/MarkdownFormatter.d.ts +5 -0
  24. package/dist/formatter/MarkdownFormatter.js +18 -0
  25. package/dist/formatter/TxtFormatter.d.ts +12 -0
  26. package/dist/formatter/TxtFormatter.js +49 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +13 -0
  29. package/dist/model/LicenseBuildReport.d.ts +4 -0
  30. package/dist/model/LicenseBuildReport.js +2 -0
  31. package/dist/model/LicenseInfo.d.ts +17 -0
  32. package/dist/model/LicenseInfo.js +2 -0
  33. package/dist/model/PackageInfo.d.ts +13 -0
  34. package/dist/model/PackageInfo.js +2 -0
  35. package/dist/scanner/PackageResolver.d.ts +4 -0
  36. package/dist/scanner/PackageResolver.js +102 -0
  37. package/dist/scanner/PackageScanner.d.ts +9 -0
  38. package/dist/scanner/PackageScanner.js +56 -0
  39. package/dist/utils/fs.d.ts +3 -0
  40. package/dist/utils/fs.js +61 -0
  41. package/dist/utils/hash.d.ts +1 -0
  42. package/dist/utils/hash.js +7 -0
  43. package/dist/utils/path.d.ts +3 -0
  44. package/dist/utils/path.js +64 -0
  45. package/package.json +58 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Axetroy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # license-checker-plugin
2
+
3
+ A bundler-agnostic plugin that generates third-party license notices for packages actually bundled into your final build. Supports **webpack 5**, **Rspack**, and **Vite**.
4
+
5
+ ## Features
6
+
7
+ - Scans the bundler's module graph for used npm packages
8
+ - Reads license metadata using the built-in license checker (zero external dependencies)
9
+ - Emits TXT, JSON, Markdown, or HTML assets at build time
10
+ - Supports compliance rules with `onlyAllow` and `failOn`
11
+ - Filter by package name and/or license type
12
+ - Deduplicates repeated license text
13
+ - Works with webpack 5, Rspack, and Vite
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install license-checker-plugin
19
+ ```
20
+
21
+ For webpack/Rspack projects, ensure `webpack` is installed (peer dependency).
22
+ For Vite projects, ensure `vite` is installed (peer dependency).
23
+
24
+ ## Usage
25
+
26
+ ### webpack / Rspack
27
+
28
+ ```js
29
+ const { LicenseWebpackPlugin } = require('license-checker-plugin');
30
+
31
+ module.exports = {
32
+ mode: 'production',
33
+ plugins: [
34
+ new LicenseWebpackPlugin({
35
+ filename: 'third-party-licenses.txt',
36
+ format: 'txt',
37
+ includeLicenseText: true,
38
+ onlyAllow: ['MIT', 'Apache-2.0', 'BSD-3-Clause'],
39
+ }),
40
+ ],
41
+ };
42
+ ```
43
+
44
+ ### Vite
45
+
46
+ ```ts
47
+ // vite.config.ts
48
+ import { defineConfig } from 'vite';
49
+ import { viteLicensePlugin } from 'license-checker-plugin';
50
+
51
+ export default defineConfig({
52
+ plugins: [
53
+ viteLicensePlugin({
54
+ filename: 'third-party-licenses.json',
55
+ format: 'json',
56
+ includeLicenseText: true,
57
+ }),
58
+ ],
59
+ });
60
+ ```
61
+
62
+ ## Options
63
+
64
+ | Option | Type | Default | Description |
65
+ |---|---|---|---|
66
+ | `filename` | `string` | `licenses.txt` | Output asset name |
67
+ | `format` | `'txt' \| 'json' \| 'markdown' \| 'html'` | `txt` | Output format |
68
+ | `includeLicenseText` | `boolean` | `true` | Include license text when supported |
69
+ | `includeRepository` | `boolean` | `true` | Include repository URLs |
70
+ | `includeHomepage` | `boolean` | `true` | Include homepage URLs |
71
+ | `includeAuthor` | `boolean` | `true` | Include author/publisher details |
72
+ | `excludePackages` | `string[]` | `[]` | Exclude listed packages from output |
73
+ | `onlyAllow` | `string[]` | `[]` | Fail build when a used license is not allowed |
74
+ | `failOn` | `string[]` | `[]` | Fail build when a used license matches the list |
75
+ | `cache` | `boolean` | `true` | Reuse the in-memory license database |
76
+ | `workspaceRoot` | `string` | Bundler's root context | Root path for license scanning |
77
+ | `recorder` | `Recorder` | — | External recorder shared across compiler instances (webpack only) |
78
+ | `recordOnly` | `boolean` | `false` | Record findings without emitting (webpack multi-compiler only) |
79
+ | `waitForRecorderCount` | `number` | — | Wait for N reports before emitting combined asset (webpack only) |
80
+
81
+ > **Note:** `recorder`, `recordOnly`, and `waitForRecorderCount` are webpack-only options and are not available in the Vite plugin.
82
+
83
+ ## How it works
84
+
85
+ The plugin uses a **two-phase** architecture:
86
+
87
+ 1. **Database phase**: Scans `node_modules` with the built-in license checker to build a comprehensive cache of all package license information.
88
+ 2. **Scan phase**: Inspects the bundler's module graph to find which packages are actually used in the final bundle.
89
+
90
+ Only packages that appear in the bundler's output are included in the license asset. DevDependencies that are never imported will not appear.
91
+
92
+ ## Filtering dependencies by package or license
93
+
94
+ The plugin starts with the set of dependency entries detected from the bundler's module graph, resolves license metadata for those entries, and then applies the configured filters. Package names and license names are matched with exact, case-sensitive string comparison.
95
+
96
+ ### Filter options
97
+
98
+ - `excludePackages`: remove entries whose package name is listed.
99
+ - `onlyAllow`: fail the build if a package license is not in the allowed list.
100
+ - `failOn`: fail the build if a package license matches the list.
101
+
102
+ ### Evaluation order
103
+
104
+ 1. `excludePackages`
105
+ 2. `onlyAllow`
106
+ 3. `failOn`
107
+
108
+ All bundled packages are included by default. `excludePackages` removes unwanted entries first, then `onlyAllow` and `failOn` enforce license policies — generating build errors and stopping output when violated.
109
+
110
+ ### Transitive dependency behavior
111
+
112
+ `excludePackages` is entry-based. Excluding `foo` removes the `foo` entry itself, but it does not automatically remove every dependency that `foo` depends on.
113
+
114
+ ### Combined filter example
115
+
116
+ ```js
117
+ new LicenseWebpackPlugin({
118
+ excludePackages: ['react-dom'],
119
+ onlyAllow: ['MIT'],
120
+ failOn: ['MIT-0'],
121
+ });
122
+ ```
123
+
124
+ With this configuration:
125
+
126
+ - all bundled packages are included by default
127
+ - `react-dom` is removed from output
128
+ - it fails the build if any remaining package does not have an MIT license
129
+ - it fails the build if any remaining package has an MIT-0 license
130
+
131
+ ## Output formats
132
+
133
+ ### TXT
134
+ Human-readable notice file for app distribution.
135
+
136
+ ### JSON
137
+ Machine-readable output for CI, audits, and internal tooling.
138
+
139
+ ### Markdown
140
+ Useful for GitHub releases or repository documentation.
141
+
142
+ ### HTML
143
+ Useful for Electron about pages and in-app license views.
144
+
145
+ ## Compliance examples
146
+
147
+ ```js
148
+ new LicenseWebpackPlugin({
149
+ onlyAllow: ['MIT', 'Apache-2.0'],
150
+ });
151
+
152
+ new LicenseWebpackPlugin({
153
+ failOn: ['GPL-3.0', 'AGPL-3.0'],
154
+ });
155
+ ```
156
+
157
+ ## Multi-compiler usage (webpack only)
158
+
159
+ When using webpack's [multi-compiler](https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations) mode (an array of configurations) each compiler runs independently. The `recorder` option lets all instances share a single `DefaultRecorder` so that one primary instance can merge all their findings and emit a single combined license file.
160
+
161
+ ```js
162
+ const { LicenseWebpackPlugin, DefaultRecorder } = require('license-checker-plugin');
163
+
164
+ const sharedRecorder = new DefaultRecorder();
165
+
166
+ module.exports = [
167
+ {
168
+ name: 'renderer',
169
+ entry: './src/renderer/index.js',
170
+ plugins: [
171
+ new LicenseWebpackPlugin({
172
+ recorder: sharedRecorder,
173
+ recordOnly: true,
174
+ }),
175
+ ],
176
+ },
177
+ {
178
+ name: 'main',
179
+ entry: './src/main/index.js',
180
+ plugins: [
181
+ new LicenseWebpackPlugin({
182
+ filename: 'third-party-licenses.txt',
183
+ recorder: sharedRecorder,
184
+ waitForRecorderCount: 2,
185
+ }),
186
+ ],
187
+ },
188
+ ];
189
+ ```
190
+
191
+ ### `Recorder` interface
192
+
193
+ ```ts
194
+ export interface Recorder {
195
+ record(report: LicenseBuildReport): void;
196
+ getReports(): LicenseBuildReport[];
197
+ waitForReports(expectedCount?: number, timeoutMs?: number): Promise<LicenseBuildReport[]>;
198
+ }
199
+ ```
200
+
201
+ `DefaultRecorder` is the built-in implementation. It stores reports in memory and resolves `waitForReports` as soon as the expected count is reached.
202
+
203
+ ## API
204
+
205
+ ### `LicenseWebpackPlugin`
206
+
207
+ webpack/Rspack plugin (class, use with `new`).
208
+
209
+ ### `viteLicensePlugin(options)`
210
+
211
+ Vite plugin (function, returns a plugin object).
212
+
213
+ ```ts
214
+ import { viteLicensePlugin } from 'license-checker-plugin';
215
+ ```
216
+
217
+ ### `LicensePluginCore`
218
+
219
+ Framework-agnostic core that can be used to build adapters for other bundlers.
220
+
221
+ ```ts
222
+ import { LicensePluginCore } from 'license-checker-plugin';
223
+ ```
224
+
225
+ ### `DefaultRecorder`
226
+
227
+ Built-in recorder implementation for multi-compiler scenarios.
228
+
229
+ ## Development
230
+
231
+ ```bash
232
+ npm install
233
+ npm run build
234
+ npm test
235
+ ```
236
+
237
+ ## License
238
+
239
+ [MIT](LICENSE)
@@ -0,0 +1,84 @@
1
+ import { OutputItem } from './model/LicenseInfo';
2
+ import { PackageInfo } from './model/PackageInfo';
3
+ import { Recorder } from './Recorder';
4
+ export type OutputFormat = 'txt' | 'json' | 'markdown' | 'html';
5
+ export interface LicensePluginOptions {
6
+ /** Output file name (e.g. `licenses.txt`, `third-party-licenses.json`). */
7
+ filename?: string;
8
+ /** Output format. */
9
+ format?: OutputFormat;
10
+ /** Include the full license text in the output. */
11
+ includeLicenseText?: boolean;
12
+ /** Include the repository URL in each entry. */
13
+ includeRepository?: boolean;
14
+ /** Include the homepage URL in each entry. */
15
+ includeHomepage?: boolean;
16
+ /** Include the author/publisher in each entry. */
17
+ includeAuthor?: boolean;
18
+ /** Exclude specific packages from the output by name. */
19
+ excludePackages?: string[];
20
+ /**
21
+ * Allow only these licenses. When set, the build fails if any bundled
22
+ * package has a license not in this list.
23
+ */
24
+ onlyAllow?: string[];
25
+ /**
26
+ * Fail the build when a bundled package license matches this list.
27
+ * Evaluated after `onlyAllow`.
28
+ */
29
+ failOn?: string[];
30
+ /**
31
+ * Reuse the in-memory license database across multiple plugin instances
32
+ * (e.g. in multi-compiler setups). Set to `false` to force a fresh scan
33
+ * each time.
34
+ */
35
+ cache?: boolean;
36
+ /**
37
+ * Root path for scanning `node_modules`. Defaults to the bundler's root
38
+ * context (project root).
39
+ */
40
+ workspaceRoot?: string;
41
+ /**
42
+ * External recorder for sharing findings across compiler instances
43
+ * (webpack multi-compiler only).
44
+ * @see DefaultRecorder
45
+ */
46
+ recorder?: Recorder;
47
+ /**
48
+ * When `true`, only record findings via `recorder` without emitting a
49
+ * license asset. Use together with `recorder` and `waitForRecorderCount`
50
+ * (webpack multi-compiler only).
51
+ */
52
+ recordOnly?: boolean;
53
+ /**
54
+ * Wait for this many reports from the shared recorder before emitting
55
+ * the combined, deduplicated license asset.
56
+ * Requires `recorder` to be set (webpack multi-compiler only).
57
+ */
58
+ waitForRecorderCount?: number;
59
+ }
60
+ export interface LicensePluginContext {
61
+ reportError(msg: string): void;
62
+ reportWarning(msg: string): void;
63
+ }
64
+ export declare class LicensePluginCore {
65
+ readonly options: Required<Omit<LicensePluginOptions, 'recorder' | 'waitForRecorderCount'>> & {
66
+ recorder: Recorder | undefined;
67
+ waitForRecorderCount: number | undefined;
68
+ };
69
+ private db;
70
+ constructor(options?: LicensePluginOptions);
71
+ initialize(startPath: string, context: LicensePluginContext): Promise<boolean>;
72
+ generateLicenseItems(packages: Map<string, PackageInfo>, context: LicensePluginContext): Promise<{
73
+ items: OutputItem[];
74
+ errors: string[];
75
+ }>;
76
+ private resolveLicenseEntries;
77
+ private checkCompliance;
78
+ private buildOutputItems;
79
+ private recordReport;
80
+ private mergeReports;
81
+ format(items: OutputItem[]): string;
82
+ private filterLicenseFields;
83
+ private createFormatter;
84
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LicensePluginCore = void 0;
4
+ const LicenseDatabase_1 = require("./checker/LicenseDatabase");
5
+ const HtmlFormatter_1 = require("./formatter/HtmlFormatter");
6
+ const JsonFormatter_1 = require("./formatter/JsonFormatter");
7
+ const MarkdownFormatter_1 = require("./formatter/MarkdownFormatter");
8
+ const TxtFormatter_1 = require("./formatter/TxtFormatter");
9
+ class LicensePluginCore {
10
+ options;
11
+ db;
12
+ constructor(options = {}) {
13
+ this.options = {
14
+ filename: options.filename || 'licenses.txt',
15
+ format: options.format || 'txt',
16
+ includeLicenseText: options.includeLicenseText !== false,
17
+ includeRepository: options.includeRepository !== false,
18
+ includeHomepage: options.includeHomepage !== false,
19
+ includeAuthor: options.includeAuthor !== false,
20
+ excludePackages: options.excludePackages || [],
21
+ onlyAllow: options.onlyAllow || [],
22
+ failOn: options.failOn || [],
23
+ cache: options.cache !== false,
24
+ workspaceRoot: options.workspaceRoot || '',
25
+ recorder: options.recorder,
26
+ recordOnly: options.recordOnly === true,
27
+ waitForRecorderCount: options.waitForRecorderCount,
28
+ };
29
+ this.db = new LicenseDatabase_1.LicenseDatabase();
30
+ }
31
+ async initialize(startPath, context) {
32
+ if (!this.options.cache) {
33
+ this.db = new LicenseDatabase_1.LicenseDatabase();
34
+ }
35
+ try {
36
+ await this.db.initialize(startPath);
37
+ return true;
38
+ }
39
+ catch (error) {
40
+ context.reportError(`LicensePlugin: Failed to initialize license database: ${String(error)}`);
41
+ return false;
42
+ }
43
+ }
44
+ async generateLicenseItems(packages, context) {
45
+ const entries = this.resolveLicenseEntries(packages);
46
+ const errors = this.checkCompliance(entries);
47
+ if (errors.length > 0) {
48
+ for (const err of errors)
49
+ context.reportError(err);
50
+ return { items: [], errors };
51
+ }
52
+ let items = this.buildOutputItems(entries);
53
+ this.recordReport(items);
54
+ if (this.options.recordOnly)
55
+ return { items: [], errors: [] };
56
+ if (this.options.recorder && this.options.waitForRecorderCount !== undefined) {
57
+ try {
58
+ items = this.mergeReports(items, await this.options.recorder.waitForReports(this.options.waitForRecorderCount));
59
+ }
60
+ catch (error) {
61
+ context.reportError(String(error));
62
+ return { items: [], errors: [String(error)] };
63
+ }
64
+ }
65
+ items.sort((a, b) => a.package.name.localeCompare(b.package.name));
66
+ return { items, errors: [] };
67
+ }
68
+ resolveLicenseEntries(packages) {
69
+ const entries = [];
70
+ for (const pkgInfo of packages.values()) {
71
+ if (this.options.excludePackages.includes(pkgInfo.name))
72
+ continue;
73
+ entries.push({
74
+ info: pkgInfo,
75
+ licenseInfo: this.filterLicenseFields(this.db.getLicense(pkgInfo.name, pkgInfo.version)),
76
+ });
77
+ }
78
+ return entries;
79
+ }
80
+ checkCompliance(entries) {
81
+ const errors = [];
82
+ for (const { info, licenseInfo } of entries) {
83
+ if (this.options.onlyAllow.length > 0 && !this.options.onlyAllow.includes(licenseInfo.license)) {
84
+ errors.push(`LicensePlugin: License "${licenseInfo.license}" for package "${info.name}@${info.version}" is not in the allowed list: ${this.options.onlyAllow.join(', ')}`);
85
+ }
86
+ else if (this.options.failOn.length > 0 && this.options.failOn.includes(licenseInfo.license)) {
87
+ errors.push(`LicensePlugin: License "${licenseInfo.license}" for package "${info.name}@${info.version}" is in the fail list`);
88
+ }
89
+ }
90
+ return errors;
91
+ }
92
+ buildOutputItems(entries) {
93
+ return entries.map(({ info, licenseInfo }) => ({
94
+ package: {
95
+ ...info,
96
+ repository: this.options.includeRepository ? info.repository : undefined,
97
+ homepage: this.options.includeHomepage ? info.homepage : undefined,
98
+ author: this.options.includeAuthor ? info.author : undefined,
99
+ },
100
+ license: { ...licenseInfo },
101
+ }));
102
+ }
103
+ recordReport(items) {
104
+ if (this.options.recorder) {
105
+ this.options.recorder.record({ items });
106
+ }
107
+ }
108
+ mergeReports(items, allReports) {
109
+ const seen = new Set();
110
+ const merged = [];
111
+ for (const report of allReports) {
112
+ for (const item of report.items) {
113
+ const key = `${item.package.name}@${item.package.version}`;
114
+ if (!seen.has(key)) {
115
+ seen.add(key);
116
+ merged.push(item);
117
+ }
118
+ }
119
+ }
120
+ return merged;
121
+ }
122
+ format(items) {
123
+ return this.createFormatter().generate(items);
124
+ }
125
+ filterLicenseFields(licenseInfo) {
126
+ const result = { license: licenseInfo.license };
127
+ if (licenseInfo.licenseFile)
128
+ result.licenseFile = licenseInfo.licenseFile;
129
+ if (this.options.includeLicenseText && licenseInfo.licenseText) {
130
+ result.licenseText = licenseInfo.licenseText;
131
+ }
132
+ return result;
133
+ }
134
+ createFormatter() {
135
+ switch (this.options.format) {
136
+ case 'json':
137
+ return new JsonFormatter_1.JsonFormatter();
138
+ case 'markdown':
139
+ return new MarkdownFormatter_1.MarkdownFormatter();
140
+ case 'html':
141
+ return new HtmlFormatter_1.HtmlFormatter();
142
+ case 'txt':
143
+ default:
144
+ return new TxtFormatter_1.TxtFormatter({ includeLicenseText: this.options.includeLicenseText });
145
+ }
146
+ }
147
+ }
148
+ exports.LicensePluginCore = LicensePluginCore;
@@ -0,0 +1,11 @@
1
+ import type { Compiler, WebpackPluginInstance } from 'webpack';
2
+ import type { LicensePluginOptions, OutputFormat } from './LicensePluginCore';
3
+ export type { LicensePluginOptions, OutputFormat };
4
+ /** @deprecated Use `LicensePluginOptions` instead. */
5
+ export type LicenseWebpackPluginOptions = LicensePluginOptions;
6
+ export declare class LicenseWebpackPlugin implements WebpackPluginInstance {
7
+ private readonly core;
8
+ constructor(options?: LicensePluginOptions);
9
+ apply(compiler: Compiler): void;
10
+ private generateLicenses;
11
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LicenseWebpackPlugin = void 0;
4
+ const LicensePluginCore_1 = require("./LicensePluginCore");
5
+ const PackageScanner_1 = require("./scanner/PackageScanner");
6
+ const PLUGIN_NAME = 'LicenseWebpackPlugin';
7
+ const DEFAULT_PROCESS_ASSETS_STAGE_REPORT = 5000;
8
+ class LicenseWebpackPlugin {
9
+ core;
10
+ constructor(options = {}) {
11
+ this.core = new LicensePluginCore_1.LicensePluginCore(options);
12
+ }
13
+ apply(compiler) {
14
+ const wp = compiler.webpack;
15
+ const processAssetsStageReport = wp?.Compilation?.PROCESS_ASSETS_STAGE_REPORT ?? DEFAULT_PROCESS_ASSETS_STAGE_REPORT;
16
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
17
+ compilation.hooks.processAssets.tapPromise({
18
+ name: PLUGIN_NAME,
19
+ stage: processAssetsStageReport,
20
+ }, async () => {
21
+ try {
22
+ await this.generateLicenses(compiler, compilation, wp);
23
+ }
24
+ catch (error) {
25
+ compilation.errors.push(error);
26
+ }
27
+ });
28
+ });
29
+ }
30
+ async generateLicenses(compiler, compilation, wp) {
31
+ const startPath = this.core.options.workspaceRoot || compiler.context;
32
+ const context = {
33
+ reportError: (msg) => compilation.errors.push(new Error(msg)),
34
+ reportWarning: (msg) => compilation.warnings.push(new Error(msg)),
35
+ };
36
+ const initialized = await this.core.initialize(startPath, context);
37
+ if (!initialized)
38
+ return;
39
+ const scanner = new PackageScanner_1.PackageScanner();
40
+ const packages = scanner.scan(compilation);
41
+ const { items, errors } = await this.core.generateLicenseItems(packages, context);
42
+ if (errors.length > 0)
43
+ return;
44
+ const sourcesApi = wp?.sources;
45
+ if (!sourcesApi) {
46
+ compilation.warnings.push(new Error('LicenseWebpackPlugin: bundler sources API not available on compiler.webpack; ' +
47
+ 'license asset will not be emitted. Ensure you are using webpack 5 or Rspack.'));
48
+ return;
49
+ }
50
+ compilation.emitAsset(this.core.options.filename, new sourcesApi.RawSource(this.core.format(items)));
51
+ }
52
+ }
53
+ exports.LicenseWebpackPlugin = LicenseWebpackPlugin;
@@ -0,0 +1,14 @@
1
+ import { LicenseBuildReport } from './model/LicenseBuildReport';
2
+ export interface Recorder {
3
+ record(report: LicenseBuildReport): void;
4
+ getReports(): LicenseBuildReport[];
5
+ waitForReports(expectedCount?: number, timeoutMs?: number): Promise<LicenseBuildReport[]>;
6
+ }
7
+ export declare class DefaultRecorder implements Recorder {
8
+ private reports;
9
+ private waiters;
10
+ record(report: LicenseBuildReport): void;
11
+ getReports(): LicenseBuildReport[];
12
+ waitForReports(expectedCount?: number, timeoutMs?: number): Promise<LicenseBuildReport[]>;
13
+ private flushWaiters;
14
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultRecorder = void 0;
4
+ class DefaultRecorder {
5
+ reports = [];
6
+ waiters = [];
7
+ record(report) {
8
+ this.reports.push(report);
9
+ this.flushWaiters();
10
+ }
11
+ getReports() {
12
+ return [...this.reports];
13
+ }
14
+ waitForReports(expectedCount, timeoutMs = 30000) {
15
+ if (expectedCount === undefined || this.reports.length >= expectedCount) {
16
+ return Promise.resolve(this.getReports());
17
+ }
18
+ return new Promise((resolve, reject) => {
19
+ const timer = setTimeout(() => {
20
+ this.waiters = this.waiters.filter((w) => w !== waiter);
21
+ reject(new Error(`LicenseWebpackPlugin: waitForReports timed out after ${timeoutMs}ms. ` +
22
+ `Expected ${expectedCount} reports but received ${this.reports.length}.`));
23
+ }, timeoutMs);
24
+ const waiter = { expectedCount, resolve, reject, timer };
25
+ this.waiters.push(waiter);
26
+ });
27
+ }
28
+ flushWaiters() {
29
+ const satisfied = [];
30
+ const remaining = [];
31
+ for (const waiter of this.waiters) {
32
+ if (this.reports.length >= waiter.expectedCount) {
33
+ satisfied.push(waiter);
34
+ }
35
+ else {
36
+ remaining.push(waiter);
37
+ }
38
+ }
39
+ this.waiters = remaining;
40
+ for (const waiter of satisfied) {
41
+ clearTimeout(waiter.timer);
42
+ waiter.resolve(this.getReports());
43
+ }
44
+ }
45
+ }
46
+ exports.DefaultRecorder = DefaultRecorder;
@@ -0,0 +1,16 @@
1
+ import type { LicensePluginOptions } from './LicensePluginCore';
2
+ interface VitePlugin {
3
+ name: string;
4
+ enforce?: 'pre' | 'post';
5
+ buildStart?: () => void | Promise<void>;
6
+ transform?: (code: string, id: string) => string | null | undefined | void;
7
+ generateBundle?: (this: {
8
+ emitFile: (opts: {
9
+ type: string;
10
+ fileName: string;
11
+ source: string;
12
+ }) => void;
13
+ }, opts: unknown, bundle: unknown) => void | Promise<void>;
14
+ }
15
+ export declare function viteLicensePlugin(options?: LicensePluginOptions): VitePlugin;
16
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.viteLicensePlugin = viteLicensePlugin;
4
+ const LicensePluginCore_1 = require("./LicensePluginCore");
5
+ const PackageResolver_1 = require("./scanner/PackageResolver");
6
+ const PLUGIN_NAME = 'vite-license-plugin';
7
+ function viteLicensePlugin(options = {}) {
8
+ const core = new LicensePluginCore_1.LicensePluginCore(options);
9
+ const resolver = new PackageResolver_1.PackageResolver();
10
+ const resolvedPackages = new Map();
11
+ return {
12
+ name: PLUGIN_NAME,
13
+ enforce: 'post',
14
+ async buildStart() {
15
+ const root = options.workspaceRoot || process.cwd();
16
+ const context = {
17
+ reportError: (msg) => { throw new Error(msg); },
18
+ reportWarning: (msg) => console.warn(`[${PLUGIN_NAME}] ${msg}`),
19
+ };
20
+ await core.initialize(root, context);
21
+ },
22
+ transform(_code, id) {
23
+ if (!id.includes('node_modules'))
24
+ return null;
25
+ const pkgInfo = resolver.resolve(id, 'main');
26
+ if (pkgInfo) {
27
+ const key = `${pkgInfo.name}@${pkgInfo.version}`;
28
+ if (!resolvedPackages.has(key)) {
29
+ resolvedPackages.set(key, pkgInfo);
30
+ }
31
+ }
32
+ return null;
33
+ },
34
+ async generateBundle(_opts, _bundle) {
35
+ if (resolvedPackages.size === 0)
36
+ return;
37
+ const warnings = [];
38
+ const context = {
39
+ reportError: (msg) => { throw new Error(msg); },
40
+ reportWarning: (msg) => warnings.push(msg),
41
+ };
42
+ const { items } = await core.generateLicenseItems(resolvedPackages, context);
43
+ const source = core.format(items);
44
+ this.emitFile({
45
+ type: 'asset',
46
+ fileName: core.options.filename,
47
+ source,
48
+ });
49
+ for (const w of warnings) {
50
+ console.warn(`[${PLUGIN_NAME}] ${w}`);
51
+ }
52
+ },
53
+ };
54
+ }