upstream-radar 0.32.0 → 0.33.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 (106) hide show
  1. package/README.md +396 -43
  2. package/cordis.patch.yml +1 -0
  3. package/dist/src/cli.js +950 -33
  4. package/dist/src/cli.js.map +1 -1
  5. package/dist/src/compatibility.d.ts +3 -1
  6. package/dist/src/compatibility.d.ts.map +1 -1
  7. package/dist/src/compatibility.js +146 -12
  8. package/dist/src/compatibility.js.map +1 -1
  9. package/dist/src/demo.d.ts +13 -0
  10. package/dist/src/demo.d.ts.map +1 -0
  11. package/dist/src/demo.js +85 -0
  12. package/dist/src/demo.js.map +1 -0
  13. package/dist/src/doctor.d.ts.map +1 -1
  14. package/dist/src/doctor.js +62 -0
  15. package/dist/src/doctor.js.map +1 -1
  16. package/dist/src/dsh-analysis.d.ts.map +1 -1
  17. package/dist/src/dsh-analysis.js +2 -1
  18. package/dist/src/dsh-analysis.js.map +1 -1
  19. package/dist/src/dsh-plugin.d.ts +9 -3
  20. package/dist/src/dsh-plugin.d.ts.map +1 -1
  21. package/dist/src/dsh-plugin.js +87 -11
  22. package/dist/src/dsh-plugin.js.map +1 -1
  23. package/dist/src/dsh-runtime.d.ts +7 -0
  24. package/dist/src/dsh-runtime.d.ts.map +1 -1
  25. package/dist/src/dsh-runtime.js +21 -0
  26. package/dist/src/dsh-runtime.js.map +1 -1
  27. package/dist/src/github-advisory.d.ts +25 -0
  28. package/dist/src/github-advisory.d.ts.map +1 -0
  29. package/dist/src/github-advisory.js +335 -0
  30. package/dist/src/github-advisory.js.map +1 -0
  31. package/dist/src/graph.d.ts +2 -0
  32. package/dist/src/graph.d.ts.map +1 -1
  33. package/dist/src/graph.js +600 -4
  34. package/dist/src/graph.js.map +1 -1
  35. package/dist/src/index.d.ts +14 -6
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +13 -5
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/init.d.ts +44 -2
  40. package/dist/src/init.d.ts.map +1 -1
  41. package/dist/src/init.js +62 -5
  42. package/dist/src/init.js.map +1 -1
  43. package/dist/src/installed-graph.d.ts +3 -1
  44. package/dist/src/installed-graph.d.ts.map +1 -1
  45. package/dist/src/installed-graph.js +101 -2
  46. package/dist/src/installed-graph.js.map +1 -1
  47. package/dist/src/inventory.d.ts.map +1 -1
  48. package/dist/src/inventory.js +76 -2
  49. package/dist/src/inventory.js.map +1 -1
  50. package/dist/src/notification-policy.d.ts +20 -0
  51. package/dist/src/notification-policy.d.ts.map +1 -0
  52. package/dist/src/notification-policy.js +87 -0
  53. package/dist/src/notification-policy.js.map +1 -0
  54. package/dist/src/npm-release.d.ts.map +1 -1
  55. package/dist/src/npm-release.js +18 -7
  56. package/dist/src/npm-release.js.map +1 -1
  57. package/dist/src/osv.d.ts.map +1 -1
  58. package/dist/src/osv.js +1 -0
  59. package/dist/src/osv.js.map +1 -1
  60. package/dist/src/quickstart.d.ts +31 -0
  61. package/dist/src/quickstart.d.ts.map +1 -0
  62. package/dist/src/quickstart.js +266 -0
  63. package/dist/src/quickstart.js.map +1 -0
  64. package/dist/src/radar-history.d.ts +22 -0
  65. package/dist/src/radar-history.d.ts.map +1 -0
  66. package/dist/src/radar-history.js +80 -0
  67. package/dist/src/radar-history.js.map +1 -0
  68. package/dist/src/radar-render.d.ts.map +1 -1
  69. package/dist/src/radar-render.js +106 -1
  70. package/dist/src/radar-render.js.map +1 -1
  71. package/dist/src/radar-state.d.ts.map +1 -1
  72. package/dist/src/radar-state.js +264 -6
  73. package/dist/src/radar-state.js.map +1 -1
  74. package/dist/src/radar-status.d.ts +36 -1
  75. package/dist/src/radar-status.d.ts.map +1 -1
  76. package/dist/src/radar-status.js +199 -16
  77. package/dist/src/radar-status.js.map +1 -1
  78. package/dist/src/radar-types.d.ts +128 -3
  79. package/dist/src/radar-types.d.ts.map +1 -1
  80. package/dist/src/radar-types.js +4 -0
  81. package/dist/src/radar-types.js.map +1 -1
  82. package/dist/src/radar.d.ts +7 -1
  83. package/dist/src/radar.d.ts.map +1 -1
  84. package/dist/src/radar.js +432 -70
  85. package/dist/src/radar.js.map +1 -1
  86. package/dist/src/render.d.ts.map +1 -1
  87. package/dist/src/render.js +10 -0
  88. package/dist/src/render.js.map +1 -1
  89. package/dist/src/threat-intel.d.ts +46 -0
  90. package/dist/src/threat-intel.d.ts.map +1 -0
  91. package/dist/src/threat-intel.js +243 -0
  92. package/dist/src/threat-intel.js.map +1 -0
  93. package/dist/src/version.d.ts +1 -1
  94. package/dist/src/version.js +1 -1
  95. package/dist/src/vulnerability-priority.d.ts +16 -0
  96. package/dist/src/vulnerability-priority.d.ts.map +1 -0
  97. package/dist/src/vulnerability-priority.js +20 -0
  98. package/dist/src/vulnerability-priority.js.map +1 -0
  99. package/dist/src/webhook.d.ts +84 -0
  100. package/dist/src/webhook.d.ts.map +1 -0
  101. package/dist/src/webhook.js +432 -0
  102. package/dist/src/webhook.js.map +1 -0
  103. package/docs/README.zh-CN.md +394 -40
  104. package/package.json +22 -3
  105. package/schemas/quickstart.schema.json +56 -0
  106. package/schemas/webhook.schema.json +53 -0
