@companion-module/tools 3.1.0 → 3.2.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/CHANGELOG.md +14 -0
- package/README.md +12 -0
- package/dist/license/index.d.ts +10 -0
- package/dist/license/index.js +10 -0
- package/dist/license/inventory.d.ts +111 -0
- package/dist/license/inventory.js +427 -0
- package/dist/license/policy.d.ts +48 -0
- package/dist/license/policy.js +376 -0
- package/dist/scripts/build-connection.js +6 -3
- package/dist/scripts/build-surface.js +6 -3
- package/dist/scripts/lib/build-util.d.ts +1 -0
- package/dist/scripts/lib/build-util.js +5 -1
- package/dist/scripts/lib/cli-options.d.ts +1 -0
- package/dist/scripts/lib/cli-options.js +4 -0
- package/dist/scripts/lib/known-package-licenses.d.ts +3 -2
- package/dist/scripts/lib/known-package-licenses.js +5 -10
- package/dist/scripts/lib/license-util.d.ts +13 -52
- package/dist/scripts/lib/license-util.js +21 -389
- package/dist/scripts/lib/license-warning-util.d.ts +6 -22
- package/dist/scripts/lib/license-warning-util.js +16 -366
- package/dist/scripts/lib/prebuild-check-util.d.ts +53 -0
- package/dist/scripts/lib/prebuild-check-util.js +453 -0
- package/package.json +14 -13
- package/tsconfig/node26/recommended.json +5 -20
|
@@ -1,74 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import * as esbuild from 'esbuild';
|
|
4
|
+
import { asLicenseString, collectInstalledPackages as collectInstalledPackagesWith, collectMetafilePackages as collectMetafilePackagesWith, createLegalInventory, } from '../../license/index.js';
|
|
5
5
|
import { createEsbuildOptions, loadModuleBuildDefinition } from './bundle-util.js';
|
|
6
6
|
import { resolveExternalDependencies, withInstalledExternalTree } from './external-install-util.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
bitbucket: 'https://bitbucket.org/',
|
|
12
|
-
};
|
|
13
|
-
export function normalizeRepositoryUrl(packageJson) {
|
|
14
|
-
const repository = packageJson.repository;
|
|
15
|
-
const declared = typeof repository === 'string'
|
|
16
|
-
? repository
|
|
17
|
-
: repository && typeof repository === 'object' && typeof repository.url === 'string'
|
|
18
|
-
? repository.url
|
|
19
|
-
: undefined;
|
|
20
|
-
let url = declared?.trim() ?? '';
|
|
21
|
-
if (url) {
|
|
22
|
-
// npm shorthands, either "user/repo" or "<host>:user/repo"
|
|
23
|
-
if (/^[\w.-]+\/[\w.-]+$/.test(url))
|
|
24
|
-
url = `${SHORTHAND_REPOSITORY_HOSTS.github}${url}`;
|
|
25
|
-
const shorthand = /^([a-z]+):([\w.-]+\/[\w.-]+)$/.exec(url);
|
|
26
|
-
if (shorthand && SHORTHAND_REPOSITORY_HOSTS[shorthand[1]]) {
|
|
27
|
-
url = `${SHORTHAND_REPOSITORY_HOSTS[shorthand[1]]}${shorthand[2]}`;
|
|
28
|
-
}
|
|
29
|
-
url = url.replace(/^git\+/, '').replace(/^(?:git|ssh):\/\/(?:git@)?/, 'https://');
|
|
30
|
-
const scpLike = /^git@([^:]+):(.+)$/.exec(url);
|
|
31
|
-
if (scpLike)
|
|
32
|
-
url = `https://${scpLike[1]}/${scpLike[2]}`;
|
|
33
|
-
url = url.replace(/\.git$/, '');
|
|
34
|
-
}
|
|
35
|
-
if (!/^https?:\/\//.test(url))
|
|
36
|
-
url = typeof packageJson.homepage === 'string' ? packageJson.homepage.trim() : '';
|
|
37
|
-
return /^https?:\/\//.test(url) ? url : undefined;
|
|
38
|
-
}
|
|
39
|
-
export function normalizeInventoryPath(inputPath, ownerRoot) {
|
|
40
|
-
const relativePath = path.relative(ownerRoot, inputPath);
|
|
41
|
-
if (relativePath === '' || relativePath === '.')
|
|
42
|
-
return '.';
|
|
43
|
-
if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
|
|
44
|
-
return '<outside package root>';
|
|
45
|
-
}
|
|
46
|
-
return relativePath.split(path.sep).join('/');
|
|
47
|
-
}
|
|
48
|
-
async function readPackageJson(packageRoot) {
|
|
49
|
-
return JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
50
|
-
}
|
|
51
|
-
function asLicenseString(license) {
|
|
52
|
-
if (typeof license === 'string')
|
|
53
|
-
return license;
|
|
54
|
-
// npm deprecated { type, url } and arrays of it long ago, but older packages are still published with them
|
|
55
|
-
if (Array.isArray(license)) {
|
|
56
|
-
const choices = license.map(asLicenseString).filter((choice) => choice !== undefined);
|
|
57
|
-
if (!choices.length)
|
|
58
|
-
return undefined;
|
|
59
|
-
return choices.length === 1 ? choices[0] : `(${choices.join(' OR ')})`; // An array offers a choice of licenses
|
|
60
|
-
}
|
|
61
|
-
if (license && typeof license === 'object')
|
|
62
|
-
return asLicenseString(license.type);
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
/** Reads a package's license, preferring the current field over the deprecated plural one */
|
|
66
|
-
function declaredLicenseOf(packageJson) {
|
|
67
|
-
return (asLicenseString(packageJson.license) ??
|
|
68
|
-
asLicenseString(packageJson.licenses) ??
|
|
69
|
-
// Only for packages declaring nothing at all, so this can never override what a package says about itself
|
|
70
|
-
knownPackageLicense(packageJson.name, packageJson.version));
|
|
71
|
-
}
|
|
7
|
+
import { MODULE_LICENSE_OVERRIDES } from './known-package-licenses.js';
|
|
8
|
+
// The engine itself is generic and lives in ../../license, so that it can also be used by the rest of the Companion
|
|
9
|
+
// project. These re-exports keep a single import for everything license related in the module scripts and tests.
|
|
10
|
+
export { NO_LICENSE_OVERRIDES, collectPackageLegalMaterial, collectPackagesFromInputPaths, createLegalInventory, getContributingInputs, normalizeInventoryPath, normalizeLegalText, normalizeRepositoryUrl, renderLicenseFile, renderNoticeFile, writeLegalArtifacts, } from '../../license/index.js';
|
|
72
11
|
async function readManifestLicense(moduleDir) {
|
|
73
12
|
try {
|
|
74
13
|
const manifest = JSON.parse(await readFile(path.join(moduleDir, 'companion', 'manifest.json'), 'utf8'));
|
|
@@ -78,291 +17,26 @@ async function readManifestLicense(moduleDir) {
|
|
|
78
17
|
return undefined; // Modules without a readable manifest fall back to the package.json license
|
|
79
18
|
}
|
|
80
19
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const segments = inputPath.split(path.sep);
|
|
88
|
-
const rootIndex = segments.lastIndexOf('node_modules') + 1;
|
|
89
|
-
const packageRoot = segments
|
|
90
|
-
.slice(0, segments[rootIndex]?.startsWith('@') ? rootIndex + 2 : rootIndex + 1)
|
|
91
|
-
.join(path.sep);
|
|
92
|
-
try {
|
|
93
|
-
await readPackageJson(packageRoot);
|
|
94
|
-
return packageRoot;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return undefined; // Without a package.json this is not a package, so the file counts as project code
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function getContributingInputs(metafile) {
|
|
101
|
-
const inputs = new Set();
|
|
102
|
-
for (const [outputPath, output] of Object.entries(metafile.outputs)) {
|
|
103
|
-
if (!outputPath.endsWith('.js'))
|
|
104
|
-
continue;
|
|
105
|
-
for (const [inputPath, input] of Object.entries(output.inputs)) {
|
|
106
|
-
if (input.bytesInOutput > 0)
|
|
107
|
-
inputs.add(inputPath);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return [...inputs];
|
|
111
|
-
}
|
|
112
|
-
function packageFromJson(kind, packageRoot, packageJson) {
|
|
20
|
+
/**
|
|
21
|
+
* How a module identifies itself to the engine. The manifest license is what the packaged module is distributed as,
|
|
22
|
+
* which is what its dependencies must fit. package.json only licenses the module's own source, so it is the fallback
|
|
23
|
+
* for modules not declaring the other.
|
|
24
|
+
*/
|
|
25
|
+
export async function moduleProjectIdentity(moduleDir) {
|
|
113
26
|
return {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
repositoryUrl: normalizeRepositoryUrl(packageJson),
|
|
119
|
-
packageRoot,
|
|
120
|
-
contributingPaths: new Set(),
|
|
27
|
+
projectRoots: [moduleDir],
|
|
28
|
+
packageRoot: moduleDir,
|
|
29
|
+
name: undefined,
|
|
30
|
+
distributionLicense: asLicenseString(await readManifestLicense(moduleDir)),
|
|
121
31
|
};
|
|
122
32
|
}
|
|
33
|
+
/** Scans a dependency tree installed alongside a module, applying the module tooling's own license overrides */
|
|
123
34
|
export async function collectInstalledPackages(nodeModulesDir) {
|
|
124
|
-
|
|
125
|
-
async function scan(currentNodeModulesDir) {
|
|
126
|
-
let entries;
|
|
127
|
-
try {
|
|
128
|
-
entries = await readdir(currentNodeModulesDir, { withFileTypes: true });
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
134
|
-
if (entry.name === '.bin' || entry.isSymbolicLink())
|
|
135
|
-
continue;
|
|
136
|
-
if (entry.name.startsWith('@')) {
|
|
137
|
-
if (!entry.isDirectory())
|
|
138
|
-
continue;
|
|
139
|
-
await scanScopedPackages(path.join(currentNodeModulesDir, entry.name));
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (!entry.isDirectory())
|
|
143
|
-
continue;
|
|
144
|
-
await scanPackage(path.join(currentNodeModulesDir, entry.name));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function scanScopedPackages(scopeDir) {
|
|
148
|
-
const entries = await readdir(scopeDir, { withFileTypes: true });
|
|
149
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
150
|
-
if (entry.isDirectory() && !entry.isSymbolicLink())
|
|
151
|
-
await scanPackage(path.join(scopeDir, entry.name));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
async function scanPackage(packageRoot) {
|
|
155
|
-
try {
|
|
156
|
-
packages.push(packageFromJson('external', packageRoot, await readPackageJson(packageRoot)));
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
await scan(path.join(packageRoot, 'node_modules'));
|
|
162
|
-
}
|
|
163
|
-
await scan(nodeModulesDir);
|
|
164
|
-
return packages;
|
|
165
|
-
}
|
|
166
|
-
const MAX_LEGAL_FILE_SIZE = 1024 * 1024;
|
|
167
|
-
export function normalizeLegalText(content) {
|
|
168
|
-
return content.replace(/\r\n/g, '\n').trim();
|
|
169
|
-
}
|
|
170
|
-
function sha256(content) {
|
|
171
|
-
return createHash('sha256').update(content).digest('hex');
|
|
172
|
-
}
|
|
173
|
-
function isPathInside(childPath, parentPath) {
|
|
174
|
-
const relativePath = path.relative(parentPath, childPath);
|
|
175
|
-
return (relativePath === '' ||
|
|
176
|
-
(!relativePath.startsWith(`..${path.sep}`) && relativePath !== '..' && !path.isAbsolute(relativePath)));
|
|
177
|
-
}
|
|
178
|
-
function legalRole(filename) {
|
|
179
|
-
if (/^(license|licence|copying)/i.test(filename))
|
|
180
|
-
return 'license';
|
|
181
|
-
if (/^notice/i.test(filename))
|
|
182
|
-
return 'notice';
|
|
183
|
-
return undefined;
|
|
35
|
+
return collectInstalledPackagesWith(nodeModulesDir, MODULE_LICENSE_OVERRIDES);
|
|
184
36
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return undefined;
|
|
189
|
-
const fileStat = await stat(filePath);
|
|
190
|
-
if (!fileStat.isFile() || fileStat.size > MAX_LEGAL_FILE_SIZE)
|
|
191
|
-
return undefined;
|
|
192
|
-
const rawContent = await readFile(filePath, 'utf8');
|
|
193
|
-
if (rawContent.includes('\0'))
|
|
194
|
-
return undefined;
|
|
195
|
-
const content = normalizeLegalText(rawContent);
|
|
196
|
-
if (!content)
|
|
197
|
-
return undefined;
|
|
198
|
-
return { role, filename: normalizeInventoryPath(filePath, packageRoot), content, sha256: sha256(content) };
|
|
199
|
-
}
|
|
200
|
-
function sourceLoader(filename) {
|
|
201
|
-
switch (path.extname(filename).toLowerCase()) {
|
|
202
|
-
case '.js':
|
|
203
|
-
case '.mjs':
|
|
204
|
-
case '.cjs':
|
|
205
|
-
return 'js';
|
|
206
|
-
case '.ts':
|
|
207
|
-
return 'ts';
|
|
208
|
-
case '.tsx':
|
|
209
|
-
return 'tsx';
|
|
210
|
-
default:
|
|
211
|
-
return undefined;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
export async function collectPackageLegalMaterial(packageInfo) {
|
|
215
|
-
const diagnostics = [];
|
|
216
|
-
const candidateFiles = new Map();
|
|
217
|
-
for (const entry of await readdir(packageInfo.packageRoot, { withFileTypes: true })) {
|
|
218
|
-
if (!entry.isFile())
|
|
219
|
-
continue;
|
|
220
|
-
const role = legalRole(entry.name);
|
|
221
|
-
if (role)
|
|
222
|
-
candidateFiles.set(entry.name, role);
|
|
223
|
-
}
|
|
224
|
-
const seeLicenseMatch = /^SEE LICENSE IN (.+)$/i.exec(packageInfo.declaredLicense ?? '');
|
|
225
|
-
if (seeLicenseMatch) {
|
|
226
|
-
const explicitPath = path.resolve(packageInfo.packageRoot, seeLicenseMatch[1]);
|
|
227
|
-
if (!isPathInside(explicitPath, packageInfo.packageRoot)) {
|
|
228
|
-
diagnostics.push(`Ignoring license file outside package root: ${seeLicenseMatch[1]}`);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
candidateFiles.set(normalizeInventoryPath(explicitPath, packageInfo.packageRoot), 'license');
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const legalTexts = [];
|
|
235
|
-
for (const [filename, role] of [...candidateFiles.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
236
|
-
try {
|
|
237
|
-
const legalText = await readLegalText(path.join(packageInfo.packageRoot, filename), packageInfo.packageRoot, role);
|
|
238
|
-
if (legalText)
|
|
239
|
-
legalTexts.push(legalText);
|
|
240
|
-
else
|
|
241
|
-
diagnostics.push(`Ignoring unreadable, binary, or oversized legal file: ${filename}`);
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
diagnostics.push(`Ignoring unreadable legal file: ${filename}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (!legalTexts.some((text) => text.role === 'license')) {
|
|
248
|
-
for (const sourcePath of [...packageInfo.contributingPaths].sort()) {
|
|
249
|
-
const loader = sourceLoader(sourcePath);
|
|
250
|
-
if (!loader) {
|
|
251
|
-
diagnostics.push(`Ignoring unsupported source file for legal comments: ${sourcePath}`);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
try {
|
|
255
|
-
const source = await readFile(path.join(packageInfo.packageRoot, sourcePath), 'utf8');
|
|
256
|
-
const result = await esbuild.transform(source, { loader, legalComments: 'external' });
|
|
257
|
-
if (result.legalComments) {
|
|
258
|
-
const content = normalizeLegalText(result.legalComments);
|
|
259
|
-
if (content) {
|
|
260
|
-
legalTexts.push({
|
|
261
|
-
role: 'source-comment',
|
|
262
|
-
filename: sourcePath,
|
|
263
|
-
content,
|
|
264
|
-
sha256: sha256(content),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
diagnostics.push(`Ignoring unreadable source file for legal comments: ${sourcePath}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const uniqueTexts = new Map();
|
|
275
|
-
for (const legalText of legalTexts) {
|
|
276
|
-
const key = legalText.role === 'source-comment'
|
|
277
|
-
? `${legalText.role}:${legalText.sha256}`
|
|
278
|
-
: `${legalText.role}:${legalText.filename}:${legalText.sha256}`;
|
|
279
|
-
uniqueTexts.set(key, legalText);
|
|
280
|
-
}
|
|
281
|
-
return { package: { ...packageInfo, legalTexts: [...uniqueTexts.values()] }, diagnostics };
|
|
282
|
-
}
|
|
283
|
-
function comparePackages(a, b) {
|
|
284
|
-
if (a.kind === 'project' && b.kind !== 'project')
|
|
285
|
-
return -1;
|
|
286
|
-
if (a.kind !== 'project' && b.kind === 'project')
|
|
287
|
-
return 1;
|
|
288
|
-
return a.name.localeCompare(b.name) || (a.version ?? '').localeCompare(b.version ?? '');
|
|
289
|
-
}
|
|
290
|
-
function packageName(packageInfo) {
|
|
291
|
-
return packageInfo.version ? `${packageInfo.name}@${packageInfo.version}` : packageInfo.name;
|
|
292
|
-
}
|
|
293
|
-
function renderLegalFile(inventory, roles) {
|
|
294
|
-
const groupedTexts = new Map();
|
|
295
|
-
for (const packageInfo of inventory.packages) {
|
|
296
|
-
for (const text of packageInfo.legalTexts) {
|
|
297
|
-
if (!roles.includes(text.role))
|
|
298
|
-
continue;
|
|
299
|
-
const group = groupedTexts.get(text.sha256) ?? { text, packages: new Map() };
|
|
300
|
-
const packageKey = `${packageInfo.kind === 'project' ? 'project' : 'dependency'}:${packageName(packageInfo)}:${packageInfo.declaredLicense ?? ''}`;
|
|
301
|
-
group.packages.set(packageKey, packageInfo);
|
|
302
|
-
groupedTexts.set(text.sha256, group);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (!groupedTexts.size)
|
|
306
|
-
return undefined;
|
|
307
|
-
const sections = [...groupedTexts.values()].sort((a, b) => comparePackages([...a.packages.values()].sort(comparePackages)[0], [...b.packages.values()].sort(comparePackages)[0]));
|
|
308
|
-
const separator = '-'.repeat(80);
|
|
309
|
-
const renderedSections = sections.map((section) => {
|
|
310
|
-
const packages = [...section.packages.values()].sort(comparePackages);
|
|
311
|
-
const packageList = packages
|
|
312
|
-
.map((packageInfo) => {
|
|
313
|
-
const label = `${packageName(packageInfo)} — ${packageInfo.declaredLicense ?? 'UNKNOWN'}`;
|
|
314
|
-
// Tells recipients where to obtain the source, which MPL-2.0 and the GPL family require
|
|
315
|
-
return packageInfo.repositoryUrl ? `${label} (${packageInfo.repositoryUrl})` : label;
|
|
316
|
-
})
|
|
317
|
-
.join(', ');
|
|
318
|
-
return `Packages: ${packageList}\n${separator}\n${normalizeLegalText(section.text.content)}`;
|
|
319
|
-
});
|
|
320
|
-
return `${renderedSections.join('\n\n')}\n`;
|
|
321
|
-
}
|
|
322
|
-
export function renderLicenseFile(inventory) {
|
|
323
|
-
return renderLegalFile(inventory, ['license', 'source-comment']) ?? '';
|
|
324
|
-
}
|
|
325
|
-
export function renderNoticeFile(inventory) {
|
|
326
|
-
return renderLegalFile(inventory, ['notice']);
|
|
327
|
-
}
|
|
328
|
-
export async function writeLegalArtifacts(outputDir, inventory) {
|
|
329
|
-
await writeFile(path.join(outputDir, 'LICENSE'), renderLicenseFile(inventory));
|
|
330
|
-
const notice = renderNoticeFile(inventory);
|
|
331
|
-
const noticePath = path.join(outputDir, 'NOTICE');
|
|
332
|
-
if (notice)
|
|
333
|
-
await writeFile(noticePath, notice);
|
|
334
|
-
else
|
|
335
|
-
await rm(noticePath, { force: true });
|
|
336
|
-
}
|
|
337
|
-
const kindPriority = {
|
|
338
|
-
project: 0,
|
|
339
|
-
bundled: 1,
|
|
340
|
-
external: 2,
|
|
341
|
-
};
|
|
342
|
-
export async function createLegalInventory(packages) {
|
|
343
|
-
const mergedPackages = new Map();
|
|
344
|
-
for (const packageInfo of packages) {
|
|
345
|
-
const existing = mergedPackages.get(packageInfo.packageRoot);
|
|
346
|
-
if (!existing) {
|
|
347
|
-
mergedPackages.set(packageInfo.packageRoot, {
|
|
348
|
-
...packageInfo,
|
|
349
|
-
contributingPaths: new Set(packageInfo.contributingPaths),
|
|
350
|
-
});
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
for (const sourcePath of packageInfo.contributingPaths)
|
|
354
|
-
existing.contributingPaths.add(sourcePath);
|
|
355
|
-
if (kindPriority[packageInfo.kind] < kindPriority[existing.kind])
|
|
356
|
-
existing.kind = packageInfo.kind;
|
|
357
|
-
}
|
|
358
|
-
const diagnostics = [];
|
|
359
|
-
const legalRecords = [];
|
|
360
|
-
for (const packageInfo of mergedPackages.values()) {
|
|
361
|
-
const material = await collectPackageLegalMaterial(packageInfo);
|
|
362
|
-
legalRecords.push(material.package);
|
|
363
|
-
diagnostics.push(...material.diagnostics);
|
|
364
|
-
}
|
|
365
|
-
return { packages: legalRecords, diagnostics };
|
|
37
|
+
/** Attributes the inputs of a module's esbuild metafile, applying the module tooling's own license overrides */
|
|
38
|
+
export async function collectMetafilePackages(moduleDir, metafile) {
|
|
39
|
+
return collectMetafilePackagesWith(metafile, moduleDir, await moduleProjectIdentity(moduleDir), MODULE_LICENSE_OVERRIDES);
|
|
366
40
|
}
|
|
367
41
|
export async function analyzeShippedLegalInventory(moduleDir) {
|
|
368
42
|
const definition = await loadModuleBuildDefinition(moduleDir);
|
|
@@ -383,45 +57,3 @@ export async function analyzeShippedLegalInventory(moduleDir) {
|
|
|
383
57
|
const inventory = await createLegalInventory(packages);
|
|
384
58
|
return { ...inventory, diagnostics: [...metafilePackages.diagnostics, ...inventory.diagnostics] };
|
|
385
59
|
}
|
|
386
|
-
export async function collectMetafilePackages(moduleDir, metafile) {
|
|
387
|
-
const resolvedModuleDir = await realpath(moduleDir);
|
|
388
|
-
const projectPackageJson = await readPackageJson(resolvedModuleDir);
|
|
389
|
-
const packages = new Map();
|
|
390
|
-
const diagnostics = [];
|
|
391
|
-
// The manifest license is what the packaged module is distributed as, which is what its dependencies must fit.
|
|
392
|
-
// package.json only licenses the module's own source, so it is a fallback for modules not declaring the other.
|
|
393
|
-
const projectLicense = (await readManifestLicense(resolvedModuleDir)) ?? declaredLicenseOf(projectPackageJson);
|
|
394
|
-
for (const input of getContributingInputs(metafile)) {
|
|
395
|
-
if (input.startsWith('<')) {
|
|
396
|
-
diagnostics.push(`Ignoring virtual esbuild input: ${input}`);
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
const inputPath = path.resolve(resolvedModuleDir, input);
|
|
400
|
-
if (!isPathInside(inputPath, resolvedModuleDir)) {
|
|
401
|
-
diagnostics.push(`Ignoring esbuild input outside module directory: ${input}`);
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
// Resolve ownership using logical node_modules path. Realpath would turn linked
|
|
405
|
-
// dependencies into paths outside moduleDir and incorrectly classify them as project code.
|
|
406
|
-
const packageRoot = (await findPackageRoot(inputPath, resolvedModuleDir)) ?? resolvedModuleDir;
|
|
407
|
-
const packageJson = packageRoot === resolvedModuleDir ? projectPackageJson : await readPackageJson(packageRoot);
|
|
408
|
-
const kind = packageRoot === resolvedModuleDir ? 'project' : 'bundled';
|
|
409
|
-
const key = `${kind}:${packageRoot}`;
|
|
410
|
-
let packageInfo = packages.get(key);
|
|
411
|
-
if (!packageInfo) {
|
|
412
|
-
packageInfo = {
|
|
413
|
-
kind,
|
|
414
|
-
name: packageJson.name ?? path.basename(packageRoot),
|
|
415
|
-
version: packageJson.version,
|
|
416
|
-
declaredLicense: kind === 'project' ? asLicenseString(projectLicense) : declaredLicenseOf(packageJson),
|
|
417
|
-
sourceLicense: kind === 'project' ? declaredLicenseOf(projectPackageJson) : undefined,
|
|
418
|
-
repositoryUrl: normalizeRepositoryUrl(packageJson),
|
|
419
|
-
packageRoot,
|
|
420
|
-
contributingPaths: new Set(),
|
|
421
|
-
};
|
|
422
|
-
packages.set(key, packageInfo);
|
|
423
|
-
}
|
|
424
|
-
packageInfo.contributingPaths.add(normalizeInventoryPath(inputPath, packageRoot));
|
|
425
|
-
}
|
|
426
|
-
return { packages: [...packages.values()], diagnostics };
|
|
427
|
-
}
|
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LegalInventory, type LicensePolicyDescriptor, type LicensePolicyIssue } from '../../license/index.js';
|
|
2
|
+
export { LicensePolicyError, SUPPORTED_PROJECT_LICENSES, resolveProjectLicense } from '../../license/index.js';
|
|
3
|
+
export type { LicensePolicyDescriptor, LicensePolicyIssue, ProjectLicense } from '../../license/index.js';
|
|
2
4
|
export type ModuleType = 'connection' | 'surface';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
packageVersion?: string;
|
|
6
|
-
packageKind: ShippedPackageLegalRecord['kind'];
|
|
7
|
-
message: string;
|
|
8
|
-
}
|
|
9
|
-
type LicensePolicyOutput = Pick<NodeJS.WriteStream, 'write'>;
|
|
10
|
-
export declare class LicensePolicyError extends Error {
|
|
11
|
-
readonly issues: LicensePolicyIssue[];
|
|
12
|
-
constructor(issues: LicensePolicyIssue[]);
|
|
13
|
-
}
|
|
14
|
-
/** Licenses a module may be declared as, each allowing a different set of dependency licenses */
|
|
15
|
-
export type ProjectLicense = 'MIT' | 'GPL-2.0-only' | 'GPL-3.0-only';
|
|
16
|
-
export declare const SUPPORTED_PROJECT_LICENSES: ProjectLicense[];
|
|
17
|
-
/** Resolve the license the packaged module is distributed under, which selects the policy applied to its dependencies */
|
|
18
|
-
export declare function resolveProjectLicense(inventory: LegalInventory): {
|
|
19
|
-
declaration: string;
|
|
20
|
-
license: ProjectLicense | undefined;
|
|
21
|
-
};
|
|
5
|
+
/** How the policy engine talks about a module: what it is called, where it declares its licenses, and how it ships */
|
|
6
|
+
export declare const MODULE_LICENSE_POLICY: LicensePolicyDescriptor;
|
|
22
7
|
export declare function createLicensePolicyIssues(inventory: LegalInventory): LicensePolicyIssue[];
|
|
23
8
|
export declare function enforceLicensePolicy(inventory: LegalInventory, options?: {
|
|
24
9
|
ignoreLicenseRules?: boolean;
|
|
25
|
-
stderr?:
|
|
10
|
+
stderr?: Pick<NodeJS.WriteStream, 'write'>;
|
|
26
11
|
}): void;
|
|
27
|
-
export {};
|