@ckeditor/ckeditor5-dev-license-checker 54.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.
package/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ Software License Agreement
2
+ ==========================
3
+
4
+ Copyright (c) 2003-2025, [CKSource](http://cksource.com) Holding sp. z o.o. All rights reserved.
5
+
6
+ Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
7
+
8
+ Sources of Intellectual Property Included in CKEditor
9
+ -----------------------------------------------------
10
+
11
+ Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
12
+
13
+ Trademarks
14
+ ----------
15
+
16
+ **CKEditor** is a trademark of [CKSource](http://cksource.com) Holding sp. z o.o. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ CKEditor 5 license checker
2
+ =============================
3
+
4
+ [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-dev-license-checker.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-license-checker)
5
+ [![CircleCI](https://circleci.com/gh/ckeditor/ckeditor5-dev.svg?style=shield)](https://app.circleci.com/pipelines/github/ckeditor/ckeditor5-dev?branch=master)
6
+
7
+ Contains tools for validating licenses.
8
+
9
+ More information about development tools packages can be found at the following URL: <https://github.com/ckeditor/ckeditor5-dev>.
10
+
11
+ ## Changelog
12
+
13
+ See the [`CHANGELOG.md`](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/ckeditor5-dev-license-checker/CHANGELOG.md) file.
14
+
15
+ ## License
16
+
17
+ Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ export { validateLicenseFiles } from './validate-license-files.js';
package/dist/index.js ADDED
@@ -0,0 +1,242 @@
1
+ import { glob, readFile, writeFile } from 'fs/promises';
2
+ import { findPackageJSON } from 'module';
3
+ import { createPatch } from 'diff';
4
+ import upath from 'upath';
5
+
6
+ /**
7
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
8
+ * For licensing, see LICENSE.md.
9
+ */
10
+ const conjunctionFormatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
11
+ /**
12
+ * @param options
13
+ * @param options.fix Whether to fix license files instead of printing errors.
14
+ * @param options.verbose Whether to print diff instead of just path to file on failed validation.
15
+ * @param options.shouldProcessRoot Whether validation should process the root.
16
+ * @param options.shouldProcessPackages Whether validation should process `packages/*`.
17
+ * @param options.isPublic Whether license should use disclaimer meant for open source repositories.
18
+ * @param options.rootDir Base directory.
19
+ * @param options.projectName Project name referred to in the licenses.
20
+ * @param options.mainPackageName Designated package that contains collected licenses from all other packages.
21
+ * @param options.copyrightOverrides Map of of copyright that can both add new ones, as well as override existing ones.
22
+ *
23
+ * @returns Exit code of the script that indicates whether it passed or errored.
24
+ */
25
+ async function validateLicenseFiles({ fix = false, verbose = false, shouldProcessRoot = false, shouldProcessPackages = false, isPublic = false, rootDir, projectName, mainPackageName, copyrightOverrides = [] }) {
26
+ const packagePaths = [];
27
+ if (shouldProcessRoot) {
28
+ packagePaths.push(rootDir);
29
+ }
30
+ if (shouldProcessPackages) {
31
+ packagePaths.push(...await fromAsync(glob(upath.join(rootDir, 'packages', '*'))));
32
+ }
33
+ if (!packagePaths.length) {
34
+ console.error([
35
+ 'No packages to parse detected. Make sure that you provided proper paths,',
36
+ 'as well as set at least one of: `shouldProcessRoot` or `shouldProcessPackages`.'
37
+ ].join('\n'));
38
+ return 1;
39
+ }
40
+ const dependencyMaps = await Promise.all(packagePaths.map(async (packagePath) => getPackageDependencyMap(packagePath, copyrightOverrides)));
41
+ console.info('Validating licenses in following packages:');
42
+ console.info(dependencyMaps.map(({ packageName }) => ` - ${packageName}`).join('\n'));
43
+ const missingCopyrightLists = getMissingCopyrightLists(dependencyMaps);
44
+ if (missingCopyrightLists.length) {
45
+ console.error('\n❌ Following packages include dependencies where finding copyright message failed. Please add an override:\n');
46
+ console.error(missingCopyrightLists.join('\n'));
47
+ return 1;
48
+ }
49
+ if (mainPackageName) {
50
+ copyDependenciesToTheMainPackage(dependencyMaps, mainPackageName);
51
+ }
52
+ const processingResults = await Promise.all(dependencyMaps.map(dependencyMap => processDependencyMap({ dependencyMap, projectName, isPublic, fix })));
53
+ const updatedLicenses = processingResults.filter(processingResult => 'updated' in processingResult);
54
+ const licensesMissing = processingResults.filter(processingResult => 'licenseMissing' in processingResult);
55
+ const sectionsMissing = processingResults.filter(processingResult => 'sectionMissing' in processingResult);
56
+ const updatesNeeded = processingResults.filter((processingResult) => 'updateNeeded' in processingResult);
57
+ if (updatedLicenses.length) {
58
+ console.info('\nUpdated the following license files:');
59
+ console.info(makeLicenseFileList(updatedLicenses));
60
+ }
61
+ if (!licensesMissing.length && !sectionsMissing.length && !updatesNeeded.length) {
62
+ console.info('\nValidation complete.');
63
+ return 0;
64
+ }
65
+ if (licensesMissing.length) {
66
+ console.error('\nFollowing license files are missing. Please create them:');
67
+ console.error(makeLicenseFileList(licensesMissing));
68
+ }
69
+ if (sectionsMissing.length) {
70
+ console.error([
71
+ '\nFailed to detect license section in following files.',
72
+ 'Please add an `Sources of Intellectual Property Included in ...` section to them:'
73
+ ].join(' '));
74
+ console.error(makeLicenseFileList(sectionsMissing));
75
+ }
76
+ if (updatesNeeded.length) {
77
+ console.error('\nFollowing license files are not up to date. Please run this script with `--fix` option and review the changes.');
78
+ if (!verbose) {
79
+ console.error(makeLicenseFileList(updatesNeeded));
80
+ }
81
+ else {
82
+ console.error('\n' + updatesNeeded.map(({ patch }) => patch).join('\n'));
83
+ }
84
+ }
85
+ return 1;
86
+ }
87
+ async function getPackageDependencyMap(packagePath, copyrightOverrides) {
88
+ const pkgJsonPath = upath.join(packagePath, 'package.json');
89
+ const pkgJsonContent = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
90
+ const dependencyNames = Object.keys(pkgJsonContent.dependencies || {})
91
+ .filter(dependency => !dependency.match(/(ckeditor)|(cksource)/i));
92
+ const packageName = pkgJsonContent.name;
93
+ const dependencyMap = {
94
+ packageName,
95
+ packagePath,
96
+ dependencies: []
97
+ };
98
+ const copyrightOverridesPackage = copyrightOverrides
99
+ .find(({ packageName: overridePackageName }) => packageName === overridePackageName);
100
+ if (copyrightOverridesPackage) {
101
+ dependencyMap.dependencies.push(...copyrightOverridesPackage.dependencies);
102
+ }
103
+ for (const dependencyName of dependencyNames) {
104
+ // If override already exists, skip parsing the dependency.
105
+ if (dependencyMap.dependencies.some(({ name }) => name === dependencyName)) {
106
+ continue;
107
+ }
108
+ const dependencyData = { name: dependencyName };
109
+ dependencyMap.dependencies.push(dependencyData);
110
+ try {
111
+ const dependencyPkgJsonPath = findPackageJSON(dependencyName, packagePath);
112
+ const dependencyPkgJsonContent = JSON.parse(await readFile(dependencyPkgJsonPath, 'utf-8'));
113
+ dependencyData.license = dependencyPkgJsonContent.license;
114
+ dependencyData.copyright = await getCopyright(dependencyPkgJsonPath);
115
+ }
116
+ catch {
117
+ // For packages such as `empathic` there is no export under that namespace, only `empathic/*`.
118
+ // This causes `findPackageJSON()` to error. We silently fail and later ask the integrator to add an override.
119
+ }
120
+ }
121
+ return dependencyMap;
122
+ }
123
+ function getMissingCopyrightLists(dependencyMaps) {
124
+ return dependencyMaps
125
+ .map(({ packageName, dependencies }) => {
126
+ const missingCopyrights = dependencies
127
+ .filter(({ license, copyright }) => !license || !copyright)
128
+ .map(({ name }) => ` - ${name}`);
129
+ if (missingCopyrights.length) {
130
+ return [
131
+ `${packageName}:`,
132
+ ...missingCopyrights,
133
+ ''
134
+ ].join('\n');
135
+ }
136
+ })
137
+ .filter((item) => typeof item === 'string');
138
+ }
139
+ function copyDependenciesToTheMainPackage(dependencyMaps, mainPackageName) {
140
+ const mainPackage = dependencyMaps.find(({ packageName }) => packageName === mainPackageName);
141
+ mainPackage.dependencies = dependencyMaps.reduce((output, item) => {
142
+ item.dependencies.forEach(dependency => {
143
+ const itemAlreadyPresent = output.some(({ name }) => name === dependency.name);
144
+ if (!itemAlreadyPresent) {
145
+ output.push(dependency);
146
+ }
147
+ });
148
+ return output;
149
+ }, []);
150
+ }
151
+ async function processDependencyMap({ dependencyMap, projectName, isPublic, fix }) {
152
+ const licenseSectionPattern = /(?<=\n)Sources of Intellectual Property Included in .*?\n[\S\s]*?(?=(\nTrademarks\n)|$)/;
153
+ const header = `Sources of Intellectual Property Included in ${projectName}`;
154
+ const licensePath = upath.join(dependencyMap.packagePath, 'LICENSE.md');
155
+ let currentLicense;
156
+ try {
157
+ currentLicense = await readFile(licensePath, 'utf-8');
158
+ }
159
+ catch {
160
+ return { licensePath, licenseMissing: true };
161
+ }
162
+ if (!currentLicense.match(licenseSectionPattern)) {
163
+ return { licensePath, sectionMissing: true };
164
+ }
165
+ const newLicense = currentLicense.replace(licenseSectionPattern, [
166
+ header,
167
+ '-'.repeat(header.length),
168
+ '',
169
+ getAuthorDisclaimer(projectName, isPublic),
170
+ '',
171
+ ...getLicenseList(projectName, dependencyMap.dependencies)
172
+ ].filter(item => typeof item === 'string').join('\n'));
173
+ if (currentLicense === newLicense) {
174
+ return { licensePath };
175
+ }
176
+ if (fix) {
177
+ await writeFile(licensePath, newLicense, 'utf-8');
178
+ return { licensePath, updated: true };
179
+ }
180
+ const patch = createPatch(licensePath, currentLicense, newLicense);
181
+ return { licensePath, updateNeeded: true, patch };
182
+ }
183
+ function getAuthorDisclaimer(projectName, isPublic) {
184
+ const authorDisclaimer = [
185
+ `Where not otherwise indicated, all ${projectName} content is authored`,
186
+ 'by CKSource engineers and consists of CKSource-owned intellectual property.'
187
+ ];
188
+ if (isPublic) {
189
+ authorDisclaimer.push(`In some specific instances, ${projectName} will incorporate work done by`, 'developers outside of CKSource with their express permission.');
190
+ }
191
+ return authorDisclaimer.join(' ');
192
+ }
193
+ function getLicenseTypeHeader(projectName, licenseType) {
194
+ return [
195
+ 'The following libraries are included in',
196
+ projectName,
197
+ `under the [${licenseType} license](https://opensource.org/licenses/${licenseType}):`
198
+ ].join(' ');
199
+ }
200
+ function getLicenseList(projectName, dependencies) {
201
+ const licenseTypes = removeDuplicates(dependencies.flatMap(dependency => dependency.license));
202
+ return licenseTypes.sort().flatMap(licenseType => [
203
+ getLicenseTypeHeader(projectName, licenseType),
204
+ '',
205
+ ...dependencies
206
+ .filter(({ license }) => license === licenseType)
207
+ .sort((a, b) => a.name.localeCompare(b.name))
208
+ .map(({ name, copyright }) => `* ${name} - ${copyright}`),
209
+ ''
210
+ ]);
211
+ }
212
+ async function getCopyright(dependencyPkgJsonPath) {
213
+ const dependencyPath = upath.dirname(dependencyPkgJsonPath);
214
+ const dependencyRootFilePaths = await fromAsync(glob(upath.join(dependencyPath, '*')));
215
+ const dependencyLicensePath = dependencyRootFilePaths.find(path => upath.basename(path).match(/license/i));
216
+ if (!dependencyLicensePath) {
217
+ return;
218
+ }
219
+ const dependencyLicenseContent = await readFile(dependencyLicensePath, 'utf-8');
220
+ const matches = dependencyLicenseContent.match(/(?<=^|\n[ \t]*?)Copyright.+/g);
221
+ if (!matches) {
222
+ return;
223
+ }
224
+ return conjunctionFormatter.format(matches.map(match => match.replace(/\.$/, '')) // Strip preexisting trailing dot.
225
+ ) + '.'; // Add the trailing dot back.
226
+ }
227
+ function removeDuplicates(array) {
228
+ return Array.from(new Set(array));
229
+ }
230
+ function makeLicenseFileList(array) {
231
+ return array.map(({ licensePath }) => ` - ${licensePath}`).join('\n');
232
+ }
233
+ // TODO: Replace with `Array.fromAsync()` once we upgrade to TS 5.5
234
+ async function fromAsync(iterable) {
235
+ const result = [];
236
+ for await (const item of iterable) {
237
+ result.push(item);
238
+ }
239
+ return result;
240
+ }
241
+
242
+ export { validateLicenseFiles };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ type CopyrightOverride = {
6
+ packageName: string;
7
+ dependencies: Array<{
8
+ license: string;
9
+ name: string;
10
+ copyright: string;
11
+ }>;
12
+ };
13
+ /**
14
+ * @param options
15
+ * @param options.fix Whether to fix license files instead of printing errors.
16
+ * @param options.verbose Whether to print diff instead of just path to file on failed validation.
17
+ * @param options.shouldProcessRoot Whether validation should process the root.
18
+ * @param options.shouldProcessPackages Whether validation should process `packages/*`.
19
+ * @param options.isPublic Whether license should use disclaimer meant for open source repositories.
20
+ * @param options.rootDir Base directory.
21
+ * @param options.projectName Project name referred to in the licenses.
22
+ * @param options.mainPackageName Designated package that contains collected licenses from all other packages.
23
+ * @param options.copyrightOverrides Map of of copyright that can both add new ones, as well as override existing ones.
24
+ *
25
+ * @returns Exit code of the script that indicates whether it passed or errored.
26
+ */
27
+ export declare function validateLicenseFiles({ fix, verbose, shouldProcessRoot, shouldProcessPackages, isPublic, rootDir, projectName, mainPackageName, copyrightOverrides }: {
28
+ fix?: boolean;
29
+ verbose?: boolean;
30
+ shouldProcessRoot?: boolean;
31
+ shouldProcessPackages?: boolean;
32
+ isPublic?: boolean;
33
+ rootDir: string;
34
+ projectName: string;
35
+ mainPackageName?: string;
36
+ copyrightOverrides?: Array<CopyrightOverride>;
37
+ }): Promise<number>;
38
+ export {};
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@ckeditor/ckeditor5-dev-license-checker",
3
+ "version": "54.0.0",
4
+ "description": "Contains tools for validating licenses.",
5
+ "keywords": [],
6
+ "author": "CKSource (http://cksource.com/)",
7
+ "license": "GPL-2.0-or-later",
8
+ "homepage": "https://github.com/ckeditor/ckeditor5-dev/tree/master/packages/ckeditor5-dev-license-checker",
9
+ "bugs": "https://github.com/ckeditor/ckeditor5/issues",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/ckeditor/ckeditor5-dev.git",
13
+ "directory": "packages/ckeditor5-dev-license-checker"
14
+ },
15
+ "engines": {
16
+ "node": ">=24.11.0",
17
+ "npm": ">=5.7.1"
18
+ },
19
+ "type": "module",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "diff": "^8.0.2",
27
+ "upath": "^2.0.1"
28
+ }
29
+ }