package/dist/src/graph.js CHANGED
@@ -49,14 +49,15 @@ function candidateDependencyPaths(parentPath, dependencyName) {
49
49
  candidates.push(`node_modules/${dependencyName}`);
50
50
  return [...new Set(candidates)];
51
51
  }
52
- function dependencyEntries(item) {
52
+ function dependencyEntries(item, includeDevelopment = true) {
53
53
  const selected = new Map();
54
54
  const add = (value, kind, selectKind) => {
55
55
  for (const [name, spec] of Object.entries(asStringRecord(value))) {
56
56
  selected.set(name, { spec, kind: selectKind?.(name) ?? kind });
57
57
  }
58
58
  };
59
- add(item.devDependencies, 'development');
59
+ if (includeDevelopment)
60
+ add(item.devDependencies, 'development');
60
61
  add(item.dependencies, 'runtime');
61
62
  const optionalPeers = optionalPeerNames(item.peerDependenciesMeta);
62
63
  add(item.peerDependencies, 'peer', name => optionalPeers.has(name) ? 'optional' : 'peer');
@@ -96,10 +97,20 @@ export function parseNpmLockGraph(lockfile, rootPackage) {
96
97
  continue;
97
98
  allNodes.set(path, { id: path, name, version });
98
99
  }
100
+ const rootRecord = asRecord(rawPackages['']);
101
+ const rootRecordName = typeof rootRecord?.name === 'string' ? rootRecord.name : undefined;
102
+ const rootRecordVersion = typeof rootRecord?.version === 'string' ? rootRecord.version : undefined;
103
+ const workspaceRootId = rootRecordName === rootPackage.name && rootRecordVersion === rootPackage.version
104
+ ? `npm:workspace-root:${rootPackage.name}@${rootPackage.version}`
105
+ : undefined;
106
+ if (workspaceRootId !== undefined && rootRecord !== undefined) {
107
+ records.set(workspaceRootId, rootRecord);
108
+ allNodes.set(workspaceRootId, { id: workspaceRootId, name: rootPackage.name, version: rootPackage.version });
109
+ }
99
110
  const roots = [...allNodes.values()]
100
111
  .filter(node => node.name === rootPackage.name && node.version === rootPackage.version)
101
112
  .sort((left, right) => left.id.length - right.id.length || left.id.localeCompare(right.id));
102
- const rootNode = roots[0];
113
+ const rootNode = workspaceRootId === undefined ? roots[0] : allNodes.get(workspaceRootId);
103
114
  if (rootNode === undefined) {
104
115
  throw new Error(`requested root package is not present in npm lockfile: ${rootPackage.name}@${rootPackage.version}`);
105
116
  }
@@ -109,7 +120,7 @@ export function parseNpmLockGraph(lockfile, rootPackage) {
109
120
  const item = records.get(parentPath);
110
121
  if (item === undefined)
111
122
  continue;
112
- for (const dependency of dependencyEntries(item)) {
123
+ for (const dependency of dependencyEntries(item, parentPath !== workspaceRootId)) {
113
124
  const targetPath = candidateDependencyPaths(parentPath, dependency.name)
114
125
  .find(candidate => allNodes.has(candidate));
115
126
  if (targetPath === undefined) {
@@ -156,6 +167,591 @@ export function parseNpmLockGraph(lockfile, rootPackage) {
156
167
  ...(reachableUnresolved.length === 0 ? {} : { unresolved: reachableUnresolved }),
157
168
  };
158
169
  }
170
+ const MAX_PNPM_LOCKFILE_BYTES = 16 * 1024 * 1024;
171
+ const MAX_PNPM_LOCKFILE_LINES = 500_000;
172
+ const MAX_PNPM_LOCKFILE_PACKAGES = 100_000;
173
+ const MAX_PNPM_LOCKFILE_EDGES = 250_000;
174
+ const PNPM_DEPENDENCY_SECTIONS = new Set(['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']);
175
+ function stripPnpmYamlComment(value) {
176
+ let quote;
177
+ for (let index = 0; index < value.length; index += 1) {
178
+ const character = value[index];
179
+ if (quote === 'single') {
180
+ if (character === "'" && value[index + 1] === "'") {
181
+ index += 1;
182
+ }
183
+ else if (character === "'") {
184
+ quote = undefined;
185
+ }
186
+ continue;
187
+ }
188
+ if (quote === 'double') {
189
+ if (character === '\\')
190
+ index += 1;
191
+ else if (character === '"')
192
+ quote = undefined;
193
+ continue;
194
+ }
195
+ if (character === "'")
196
+ quote = 'single';
197
+ else if (character === '"')
198
+ quote = 'double';
199
+ else if (character === '#' && (index === 0 || /\s/.test(value[index - 1] ?? '')))
200
+ return value.slice(0, index).trimEnd();
201
+ }
202
+ return value.trimEnd();
203
+ }
204
+ function pnpmMappingColon(value) {
205
+ let quote;
206
+ for (let index = 0; index < value.length; index += 1) {
207
+ const character = value[index];
208
+ if (quote === 'single') {
209
+ if (character === "'" && value[index + 1] === "'")
210
+ index += 1;
211
+ else if (character === "'")
212
+ quote = undefined;
213
+ continue;
214
+ }
215
+ if (quote === 'double') {
216
+ if (character === '\\')
217
+ index += 1;
218
+ else if (character === '"')
219
+ quote = undefined;
220
+ continue;
221
+ }
222
+ if (character === "'")
223
+ quote = 'single';
224
+ else if (character === '"')
225
+ quote = 'double';
226
+ else if (character === ':' && (index + 1 === value.length || /\s/.test(value[index + 1] ?? '')))
227
+ return index;
228
+ }
229
+ return -1;
230
+ }
231
+ function parsePnpmYamlScalar(value, line) {
232
+ const trimmed = stripPnpmYamlComment(value).trim();
233
+ if (trimmed === '')
234
+ return '';
235
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
236
+ return trimmed.slice(1, -1).replaceAll("''", "'");
237
+ }
238
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
239
+ try {
240
+ const parsed = JSON.parse(trimmed);
241
+ if (typeof parsed !== 'string')
242
+ throw new Error('not a string');
243
+ return parsed;
244
+ }
245
+ catch {
246
+ throw new Error(`pnpm lockfile has an invalid quoted scalar on line ${line}`);
247
+ }
248
+ }
249
+ if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('&') || trimmed.startsWith('*')) {
250
+ return trimmed;
251
+ }
252
+ return trimmed;
253
+ }
254
+ function parsePnpmMapping(line) {
255
+ const colon = pnpmMappingColon(line.content);
256
+ if (colon < 0)
257
+ return undefined;
258
+ const key = parsePnpmYamlScalar(line.content.slice(0, colon), line.line);
259
+ if (key === '')
260
+ throw new Error(`pnpm lockfile has an empty mapping key on line ${line.line}`);
261
+ return { key, value: parsePnpmYamlScalar(line.content.slice(colon + 1), line.line) };
262
+ }
263
+ function preparePnpmYamlLines(text) {
264
+ if (Buffer.byteLength(text, 'utf8') > MAX_PNPM_LOCKFILE_BYTES) {
265
+ throw new Error(`pnpm lockfile exceeds the ${MAX_PNPM_LOCKFILE_BYTES} byte limit`);
266
+ }
267
+ const rawLines = text.split(/\r?\n/);
268
+ if (rawLines.length > MAX_PNPM_LOCKFILE_LINES) {
269
+ throw new Error(`pnpm lockfile exceeds the ${MAX_PNPM_LOCKFILE_LINES} line limit`);
270
+ }
271
+ const lines = [];
272
+ for (let index = 0; index < rawLines.length; index += 1) {
273
+ const raw = rawLines[index] ?? '';
274
+ if (raw.includes('\t'))
275
+ throw new Error(`pnpm lockfile uses tabs on line ${index + 1}`);
276
+ const content = stripPnpmYamlComment(raw).trimEnd();
277
+ if (content.trim() === '' || content.trim() === '---' || content.trim() === '...')
278
+ continue;
279
+ const indent = raw.length - raw.trimStart().length;
280
+ if (indent > 256)
281
+ throw new Error(`pnpm lockfile indentation is too deep on line ${index + 1}`);
282
+ lines.push({ indent, content: content.slice(indent), line: index + 1 });
283
+ }
284
+ return lines;
285
+ }
286
+ function pnpmSectionRange(lines, section) {
287
+ const start = lines.findIndex(line => line.indent === 0 && parsePnpmMapping(line)?.key === section);
288
+ if (start < 0)
289
+ throw new Error(`pnpm lockfile does not contain a ${section} section`);
290
+ let end = lines.length;
291
+ for (let index = start + 1; index < lines.length; index += 1) {
292
+ if (lines[index]?.indent === 0) {
293
+ end = index;
294
+ break;
295
+ }
296
+ }
297
+ return { start, end };
298
+ }
299
+ function parsePnpmInlineDependencyMap(value, line) {
300
+ const trimmed = value.trim();
301
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
302
+ return {};
303
+ const result = {};
304
+ let start = 1;
305
+ let quote;
306
+ let depth = 0;
307
+ const parts = [];
308
+ for (let index = 1; index < trimmed.length - 1; index += 1) {
309
+ const character = trimmed[index];
310
+ if (quote === 'single') {
311
+ if (character === "'" && trimmed[index + 1] === "'")
312
+ index += 1;
313
+ else if (character === "'")
314
+ quote = undefined;
315
+ }
316
+ else if (quote === 'double') {
317
+ if (character === '\\')
318
+ index += 1;
319
+ else if (character === '"')
320
+ quote = undefined;
321
+ }
322
+ else if (character === "'")
323
+ quote = 'single';
324
+ else if (character === '"')
325
+ quote = 'double';
326
+ else if (character === '{' || character === '[')
327
+ depth += 1;
328
+ else if (character === '}' || character === ']')
329
+ depth -= 1;
330
+ else if (character === ',' && depth === 0) {
331
+ parts.push(trimmed.slice(start, index));
332
+ start = index + 1;
333
+ }
334
+ }
335
+ parts.push(trimmed.slice(start, -1));
336
+ for (const part of parts) {
337
+ const colon = pnpmMappingColon(part.trim());
338
+ if (colon < 0)
339
+ throw new Error(`pnpm lockfile has an invalid inline dependency map on line ${line}`);
340
+ const key = parsePnpmYamlScalar(part.trim().slice(0, colon), line);
341
+ const dependency = parsePnpmYamlScalar(part.trim().slice(colon + 1), line);
342
+ if (key === '' || dependency === '')
343
+ throw new Error(`pnpm lockfile has an invalid inline dependency map on line ${line}`);
344
+ if (Object.hasOwn(result, key))
345
+ throw new Error(`pnpm lockfile repeats dependency ${key} on line ${line}`);
346
+ result[key] = dependency;
347
+ }
348
+ return result;
349
+ }
350
+ function parsePnpmChildMap(lines, start, end, parentIndent, label) {
351
+ const result = {};
352
+ for (let index = start; index < end; index += 1) {
353
+ const line = lines[index];
354
+ if (line === undefined || line.indent <= parentIndent)
355
+ break;
356
+ if (line.indent !== parentIndent + 2)
357
+ continue;
358
+ const mapping = parsePnpmMapping(line);
359
+ if (mapping === undefined)
360
+ continue;
361
+ if (mapping.value === '')
362
+ continue;
363
+ if (Object.hasOwn(result, mapping.key))
364
+ throw new Error(`pnpm lockfile repeats ${label} ${mapping.key} on line ${line.line}`);
365
+ result[mapping.key] = mapping.value;
366
+ }
367
+ return result;
368
+ }
369
+ function parsePnpmEntry(lines, start, end) {
370
+ const entry = {
371
+ dependencies: {},
372
+ devDependencies: {},
373
+ optionalDependencies: {},
374
+ peerDependencies: {},
375
+ optionalPeers: new Set(),
376
+ };
377
+ for (let index = start + 1; index < end; index += 1) {
378
+ const line = lines[index];
379
+ if (line === undefined || line.indent <= 2)
380
+ break;
381
+ if (line.indent !== 4)
382
+ continue;
383
+ const mapping = parsePnpmMapping(line);
384
+ if (mapping === undefined)
385
+ continue;
386
+ if (PNPM_DEPENDENCY_SECTIONS.has(mapping.key)) {
387
+ const parsed = mapping.value.startsWith('{')
388
+ ? parsePnpmInlineDependencyMap(mapping.value, line.line)
389
+ : parsePnpmChildMap(lines, index + 1, end, line.indent, mapping.key);
390
+ Object.assign(entry[mapping.key], parsed);
391
+ }
392
+ else if (mapping.key === 'peerDependenciesMeta' && mapping.value === '') {
393
+ for (let child = index + 1; child < end; child += 1) {
394
+ const childLine = lines[child];
395
+ if (childLine === undefined || childLine.indent <= line.indent)
396
+ break;
397
+ if (childLine.indent !== line.indent + 2)
398
+ continue;
399
+ const childMapping = parsePnpmMapping(childLine);
400
+ if (childMapping === undefined || childMapping.value !== '')
401
+ continue;
402
+ for (let meta = child + 1; meta < end; meta += 1) {
403
+ const metaLine = lines[meta];
404
+ if (metaLine === undefined || metaLine.indent <= childLine.indent)
405
+ break;
406
+ if (metaLine.indent !== childLine.indent + 2)
407
+ continue;
408
+ const metaMapping = parsePnpmMapping(metaLine);
409
+ if (metaMapping?.key === 'optional' && metaMapping.value === 'true')
410
+ entry.optionalPeers.add(childMapping.key);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ return entry;
416
+ }
417
+ function parsePnpmSectionEntries(lines, section) {
418
+ const range = pnpmSectionRange(lines, section);
419
+ const result = new Map();
420
+ for (let index = range.start + 1; index < range.end; index += 1) {
421
+ const line = lines[index];
422
+ if (line === undefined || line.indent !== 2)
423
+ continue;
424
+ const mapping = parsePnpmMapping(line);
425
+ if (mapping === undefined)
426
+ continue;
427
+ if (mapping.value !== '' && mapping.value !== '{}')
428
+ throw new Error(`pnpm lockfile ${section} entry ${mapping.key} must be a mapping on line ${line.line}`);
429
+ let end = range.end;
430
+ for (let next = index + 1; next < range.end; next += 1) {
431
+ if (lines[next]?.indent !== undefined && (lines[next]?.indent ?? 0) <= 2) {
432
+ end = next;
433
+ break;
434
+ }
435
+ }
436
+ if (result.has(mapping.key))
437
+ throw new Error(`pnpm lockfile repeats ${section} entry ${mapping.key}`);
438
+ result.set(mapping.key, parsePnpmEntry(lines, index, end));
439
+ index = end - 1;
440
+ }
441
+ return result;
442
+ }
443
+ function pnpmTopLevelSectionExists(lines, section) {
444
+ return lines.some(line => line.indent === 0 && parsePnpmMapping(line)?.key === section);
445
+ }
446
+ function parsePnpmTopLevelDependencyMap(lines, section) {
447
+ if (!pnpmTopLevelSectionExists(lines, section))
448
+ return {};
449
+ const range = pnpmSectionRange(lines, section);
450
+ const result = {};
451
+ for (let index = range.start + 1; index < range.end; index += 1) {
452
+ const line = lines[index];
453
+ if (line === undefined || line.indent !== 2)
454
+ continue;
455
+ const mapping = parsePnpmMapping(line);
456
+ if (mapping === undefined || mapping.value === '')
457
+ continue;
458
+ if (Object.hasOwn(result, mapping.key))
459
+ throw new Error(`pnpm lockfile repeats ${section} dependency ${mapping.key}`);
460
+ result[mapping.key] = mapping.value;
461
+ }
462
+ return result;
463
+ }
464
+ function parsePnpmRootImporter(lines) {
465
+ if (!pnpmTopLevelSectionExists(lines, 'importers'))
466
+ return undefined;
467
+ const range = pnpmSectionRange(lines, 'importers');
468
+ let start = -1;
469
+ for (let index = range.start + 1; index < range.end; index += 1) {
470
+ const line = lines[index];
471
+ if (line?.indent === 2 && parsePnpmMapping(line)?.key === '.') {
472
+ start = index;
473
+ break;
474
+ }
475
+ }
476
+ if (start < 0)
477
+ return undefined;
478
+ let end = range.end;
479
+ for (let index = start + 1; index < range.end; index += 1) {
480
+ if (lines[index]?.indent !== undefined && (lines[index]?.indent ?? 0) <= 2) {
481
+ end = index;
482
+ break;
483
+ }
484
+ }
485
+ const entry = {
486
+ dependencies: {},
487
+ devDependencies: {},
488
+ optionalDependencies: {},
489
+ peerDependencies: {},
490
+ optionalPeers: new Set(),
491
+ };
492
+ for (let index = start + 1; index < end; index += 1) {
493
+ const line = lines[index];
494
+ if (line === undefined || line.indent !== 4)
495
+ continue;
496
+ const mapping = parsePnpmMapping(line);
497
+ if (mapping === undefined || !PNPM_DEPENDENCY_SECTIONS.has(mapping.key))
498
+ continue;
499
+ const target = entry[mapping.key];
500
+ if (mapping.value !== '') {
501
+ Object.assign(target, parsePnpmInlineDependencyMap(mapping.value, line.line));
502
+ continue;
503
+ }
504
+ for (let child = index + 1; child < end; child += 1) {
505
+ const childLine = lines[child];
506
+ if (childLine === undefined || childLine.indent <= line.indent)
507
+ break;
508
+ if (childLine.indent !== line.indent + 2)
509
+ continue;
510
+ const childMapping = parsePnpmMapping(childLine);
511
+ if (childMapping === undefined)
512
+ continue;
513
+ let version = childMapping.value;
514
+ if (version === '') {
515
+ for (let detail = child + 1; detail < end; detail += 1) {
516
+ const detailLine = lines[detail];
517
+ if (detailLine === undefined || detailLine.indent <= childLine.indent)
518
+ break;
519
+ if (detailLine.indent !== childLine.indent + 2)
520
+ continue;
521
+ const detailMapping = parsePnpmMapping(detailLine);
522
+ if (detailMapping?.key === 'version')
523
+ version = detailMapping.value;
524
+ }
525
+ }
526
+ if (version !== '')
527
+ target[childMapping.key] = version;
528
+ }
529
+ }
530
+ return entry;
531
+ }
532
+ function splitPnpmLocatorVersion(value) {
533
+ const markers = [value.indexOf('('), value.indexOf('_')].filter(index => index >= 0);
534
+ const marker = markers.length === 0 ? -1 : Math.min(...markers);
535
+ const version = marker < 0 ? value : value.slice(0, marker);
536
+ if (version === '')
537
+ return undefined;
538
+ return { version, suffix: marker < 0 ? '' : value.slice(marker) };
539
+ }
540
+ function parsePnpmLocator(value) {
541
+ let key = value.trim();
542
+ if (key.startsWith('/'))
543
+ key = key.slice(1);
544
+ if (key === '' || key.startsWith('link:') || key.startsWith('file:') || key.startsWith('workspace:'))
545
+ return undefined;
546
+ let name;
547
+ let versionWithSuffix;
548
+ const firstSlash = key.indexOf('/');
549
+ const peerMarkers = [key.indexOf('('), key.indexOf('_')].filter(index => index >= 0);
550
+ const firstPeerMarker = peerMarkers.length === 0 ? -1 : Math.min(...peerMarkers);
551
+ if (key.startsWith('@')) {
552
+ const secondSlash = firstSlash < 0 ? -1 : key.indexOf('/', firstSlash + 1);
553
+ const atAfterName = firstSlash < 0 ? -1 : key.indexOf('@', firstSlash + 1);
554
+ if (secondSlash > firstSlash && (atAfterName < 0 || secondSlash < atAfterName)) {
555
+ name = key.slice(0, secondSlash);
556
+ versionWithSuffix = key.slice(secondSlash + 1);
557
+ }
558
+ else if (atAfterName > firstSlash) {
559
+ name = key.slice(0, atAfterName);
560
+ versionWithSuffix = key.slice(atAfterName + 1);
561
+ }
562
+ else {
563
+ return undefined;
564
+ }
565
+ }
566
+ else {
567
+ const at = key.indexOf('@');
568
+ if (firstSlash > 0 && (firstPeerMarker < 0 || firstSlash < firstPeerMarker) && (at < 0 || firstSlash < at || (firstPeerMarker >= 0 && at > firstPeerMarker))) {
569
+ name = key.slice(0, firstSlash);
570
+ versionWithSuffix = key.slice(firstSlash + 1);
571
+ }
572
+ else if (at > 0) {
573
+ name = key.slice(0, at);
574
+ versionWithSuffix = key.slice(at + 1);
575
+ }
576
+ else {
577
+ return undefined;
578
+ }
579
+ }
580
+ const split = splitPnpmLocatorVersion(versionWithSuffix);
581
+ if (split === undefined || name === '' || split.version === '')
582
+ return undefined;
583
+ return { key, name, version: split.version, suffix: split.suffix };
584
+ }
585
+ function pnpmDependencyEntries(entry) {
586
+ const selected = new Map();
587
+ const add = (value, kind, selectKind) => {
588
+ for (const [name, spec] of Object.entries(value))
589
+ selected.set(name, { spec, kind: selectKind?.(name) ?? kind });
590
+ };
591
+ add(entry.devDependencies, 'development');
592
+ add(entry.dependencies, 'runtime');
593
+ add(entry.peerDependencies, 'peer', name => entry.optionalPeers.has(name) ? 'optional' : 'peer');
594
+ add(entry.optionalDependencies, 'optional');
595
+ return [...selected.entries()].map(([name, value]) => ({ name, ...value }));
596
+ }
597
+ function normalizedPnpmSuffix(value) {
598
+ const trimmed = value.trim();
599
+ if (trimmed.startsWith('_'))
600
+ return trimmed.slice(1);
601
+ if (trimmed.startsWith('(') && trimmed.endsWith(')'))
602
+ return trimmed.slice(1, -1);
603
+ return trimmed;
604
+ }
605
+ function pnpmDependencyReference(spec, dependencyName) {
606
+ const trimmed = spec.trim();
607
+ const firstPeerMarker = [trimmed.indexOf('('), trimmed.indexOf('_')].filter(index => index >= 0);
608
+ const firstAt = trimmed.indexOf('@');
609
+ const firstMarker = firstPeerMarker.length === 0 ? -1 : Math.min(...firstPeerMarker);
610
+ const canBeFullLocator = trimmed.startsWith('/')
611
+ || (firstAt >= 0 && (firstMarker < 0 || firstAt < firstMarker));
612
+ if (canBeFullLocator) {
613
+ const full = parsePnpmLocator(trimmed);
614
+ if (full === undefined || full.name !== dependencyName)
615
+ return undefined;
616
+ return { version: full.version, suffix: full.suffix };
617
+ }
618
+ const split = splitPnpmLocatorVersion(trimmed);
619
+ return split;
620
+ }
621
+ function pnpmRecordCoordinateKey(name, version) {
622
+ return `${name}\0${version}`;
623
+ }
624
+ /** Parse pnpm lockfile v6/v9 package snapshots without installing or executing package code. */
625
+ export function parsePnpmLockGraph(text, rootPackage) {
626
+ const lines = preparePnpmYamlLines(text);
627
+ const packageEntries = parsePnpmSectionEntries(lines, 'packages');
628
+ const snapshotEntries = lines.some(line => line.indent === 0 && parsePnpmMapping(line)?.key === 'snapshots')
629
+ ? parsePnpmSectionEntries(lines, 'snapshots')
630
+ : new Map();
631
+ if (packageEntries.size > MAX_PNPM_LOCKFILE_PACKAGES || snapshotEntries.size > MAX_PNPM_LOCKFILE_PACKAGES) {
632
+ throw new Error(`pnpm lockfile exceeds the ${MAX_PNPM_LOCKFILE_PACKAGES} package limit`);
633
+ }
634
+ const records = new Map();
635
+ const addRecords = (entries) => {
636
+ for (const [rawKey, entry] of entries) {
637
+ const locator = parsePnpmLocator(rawKey);
638
+ if (locator === undefined)
639
+ continue;
640
+ const existing = records.get(locator.key);
641
+ if (existing === undefined) {
642
+ records.set(locator.key, { ...locator, id: `pnpm:${locator.key}`, entry });
643
+ }
644
+ else {
645
+ existing.entry = {
646
+ dependencies: { ...existing.entry.dependencies, ...entry.dependencies },
647
+ devDependencies: { ...existing.entry.devDependencies, ...entry.devDependencies },
648
+ optionalDependencies: { ...existing.entry.optionalDependencies, ...entry.optionalDependencies },
649
+ peerDependencies: { ...existing.entry.peerDependencies, ...entry.peerDependencies },
650
+ optionalPeers: new Set([...existing.entry.optionalPeers, ...entry.optionalPeers]),
651
+ };
652
+ }
653
+ }
654
+ };
655
+ addRecords(packageEntries);
656
+ addRecords(snapshotEntries);
657
+ if (records.size > MAX_PNPM_LOCKFILE_PACKAGES) {
658
+ throw new Error(`pnpm lockfile exceeds the ${MAX_PNPM_LOCKFILE_PACKAGES} package limit`);
659
+ }
660
+ const roots = [...records.values()]
661
+ .filter(record => record.name === rootPackage.name && record.version === rootPackage.version)
662
+ .sort((left, right) => left.suffix.length - right.suffix.length || left.key.localeCompare(right.key));
663
+ const root = roots[0] ?? (() => {
664
+ const importer = parsePnpmRootImporter(lines);
665
+ const legacyImporter = {
666
+ dependencies: parsePnpmTopLevelDependencyMap(lines, 'dependencies'),
667
+ devDependencies: parsePnpmTopLevelDependencyMap(lines, 'devDependencies'),
668
+ optionalDependencies: parsePnpmTopLevelDependencyMap(lines, 'optionalDependencies'),
669
+ peerDependencies: {},
670
+ optionalPeers: new Set(),
671
+ };
672
+ const hasImporterDependencies = importer !== undefined && pnpmDependencyEntries(importer).length > 0;
673
+ const hasLegacyDependencies = pnpmDependencyEntries(legacyImporter).length > 0;
674
+ if (!hasImporterDependencies && !hasLegacyDependencies)
675
+ return undefined;
676
+ const synthetic = {
677
+ key: `workspace-root:${rootPackage.name}@${rootPackage.version}`,
678
+ name: rootPackage.name,
679
+ version: rootPackage.version,
680
+ suffix: '',
681
+ id: `pnpm:workspace-root:${rootPackage.name}@${rootPackage.version}`,
682
+ entry: hasImporterDependencies ? importer : legacyImporter,
683
+ };
684
+ records.set(synthetic.key, synthetic);
685
+ return synthetic;
686
+ })();
687
+ if (root === undefined) {
688
+ throw new Error(`requested root package is not present in pnpm lockfile: ${rootPackage.name}@${rootPackage.version}`);
689
+ }
690
+ const byCoordinate = new Map();
691
+ for (const record of records.values()) {
692
+ const key = pnpmRecordCoordinateKey(record.name, record.version);
693
+ const list = byCoordinate.get(key) ?? [];
694
+ list.push(record);
695
+ byCoordinate.set(key, list);
696
+ }
697
+ const resolveDependency = (name, spec) => {
698
+ const reference = pnpmDependencyReference(spec, name);
699
+ if (reference === undefined)
700
+ return undefined;
701
+ const candidates = byCoordinate.get(pnpmRecordCoordinateKey(name, reference.version)) ?? [];
702
+ if (candidates.length === 0)
703
+ return undefined;
704
+ const wantedSuffix = normalizedPnpmSuffix(reference.suffix);
705
+ if (wantedSuffix !== '') {
706
+ const exact = candidates.find(candidate => normalizedPnpmSuffix(candidate.suffix) === wantedSuffix);
707
+ if (exact !== undefined)
708
+ return exact;
709
+ }
710
+ const base = candidates.find(candidate => candidate.suffix === '');
711
+ if (base !== undefined)
712
+ return base;
713
+ return candidates.length === 1 ? candidates[0] : undefined;
714
+ };
715
+ const nodesById = new Map();
716
+ const edges = [];
717
+ const unresolved = [];
718
+ const queue = [root];
719
+ const visited = new Set();
720
+ while (queue.length > 0) {
721
+ const current = queue.shift();
722
+ if (current === undefined || visited.has(current.id))
723
+ continue;
724
+ visited.add(current.id);
725
+ nodesById.set(current.id, { id: current.id, name: current.name, version: current.version });
726
+ for (const dependency of pnpmDependencyEntries(current.entry)) {
727
+ const target = resolveDependency(dependency.name, dependency.spec);
728
+ if (target === undefined) {
729
+ unresolved.push({ from: current.id, ...dependency });
730
+ if (unresolved.length > MAX_PNPM_LOCKFILE_EDGES)
731
+ throw new Error(`pnpm dependency graph exceeds the ${MAX_PNPM_LOCKFILE_EDGES} edge limit`);
732
+ continue;
733
+ }
734
+ if (edges.length >= MAX_PNPM_LOCKFILE_EDGES)
735
+ throw new Error(`pnpm dependency graph exceeds the ${MAX_PNPM_LOCKFILE_EDGES} edge limit`);
736
+ edges.push({ from: current.id, to: target.id, kind: dependency.kind });
737
+ if (!visited.has(target.id))
738
+ queue.push(target);
739
+ }
740
+ }
741
+ const nodes = [...nodesById.values()]
742
+ .sort((left, right) => left.id === root.id ? -1 : right.id === root.id ? 1 : left.id.localeCompare(right.id));
743
+ const sortedEdges = edges.sort((left, right) => (left.from.localeCompare(right.from) || left.to.localeCompare(right.to) || left.kind.localeCompare(right.kind)));
744
+ const sortedUnresolved = unresolved.sort((left, right) => (left.from.localeCompare(right.from) || left.name.localeCompare(right.name) || left.spec.localeCompare(right.spec)));
745
+ return {
746
+ schema: DEPENDENCY_GRAPH_SCHEMA,
747
+ rootNodeId: root.id,
748
+ nodes,
749
+ edges: sortedEdges,
750
+ source: 'pnpm-lock',
751
+ digest: dependencyGraphDigest(nodes, sortedEdges),
752
+ ...(sortedUnresolved.length === 0 ? {} : { unresolved: sortedUnresolved }),
753
+ };
754
+ }
159
755
  /** Return bounded, cycle-safe root-to-node paths for an alert. */
160
756
  export function findDependencyPaths(graph, targetNodeId, options = {}) {
161
757
  const maxPaths = options.maxPaths ?? 20;