@preventive/triage 1.0.0-alpha.5 → 1.0.0-alpha.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +202 -0
- package/README.md +16 -0
- package/api/reap.ts +17 -0
- package/cli.js +6 -0
- package/client/dependency-paths.d.ts +3 -0
- package/client/dependency-paths.js +36 -0
- package/client/finding-link.js +305 -0
- package/client/linked-findings.d.ts +1 -0
- package/client/linked-findings.js +115 -0
- package/common/bundle-commit.d.ts +1 -0
- package/common/bundle-commit.js +4 -0
- package/common/bundle-compare-diff.d.ts +3 -0
- package/common/bundle-compare-diff.js +357 -0
- package/common/bundle-compare-inputs.d.ts +2 -0
- package/common/bundle-compare-inputs.js +65 -0
- 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-metadata.d.ts +15 -0
- package/common/bundle-metadata.js +226 -0
- package/common/bundle-pkg-of.d.ts +1 -0
- package/common/bundle-pkg-of.js +90 -0
- package/common/bundle-reasons.d.ts +2 -0
- package/common/bundle-reasons.js +21 -0
- package/common/bundle-source-links.d.ts +6 -0
- package/common/bundle-source-links.js +216 -0
- package/common/bundle-sourcemap.d.ts +10 -0
- package/common/bundle-sourcemap.js +100 -0
- package/common/bundle-sources.d.ts +7 -0
- package/common/bundle-sources.js +333 -0
- package/common/bundle-stats.js +41 -0
- package/common/bundle-tabs.js +1 -0
- package/common/code-language.js +65 -0
- package/common/default-scan-models.ts +31 -0
- package/common/dependency-reviews.d.ts +18 -0
- package/common/dependency-reviews.js +64 -0
- package/common/finding-duplicates.js +58 -0
- package/common/finding-filters.js +274 -0
- package/common/finding-id.js +47 -0
- package/common/github-issue-labels.d.ts +1 -0
- package/common/github-issue-labels.js +11 -0
- package/common/github-pr.ts +56 -0
- package/common/gzip.js +59 -0
- package/common/line-diff.d.ts +8 -0
- package/common/line-diff.js +57 -0
- package/common/linked-workspace-groups.js +63 -0
- package/common/managed/agent-tasks.ts +292 -0
- package/common/managed/comments.ts +34 -0
- package/common/managed/finding-detail.d.ts +2 -0
- package/common/managed/finding-detail.js +32 -0
- package/common/managed/github-agents.ts +114 -0
- package/common/managed/github-logins.ts +49 -0
- package/common/managed/npm-packages.d.ts +5 -0
- package/common/managed/npm-packages.js +34 -0
- package/common/managed/permissions.ts +31 -0
- package/common/managed/report-content.ts +72 -0
- package/common/managed/report-filter.ts +109 -0
- package/common/managed/repository-alias.ts +65 -0
- package/common/managed/roles.ts +35 -0
- package/common/managed/routes.d.ts +2 -0
- package/common/managed/routes.js +347 -0
- package/common/managed/scan-models.ts +6 -0
- package/common/managed/team-app.d.ts +5 -0
- package/common/managed/team-app.js +31 -0
- package/common/managed/triage.ts +83 -0
- package/common/managed/upload-seal.ts +126 -0
- package/common/pretty-print.d.ts +5 -0
- package/common/pretty-print.js +24 -0
- package/common/revalidation-conflicts.js +48 -0
- package/common/revalidation-input-groups.js +56 -0
- package/common/save-error-reason.ts +20 -7
- package/common/scan-server.ts +13 -0
- package/common/security-links.d.ts +5 -0
- package/common/security-links.js +33 -0
- package/common/server-info.ts +46 -0
- package/common/utf8.d.ts +3 -0
- package/common/utf8.js +45 -0
- package/common/workspace-app.js +39 -0
- package/common/workspace-groups.js +176 -0
- package/out/activity.js +4 -0
- package/out/brotli-fallback.js +3 -3
- package/out/chunk-2WDZPXVL.js +3 -0
- package/out/chunk-2XUEZHIP.js +28 -0
- package/out/chunk-2ZDZWLFY.js +1 -0
- package/out/chunk-4HI2MSAV.js +17 -0
- package/out/chunk-4XMP7YVH.js +36 -0
- package/out/chunk-6LNQTKAV.js +52 -0
- package/out/chunk-6S2U2UYG.js +232 -0
- package/out/chunk-75YLHBCM.js +3 -0
- package/out/chunk-7BKKYVWR.js +7 -0
- package/out/chunk-7C7DWL7I.js +1 -0
- package/out/chunk-7VAWINQF.js +6 -0
- package/out/chunk-C4Z6ZBXV.js +10 -0
- package/out/chunk-FC65TPUB.js +1 -0
- package/out/chunk-FKBXNUW4.js +1 -0
- package/out/chunk-GDPCLSOS.js +2 -0
- package/out/chunk-GO7FT4OD.js +1 -0
- package/out/chunk-HTBYHSPG.js +1 -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-NRPC74AM.js +1 -0
- package/out/chunk-OOJM4CTU.js +1 -0
- package/out/chunk-PBZPYIG7.js +1 -0
- package/out/chunk-PNHBZGDD.js +1 -0
- package/out/chunk-PWFUOLZK.js +17 -0
- package/out/chunk-PXK7EU4V.js +1 -0
- package/out/chunk-QB4CBOX2.js +1 -0
- package/out/chunk-R3NZFXVR.js +1 -0
- package/out/chunk-R5O6FAW4.js +2 -0
- package/out/chunk-SXV2T5C4.js +80 -0
- package/out/chunk-TQJDYHVE.js +2 -0
- package/out/chunk-TUII34K2.js +1 -0
- package/out/chunk-UK7RGJT6.js +3 -0
- package/out/chunk-VFWMQ3LG.js +2 -0
- package/out/chunk-VH4XMZIW.js +2 -0
- package/out/chunk-WCPJDAYA.js +10 -0
- package/out/chunk-WFYLMF6Z.js +10 -0
- package/out/chunk-WYLQWYLA.js +1 -0
- package/out/client-managed-import.js +4 -0
- package/out/client-managed.js +33 -0
- package/out/client-sync.js +11 -17
- package/out/compare.js +35 -0
- package/out/dialogs.js +55 -0
- package/out/github-issue-authorized.html +3 -0
- package/out/github-issue-authorized.js +1 -0
- package/out/graph.js +17 -4
- package/out/index.html +90 -8
- package/out/prism.js +14 -10
- package/out/scan.js +2 -0
- package/out/stasis.svg +20 -0
- package/out/terminal.js +366 -39
- package/out/view.css +1 -1
- package/out/view.js +19 -144
- package/package.json +362 -59
- package/risk/actions/findings.js +161 -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 +169 -0
- package/risk/actions/uses.js +76 -0
- package/risk/actions/workflow.js +110 -0
- package/risk/files/characters.js +196 -0
- package/risk/files/hosts.js +205 -0
- package/risk/files/readability.js +177 -0
- package/risk/files/runs-code.js +615 -0
- package/risk/files/script.js +118 -0
- package/risk/files/stamps.js +47 -0
- package/risk/index.d.ts +254 -0
- package/risk/index.js +11 -0
- package/risk/npm/assess.js +285 -0
- package/risk/npm/combined.js +68 -0
- package/risk/npm/compare.js +257 -0
- package/risk/npm/dependencies.js +172 -0
- package/risk/npm/diff.js +79 -0
- package/risk/npm/downloads.js +84 -0
- package/risk/npm/files.js +445 -0
- package/risk/npm/held.js +63 -0
- package/risk/npm/install-scripts.js +34 -0
- package/risk/npm/malware.js +36 -0
- package/risk/npm/manifest.js +65 -0
- package/risk/npm/new-dependency.js +60 -0
- package/risk/npm/old-version.js +108 -0
- package/risk/npm/registry.js +388 -0
- package/risk/npm/release.js +85 -0
- package/risk/npm/score.js +59 -0
- package/risk/npm/specs.js +124 -0
- package/risk/npm/tarball.js +96 -0
- package/server-common/database-config.ts +16 -0
- package/server-common/initialize.ts +18 -0
- package/server-common/npm-advisories.ts +101 -0
- package/{server → server-common}/origin.ts +5 -5
- package/server-common/reap.ts +48 -0
- package/server-common/scan-config.ts +19 -0
- package/server-common/standalone.ts +29 -0
- package/server-common/storage-crypto.ts +182 -0
- package/server-common/storage-log.ts +38 -0
- package/server-common/vercel-blob.ts +103 -0
- package/server-e2e/app.ts +487 -0
- package/{server → server-e2e}/bus-receiver.ts +2 -1
- package/{server → server-e2e}/cli.js +9 -4
- package/{server → server-e2e}/config.ts +50 -42
- package/{server → server-e2e}/db-neon.ts +2 -2
- package/{server → server-e2e}/db-revision-sql.ts +1 -1
- package/{server → server-e2e}/db-stmt.ts +2 -2
- package/{server → server-e2e}/db.ts +7 -7
- package/{server → server-e2e}/http.ts +44 -13
- package/{server → server-e2e}/hub.ts +40 -9
- package/server-e2e/index.ts +17 -0
- package/server-e2e/lifecycle.ts +95 -0
- package/{server → server-e2e}/neon-driver.ts +2 -2
- package/{server → server-e2e}/npm-proxy.ts +12 -145
- package/{server → server-e2e}/objstore/blob-vercel.ts +10 -114
- package/{server → server-e2e}/objstore/blob.ts +1 -1
- package/server-e2e/objstore/fetch-mint-guard.ts +74 -0
- package/{server → server-e2e}/objstore/handlers.ts +9 -8
- package/{server → server-e2e}/objstore/init.ts +19 -27
- package/{server → server-e2e}/objstore/reaper.ts +1 -1
- package/server-e2e/objstore/rest-deny.ts +28 -0
- package/server-e2e/objstore/rest-mint.ts +224 -0
- package/{server → server-e2e}/objstore/rest.ts +3 -3
- package/{server → server-e2e}/objstore/store-neon.ts +6 -10
- package/{server → server-e2e}/objstore/store.ts +7 -7
- package/{server → server-e2e}/objstore/tokens.ts +2 -2
- package/{server → server-e2e}/pubsub.ts +12 -9
- package/{server → server-e2e}/sign.ts +4 -4
- package/{server → server-e2e}/sse-server.ts +60 -33
- package/{server → server-e2e}/static.ts +31 -21
- package/{server → server-e2e}/sync-handlers.ts +64 -30
- package/{server → server-e2e}/ws-server.ts +20 -9
- package/server-managed/activity.ts +258 -0
- package/server-managed/agent-merges.ts +109 -0
- package/server-managed/app-agents.ts +21 -0
- package/server-managed/avatar-store.ts +13 -0
- package/server-managed/blob-store.ts +17 -0
- package/server-managed/brotli.ts +10 -0
- package/server-managed/bundle-advisories.ts +120 -0
- package/server-managed/bundle-advisory-inventory.ts +77 -0
- package/server-managed/bundle-build-diagnostics.d.ts +10 -0
- package/server-managed/bundle-build-diagnostics.js +23 -0
- package/server-managed/bundle-build-leases.ts +41 -0
- package/server-managed/bundle-build-worker.js +115 -0
- package/server-managed/bundle-build.ts +200 -0
- package/server-managed/bundle-cache.ts +211 -0
- package/server-managed/bundle-catalog.ts +29 -0
- package/server-managed/bundle-commits.ts +140 -0
- package/server-managed/bundle-store.ts +24 -0
- package/server-managed/bundle-summary-cache.ts +130 -0
- package/server-managed/bundle.ts +119 -0
- package/server-managed/cache-storage.ts +20 -0
- package/server-managed/cli.js +21 -0
- package/server-managed/combined.ts +47 -0
- package/server-managed/comments.ts +151 -0
- package/server-managed/commit-coauthors.ts +106 -0
- package/server-managed/commit-lockfiles.ts +73 -0
- package/server-managed/comparison.ts +41 -0
- package/server-managed/config.ts +164 -0
- package/server-managed/content-access.ts +15 -0
- package/server-managed/crypto.ts +25 -0
- package/server-managed/db-methods.ts +1923 -0
- package/server-managed/db-neon.ts +458 -0
- package/server-managed/db-schema.ts +238 -0
- package/server-managed/db-table-names.ts +22 -0
- package/server-managed/db.ts +142 -0
- package/server-managed/dependabot-access.ts +60 -0
- package/server-managed/dependabot-alerts.ts +185 -0
- package/server-managed/dependency-review-comments.ts +100 -0
- package/server-managed/dependency-reviews.ts +346 -0
- package/server-managed/github-accounts.ts +60 -0
- package/server-managed/github-actions.ts +71 -0
- package/server-managed/github-app.ts +415 -0
- package/server-managed/github-errors.ts +70 -0
- package/server-managed/github-graphql.ts +39 -0
- package/server-managed/github-issue-links.ts +140 -0
- package/server-managed/github-issue-oauth.ts +47 -0
- package/server-managed/github-issues.ts +152 -0
- package/server-managed/github-link-oauth.ts +69 -0
- package/server-managed/github-links.ts +204 -0
- package/server-managed/github-metadata.ts +213 -0
- package/server-managed/github-oauth.ts +238 -0
- package/server-managed/github-pulls.ts +227 -0
- package/server-managed/github-requests.ts +39 -0
- package/server-managed/github-users.ts +30 -0
- package/server-managed/http-response.ts +76 -0
- package/server-managed/http.ts +5329 -0
- package/server-managed/import-triage.ts +51 -0
- package/server-managed/index.ts +192 -0
- package/server-managed/kept-briefly.ts +37 -0
- package/server-managed/kept.ts +28 -0
- package/server-managed/link-reports.ts +162 -0
- package/server-managed/locked-packages.ts +129 -0
- package/server-managed/lockfile-scope.ts +49 -0
- package/server-managed/lru.ts +30 -0
- package/server-managed/managed-issues.ts +78 -0
- package/server-managed/management-catalog.ts +45 -0
- package/server-managed/management.ts +99 -0
- package/server-managed/npm-delta-worker.js +23 -0
- package/server-managed/npm-delta.ts +194 -0
- package/server-managed/npm-insights.ts +449 -0
- package/server-managed/npm-kept.ts +215 -0
- package/server-managed/npm-loads.ts +264 -0
- package/server-managed/npm-manifest.ts +115 -0
- package/server-managed/npm-packages.ts +454 -0
- package/server-managed/npm-provenance.ts +123 -0
- package/server-managed/npm-risk.ts +285 -0
- package/server-managed/object-storage-disk.ts +148 -0
- package/server-managed/object-storage-vercel.ts +67 -0
- package/server-managed/object-storage.ts +75 -0
- package/server-managed/package-entry-points.ts +51 -0
- package/server-managed/pretty-print.ts +434 -0
- package/server-managed/public-workspace.ts +238 -0
- package/server-managed/pull-comment-reviews.ts +161 -0
- package/server-managed/pull-dependencies.ts +348 -0
- package/server-managed/pull-dependency-reviews.ts +328 -0
- package/server-managed/pull-reviews.ts +205 -0
- package/server-managed/queue.ts +39 -0
- package/server-managed/release-commits.ts +84 -0
- package/server-managed/rename-diff-worker.js +26 -0
- package/server-managed/rename-diff.ts +279 -0
- package/server-managed/repo-path.ts +21 -0
- package/server-managed/report-migration.ts +35 -0
- package/server-managed/report-query.ts +10 -0
- package/server-managed/report-response.ts +16 -0
- package/server-managed/report-sources.ts +178 -0
- package/server-managed/report-store.ts +85 -0
- package/server-managed/repository-actions.ts +218 -0
- package/server-managed/repository-agents.ts +355 -0
- package/server-managed/repository-aliases.ts +91 -0
- package/server-managed/repository-browser.ts +294 -0
- package/server-managed/repository-changes.ts +284 -0
- package/server-managed/repository-code.ts +215 -0
- package/server-managed/repository-dependency-reviews.ts +165 -0
- package/server-managed/repository-discovery.ts +82 -0
- package/server-managed/repository-history.ts +432 -0
- package/server-managed/repository-issues.ts +195 -0
- package/server-managed/repository-policy.ts +27 -0
- package/server-managed/repository-pulls.ts +156 -0
- package/server-managed/repository-pulse.ts +460 -0
- package/server-managed/repository-risk-assess.ts +105 -0
- package/server-managed/repository-risk-audit.ts +79 -0
- package/server-managed/repository-risk-read.ts +170 -0
- package/server-managed/repository-risk.ts +491 -0
- package/server-managed/repository-squashes.ts +168 -0
- package/server-managed/request-timing.ts +63 -0
- package/server-managed/reviewed-dependencies.ts +150 -0
- package/server-managed/revisions.ts +35 -0
- package/server-managed/scan-downloads.ts +87 -0
- package/server-managed/session.ts +132 -0
- package/server-managed/slugs.ts +38 -0
- package/server-managed/solidity-entry-points.ts +71 -0
- package/server-managed/sql-postgres.ts +34 -0
- package/server-managed/sql.ts +88 -0
- package/server-managed/static.ts +28 -0
- package/server-managed/storage-command.ts +16 -0
- package/server-managed/storage-db.ts +183 -0
- package/server-managed/storage-encryption.ts +290 -0
- package/server-managed/storage-maintenance.ts +259 -0
- package/server-managed/storage-payload.ts +121 -0
- package/server-managed/storage-stores.ts +91 -0
- package/server-managed/storage.ts +82 -0
- package/server-managed/team-app.ts +134 -0
- package/server-managed/team-catalog.ts +7 -0
- package/server-managed/team-feed.ts +180 -0
- package/server-managed/team-npm-scopes.ts +106 -0
- package/server-managed/team-reports.ts +307 -0
- package/server-managed/triage-response.ts +16 -0
- package/server-managed/type-only-worker.js +19 -0
- package/server-managed/type-only.ts +105 -0
- package/server-managed/uploads.ts +106 -0
- package/server-managed/upstream-cache.ts +243 -0
- package/server-managed/view-as.ts +58 -0
- package/server-managed/workspace-shares.ts +169 -0
- package/server.ts +70 -0
- package/server/index.ts +0 -480
- package/server/lifecycle.ts +0 -208
- /package/{LICENSE → LICENSE-MIT} +0 -0
- /package/{server → server-e2e}/auth.ts +0 -0
- /package/{server → server-e2e}/config.example.json +0 -0
- /package/{server → server-e2e}/objstore/blob-fs.ts +0 -0
- /package/{server → server-e2e}/objstore/fs.ts +0 -0
- /package/{server → server-e2e}/objstore/sign.ts +0 -0
- /package/{server → server-e2e}/peer.ts +0 -0
- /package/{server → server-e2e}/sse-session.ts +0 -0
- /package/{server → server-e2e}/util.ts +0 -0
- /package/{server → server-e2e}/validation.ts +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// What calls for a look among the dependencies a package version names: one
|
|
2
|
+
// npm fetches from elsewhere than its registry, and one nothing of the
|
|
3
|
+
// version imports, which npm installs all the same, running its install
|
|
4
|
+
// scripts, as a dependency added to run them alone would be.
|
|
5
|
+
import { isNpmPackageName } from '../../common/managed/npm-packages.js'
|
|
6
|
+
import { NPM_PROSE } from '../files/readability.js'
|
|
7
|
+
import { npmAlias, npmDependencySource, npmRegistrySpec } from './specs.js'
|
|
8
|
+
import { npmInstallScripts } from './install-scripts.js'
|
|
9
|
+
|
|
10
|
+
// The kinds of dependency a manifest names, by its field (npmDependencies).
|
|
11
|
+
const NPM_DEPENDENCY_KINDS = [['dependencies', null], ['peerDependencies', 'peer'], ['optionalDependencies', 'optional']]
|
|
12
|
+
|
|
13
|
+
// A version's dependencies, each `{ name, range, kind, opens }`, in name
|
|
14
|
+
// order: `kind` null for a plain dependency, else 'peer' or 'optional';
|
|
15
|
+
// `opens` the package the viewer opens for it, the one an `npm:` alias
|
|
16
|
+
// names, or null when it names none, or where npm doesn't install it from
|
|
17
|
+
// its registry (specs.js npmRegistrySpec: a path, a repository, a URL),
|
|
18
|
+
// whose package of that name isn't what it installs.
|
|
19
|
+
export function npmDependencies(manifest) {
|
|
20
|
+
const rows = []
|
|
21
|
+
for (const [field, kind] of NPM_DEPENDENCY_KINDS) {
|
|
22
|
+
for (const [name, range] of Object.entries(manifest?.[field] ?? {})) {
|
|
23
|
+
const opens = npmAlias(range)?.name ?? name
|
|
24
|
+
rows.push({ name, range, kind, opens: isNpmPackageName(opens) && npmRegistrySpec(range) ? opens : null })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return rows.toSorted((a, b) => a.name.localeCompare(b.name) || (a.kind ?? '').localeCompare(b.kind ?? ''))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// What two versions' dependencies differ in, by name and kind: those only
|
|
31
|
+
// `base` has, those only `other` has, and those whose range changed.
|
|
32
|
+
export function npmDependencyChanges(base, other) {
|
|
33
|
+
const index = manifest => new Map(npmDependencies(manifest).map(row => [`${row.kind ?? ''}\0${row.name}`, row]))
|
|
34
|
+
const after = index(other), before = index(base)
|
|
35
|
+
const added = [], changed = [], removed = []
|
|
36
|
+
for (const [key, { name, kind, range, opens }] of before) {
|
|
37
|
+
const next = after.get(key)
|
|
38
|
+
if (!next) removed.push({ key, name, kind, range, opens })
|
|
39
|
+
else if (next.range !== range) changed.push({ key, name, kind, from: range, to: next.range, opens: next.opens })
|
|
40
|
+
}
|
|
41
|
+
for (const [key, { name, kind, range, opens }] of after) if (!before.has(key)) added.push({ key, name, kind, range, opens })
|
|
42
|
+
return { removed, added, changed }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Files no import is read from: prose, as the Overview tells it
|
|
46
|
+
// (readability.js NPM_PROSE; not code named alike, as
|
|
47
|
+
// `lib/history.js`), but MDX, which imports what it renders; source maps,
|
|
48
|
+
// which quote whatever their sources import, and lockfiles, which name every
|
|
49
|
+
// package installed; nor the package's own package.json, which names them all.
|
|
50
|
+
const MDX = /\.mdx$/iu
|
|
51
|
+
const NO_IMPORTS = /(?:\.map|(?:^|\/)(?:package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml))$/iu
|
|
52
|
+
// A quote, then a run of the characters names and paths are made of (after
|
|
53
|
+
// webpack's `~` too), and what ends it: a quote, escaped too, as a quote in
|
|
54
|
+
// a quoted command is in binding.gyp (`require(\'nan\')`), or a template's `${`.
|
|
55
|
+
const QUOTED = /["'`]~?([\w@./-]+)(\\?["'`]|\$\{)?/gu
|
|
56
|
+
|
|
57
|
+
// What of a version names a package as an import would, once a version, in
|
|
58
|
+
// one pass over its code files however many names are asked about: `whole`,
|
|
59
|
+
// each name quoted alone or before a path in it (`'name'`, `"name/sub"`);
|
|
60
|
+
// `parts`, each part of a name up to a `-` or `/` that a quote or a
|
|
61
|
+
// template's `${` ends, as a package builds a name from one to pick the
|
|
62
|
+
// build for the platform it runs on (`` `@rollup/rollup-${platform}` ``,
|
|
63
|
+
// `"@typescript/" + name`); and `commands`, the install scripts npm runs.
|
|
64
|
+
const importsByVersion = new WeakMap()
|
|
65
|
+
function importsOf({ json, npm }) {
|
|
66
|
+
let found = importsByVersion.get(json)
|
|
67
|
+
if (found) return found
|
|
68
|
+
const sources = json.sources ?? [], texts = json.sourcesContent ?? []
|
|
69
|
+
found = { whole: new Set(), parts: new Set(), commands: npmInstallScripts(npm?.manifest, sources).map(([, command]) => command) }
|
|
70
|
+
for (const [i, path] of sources.entries()) {
|
|
71
|
+
if (typeof texts[i] !== 'string' || path === 'package.json' || (NPM_PROSE.test(path) && !MDX.test(path)) || NO_IMPORTS.test(path)) continue
|
|
72
|
+
for (const [, run, end] of texts[i].matchAll(QUOTED)) {
|
|
73
|
+
if (end && '-/'.includes(run.at(-1))) found.parts.add(run)
|
|
74
|
+
if (end && end !== '${') found.whole.add(run)
|
|
75
|
+
for (let at = run.indexOf('/'); at !== -1; at = run.indexOf('/', at + 1)) found.whole.add(run.slice(0, at))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
importsByVersion.set(json, found)
|
|
79
|
+
return found
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Whether a command names `name`: as a word (`prebuild-install || node-gyp
|
|
83
|
+
// rebuild`), or as the package installed in `node_modules` that a path
|
|
84
|
+
// leads into (`node node_modules/name/setup.js`); not as part of another
|
|
85
|
+
// path (`node scripts/name.js`, `node_modules/@scope/name`).
|
|
86
|
+
function commandNames(command, name) {
|
|
87
|
+
for (let at = command.indexOf(name); at !== -1; at = command.indexOf(name, at + 1)) {
|
|
88
|
+
const before = command.slice(0, at)
|
|
89
|
+
if ((!/[\w@./-]$/u.test(before) || /node_modules\/(?:\.bin\/)?$/u.test(before)) && !/^[\w.-]/u.test(command.slice(at + name.length))) return true
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// DefinitelyTyped's stub of a package's types, once the package has types
|
|
95
|
+
// of its own (`@types/mkdirp` 2.0.0), depending on that package, the
|
|
96
|
+
// registry's by its name (`spec`, no `npm:` alias of another, no repository,
|
|
97
|
+
// URL or path): named as DefinitelyTyped publishes the package's types
|
|
98
|
+
// (`@types/name`, a scoped one's `@types/scope__name`), with nothing but
|
|
99
|
+
// prose beside its package.json, no declarations of its own.
|
|
100
|
+
const typesOf = name => `@types/${name.startsWith('@') ? name.slice(1).replace('/', '__') : name}`
|
|
101
|
+
const typesStubOf = ({ json, npm }, name, spec) => npm?.name === typesOf(name) && npmRegistrySpec(spec) && (npmAlias(spec)?.name ?? name) === name
|
|
102
|
+
&& (json.sources ?? []).every(path => path === 'package.json' || (NPM_PROSE.test(path) && !MDX.test(path)))
|
|
103
|
+
|
|
104
|
+
// Whether nothing of a version (`details`, its files and manifest) imports
|
|
105
|
+
// a dependency it names (`{ name, kind, range }`, or a changed one's `to`,
|
|
106
|
+
// npm-package.js npmDependencies): no code file names it as an import
|
|
107
|
+
// would, whole or by a part of it, nor does an install script npm runs
|
|
108
|
+
// (importsOf); never where its files aren't read. Not a peer dependency,
|
|
109
|
+
// which the package expects whoever installs it to provide, nor types from
|
|
110
|
+
// DefinitelyTyped (`@types/`), imported by another name, where no one else
|
|
111
|
+
// publishes; nor tslib, TypeScript's helpers, which a package compiled
|
|
112
|
+
// with them inlined names all the same, as a third of popular packages
|
|
113
|
+
// naming a dependency nothing imports do; each where npm installs that
|
|
114
|
+
// package from its registry (installsAs), not another under its name
|
|
115
|
+
// (`"tslib": "npm:other@1"`, a repository); nor the package a stub of
|
|
116
|
+
// DefinitelyTyped's types of it depends on (typesStubOf), with no code to
|
|
117
|
+
// import it.
|
|
118
|
+
const installsAs = (name, spec) => npmRegistrySpec(spec) ? npmAlias(spec)?.name ?? name : null
|
|
119
|
+
export function npmDependencyUnimported(details, { name, kind, range, to }) {
|
|
120
|
+
const installs = installsAs(name, to ?? range)
|
|
121
|
+
if (!details?.json || kind === 'peer' || (name.startsWith('@types/') && installs?.startsWith('@types/')) || (name === 'tslib' && installs === 'tslib')
|
|
122
|
+
|| typesStubOf(details, name, to ?? range)) return false
|
|
123
|
+
const { whole, parts, commands } = importsOf(details)
|
|
124
|
+
return !whole.has(name) && ![...name.slice(1).matchAll(/[-/]/gu)].some(({ index }) => parts.has(name.slice(0, index + 2)))
|
|
125
|
+
&& !commands.some(command => commandNames(command, name))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Whether a dependency nothing of a version imports (`{ name, range }`,
|
|
129
|
+
// npmDependencyUnimported) counts toward its risk (assess.js): not lodash,
|
|
130
|
+
// which packages keep naming long after their code stopped importing it, nor
|
|
131
|
+
// Babel's own presets (`@babel/preset-*`), which a config names by a string
|
|
132
|
+
// (an ESLint config's parser options); each where npm installs that package
|
|
133
|
+
// from its registry (installsAs), not another under its name.
|
|
134
|
+
export function npmUnusedDependencyCounted({ name, range }) {
|
|
135
|
+
const installs = installsAs(name, range)
|
|
136
|
+
return !(name === 'lodash' && installs === 'lodash') && !(name.startsWith('@babel/preset-') && installs?.startsWith('@babel/preset-'))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Where a source takes a dependency from, in a sentence.
|
|
140
|
+
const SOURCE_NAMES = new Map([['URL', 'a URL'], ['git', 'a git repository']])
|
|
141
|
+
|
|
142
|
+
// What calls for a look at a dependency a version (`details`) names
|
|
143
|
+
// (`{ name, kind, range }`, or a changed one's `to`), as tags
|
|
144
|
+
// (`{ check, label, tooltip }`): `source` where npm fetches it from
|
|
145
|
+
// elsewhere than its registry (npmDependencySource), its spec in the
|
|
146
|
+
// tooltip, and `unimported` where nothing of the version imports it
|
|
147
|
+
// (npmDependencyUnimported). Against the specs another version named its
|
|
148
|
+
// dependencies by (`was`, npmDependencySpecs), only where it named this one
|
|
149
|
+
// by another spec, or, imported by nothing, not at all.
|
|
150
|
+
export function npmDependencyFlags(details, row, was = null) {
|
|
151
|
+
const spec = row.to ?? row.range
|
|
152
|
+
const source = npmDependencySource(spec)
|
|
153
|
+
return [
|
|
154
|
+
...source && was?.get(row.name) !== spec ? [{ check: 'source', label: 'non-registry', tooltip: `From ${SOURCE_NAMES.get(source) ?? source}, not npm's registry: ${spec}` }] : [],
|
|
155
|
+
...!was?.has(row.name) && npmDependencyUnimported(details, row) ? [{ check: 'unimported', label: 'unimported',
|
|
156
|
+
tooltip: `${was ? `New in ${details.npm?.version}, and nothing of it` : `Nothing of ${details.npm?.version}`} imports it: no code file names it as an import would, nor does an install script` }] : [],
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The specs a manifest names its dependencies by, by name, whatever their
|
|
161
|
+
// kind.
|
|
162
|
+
export const npmDependencySpecs = manifest => new Map(NPM_DEPENDENCY_KINDS.flatMap(([field]) => Object.entries(manifest?.[field] ?? {})))
|
|
163
|
+
|
|
164
|
+
// Two versions' dependency changes (npm-package.js npmDependencyChanges),
|
|
165
|
+
// each the open version (`other`) adds or changes with its `flags`
|
|
166
|
+
// (npmDependencyFlags) against the one compared with (`base`), or alone,
|
|
167
|
+
// none given.
|
|
168
|
+
export function npmFlagDependencyChanges({ removed, added, changed }, base, other) {
|
|
169
|
+
const was = base ? npmDependencySpecs(base.npm?.manifest) : null
|
|
170
|
+
const flag = row => ({ ...row, flags: npmDependencyFlags(other, row, was) })
|
|
171
|
+
return { removed, added: added.map(flag), changed: changed.map(flag) }
|
|
172
|
+
}
|
package/risk/npm/diff.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// How two versions' files differ, as Compare's diff has them
|
|
2
|
+
// (ui/view/bundle-compare-diff.js computeBundleDiff), for risk's findings
|
|
3
|
+
// (compare.js): the files the open version adds, and those it changes, a
|
|
4
|
+
// build's content-hashed file paired with the one it replaces by its name
|
|
5
|
+
// (hashedKey), and a file moved to another path with its old one by content,
|
|
6
|
+
// as git diff -M pairs them (@preventive/compare compareTrees).
|
|
7
|
+
import { compareTrees } from '@preventive/compare'
|
|
8
|
+
import { bundleFilesAsMap } from '../../common/bundle-sources.js'
|
|
9
|
+
|
|
10
|
+
// git's diff.renameLimit for a package's renames: past this many removed files
|
|
11
|
+
// by added ones, squared, no pair is scored, and only identical content pairs
|
|
12
|
+
// them.
|
|
13
|
+
const RENAME_LIMIT = 250
|
|
14
|
+
|
|
15
|
+
// Whether two sides hold the same file: text as text, one that isn't by its
|
|
16
|
+
// digest (bundle-compare-diff.js sameContent).
|
|
17
|
+
const same = (a, b) => a === b || (a?.format === 'digest' && b?.format === 'digest' && a.digest === b.digest)
|
|
18
|
+
|
|
19
|
+
// A file's content as compareTrees reads it to find renames: its text, its
|
|
20
|
+
// digest for one that isn't, or else its place in its list, so that only its
|
|
21
|
+
// path pairs it (bundle-compare-diff.js treeData).
|
|
22
|
+
const treeData = (content, at) => typeof content === 'string' ? `\0text\0${content.toWellFormed()}`
|
|
23
|
+
: content?.format === 'digest' ? `\0digest\0${content.digest}` : `\0none\0${at}`
|
|
24
|
+
|
|
25
|
+
// A build's content-hashed file (`assets/index-CyWAfh-p.js`, Vite's and
|
|
26
|
+
// Rollup's `[name]-[hash]`, webpack's `[name].[contenthash].js`) by its name
|
|
27
|
+
// but its hash, `assets/index\0.js`: a release renames it as its content
|
|
28
|
+
// changes, the same file. Null for a name whose last part before its
|
|
29
|
+
// extension is no hash: 8 to 32 of base64url's characters with a letter, and
|
|
30
|
+
// a digit or a capital, as neither a lowercase word (`index-client.js`,
|
|
31
|
+
// `lodash.debounce.js`) nor a number (a date, `migration-20240101.js`) is. Its hash its last 8 or more such characters, so that chunks named
|
|
32
|
+
// after a module (`vendor-react-DQ4mN2sx.js`) pair each with its own; a
|
|
33
|
+
// longer hash holding a `-` (Rollup's `[hash:16]`) is cut there, so that a
|
|
34
|
+
// rebuild pairs by its content alone, as its parts aren't told from a
|
|
35
|
+
// chunk's name's (`vendor-ui-…`) without it.
|
|
36
|
+
const HASHED_NAME = /^(.+)[-.]([\w-]{8,32})((?:\.[A-Za-z\d]+)+)$/u
|
|
37
|
+
function hashedKey(path) {
|
|
38
|
+
const at = path.lastIndexOf('/') + 1
|
|
39
|
+
const [, stem, hash, extension] = HASHED_NAME.exec(path.slice(at)) ?? []
|
|
40
|
+
if (!hash || !/[A-Za-z]/u.test(hash) || !/[\dA-Z]/u.test(hash)) return null
|
|
41
|
+
return `${path.slice(0, at)}${stem}\0${extension}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// `onlyOther`, the files the open version (`other`) adds, `{ path }` each, and
|
|
45
|
+
// `changed`, those it changes, `{ path, modified }`, or moves, `{ path,
|
|
46
|
+
// renamedFrom, modified }`, `modified` whether its content changed too; each
|
|
47
|
+
// version as compare.js takes one. A file that isn't text is
|
|
48
|
+
// told by its digest (npmBinaries), as the viewer's Compare tells it.
|
|
49
|
+
export function npmFilesDiff(base, other) {
|
|
50
|
+
const after = bundleFilesAsMap(other), before = bundleFilesAsMap(base)
|
|
51
|
+
const changed = [], onlyBase = [], onlyOther = []
|
|
52
|
+
for (const [path, text] of after) {
|
|
53
|
+
if (!before.has(path)) onlyOther.push(path)
|
|
54
|
+
else if (!same(before.get(path), text)) changed.push({ path, modified: true })
|
|
55
|
+
}
|
|
56
|
+
for (const path of before.keys()) if (!after.has(path)) onlyBase.push(path)
|
|
57
|
+
// A content-hashed file added where one by the same name but its hash
|
|
58
|
+
// (hashedKey) is removed, one each, is that file changed, as a build
|
|
59
|
+
// renames it: what it runs, how it reads and how large it is told against
|
|
60
|
+
// it, not all of it new.
|
|
61
|
+
const hashed = paths => Map.groupBy(paths.filter(hashedKey), hashedKey)
|
|
62
|
+
const moved = new Set(), removed = hashed(onlyBase)
|
|
63
|
+
for (const [key, paths] of hashed(onlyOther)) {
|
|
64
|
+
const was = removed.get(key)
|
|
65
|
+
if (paths.length !== 1 || was?.length !== 1) continue
|
|
66
|
+
changed.push({ path: paths[0], renamedFrom: was[0], modified: !same(before.get(was[0]), after.get(paths[0])) })
|
|
67
|
+
moved.add(paths[0])
|
|
68
|
+
}
|
|
69
|
+
// The rest by content, against every file removed: one paired by its name
|
|
70
|
+
// may be another's too, a build's copy of it elsewhere.
|
|
71
|
+
const entries = (paths, files, side) => paths.filter(path => path.isWellFormed()).map((path, at) => ({ name: path, data: treeData(files.get(path), `${side}${at}`) }))
|
|
72
|
+
const renames = compareTrees(entries(onlyBase, before, 'a'), entries(onlyOther.filter(path => !moved.has(path)), after, 'b'), { paths: 'keys', renameLimit: RENAME_LIMIT, lines: false })
|
|
73
|
+
.filter(change => change.status === 'renamed')
|
|
74
|
+
for (const change of renames) {
|
|
75
|
+
changed.push({ path: change.new.path, renamedFrom: change.old.path, modified: change.similarity !== 100 })
|
|
76
|
+
moved.add(change.new.path)
|
|
77
|
+
}
|
|
78
|
+
return { onlyOther: onlyOther.filter(path => !moved.has(path)).map(path => ({ path })), changed }
|
|
79
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// A package's downloads, as npm's downloads API counts them a day
|
|
2
|
+
// (`{ start, end, days }`, the first day's date and a count a day from it):
|
|
3
|
+
// in weeks and months, the tier among npm's packages they put it in, and a
|
|
4
|
+
// dependency's to the day before its latest two weeks.
|
|
5
|
+
|
|
6
|
+
export const DAY_MS = 24 * 60 * 60_000
|
|
7
|
+
// The date `offset` days past a count's first day (`start`).
|
|
8
|
+
export const dayOf = (start, offset) => new Date(Date.parse(start) + offset * DAY_MS)
|
|
9
|
+
|
|
10
|
+
// The downloads in 7-day weeks ending on the last day, oldest first, a
|
|
11
|
+
// partial oldest week left out: `{ from, to, total }`, dates as `Date`s.
|
|
12
|
+
export function npmDownloadWeeks(downloads) {
|
|
13
|
+
const weeks = []
|
|
14
|
+
if (!downloads?.days) return weeks
|
|
15
|
+
for (let end = downloads.days.length - 1; end >= 6; end -= 7) {
|
|
16
|
+
let total = 0
|
|
17
|
+
for (let day = end - 6; day <= end; day++) total += downloads.days[day]
|
|
18
|
+
weeks.push({ from: dayOf(downloads.start, end - 6), to: dayOf(downloads.start, end), total })
|
|
19
|
+
}
|
|
20
|
+
return weeks.toReversed()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The downloads in calendar months (UTC), oldest first, the partial months
|
|
24
|
+
// at either end left out: `{ from, to, total }`, as npmDownloadWeeks has them.
|
|
25
|
+
export function npmDownloadMonths(downloads) {
|
|
26
|
+
const months = []
|
|
27
|
+
if (!downloads?.days) return months
|
|
28
|
+
const start = Date.parse(downloads.start)
|
|
29
|
+
let month = null
|
|
30
|
+
downloads.days.forEach((count, i) => {
|
|
31
|
+
const day = new Date(start + i * DAY_MS)
|
|
32
|
+
if (day.getUTCDate() === 1) {
|
|
33
|
+
month = { from: day, to: day, total: 0 }
|
|
34
|
+
months.push(month)
|
|
35
|
+
}
|
|
36
|
+
if (month) { month.total += count; month.to = day }
|
|
37
|
+
})
|
|
38
|
+
const last = months.at(-1)
|
|
39
|
+
if (last && new Date(last.to.getTime() + DAY_MS).getUTCDate() !== 1) months.pop()
|
|
40
|
+
return months
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The weekly downloads from which a package is among npm's top 10, 20, 50,
|
|
44
|
+
// 100, …, 100,000 packages, rounded: download-counts' snapshot of a month's
|
|
45
|
+
// downloads (Jan 30 – Feb 28, 2026), each rank's count grown by the median
|
|
46
|
+
// growth of the packages ranked around it to the 30 days Sep 9 – Oct 8,
|
|
47
|
+
// 2026, as npm counts them, days it failed to count among them, then made a
|
|
48
|
+
// week. An estimate, to be revisited. Below the last, a package is
|
|
49
|
+
// unpopular (Infinity).
|
|
50
|
+
const TIERS = [
|
|
51
|
+
[10, 550e6], [20, 405e6], [50, 285e6], [100, 225e6], [200, 165e6], [500, 95e6], [1000, 50e6], [2000, 20e6], [5000, 4e6],
|
|
52
|
+
[10_000, 850e3], [20_000, 155e3], [50_000, 18e3], [100_000, 2e3], [Infinity, 0],
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
// The tier a package's downloads put it in (TIERS), by the most it had in any
|
|
56
|
+
// of its latest six 7-day weeks (npmDownloadWeeks): a week lower for a day
|
|
57
|
+
// npm failed to count, as it now and then does, or for a holiday, is passed
|
|
58
|
+
// over. Infinity for an unpopular one; null without a week.
|
|
59
|
+
export function npmDownloadTier(downloads) {
|
|
60
|
+
const latest = npmDownloadWeeks(downloads).slice(-6)
|
|
61
|
+
if (latest.length === 0) return null
|
|
62
|
+
const most = Math.max(...latest.map(week => week.total))
|
|
63
|
+
return TIERS.find(([, from]) => most >= from)[0]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A tier's name, and its tint: solid green to the top 200, green to the top
|
|
67
|
+
// 5,000, yellow to the top 20,000, orange to the top 100,000, and red for
|
|
68
|
+
// the unpopular, a package few would notice changing.
|
|
69
|
+
export const npmTierLevel = tier => tier <= 200 ? 'is-solid' : tier <= 5000 ? 'is-top' : tier <= 20_000 ? 'is-mid' : tier <= 100_000 ? 'is-low' : 'is-unpopular'
|
|
70
|
+
// A dependency's downloads to the day before its latest two weeks: a
|
|
71
|
+
// package a release just pulled in, as a compromised one depending on it
|
|
72
|
+
// would, could owe those to that release alone. Its own package's tier goes
|
|
73
|
+
// by all of them, its Overview charting them.
|
|
74
|
+
const DEPENDENCY_DAYS_LEFT_OUT = 14
|
|
75
|
+
export function npmSettledDownloads(downloads, now = Date.now()) {
|
|
76
|
+
if (!downloads) return null
|
|
77
|
+
const first = Math.floor(Date.parse(downloads.start) / DAY_MS), last = Math.floor(now / DAY_MS) - 1 - DEPENDENCY_DAYS_LEFT_OUT
|
|
78
|
+
const days = downloads.days.slice(0, Math.max(0, last - first + 1))
|
|
79
|
+
return { start: downloads.start, end: new Date((first + days.length - 1) * DAY_MS).toISOString().slice(0, 10), days }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A tier orange or red, past the top 20,000: one few would notice changing,
|
|
83
|
+
// as a release past the top 20,000 is flagged (compare.js).
|
|
84
|
+
export const npmTierLow = tier => tier > 20_000
|