@preventive/triage 1.0.0-alpha.43 → 1.0.0-alpha.45
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/common/bundle-compare-diff.js +2 -167
- package/common/bundle-compare-versions.js +172 -0
- package/common/bundle-comparison.d.ts +8 -0
- package/common/bundle-comparison.js +62 -0
- package/common/bundle-sources.js +20 -1
- package/common/default-scan-models.ts +1 -1
- package/common/dependency-reviews.d.ts +0 -5
- package/common/dependency-reviews.js +0 -17
- package/common/managed/agent-tasks.ts +292 -0
- package/common/managed/github-agents.ts +92 -0
- package/common/managed/github-logins.ts +49 -0
- package/common/managed/roles.ts +7 -0
- package/common/managed/routes.js +91 -10
- package/common/managed/team-app.d.ts +5 -2
- package/common/managed/team-app.js +2 -2
- package/common/server-info.ts +11 -0
- package/common/workspace-app.js +19 -3
- package/out/brotli-fallback.js +3 -3
- package/out/chunk-2XUEZHIP.js +18 -18
- package/out/chunk-2ZDZWLFY.js +1 -0
- package/out/chunk-3PS4KWD2.js +10 -0
- package/out/chunk-5DHL5NG3.js +2 -0
- package/out/chunk-6HNXNIQY.js +52 -0
- package/out/chunk-7C7DWL7I.js +1 -0
- package/out/chunk-AFLZH7WG.js +1 -0
- package/out/chunk-BC2UCLFJ.js +1 -0
- package/out/chunk-C4Z6ZBXV.js +6 -6
- package/out/chunk-GDPCLSOS.js +2 -0
- package/out/chunk-GO7FT4OD.js +1 -0
- package/out/chunk-HMAJDVXN.js +1 -0
- package/out/chunk-I2MDGSQX.js +3 -0
- package/out/chunk-IYPY47MC.js +1 -0
- package/out/chunk-IZ7POWI3.js +18 -0
- package/out/chunk-JCIIBLKR.js +1 -0
- package/out/chunk-NPW52BM2.js +17 -0
- package/out/chunk-NRPC74AM.js +1 -1
- package/out/chunk-NV7ES4EW.js +1 -0
- package/out/chunk-OKRLLE5I.js +17 -0
- package/out/chunk-OOJM4CTU.js +1 -1
- package/out/chunk-PNHBZGDD.js +1 -1
- package/out/chunk-PTGDYLNS.js +36 -0
- package/out/chunk-PXK7EU4V.js +1 -0
- package/out/chunk-Q6MVYGYV.js +3 -0
- package/out/chunk-QB4CBOX2.js +1 -0
- package/out/chunk-QRD6GIG5.js +229 -0
- package/out/chunk-R3NZFXVR.js +1 -0
- package/out/chunk-R5O6FAW4.js +1 -1
- package/out/chunk-SXV2T5C4.js +80 -0
- package/out/chunk-TUII34K2.js +1 -1
- package/out/chunk-VFWMQ3LG.js +2 -0
- package/out/chunk-VH4XMZIW.js +2 -0
- package/out/chunk-WFYLMF6Z.js +10 -0
- package/out/chunk-WI7Z5A2T.js +6 -0
- package/out/chunk-WQ74ZBZF.js +7 -0
- package/out/chunk-XVBEOGOS.js +3 -0
- package/out/client-managed-import.js +3 -3
- package/out/client-managed.js +31 -35
- package/out/client-sync.js +11 -11
- package/out/compare.js +35 -0
- package/out/dialogs.js +51 -0
- package/out/github-issue-authorized.html +1 -1
- package/out/github-issue-authorized.js +1 -1
- package/out/graph.js +17 -17
- package/out/index.html +31 -31
- package/out/prism.js +14 -14
- package/out/scan.js +2 -0
- package/out/terminal.js +294 -293
- package/out/view.css +1 -1
- package/out/view.js +16 -107
- package/package.json +55 -6
- package/risk/actions/findings.js +156 -0
- package/risk/actions/resolve.js +219 -0
- package/risk/actions/scripts/commands.js +182 -0
- package/risk/actions/scripts/grade.js +113 -0
- package/risk/actions/scripts/packages.js +123 -0
- package/risk/actions/scripts/shell.js +165 -0
- package/risk/actions/uses.js +76 -0
- package/risk/actions/workflow.js +110 -0
- package/risk/files/hosts.js +6 -6
- package/risk/files/readability.js +23 -17
- package/risk/files/runs-code.js +18 -6
- package/risk/files/script.js +7 -3
- package/risk/files/stamps.js +8 -7
- package/risk/index.d.ts +68 -1
- package/risk/index.js +6 -2
- package/risk/npm/assess.js +13 -8
- package/risk/npm/compare.js +34 -10
- package/risk/npm/dependencies.js +11 -0
- package/risk/npm/files.js +413 -2
- package/risk/npm/tarball.js +56 -73
- package/server-managed/agent-merges.ts +198 -0
- package/server-managed/app-agents.ts +30 -0
- package/server-managed/avatar-store.ts +1 -0
- package/server-managed/commit-coauthors.ts +83 -0
- package/server-managed/commit-lockfiles.ts +62 -0
- package/server-managed/comparison.ts +41 -0
- package/server-managed/db-methods.ts +29 -10
- package/server-managed/db-neon.ts +30 -2
- package/server-managed/db-schema.ts +9 -2
- package/server-managed/db.ts +7 -0
- package/server-managed/dependabot-access.ts +60 -0
- package/server-managed/dependabot-alerts.ts +185 -0
- package/server-managed/dependency-review-comments.ts +97 -0
- package/server-managed/dependency-reviews.ts +65 -15
- package/server-managed/github-accounts.ts +33 -0
- package/server-managed/github-actions.ts +71 -0
- package/server-managed/github-app.ts +23 -9
- package/server-managed/github-errors.ts +2 -0
- package/server-managed/github-link-oauth.ts +69 -0
- package/server-managed/github-links.ts +204 -0
- package/server-managed/github-oauth.ts +5 -2
- package/server-managed/github-users.ts +30 -0
- package/server-managed/http.ts +1219 -81
- package/server-managed/index.ts +6 -2
- package/server-managed/locked-packages.ts +48 -16
- package/server-managed/lockfile-scope.ts +49 -0
- package/server-managed/npm-delta-worker.js +23 -0
- package/server-managed/npm-delta.ts +173 -0
- package/server-managed/npm-insights.ts +20 -11
- package/server-managed/npm-kept.ts +51 -4
- package/server-managed/npm-loads.ts +20 -13
- package/server-managed/npm-packages.ts +24 -64
- package/server-managed/pretty-print.ts +14 -9
- package/server-managed/public-workspace.ts +26 -8
- package/server-managed/pull-comment-reviews.ts +161 -0
- package/server-managed/pull-dependencies.ts +280 -0
- package/server-managed/pull-dependency-reviews.ts +318 -0
- package/server-managed/pull-reviews.ts +205 -0
- package/server-managed/release-commits.ts +84 -0
- package/server-managed/repository-actions.ts +209 -0
- package/server-managed/repository-agents.ts +220 -0
- package/server-managed/repository-browser.ts +54 -13
- package/server-managed/repository-changes.ts +284 -0
- package/server-managed/repository-code.ts +52 -0
- package/server-managed/repository-dependency-reviews.ts +135 -0
- package/server-managed/repository-history.ts +445 -0
- package/server-managed/repository-issues.ts +181 -0
- package/server-managed/repository-pulse.ts +432 -0
- package/server-managed/repository-risk-read.ts +165 -0
- package/server-managed/repository-risk.ts +116 -106
- package/server-managed/reviewed-dependencies.ts +149 -0
- package/server-managed/session.ts +9 -5
- package/server-managed/sql.ts +1 -1
- package/server-managed/storage-stores.ts +5 -0
- package/server-managed/team-app.ts +54 -14
- package/server-managed/workspace-shares.ts +4 -1
- package/server.ts +11 -0
- package/out/chunk-2KBF5TZ7.js +0 -2
- package/out/chunk-35VE6CDH.js +0 -2
- package/out/chunk-3BZ7JERB.js +0 -1
- package/out/chunk-4F6JSF57.js +0 -3
- package/out/chunk-4XCDFGDP.js +0 -10
- package/out/chunk-7RU5EDRO.js +0 -226
- package/out/chunk-A7M4GV7M.js +0 -1
- package/out/chunk-CLPKNNC7.js +0 -51
- package/out/chunk-CMQA4DM6.js +0 -1
- package/out/chunk-CVVJAXVY.js +0 -2
- package/out/chunk-DIJJNTRH.js +0 -10
- package/out/chunk-GJ4IUCNS.js +0 -1
- package/out/chunk-GNXOKIGP.js +0 -80
- package/out/chunk-GZ4MS3TE.js +0 -13
- package/out/chunk-IU3CCINR.js +0 -3
- package/out/chunk-K3X5EJBQ.js +0 -2
- package/out/chunk-KEX2ATEW.js +0 -2
- package/out/chunk-KYCNJVB7.js +0 -1
- package/out/chunk-LAAB5SZT.js +0 -2
- package/out/chunk-NWZ4PVPN.js +0 -1
- package/out/chunk-OSIMGFUF.js +0 -1
- package/out/chunk-RFZQQXSG.js +0 -17
- package/out/chunk-RSHMVYNS.js +0 -1
- package/out/chunk-TZ5KB2Y5.js +0 -10
- package/out/chunk-UNTRGE26.js +0 -2
- package/out/chunk-UXNUAIIC.js +0 -33
- package/out/chunk-UYAXVRVL.js +0 -1
- package/out/chunk-V3ME6FI5.js +0 -2
- package/out/chunk-V5O7M2YT.js +0 -1
- package/out/chunk-VIHGO77Y.js +0 -1
- package/out/chunk-VQJCXGIY.js +0 -7
- package/out/chunk-W4J3MGLB.js +0 -1
- package/out/chunk-WGG7KQUH.js +0 -1
- package/out/chunk-WZDTZE64.js +0 -1
- package/out/chunk-XOCAPDCM.js +0 -1
- package/out/chunk-XYFXXB2G.js +0 -10
- package/out/chunk-YVZH5SHP.js +0 -1
- package/out/chunk-ZBBOBQ43.js +0 -1
- package/out/chunk-ZMQQCGF6.js +0 -2
|
@@ -353,170 +353,5 @@ export function computeBundleDiff(base, other, pkgOf, layout = treeLayout(base.k
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
// The
|
|
357
|
-
|
|
358
|
-
// records for it (`bundlePackageVersions`). A package only one side has is
|
|
359
|
-
// removed or added, with its size and versions there; one both have is
|
|
360
|
-
// changed when its size or its versions moved, with both sides of each —
|
|
361
|
-
// own source and workspace modules carry sizes alone.
|
|
362
|
-
export function comparePackages(packages, baseVersions, otherVersions) {
|
|
363
|
-
const sizes = new Map()
|
|
364
|
-
for (const r of packages.onlyBase) sizes.set(r.pkg, { base: r.bytes, other: null })
|
|
365
|
-
for (const r of packages.onlyOther) sizes.set(r.pkg, { base: null, other: r.bytes })
|
|
366
|
-
for (const r of packages.changed) sizes.set(r.pkg, { base: r.baseBytes, other: r.otherBytes })
|
|
367
|
-
for (const r of packages.unchanged ?? []) sizes.set(r.pkg, { base: r.bytes, other: r.bytes })
|
|
368
|
-
const versionsOf = (map, pkg) => [...map.get(pkg) ?? []].toSorted(compareSemver)
|
|
369
|
-
const moved = new Set([...packages.onlyBase, ...packages.onlyOther, ...packages.changed].map(r => r.pkg))
|
|
370
|
-
for (const pkg of new Set([...baseVersions.keys(), ...otherVersions.keys()])) {
|
|
371
|
-
if (!sameVersions(versionsOf(baseVersions, pkg), versionsOf(otherVersions, pkg))) moved.add(pkg)
|
|
372
|
-
}
|
|
373
|
-
const added = [], changed = [], removed = []
|
|
374
|
-
for (const pkg of moved) {
|
|
375
|
-
const size = sizes.get(pkg) ?? { base: null, other: null }
|
|
376
|
-
const after = versionsOf(otherVersions, pkg), before = versionsOf(baseVersions, pkg)
|
|
377
|
-
const onBase = size.base !== null || before.length > 0
|
|
378
|
-
const onOther = size.other !== null || after.length > 0
|
|
379
|
-
if (onBase && !onOther) removed.push({ pkg, bytes: size.base, versions: before })
|
|
380
|
-
else if (onOther && !onBase) added.push({ pkg, bytes: size.other, versions: after })
|
|
381
|
-
else {
|
|
382
|
-
changed.push({
|
|
383
|
-
pkg, baseBytes: size.base, otherBytes: size.other,
|
|
384
|
-
delta: size.base !== null && size.other !== null ? size.other - size.base : null,
|
|
385
|
-
baseVersions: before, otherVersions: after,
|
|
386
|
-
direction: before.length > 0 && after.length > 0 && !sameVersions(before, after) ? versionDirection(before, after) : null,
|
|
387
|
-
})
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const byPkg = (a, b) => a.pkg.localeCompare(b.pkg)
|
|
391
|
-
return { removed: removed.toSorted(byPkg), added: added.toSorted(byPkg), changed: changed.toSorted(byPkg) }
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Split a version into its dotted-numeric core and its prerelease tail,
|
|
395
|
-
// dropping any `+build` metadata (semver ignores it for precedence).
|
|
396
|
-
// `1.2.3-rc.1+sha` → { core: '1.2.3', pre: 'rc.1' }.
|
|
397
|
-
function splitVersion(v) {
|
|
398
|
-
const s = String(v).trim()
|
|
399
|
-
const noBuild = s.split('+', 1)[0]
|
|
400
|
-
const dash = noBuild.indexOf('-')
|
|
401
|
-
return dash === -1
|
|
402
|
-
? { core: noBuild, pre: '' }
|
|
403
|
-
: { core: noBuild.slice(0, dash), pre: noBuild.slice(dash + 1) }
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Lightweight semver-ish comparison, enough to sort versions ascending
|
|
407
|
-
// and label an update ↑/↓. Compares the dotted-numeric core part by
|
|
408
|
-
// part (a missing part counts as 0); on an equal core a release ranks
|
|
409
|
-
// above a prerelease (`1.0.0` > `1.0.0-rc.1`) and two prereleases
|
|
410
|
-
// compare by their dot identifiers (numeric where both are numeric,
|
|
411
|
-
// else lexical) per the semver precedence rules. NOT a full
|
|
412
|
-
// implementation — a non-numeric core segment falls back to a string
|
|
413
|
-
// compare of the whole version so ordering stays total and stable.
|
|
414
|
-
export function compareSemver(a, b) {
|
|
415
|
-
if (a === b) return 0
|
|
416
|
-
const A = splitVersion(a)
|
|
417
|
-
const B = splitVersion(b)
|
|
418
|
-
const ap = A.core.split('.')
|
|
419
|
-
const bp = B.core.split('.')
|
|
420
|
-
for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
|
|
421
|
-
const x = Number(ap[i] ?? 0)
|
|
422
|
-
const y = Number(bp[i] ?? 0)
|
|
423
|
-
if (Number.isNaN(x) || Number.isNaN(y)) return a < b ? -1 : 1
|
|
424
|
-
if (x !== y) return x < y ? -1 : 1
|
|
425
|
-
}
|
|
426
|
-
if (A.pre === B.pre) return 0
|
|
427
|
-
// A core with no prerelease is the higher (released) version.
|
|
428
|
-
if (!A.pre) return 1
|
|
429
|
-
if (!B.pre) return -1
|
|
430
|
-
const ai = A.pre.split('.')
|
|
431
|
-
const bi = B.pre.split('.')
|
|
432
|
-
for (let i = 0; i < Math.max(ai.length, bi.length); i++) {
|
|
433
|
-
const x = ai[i]
|
|
434
|
-
const y = bi[i]
|
|
435
|
-
if (x === undefined) return -1
|
|
436
|
-
if (y === undefined) return 1
|
|
437
|
-
if (x === y) continue
|
|
438
|
-
const xNum = /^\d+$/u.test(x)
|
|
439
|
-
const yNum = /^\d+$/u.test(y)
|
|
440
|
-
// Numeric identifiers always rank below non-numeric ones (semver),
|
|
441
|
-
// and compare numerically against each other.
|
|
442
|
-
if (xNum && yNum) return Number(x) < Number(y) ? -1 : 1
|
|
443
|
-
if (xNum !== yNum) return xNum ? -1 : 1
|
|
444
|
-
return x < y ? -1 : 1
|
|
445
|
-
}
|
|
446
|
-
return 0
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// True when two ascending-sorted version arrays hold the same members.
|
|
450
|
-
function sameVersions(a, b) {
|
|
451
|
-
if (a.length !== b.length) return false
|
|
452
|
-
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false
|
|
453
|
-
return true
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Direction of a version move, from the versions dropped and the versions
|
|
457
|
-
// taken on: 'up' when everything dropped is older than everything taken
|
|
458
|
-
// on (`1.0.0` → `2.0.0`), or, with only drops or only additions, than the
|
|
459
|
-
// versions kept (`1.0.0, 2.0.0` → `2.0.0` drops the older copy, and a newer
|
|
460
|
-
// copy added beside the old is up too); 'down' the other way round; and
|
|
461
|
-
// 'changed' when the moves fall between (`1.0.0, 1.2.0, 2.0.0` → `1.0.0,
|
|
462
|
-
// 2.0.0`).
|
|
463
|
-
export function versionDirection(baseVersions, otherVersions) {
|
|
464
|
-
const dropped = baseVersions.filter(v => !otherVersions.includes(v)).toSorted(compareSemver)
|
|
465
|
-
const taken = otherVersions.filter(v => !baseVersions.includes(v)).toSorted(compareSemver)
|
|
466
|
-
const kept = baseVersions.filter(v => otherVersions.includes(v)).toSorted(compareSemver)
|
|
467
|
-
const [from, to] = dropped.length > 0 && taken.length > 0 ? [dropped, taken]
|
|
468
|
-
: dropped.length > 0 ? [dropped, kept] : [kept, taken]
|
|
469
|
-
if (from.length === 0 || to.length === 0) return 'changed'
|
|
470
|
-
// Every version of `a` older than every version of `b`.
|
|
471
|
-
const before = (a, b) => compareSemver(a.at(-1), b[0]) < 0
|
|
472
|
-
return before(from, to) ? 'up' : before(to, from) ? 'down' : 'changed'
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Diff two `Map<packageName, Set<version>>` inventories (as
|
|
476
|
-
// `bundlePackageVersions` returns for each bundle) into the dependency
|
|
477
|
-
// version changes between them. Mirrors the file/package diff framing:
|
|
478
|
-
// `base` is the open bundle, `other` the one compared against.
|
|
479
|
-
//
|
|
480
|
-
// {
|
|
481
|
-
// updated: [{ pkg, baseVersions: [...], otherVersions: [...],
|
|
482
|
-
// direction: 'up' | 'down' | 'changed' }],
|
|
483
|
-
// added: [{ pkg, versions: [...] }], // dependency only in other
|
|
484
|
-
// removed: [{ pkg, versions: [...] }], // dependency only in base
|
|
485
|
-
// totals: { baseDeps, otherDeps }, // distinct package counts
|
|
486
|
-
// }
|
|
487
|
-
//
|
|
488
|
-
// `updated` is the headline — a package present on BOTH sides whose set
|
|
489
|
-
// of versions changed (the "what did this bump pull in?" answer);
|
|
490
|
-
// packages whose versions are byte-for-byte the same are dropped.
|
|
491
|
-
// Version arrays are sorted ascending; the three lists are sorted by
|
|
492
|
-
// package name so the dependency list reads alphabetically.
|
|
493
|
-
export function computeVersionUpdates(baseVersions, otherVersions) {
|
|
494
|
-
const updated = []
|
|
495
|
-
const added = []
|
|
496
|
-
const removed = []
|
|
497
|
-
const names = new Set([...baseVersions.keys(), ...otherVersions.keys()])
|
|
498
|
-
for (const pkg of names) {
|
|
499
|
-
const bSet = baseVersions.get(pkg)
|
|
500
|
-
const oSet = otherVersions.get(pkg)
|
|
501
|
-
if (bSet && oSet) {
|
|
502
|
-
const b = [...bSet].toSorted(compareSemver)
|
|
503
|
-
const o = [...oSet].toSorted(compareSemver)
|
|
504
|
-
if (sameVersions(b, o)) continue
|
|
505
|
-
updated.push({ pkg, baseVersions: b, otherVersions: o, direction: versionDirection(b, o) })
|
|
506
|
-
} else if (bSet) {
|
|
507
|
-
removed.push({ pkg, versions: [...bSet].toSorted(compareSemver) })
|
|
508
|
-
} else {
|
|
509
|
-
added.push({ pkg, versions: [...oSet].toSorted(compareSemver) })
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
const byPkg = (a, b) => a.pkg.localeCompare(b.pkg)
|
|
513
|
-
updated.sort(byPkg)
|
|
514
|
-
added.sort(byPkg)
|
|
515
|
-
removed.sort(byPkg)
|
|
516
|
-
return {
|
|
517
|
-
updated,
|
|
518
|
-
added,
|
|
519
|
-
removed,
|
|
520
|
-
totals: { baseDeps: baseVersions.size, otherDeps: otherVersions.size },
|
|
521
|
-
}
|
|
522
|
-
}
|
|
356
|
+
// The versions' part (bundle-compare-versions.js), as one comparison reads it.
|
|
357
|
+
export { comparePackages, compareSemver, computeVersionUpdates, versionDirection } from './bundle-compare-versions.js'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Dependency versions two bundles record, compared: sorting versions as
|
|
2
|
+
// semver orders them, which way a package moved, and the Overview's package
|
|
3
|
+
// rows from computeBundleDiff's sizes. No @preventive/compare here, so a view
|
|
4
|
+
// that only orders versions loads none of the comparison's pairing.
|
|
5
|
+
|
|
6
|
+
// The Overview's package rows: each package that differs, once, from its
|
|
7
|
+
// sizes (computeBundleDiff's `packages`) and the versions each bundle
|
|
8
|
+
// records for it (`bundlePackageVersions`). A package only one side has is
|
|
9
|
+
// removed or added, with its size and versions there; one both have is
|
|
10
|
+
// changed when its size or its versions moved, with both sides of each —
|
|
11
|
+
// own source and workspace modules carry sizes alone.
|
|
12
|
+
export function comparePackages(packages, baseVersions, otherVersions) {
|
|
13
|
+
const sizes = new Map()
|
|
14
|
+
for (const r of packages.onlyBase) sizes.set(r.pkg, { base: r.bytes, other: null })
|
|
15
|
+
for (const r of packages.onlyOther) sizes.set(r.pkg, { base: null, other: r.bytes })
|
|
16
|
+
for (const r of packages.changed) sizes.set(r.pkg, { base: r.baseBytes, other: r.otherBytes })
|
|
17
|
+
for (const r of packages.unchanged ?? []) sizes.set(r.pkg, { base: r.bytes, other: r.bytes })
|
|
18
|
+
const versionsOf = (map, pkg) => [...map.get(pkg) ?? []].toSorted(compareSemver)
|
|
19
|
+
const moved = new Set([...packages.onlyBase, ...packages.onlyOther, ...packages.changed].map(r => r.pkg))
|
|
20
|
+
for (const pkg of new Set([...baseVersions.keys(), ...otherVersions.keys()])) {
|
|
21
|
+
if (!sameVersions(versionsOf(baseVersions, pkg), versionsOf(otherVersions, pkg))) moved.add(pkg)
|
|
22
|
+
}
|
|
23
|
+
const added = [], changed = [], removed = []
|
|
24
|
+
for (const pkg of moved) {
|
|
25
|
+
const size = sizes.get(pkg) ?? { base: null, other: null }
|
|
26
|
+
const after = versionsOf(otherVersions, pkg), before = versionsOf(baseVersions, pkg)
|
|
27
|
+
const onBase = size.base !== null || before.length > 0
|
|
28
|
+
const onOther = size.other !== null || after.length > 0
|
|
29
|
+
if (onBase && !onOther) removed.push({ pkg, bytes: size.base, versions: before })
|
|
30
|
+
else if (onOther && !onBase) added.push({ pkg, bytes: size.other, versions: after })
|
|
31
|
+
else {
|
|
32
|
+
changed.push({
|
|
33
|
+
pkg, baseBytes: size.base, otherBytes: size.other,
|
|
34
|
+
delta: size.base !== null && size.other !== null ? size.other - size.base : null,
|
|
35
|
+
baseVersions: before, otherVersions: after,
|
|
36
|
+
direction: before.length > 0 && after.length > 0 && !sameVersions(before, after) ? versionDirection(before, after) : null,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const byPkg = (a, b) => a.pkg.localeCompare(b.pkg)
|
|
41
|
+
return { removed: removed.toSorted(byPkg), added: added.toSorted(byPkg), changed: changed.toSorted(byPkg) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Split a version into its dotted-numeric core and its prerelease tail,
|
|
45
|
+
// dropping any `+build` metadata (semver ignores it for precedence).
|
|
46
|
+
// `1.2.3-rc.1+sha` → { core: '1.2.3', pre: 'rc.1' }.
|
|
47
|
+
function splitVersion(v) {
|
|
48
|
+
const s = String(v).trim()
|
|
49
|
+
const noBuild = s.split('+', 1)[0]
|
|
50
|
+
const dash = noBuild.indexOf('-')
|
|
51
|
+
return dash === -1
|
|
52
|
+
? { core: noBuild, pre: '' }
|
|
53
|
+
: { core: noBuild.slice(0, dash), pre: noBuild.slice(dash + 1) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Lightweight semver-ish comparison, enough to sort versions ascending
|
|
57
|
+
// and label an update ↑/↓. Compares the dotted-numeric core part by
|
|
58
|
+
// part (a missing part counts as 0); on an equal core a release ranks
|
|
59
|
+
// above a prerelease (`1.0.0` > `1.0.0-rc.1`) and two prereleases
|
|
60
|
+
// compare by their dot identifiers (numeric where both are numeric,
|
|
61
|
+
// else lexical) per the semver precedence rules. NOT a full
|
|
62
|
+
// implementation — a non-numeric core segment falls back to a string
|
|
63
|
+
// compare of the whole version so ordering stays total and stable.
|
|
64
|
+
export function compareSemver(a, b) {
|
|
65
|
+
if (a === b) return 0
|
|
66
|
+
const A = splitVersion(a)
|
|
67
|
+
const B = splitVersion(b)
|
|
68
|
+
const ap = A.core.split('.')
|
|
69
|
+
const bp = B.core.split('.')
|
|
70
|
+
for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
|
|
71
|
+
const x = Number(ap[i] ?? 0)
|
|
72
|
+
const y = Number(bp[i] ?? 0)
|
|
73
|
+
if (Number.isNaN(x) || Number.isNaN(y)) return a < b ? -1 : 1
|
|
74
|
+
if (x !== y) return x < y ? -1 : 1
|
|
75
|
+
}
|
|
76
|
+
if (A.pre === B.pre) return 0
|
|
77
|
+
// A core with no prerelease is the higher (released) version.
|
|
78
|
+
if (!A.pre) return 1
|
|
79
|
+
if (!B.pre) return -1
|
|
80
|
+
const ai = A.pre.split('.')
|
|
81
|
+
const bi = B.pre.split('.')
|
|
82
|
+
for (let i = 0; i < Math.max(ai.length, bi.length); i++) {
|
|
83
|
+
const x = ai[i]
|
|
84
|
+
const y = bi[i]
|
|
85
|
+
if (x === undefined) return -1
|
|
86
|
+
if (y === undefined) return 1
|
|
87
|
+
if (x === y) continue
|
|
88
|
+
const xNum = /^\d+$/u.test(x)
|
|
89
|
+
const yNum = /^\d+$/u.test(y)
|
|
90
|
+
// Numeric identifiers always rank below non-numeric ones (semver),
|
|
91
|
+
// and compare numerically against each other.
|
|
92
|
+
if (xNum && yNum) return Number(x) < Number(y) ? -1 : 1
|
|
93
|
+
if (xNum !== yNum) return xNum ? -1 : 1
|
|
94
|
+
return x < y ? -1 : 1
|
|
95
|
+
}
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// True when two ascending-sorted version arrays hold the same members.
|
|
100
|
+
function sameVersions(a, b) {
|
|
101
|
+
if (a.length !== b.length) return false
|
|
102
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Direction of a version move, from the versions dropped and the versions
|
|
107
|
+
// taken on: 'up' when everything dropped is older than everything taken
|
|
108
|
+
// on (`1.0.0` → `2.0.0`), or, with only drops or only additions, than the
|
|
109
|
+
// versions kept (`1.0.0, 2.0.0` → `2.0.0` drops the older copy, and a newer
|
|
110
|
+
// copy added beside the old is up too); 'down' the other way round; and
|
|
111
|
+
// 'changed' when the moves fall between (`1.0.0, 1.2.0, 2.0.0` → `1.0.0,
|
|
112
|
+
// 2.0.0`).
|
|
113
|
+
export function versionDirection(baseVersions, otherVersions) {
|
|
114
|
+
const dropped = baseVersions.filter(v => !otherVersions.includes(v)).toSorted(compareSemver)
|
|
115
|
+
const taken = otherVersions.filter(v => !baseVersions.includes(v)).toSorted(compareSemver)
|
|
116
|
+
const kept = baseVersions.filter(v => otherVersions.includes(v)).toSorted(compareSemver)
|
|
117
|
+
const [from, to] = dropped.length > 0 && taken.length > 0 ? [dropped, taken]
|
|
118
|
+
: dropped.length > 0 ? [dropped, kept] : [kept, taken]
|
|
119
|
+
if (from.length === 0 || to.length === 0) return 'changed'
|
|
120
|
+
// Every version of `a` older than every version of `b`.
|
|
121
|
+
const before = (a, b) => compareSemver(a.at(-1), b[0]) < 0
|
|
122
|
+
return before(from, to) ? 'up' : before(to, from) ? 'down' : 'changed'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Diff two `Map<packageName, Set<version>>` inventories (as
|
|
126
|
+
// `bundlePackageVersions` returns for each bundle) into the dependency
|
|
127
|
+
// version changes between them. Mirrors the file/package diff framing:
|
|
128
|
+
// `base` is the open bundle, `other` the one compared against.
|
|
129
|
+
//
|
|
130
|
+
// {
|
|
131
|
+
// updated: [{ pkg, baseVersions: [...], otherVersions: [...],
|
|
132
|
+
// direction: 'up' | 'down' | 'changed' }],
|
|
133
|
+
// added: [{ pkg, versions: [...] }], // dependency only in other
|
|
134
|
+
// removed: [{ pkg, versions: [...] }], // dependency only in base
|
|
135
|
+
// totals: { baseDeps, otherDeps }, // distinct package counts
|
|
136
|
+
// }
|
|
137
|
+
//
|
|
138
|
+
// `updated` is the headline — a package present on BOTH sides whose set
|
|
139
|
+
// of versions changed (the "what did this bump pull in?" answer);
|
|
140
|
+
// packages whose versions are byte-for-byte the same are dropped.
|
|
141
|
+
// Version arrays are sorted ascending; the three lists are sorted by
|
|
142
|
+
// package name so the dependency list reads alphabetically.
|
|
143
|
+
export function computeVersionUpdates(baseVersions, otherVersions) {
|
|
144
|
+
const updated = []
|
|
145
|
+
const added = []
|
|
146
|
+
const removed = []
|
|
147
|
+
const names = new Set([...baseVersions.keys(), ...otherVersions.keys()])
|
|
148
|
+
for (const pkg of names) {
|
|
149
|
+
const bSet = baseVersions.get(pkg)
|
|
150
|
+
const oSet = otherVersions.get(pkg)
|
|
151
|
+
if (bSet && oSet) {
|
|
152
|
+
const b = [...bSet].toSorted(compareSemver)
|
|
153
|
+
const o = [...oSet].toSorted(compareSemver)
|
|
154
|
+
if (sameVersions(b, o)) continue
|
|
155
|
+
updated.push({ pkg, baseVersions: b, otherVersions: o, direction: versionDirection(b, o) })
|
|
156
|
+
} else if (bSet) {
|
|
157
|
+
removed.push({ pkg, versions: [...bSet].toSorted(compareSemver) })
|
|
158
|
+
} else {
|
|
159
|
+
added.push({ pkg, versions: [...oSet].toSorted(compareSemver) })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const byPkg = (a, b) => a.pkg.localeCompare(b.pkg)
|
|
163
|
+
updated.sort(byPkg)
|
|
164
|
+
added.sort(byPkg)
|
|
165
|
+
removed.sort(byPkg)
|
|
166
|
+
return {
|
|
167
|
+
updated,
|
|
168
|
+
added,
|
|
169
|
+
removed,
|
|
170
|
+
totals: { baseDeps: baseVersions.size, otherDeps: otherVersions.size },
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Root } from '@preventive/compare'
|
|
2
|
+
import type { BundleDetails } from './bundle-metadata.js'
|
|
3
|
+
// A comparison as Compare and Review read it; `files.basePaths` maps every
|
|
4
|
+
// base file to how it reads as the other side lays its files out.
|
|
5
|
+
export interface Comparison extends Record<string, unknown> {
|
|
6
|
+
files: Record<string, unknown> & { root: Root | null; basePaths: Map<string, string> }
|
|
7
|
+
}
|
|
8
|
+
export function prepareComparison(base: BundleDetails, other: BundleDetails, scope?: string | null): Comparison
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// One comparison model for Compare and Review: bytes, metadata, resolutions,
|
|
2
|
+
// dependency versions and line counts, its files paired by
|
|
3
|
+
// @preventive/compare (computeBundleDiff, treeLayout). Worked out by the
|
|
4
|
+
// server for two managed bundles or two versions of a package
|
|
5
|
+
// (server-managed/comparison.ts), and by the viewer only for bundles it opened
|
|
6
|
+
// itself, which no server holds (ui/view/bundle-comparison.js): a managed
|
|
7
|
+
// viewer loads none of it.
|
|
8
|
+
import { layoutPackageDirs, treeLayout } from '@preventive/compare'
|
|
9
|
+
import { bundlePkgOf } from './bundle-pkg-of.js'
|
|
10
|
+
import { bundlePackageDirs, bundlePackageVersions } from './bundle-sources.js'
|
|
11
|
+
import { bundleFileMeta, computeBundleDiff, computeResolutionDiff } from './bundle-compare-diff.js'
|
|
12
|
+
import { comparePackages, computeVersionUpdates } from './bundle-compare-versions.js'
|
|
13
|
+
import { bundleCompareFiles, bundleCompareResolutions } from './bundle-compare-inputs.js'
|
|
14
|
+
import { bundleLineCounts } from './bundle-metadata.js'
|
|
15
|
+
|
|
16
|
+
export function prepareComparison(base, other, scope = null) {
|
|
17
|
+
// Every file each side mounts, resources included: a diff of
|
|
18
|
+
// source alone would call two bundles identical when only their
|
|
19
|
+
// images differ, and total less than the Overview does.
|
|
20
|
+
// From the bundle picked to the open one.
|
|
21
|
+
const [from, to] = [base, other]
|
|
22
|
+
const baseSources = bundleCompareFiles(from, scope)
|
|
23
|
+
const otherSources = bundleCompareFiles(to, scope)
|
|
24
|
+
// Classify original paths and preserve recorded Stasis package
|
|
25
|
+
// boundaries from either side, as the open one lays its files out
|
|
26
|
+
// where their roots differ. Own source has one identity.
|
|
27
|
+
const layout = treeLayout(baseSources.keys(), otherSources.keys())
|
|
28
|
+
const packageDirs = layoutPackageDirs(bundlePackageDirs(from), bundlePackageDirs(to), layout)
|
|
29
|
+
const pkgOf = (p) => bundlePkgOf(p, { packageDir: packageDirs?.get(p) })
|
|
30
|
+
const diff = computeBundleDiff(baseSources, otherSources, pkgOf, layout, [bundleFileMeta(from), bundleFileMeta(to)])
|
|
31
|
+
// Each side's lines of code (bundleLineCounts), on its rows and in
|
|
32
|
+
// the totals; a file that isn't text has none.
|
|
33
|
+
const baseLines = bundleLineCounts(from), otherLines = bundleLineCounts(to)
|
|
34
|
+
const linesOf = (lines, paths) => { let total = 0; for (const path of paths) total += lines.get(path) ?? 0; return total }
|
|
35
|
+
Object.assign(diff.totals, { baseLines: linesOf(baseLines, baseSources.keys()), otherLines: linesOf(otherLines, otherSources.keys()) })
|
|
36
|
+
for (const row of diff.files.onlyBase) row.lines = baseLines.get(row.basePath ?? row.path) ?? 0
|
|
37
|
+
for (const row of diff.files.onlyOther) row.lines = otherLines.get(row.path) ?? 0
|
|
38
|
+
for (const row of diff.files.changed) Object.assign(row, { baseLines: baseLines.get(row.basePath ?? row.path) ?? 0, otherLines: otherLines.get(row.path) ?? 0 })
|
|
39
|
+
diff.resolutions = computeResolutionDiff(
|
|
40
|
+
bundleCompareResolutions(from, scope),
|
|
41
|
+
bundleCompareResolutions(to, scope),
|
|
42
|
+
new Map(diff.files.changed.filter(row => row.renamedFrom != null).map(row => [row.basePath, row.path])),
|
|
43
|
+
diff.files.basePaths,
|
|
44
|
+
diff.files.root,
|
|
45
|
+
)
|
|
46
|
+
// Dependency versions come from the stasis per-module `{ name,
|
|
47
|
+
// version }` metadata, not the path/byte walk above, so they're
|
|
48
|
+
// computed alongside and hung off the same memo: the package rows
|
|
49
|
+
// join them to the sizes, the summary counts them. Empty for
|
|
50
|
+
// sourcemap / v0 pairs (no version metadata), whose rows carry sizes
|
|
51
|
+
// alone.
|
|
52
|
+
// Keyed by install directory, as the sizes are (`bundlePkgOf`).
|
|
53
|
+
const versionKey = dir => bundlePkgOf('', { packageDir: dir })
|
|
54
|
+
const baseVersions = bundlePackageVersions(from, scope ? baseSources.keys() : null, versionKey)
|
|
55
|
+
const otherVersions = bundlePackageVersions(to, scope ? otherSources.keys() : null, versionKey)
|
|
56
|
+
diff.versionUpdates = computeVersionUpdates(baseVersions, otherVersions)
|
|
57
|
+
diff.packageRows = comparePackages(diff.packages, baseVersions, otherVersions)
|
|
58
|
+
// The reason scope its files are within, for what the server works out of
|
|
59
|
+
// them alike (rename-diff.js, type-only.js).
|
|
60
|
+
diff.scope = scope ?? ''
|
|
61
|
+
return diff
|
|
62
|
+
}
|
package/common/bundle-sources.js
CHANGED
|
@@ -111,11 +111,30 @@ export function bundleSourceOrder(sources) {
|
|
|
111
111
|
// Directory captures stay out. They are the one entry that is not a
|
|
112
112
|
// file in any encoding, and mounting one puts a second `lib` beside
|
|
113
113
|
// the directory it names.
|
|
114
|
+
// Files as the managed server reads a tarball's (server-managed/npm-packages.ts
|
|
115
|
+
// npmFileRows), `[path, bytes, text]` or `[path, bytes, null, digest]`, as
|
|
116
|
+
// details of `entry` in the shape of a sourcemap carrying its sources: one
|
|
117
|
+
// source per file, text inline, and every file's size from its bytes, so a
|
|
118
|
+
// file that is not text still lists with its size. Those carry a digest of
|
|
119
|
+
// their bytes instead (`npmBinaries`), which Compare compares them by. An npm
|
|
120
|
+
// version's files come so (view/npm-package.js), and a repository
|
|
121
|
+
// directory's (view/repository-code.js).
|
|
122
|
+
export function fileRowDetails(entry, rows) {
|
|
123
|
+
const files = Array.isArray(rows) ? rows : []
|
|
124
|
+
return {
|
|
125
|
+
integrity: entry.integrity, kind: 'sourcemap', size: entry.size,
|
|
126
|
+
json: { version: 3, sources: files.map(([path]) => path), sourcesContent: files.map(([, , text]) => typeof text === 'string' ? text : null) },
|
|
127
|
+
fileSizes: new Map(files.map(([path, size]) => [path, size])),
|
|
128
|
+
npmBinaries: new Map(files.filter(([, , text, digest]) => typeof text !== 'string' && typeof digest === 'string')
|
|
129
|
+
.map(([path, size, , digest]) => [path, { size, digest }])),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
114
133
|
export function bundleFilesAsMap(details) {
|
|
115
134
|
const key = details?.bundle ?? details?.json
|
|
116
135
|
if (key && filesCache.has(key)) return filesCache.get(key)
|
|
117
136
|
const files = new Map(bundleSourcesAsMap(details))
|
|
118
|
-
// An npm package's files that are not text (
|
|
137
|
+
// An npm package's files that are not text (fileRowDetails) carry
|
|
119
138
|
// their size and digest alone: enough for Compare to tell a changed one
|
|
120
139
|
// from an unchanged one, as `{ format: 'digest', size, digest }`.
|
|
121
140
|
for (const [path, file] of details?.npmBinaries ?? []) files.set(path, { format: 'digest', ...file })
|
|
@@ -13,7 +13,7 @@ export function defaultScanModels(provider: string | null = null) {
|
|
|
13
13
|
'openai/gpt-6-luna',
|
|
14
14
|
'openai/gpt-6-luna-pro',
|
|
15
15
|
'moonshotai/kimi-k3',
|
|
16
|
-
'google/gemini-
|
|
16
|
+
'google/gemini-4-argon',
|
|
17
17
|
'google/gemma-4-31b-it',
|
|
18
18
|
'x-ai/grok-4.7',
|
|
19
19
|
'z-ai/glm-5.3',
|
|
@@ -16,8 +16,3 @@ export function npmReviewVerdictLimit(npm: ReviewNpmVersion | null | undefined,
|
|
|
16
16
|
socket?: { alerts: { type: string }[] } | null, risk?: { findings: { id: string }[] } | null): ReviewVerdict
|
|
17
17
|
export function needsPackageJsonReview(entries: { file: string; status: ReviewVerdict | null }[], files: string[], nonTextFiles?: string[] | null): boolean
|
|
18
18
|
export function parseReviewNotes(comment: string): { text: string; bullet: boolean }[]
|
|
19
|
-
export function auditMarkdown(review: ReviewSummary & {
|
|
20
|
-
package: string; version: string; integrity: string; basePackage: string | null; baseVersion: string | null
|
|
21
|
-
publishedAt: number | null; revokedAt: number | null; entries: { file: string; comment: string }[]
|
|
22
|
-
status: ReviewVerdict | null; comment: string
|
|
23
|
-
}): string | null
|
|
@@ -58,24 +58,7 @@ export function needsPackageJsonReview(entries, files, nonTextFiles = []) {
|
|
|
58
58
|
|| !entries.some(entry => entry.file === 'package.json' && Object.keys(REVIEW_VERDICTS).includes(entry.status)))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const markdownText = text => text.replaceAll(/[\\`*_{}[\]<>]/gu, '\\$&').replaceAll(/\r\n?/gu, '\n').replaceAll('\n', '\n ')
|
|
62
|
-
|
|
63
61
|
export function parseReviewNotes(comment) {
|
|
64
62
|
return comment.replaceAll(/\r\n?/gu, '\n').split(/(?:^|\n)[\t ]*\*[\t ]+/u)
|
|
65
63
|
.map((text, index) => ({ text: text.trim(), bullet: index > 0 })).filter(note => note.text)
|
|
66
64
|
}
|
|
67
|
-
|
|
68
|
-
function auditNotes(comment, prefix = '') {
|
|
69
|
-
return parseReviewNotes(comment).map(note => `* ${prefix}${note.text.replaceAll('\n', '\n ')}`)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function auditMarkdown(review) {
|
|
73
|
-
if (review.publishedAt === null || review.revokedAt !== null || review.coverage !== 'full'
|
|
74
|
-
|| !['accept', 'accept-with-nits'].includes(review.status)) return null
|
|
75
|
-
const target = `${review.package} ${review.version} ${review.integrity}`
|
|
76
|
-
const dependency = review.basePackage === null ? target
|
|
77
|
-
: review.basePackage === review.package ? `${review.package} ${review.baseVersion} -> ${review.version} ${review.integrity}`
|
|
78
|
-
: `${review.basePackage} ${review.baseVersion} -> ${target}`
|
|
79
|
-
const notes = [...review.entries.filter(entry => entry.status).flatMap(entry => auditNotes(entry.comment, `In ${markdownText(entry.file)}: `)), ...auditNotes(review.comment)]
|
|
80
|
-
return `Audited:\n\`\`\`\n${dependency}\n\`\`\`${notes.length > 0 ? `\n\nNotes:\n${notes.join('\n')}` : ''}`
|
|
81
|
-
}
|