UNPKG

7.69 kBPlain TextView Raw
1/* -----------------------------------------------------------------------------
2| Copyright (c) Jupyter Development Team.
3| Distributed under the terms of the Modified BSD License.
4|----------------------------------------------------------------------------*/
5
6import DuplicatePackageCheckerPlugin from 'duplicate-package-checker-webpack-plugin';
7import * as fs from 'fs-extra';
8import * as webpack from 'webpack';
9import { LicenseWebpackPlugin } from 'license-webpack-plugin';
10import { LicenseIdentifiedModule } from 'license-webpack-plugin/dist/LicenseIdentifiedModule';
11import { PluginOptions } from 'license-webpack-plugin/dist/PluginOptions';
12
13// From
14// https://github.com/webpack/webpack/blob/95120bdf98a01649740b104bebc426b0123651ce/lib/WatchIgnorePlugin.js
15const IGNORE_TIME_ENTRY = 'ignore';
16
17export namespace WPPlugin {
18 /**
19 * A WebPack Plugin that copies the assets to the static directory
20 */
21 export class FrontEndPlugin {
22 constructor(buildDir: string, staticDir: string) {
23 this.buildDir = buildDir;
24 this.staticDir = staticDir;
25
26 this._first = true;
27 }
28
29 apply(compiler: webpack.Compiler): void {
30 compiler.hooks.afterEmit.tap('FrontEndPlugin', () => {
31 // bail if no staticDir
32 if (!this.staticDir) {
33 return;
34 }
35
36 // ensure a clean static directory on the first emit
37 if (this._first && fs.existsSync(this.staticDir)) {
38 fs.removeSync(this.staticDir);
39 }
40 this._first = false;
41
42 fs.copySync(this.buildDir, this.staticDir);
43 });
44 }
45
46 buildDir: string;
47 staticDir: string;
48
49 private _first: boolean;
50 }
51
52 /**
53 * A helper class for the WatchIgnoreFilterPlugin. This is a close copy of
54 * (the non-exported) webpack.IgnoringWatchFileSystem
55 */
56 class FilterIgnoringWatchFileSystem {
57 constructor(wfs: any, ignored: (path: string) => boolean) {
58 this.wfs = wfs;
59
60 // ignored should be a callback function that filters the build files
61 this.ignored = ignored;
62 }
63
64 watch(
65 files: any,
66 dirs: any,
67 missing: any,
68 startTime: any,
69 options: any,
70 callback: any,
71 callbackUndelayed: any
72 ) {
73 files = Array.from(files);
74 dirs = Array.from(dirs);
75 const notIgnored = (path: string) => !this.ignored(path);
76 const ignoredFiles = files.filter(this.ignored);
77 const ignoredDirs = dirs.filter(this.ignored);
78
79 const watcher = this.wfs.watch(
80 files.filter(notIgnored),
81 dirs.filter(notIgnored),
82 missing,
83 startTime,
84 options,
85 (
86 err: any,
87 fileTimestamps: any,
88 dirTimestamps: any,
89 changedFiles: any,
90 removedFiles: any
91 ) => {
92 if (err) return callback(err);
93 for (const path of ignoredFiles) {
94 fileTimestamps.set(path, IGNORE_TIME_ENTRY);
95 }
96
97 for (const path of ignoredDirs) {
98 dirTimestamps.set(path, IGNORE_TIME_ENTRY);
99 }
100
101 callback(
102 err,
103 fileTimestamps,
104 dirTimestamps,
105 changedFiles,
106 removedFiles
107 );
108 },
109 callbackUndelayed
110 );
111
112 return {
113 close: () => watcher.close(),
114 pause: () => watcher.pause(),
115 getContextTimeInfoEntries: () => {
116 const dirTimestamps = watcher.getContextTimeInfoEntries();
117 for (const path of ignoredDirs) {
118 dirTimestamps.set(path, IGNORE_TIME_ENTRY);
119 }
120 return dirTimestamps;
121 },
122 getFileTimeInfoEntries: () => {
123 const fileTimestamps = watcher.getFileTimeInfoEntries();
124 for (const path of ignoredFiles) {
125 fileTimestamps.set(path, IGNORE_TIME_ENTRY);
126 }
127 return fileTimestamps;
128 }
129 };
130 }
131
132 ignored: (path: string) => boolean;
133 wfs: any;
134 }
135
136 /**
137 * A WebPack Plugin that ignores files files that are filtered
138 * by a callback during a `--watch` build
139 */
140 export class FilterWatchIgnorePlugin {
141 constructor(ignored: (path: string) => boolean) {
142 this.ignored = ignored;
143 }
144
145 apply(compiler: webpack.Compiler): void {
146 compiler.hooks.afterEnvironment.tap('FilterWatchIgnorePlugin', () => {
147 compiler.watchFileSystem = new FilterIgnoringWatchFileSystem(
148 compiler.watchFileSystem,
149 this.ignored
150 );
151 });
152 }
153
154 ignored: (path: string) => boolean;
155 }
156
157 export class NowatchDuplicatePackageCheckerPlugin extends DuplicatePackageCheckerPlugin {
158 apply(compiler: webpack.Compiler): void {
159 const options = this.options;
160
161 compiler.hooks.run.tap(
162 'NowatchDuplicatePackageCheckerPlugin',
163 compiler => {
164 const p = new DuplicatePackageCheckerPlugin(options);
165 p.apply(compiler);
166 }
167 );
168 }
169
170 options: DuplicatePackageCheckerPlugin.Options;
171 }
172
173 /**
174 * A top-level report of the licenses for all code included in a bundle
175 *
176 * ### Note
177 *
178 * This is roughly informed by the terms defined in the SPDX spec, though is not
179 * an SPDX Document, since there seem to be several (incompatible) specs
180 * in that repo.
181 *
182 * @see https://github.com/spdx/spdx-spec/blob/development/v2.2.1/schemas/spdx-schema.json
183 **/
184 export interface ILicenseReport {
185 packages: IPackageLicenseInfo[];
186 }
187
188 /**
189 * A best-effort single bundled package's information.
190 *
191 * ### Note
192 *
193 * This is roughly informed by SPDX `packages` and `hasExtractedLicenseInfos`,
194 * as making it conformant would vastly complicate the structure.
195 *
196 * @see https://github.com/spdx/spdx-spec/blob/development/v2.2.1/schemas/spdx-schema.json
197 **/
198 export interface IPackageLicenseInfo {
199 /** the name of the package as it appears in node_modules */
200 name: string;
201 /** the version of the package, or an empty string if unknown */
202 versionInfo: string;
203 /** an SPDX license or LicenseRef, or an empty string if unknown */
204 licenseId: string;
205 /** the verbatim extracted text of the license, or an empty string if unknown */
206 extractedText: string;
207 }
208
209 /**
210 * A well-known filename for third-party license information.
211 *
212 * ### Note
213 * If an alternate JupyterLab-based ecosystem wanted to implement a different
214 * name, they may _still_ need to handle the presence of this file if reusing
215 * any core files or extensions.
216 *
217 * If multiple files are found by `jupyterlab_server, their `packages` will
218 * be concatenated.
219 */
220 export const DEFAULT_LICENSE_REPORT_FILENAME = 'third-party-licenses.json';
221
222 /**
223 * a plugin that creates a predictable, machine-readable report of licenses for
224 * all modules included in this build
225 */
226 export class JSONLicenseWebpackPlugin extends LicenseWebpackPlugin {
227 constructor(pluginOptions: PluginOptions = {}) {
228 super({
229 outputFilename: DEFAULT_LICENSE_REPORT_FILENAME,
230 ...pluginOptions,
231 renderLicenses: modules => this.renderLicensesJSON(modules),
232 perChunkOutput: false
233 });
234 }
235
236 /** render an SPDX-like record */
237 renderLicensesJSON(modules: LicenseIdentifiedModule[]): string {
238 const report: ILicenseReport = { packages: [] };
239
240 modules.sort((left, right) => (left.name < right.name ? -1 : 1));
241
242 for (const mod of modules) {
243 report.packages.push({
244 name: mod.name || '',
245 versionInfo: mod.packageJson.version || '',
246 licenseId: mod.licenseId || '',
247 extractedText: mod.licenseText || ''
248 });
249 }
250
251 return JSON.stringify(report, null, 2);
252 }
253 }
254}