@preventive/triage 1.0.0-alpha.4 → 1.0.0-alpha.41
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 +2 -0
- package/client/dependency-paths.js +24 -0
- package/client/finding-link.js +305 -0
- package/client/linked-findings.d.ts +1 -0
- package/client/linked-findings.js +112 -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 +522 -0
- package/common/bundle-compare-inputs.d.ts +2 -0
- package/common/bundle-compare-inputs.js +65 -0
- package/common/bundle-metadata.d.ts +14 -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 +310 -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 +23 -0
- package/common/dependency-reviews.js +81 -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 +56 -0
- package/common/linked-workspace-groups.js +63 -0
- package/common/managed/comments.ts +34 -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 +108 -0
- package/common/managed/repository-alias.ts +65 -0
- package/common/managed/roles.ts +28 -0
- package/common/managed/routes.d.ts +2 -0
- package/common/managed/routes.js +254 -0
- package/common/managed/scan-models.ts +6 -0
- package/common/managed/team-app.d.ts +2 -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/server-info.ts +35 -0
- package/common/utf8.d.ts +3 -0
- package/common/utf8.js +45 -0
- package/common/workspace-app.js +23 -0
- package/common/workspace-groups.js +176 -0
- package/out/brotli-fallback.js +3 -3
- package/out/chunk-2AFQKRAM.js +51 -0
- package/out/chunk-2KBF5TZ7.js +2 -0
- package/out/chunk-2XUEZHIP.js +28 -0
- package/out/chunk-35VE6CDH.js +2 -0
- package/out/chunk-3BZ7JERB.js +1 -0
- package/out/chunk-4F6JSF57.js +3 -0
- package/out/chunk-4XCDFGDP.js +10 -0
- package/out/chunk-C4Z6ZBXV.js +10 -0
- package/out/chunk-CMQA4DM6.js +1 -0
- package/out/chunk-CVVJAXVY.js +2 -0
- package/out/chunk-DIJJNTRH.js +10 -0
- package/out/chunk-GJ4IUCNS.js +1 -0
- package/out/chunk-GNXOKIGP.js +80 -0
- package/out/chunk-GZ4MS3TE.js +13 -0
- package/out/chunk-IU3CCINR.js +3 -0
- package/out/chunk-K3X5EJBQ.js +2 -0
- package/out/chunk-KEX2ATEW.js +2 -0
- package/out/chunk-KVEUXIEU.js +1 -0
- package/out/chunk-KYCNJVB7.js +1 -0
- package/out/chunk-NRPC74AM.js +1 -0
- package/out/chunk-OOJM4CTU.js +1 -0
- package/out/chunk-PNHBZGDD.js +1 -0
- package/out/chunk-QZ3A376W.js +1 -0
- package/out/chunk-R5O6FAW4.js +2 -0
- package/out/chunk-RFZQQXSG.js +17 -0
- package/out/chunk-RSHMVYNS.js +1 -0
- package/out/chunk-TUII34K2.js +1 -0
- package/out/chunk-TY4RZD3Z.js +226 -0
- package/out/chunk-TZ5KB2Y5.js +10 -0
- package/out/chunk-U6OPEXIA.js +1 -0
- package/out/chunk-UNTRGE26.js +2 -0
- package/out/chunk-UXNUAIIC.js +33 -0
- package/out/chunk-UYAXVRVL.js +1 -0
- package/out/chunk-UZAZM2SX.js +2 -0
- package/out/chunk-V3ME6FI5.js +2 -0
- package/out/chunk-V5O7M2YT.js +1 -0
- package/out/chunk-VIHGO77Y.js +1 -0
- package/out/chunk-VQJCXGIY.js +7 -0
- package/out/chunk-W4J3MGLB.js +1 -0
- package/out/chunk-WGG7KQUH.js +1 -0
- package/out/chunk-WZDTZE64.js +1 -0
- package/out/chunk-XL35ZV6R.js +1 -0
- package/out/chunk-XYFXXB2G.js +10 -0
- package/out/chunk-YVZH5SHP.js +1 -0
- package/out/chunk-ZBBOBQ43.js +1 -0
- package/out/chunk-ZMQQCGF6.js +2 -0
- package/out/client-managed-import.js +4 -0
- package/out/client-managed.js +37 -0
- package/out/client-sync.js +10 -13
- 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 +6 -2
- package/out/stasis.svg +20 -0
- package/out/terminal.js +365 -39
- package/out/view.css +1 -1
- package/out/view.js +83 -117
- package/package.json +298 -59
- package/risk/files/characters.js +196 -0
- package/risk/files/hosts.js +205 -0
- package/risk/files/readability.js +171 -0
- package/risk/files/runs-code.js +595 -0
- package/risk/files/script.js +114 -0
- package/risk/files/stamps.js +46 -0
- package/risk/index.d.ts +156 -0
- package/risk/index.js +7 -0
- package/risk/npm/assess.js +223 -0
- package/risk/npm/combined.js +65 -0
- package/risk/npm/compare.js +228 -0
- package/risk/npm/dependencies.js +153 -0
- package/risk/npm/diff.js +43 -0
- package/risk/npm/downloads.js +84 -0
- package/risk/npm/files.js +34 -0
- package/risk/npm/held.js +60 -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 +30 -0
- package/risk/npm/old-version.js +108 -0
- package/risk/npm/registry.js +282 -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 +113 -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 +486 -0
- package/{server → server-e2e}/auth.ts +5 -1
- package/{server → server-e2e}/bus-receiver.ts +2 -1
- package/{server → server-e2e}/cli.js +9 -4
- package/{server → server-e2e}/config.ts +56 -35
- 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 +116 -18
- 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 +35 -18
- 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 +27 -26
- package/{server → server-e2e}/objstore/sign.ts +105 -0
- 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 +102 -68
- package/{server → server-e2e}/sse-session.ts +28 -8
- package/{server → server-e2e}/static.ts +31 -21
- package/server-e2e/sync-handlers.ts +408 -0
- package/{server → server-e2e}/ws-server.ts +20 -9
- package/server-managed/activity.ts +245 -0
- package/server-managed/avatar-store.ts +12 -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 +111 -0
- package/server-managed/bundle-build.ts +198 -0
- package/server-managed/bundle-cache.ts +199 -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 +120 -0
- package/server-managed/cache-storage.ts +19 -0
- package/server-managed/cli.js +21 -0
- package/server-managed/combined.ts +46 -0
- package/server-managed/comments.ts +151 -0
- package/server-managed/config.ts +158 -0
- package/server-managed/content-access.ts +15 -0
- package/server-managed/crypto.ts +25 -0
- package/server-managed/db-methods.ts +1725 -0
- package/server-managed/db-neon.ts +367 -0
- package/server-managed/db-schema.ts +227 -0
- package/server-managed/db-table-names.ts +22 -0
- package/server-managed/db.ts +132 -0
- package/server-managed/dependency-reviews.ts +248 -0
- package/server-managed/github-app.ts +391 -0
- package/server-managed/github-errors.ts +68 -0
- package/server-managed/github-issue-links.ts +127 -0
- package/server-managed/github-issue-oauth.ts +47 -0
- package/server-managed/github-issues.ts +152 -0
- package/server-managed/github-metadata.ts +213 -0
- package/server-managed/github-oauth.ts +235 -0
- package/server-managed/github-pulls.ts +227 -0
- package/server-managed/http-response.ts +50 -0
- package/server-managed/http.ts +3472 -0
- package/server-managed/import-triage.ts +51 -0
- package/server-managed/index.ts +187 -0
- package/server-managed/kept.ts +28 -0
- package/server-managed/link-reports.ts +158 -0
- package/server-managed/locked-packages.ts +85 -0
- package/server-managed/lru.ts +30 -0
- package/server-managed/managed-issues.ts +75 -0
- package/server-managed/management-catalog.ts +45 -0
- package/server-managed/management.ts +99 -0
- package/server-managed/npm-insights.ts +440 -0
- package/server-managed/npm-kept.ts +65 -0
- package/server-managed/npm-loads.ts +167 -0
- package/server-managed/npm-manifest.ts +115 -0
- package/server-managed/npm-packages.ts +378 -0
- package/server-managed/npm-provenance.ts +123 -0
- package/server-managed/npm-risk.ts +177 -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 +74 -0
- package/server-managed/package-entry-points.ts +51 -0
- package/server-managed/pretty-print.ts +352 -0
- package/server-managed/public-workspace.ts +220 -0
- package/server-managed/rename-diff-worker.js +26 -0
- package/server-managed/rename-diff.ts +270 -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 +47 -0
- package/server-managed/repository-aliases.ts +91 -0
- package/server-managed/repository-browser.ts +153 -0
- package/server-managed/repository-discovery.ts +82 -0
- package/server-managed/repository-policy.ts +27 -0
- package/server-managed/repository-risk.ts +420 -0
- package/server-managed/revisions.ts +35 -0
- package/server-managed/session.ts +92 -0
- package/server-managed/slugs.ts +38 -0
- package/server-managed/solidity-entry-points.ts +69 -0
- package/server-managed/sql-postgres.ts +31 -0
- package/server-managed/sql.ts +76 -0
- package/server-managed/static.ts +28 -0
- package/server-managed/storage-command.ts +16 -0
- package/server-managed/storage-db.ts +175 -0
- package/server-managed/storage-encryption.ts +279 -0
- package/server-managed/storage-maintenance.ts +256 -0
- package/server-managed/storage-payload.ts +103 -0
- package/server-managed/storage-stores.ts +85 -0
- package/server-managed/storage.ts +78 -0
- package/server-managed/team-app.ts +94 -0
- package/server-managed/team-catalog.ts +7 -0
- package/server-managed/team-feed.ts +173 -0
- package/server-managed/team-npm-scopes.ts +77 -0
- package/server-managed/team-reports.ts +228 -0
- package/server-managed/triage-response.ts +16 -0
- package/server-managed/type-only-worker.js +19 -0
- package/server-managed/type-only.ts +103 -0
- package/server-managed/uploads.ts +86 -0
- package/server-managed/upstream-cache.ts +215 -0
- package/server-managed/view-as.ts +58 -0
- package/server-managed/workspace-shares.ts +166 -0
- package/server.ts +50 -0
- package/server/index.ts +0 -477
- package/server/lifecycle.ts +0 -204
- package/server/sync-handlers.ts +0 -320
- /package/{LICENSE → LICENSE-MIT} +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}/peer.ts +0 -0
- /package/{server → server-e2e}/util.ts +0 -0
- /package/{server → server-e2e}/validation.ts +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SourceMap } from '@preventive/sourcemap'
|
|
2
|
+
import type { Edge } from '@preventive/sourcemap/edges.js'
|
|
3
|
+
import type { BundleDetails } from './bundle-metadata.js'
|
|
4
|
+
export function parseSourcemap(text: string): { json: { version: unknown; file: unknown; sourceRoot: unknown }; map: SourceMap; namesCount: number | null }
|
|
5
|
+
export function sourcemapEntries(details: BundleDetails): [string, string | null][]
|
|
6
|
+
export type SourcemapEdge = [from: string, to: string] | [from: string, to: string, specifier: string]
|
|
7
|
+
export function sourcemapEdges(map: SourceMap, read: (map: SourceMap) => { edges: Edge[] }): SourcemapEdge[]
|
|
8
|
+
export function bundleSourcemapEdges(details: BundleDetails): readonly SourcemapEdge[]
|
|
9
|
+
export function bundleSourcemapImports(details: BundleDetails): Map<string, Set<string>>
|
|
10
|
+
export function bundleSourcemapSpecifiers(details: BundleDetails): Map<string, Map<string, string>>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// A sourcemap bundle as @preventive/sourcemap reads it: plain or indexed,
|
|
2
|
+
// each file once, with the edges between its files, which the graph draws
|
|
3
|
+
// and Code links follow by the specifier of each import. A file keeps the key
|
|
4
|
+
// it had before the package read it, the spelling the map lists it under
|
|
5
|
+
// (`sourceRoot` in front), so Code links, hashes and reports that name one
|
|
6
|
+
// still find it.
|
|
7
|
+
//
|
|
8
|
+
// Edges take a parser for most maps. The client goes without one: it reads
|
|
9
|
+
// them with edges-lite.js, which knows Metro's maps alone. The server reads
|
|
10
|
+
// every map's with oxc-parser (server-managed/bundle-cache.ts) and keeps
|
|
11
|
+
// them in the bundle's metadata, which a client takes over its own.
|
|
12
|
+
import { readSourceMap } from '@preventive/sourcemap'
|
|
13
|
+
import { bundleEdges as metroEdges } from '@preventive/sourcemap/edges-lite.js'
|
|
14
|
+
|
|
15
|
+
// The map as read, and what the Overview shows of the JSON: its header,
|
|
16
|
+
// and how many names it maps. The JSON itself is not kept, its mappings
|
|
17
|
+
// read and its sources the map's files.
|
|
18
|
+
export function parseSourcemap(text) {
|
|
19
|
+
// The `)]}'` line that guards a map against being run, which readSourceMap
|
|
20
|
+
// skips in text it parses itself.
|
|
21
|
+
const json = JSON.parse(text.replace(/^\)\]\}'[^\n]*\n/u, ''))
|
|
22
|
+
const map = readSourceMap(json)
|
|
23
|
+
const namesCount = json.sections
|
|
24
|
+
? json.sections.reduce((count, section) => count + (section.map.names?.length ?? 0), 0)
|
|
25
|
+
: json.names?.length ?? null
|
|
26
|
+
return { json: { version: json.version, file: json.file, sourceRoot: json.sourceRoot }, map, namesCount }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The map's files as `[key, content or null]`, in its order. Details with
|
|
30
|
+
// no map read (the npm viewer's, a map's arrays as written) list what their
|
|
31
|
+
// `sources` does, a repeated path repeated.
|
|
32
|
+
export function sourcemapEntries(details) {
|
|
33
|
+
if (details.map) return details.map.files.flatMap((file) => file.source === null ? [] : [[file.source, file.content]])
|
|
34
|
+
const contents = details.json?.sourcesContent ?? []
|
|
35
|
+
return (details.json?.sources ?? []).map((path, i) => [path, contents[i] ?? null])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// `read`'s edges between files of the map, as rows by key: `[from, to]`,
|
|
39
|
+
// and `[from, to, specifier]` for an import its specifier names. An edge to
|
|
40
|
+
// no file of it (a package left out, a builtin) leads nowhere here, and one
|
|
41
|
+
// to its own file is none, which metadata refuses to hold.
|
|
42
|
+
export function sourcemapEdges(map, read) {
|
|
43
|
+
const rows = new Map()
|
|
44
|
+
for (const { from, to, specifier } of read(map).edges) {
|
|
45
|
+
if (from.source === null || to?.source == null || to.source === from.source) continue
|
|
46
|
+
const row = typeof specifier === 'string' ? [from.source, to.source, specifier] : [from.source, to.source]
|
|
47
|
+
rows.set(row.join('\0'), row)
|
|
48
|
+
}
|
|
49
|
+
return [...rows.values()]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// edges-lite.js throws for a map that is not Metro's, or one with no
|
|
53
|
+
// sourcesContent to read: one with no edges the client can tell.
|
|
54
|
+
function liteEdges(map) {
|
|
55
|
+
try { return metroEdges(map) } catch { return { edges: [] } }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const NO_EDGES = Object.freeze([])
|
|
59
|
+
|
|
60
|
+
// The edges a view reads: those the metadata carries, else what the client
|
|
61
|
+
// reads of its own map. None without a map.
|
|
62
|
+
export function bundleSourcemapEdges(details) {
|
|
63
|
+
if (details?.edges) return details.edges
|
|
64
|
+
if (!details?.map) return NO_EDGES
|
|
65
|
+
details.edges = sourcemapEdges(details.map, liteEdges)
|
|
66
|
+
return details.edges
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const importsCache = new WeakMap()
|
|
70
|
+
const specifiersCache = new WeakMap()
|
|
71
|
+
|
|
72
|
+
// The graph's: `Map<from, Set<to>>`.
|
|
73
|
+
export function bundleSourcemapImports(details) {
|
|
74
|
+
const rows = bundleSourcemapEdges(details)
|
|
75
|
+
if (!importsCache.has(rows)) {
|
|
76
|
+
const imports = new Map()
|
|
77
|
+
for (const [from, to] of rows) {
|
|
78
|
+
if (!imports.has(from)) imports.set(from, new Set())
|
|
79
|
+
imports.get(from).add(to)
|
|
80
|
+
}
|
|
81
|
+
importsCache.set(rows, imports)
|
|
82
|
+
}
|
|
83
|
+
return importsCache.get(rows)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Code links': the file each import's specifier names, `Map<from,
|
|
87
|
+
// Map<specifier, to>>`.
|
|
88
|
+
export function bundleSourcemapSpecifiers(details) {
|
|
89
|
+
const rows = bundleSourcemapEdges(details)
|
|
90
|
+
if (!specifiersCache.has(rows)) {
|
|
91
|
+
const specifiers = new Map()
|
|
92
|
+
for (const [from, to, specifier] of rows) {
|
|
93
|
+
if (specifier === undefined) continue
|
|
94
|
+
if (!specifiers.has(from)) specifiers.set(from, new Map())
|
|
95
|
+
specifiers.get(from).set(specifier, to)
|
|
96
|
+
}
|
|
97
|
+
specifiersCache.set(rows, specifiers)
|
|
98
|
+
}
|
|
99
|
+
return specifiersCache.get(rows)
|
|
100
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BundleDetails } from './bundle-metadata.js'
|
|
2
|
+
export function bundleSourcesAsMap(details: BundleDetails): Map<string, string>
|
|
3
|
+
export function bundleSourceLines(content: string): number
|
|
4
|
+
export function bundleSourceOrder(sources: Map<string, string>): { paths: string[]; numbers: Map<string, number> }
|
|
5
|
+
export function bundleFilesAsMap(details: BundleDetails): Map<string, string | { format: 'base64'; data: string }>
|
|
6
|
+
export function bundlePackageDirs(details: BundleDetails): Map<string, string> | null
|
|
7
|
+
export function bundlePackageVersions(details: BundleDetails, paths?: Iterable<string> | null, keyOf?: (dir: string, info: { name: string; version: string }) => string): Map<string, Set<string>>
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Shared helper: given a parsed bundle `details`, return a
|
|
2
|
+
// Map<file, content-string> of its source files. Stasis bundles
|
|
3
|
+
// expose sources via `@exodus/stasis-core`'s `Bundle.sources` getter
|
|
4
|
+
// (flat Map<projectRelPath, content> from `.modules`); sourcemaps list
|
|
5
|
+
// their files as bundle-sourcemap.js reads them. Sourcemap sources whose
|
|
6
|
+
// `sourcesContent[i]` was omitted are skipped.
|
|
7
|
+
//
|
|
8
|
+
// What a stasis bundle records is not all source: `Bundle.formats`
|
|
9
|
+
// marks each entry, and three of those formats are files only in the
|
|
10
|
+
// sense that they occupy a path. `Bundle.sources` hands back the raw
|
|
11
|
+
// stored content for every one of them, so the format is what has to
|
|
12
|
+
// be asked — the content's JS type cannot tell them apart:
|
|
13
|
+
//
|
|
14
|
+
// directory a readdir capture, content `["a.js","b.js"]`
|
|
15
|
+
// resource:base64 an image or font, content its base64 text
|
|
16
|
+
// resource raw bytes, the one case not a string already
|
|
17
|
+
//
|
|
18
|
+
// A directory capture is the one that does real damage, because it
|
|
19
|
+
// claims a path a directory also occupies: hand it to the terminal
|
|
20
|
+
// and `ls` lists the name twice and stops listing what is under it.
|
|
21
|
+
// The base64 resources are quieter but equally wrong — a PNG read as
|
|
22
|
+
// a wall of text, counted toward the language shares that
|
|
23
|
+
// render-bundle.js says resources are kept out of.
|
|
24
|
+
//
|
|
25
|
+
// Own module so `render-bundle.js` (Code tab, finding tree, graph
|
|
26
|
+
// data) and `terminal-attach.js` (in-shell FS) read one definition —
|
|
27
|
+
// a new bundle kind or field handled here is visible to both.
|
|
28
|
+
|
|
29
|
+
import { Bundle } from '@exodus/stasis-core/bundle'
|
|
30
|
+
import { sourcemapEntries } from './bundle-sourcemap.js'
|
|
31
|
+
import { utf8ByteLength } from './utf8.js'
|
|
32
|
+
|
|
33
|
+
const sourcesCache = new WeakMap()
|
|
34
|
+
const filesCache = new WeakMap()
|
|
35
|
+
const sizesCache = new WeakMap()
|
|
36
|
+
const sourceSizesCache = new WeakMap()
|
|
37
|
+
const kindsCache = new WeakMap()
|
|
38
|
+
const unsizedCache = new WeakMap()
|
|
39
|
+
|
|
40
|
+
export function bundleSourcesAsMap(details) {
|
|
41
|
+
if (details?.metadataOnly) return new Map()
|
|
42
|
+
const key = details?.bundle ?? details?.json
|
|
43
|
+
if (key && sourcesCache.has(key)) return sourcesCache.get(key)
|
|
44
|
+
const result = new Map()
|
|
45
|
+
if (!details) return result
|
|
46
|
+
if (details.kind === 'stasis') {
|
|
47
|
+
if (!details.bundle) return result
|
|
48
|
+
const formats = details.bundle.formats
|
|
49
|
+
for (const [file, content] of details.bundle.sources) {
|
|
50
|
+
if (typeof content !== 'string') continue
|
|
51
|
+
// `isResourceFormat` is the package's own list, so a format it
|
|
52
|
+
// adds later is excluded here without this needing to know. A v0
|
|
53
|
+
// bundle records no formats at all: `get` returns undefined,
|
|
54
|
+
// which is not a resource format, and every entry is kept as
|
|
55
|
+
// before.
|
|
56
|
+
if (Bundle.isResourceFormat(formats?.get(file))) continue
|
|
57
|
+
result.set(file, content)
|
|
58
|
+
}
|
|
59
|
+
} else if (details.kind === 'sourcemap') {
|
|
60
|
+
for (const [path, content] of sourcemapEntries(details)) {
|
|
61
|
+
if (typeof content === 'string') result.set(path, content)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (key) sourcesCache.set(key, result)
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A source's lines as the viewer numbers them: one more than its newlines.
|
|
69
|
+
export function bundleSourceLines(content) {
|
|
70
|
+
let lines = 1
|
|
71
|
+
for (let at = content.indexOf('\n'); at !== -1; at = content.indexOf('\n', at + 1)) lines++
|
|
72
|
+
return lines
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A bundle's sources in path order, and each one's number in it from 1: how a
|
|
76
|
+
// managed Code link names a file. The content hash that names a bundle fixes
|
|
77
|
+
// its sources, so the numbers stay put.
|
|
78
|
+
const sourceOrders = new WeakMap()
|
|
79
|
+
export function bundleSourceOrder(sources) {
|
|
80
|
+
let order = sourceOrders.get(sources)
|
|
81
|
+
if (!order) {
|
|
82
|
+
const paths = [...sources.keys()].toSorted()
|
|
83
|
+
order = { paths, numbers: new Map(paths.map((path, index) => [path, index + 1])) }
|
|
84
|
+
sourceOrders.set(sources, order)
|
|
85
|
+
}
|
|
86
|
+
return order
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Every file the bundle carries, for a reader that wants the filesystem
|
|
90
|
+
// rather than the source: what `bundleSourcesAsMap` returns, plus the
|
|
91
|
+
// resources it leaves out, each in the form that is true to it.
|
|
92
|
+
//
|
|
93
|
+
// The two resource formats are not two encodings of the same thing.
|
|
94
|
+
// Stasis picks between them by whether the bytes are valid UTF-8:
|
|
95
|
+
// `resource` is that text, stored verbatim, so it stays a string and a
|
|
96
|
+
// reader sees the SVG or the licence file it is. `resource:base64` is
|
|
97
|
+
// the other case — bytes no string spells — and it is handed over
|
|
98
|
+
// still spelt that way, as `{ format: 'base64', data }`. The terminal
|
|
99
|
+
// takes that form since 2.0 and decodes it the first time the file is
|
|
100
|
+
// read, so a bundle carrying a hundred images costs a hundred strings
|
|
101
|
+
// rather than a hundred decoded buffers, and pays for the one that is
|
|
102
|
+
// opened. Either way it knows what it is holding: `ls -l` and `wc -c`
|
|
103
|
+
// report the real byte count rather than the length of the base64, and
|
|
104
|
+
// `cat` declines instead of printing mojibake.
|
|
105
|
+
//
|
|
106
|
+
// Decoding there also puts the diagnostic where the file is named: a
|
|
107
|
+
// spelling that does not decode is reported by the command that read
|
|
108
|
+
// it, under the detail `base64 source`. Decoding here could only drop
|
|
109
|
+
// the file and say nothing.
|
|
110
|
+
//
|
|
111
|
+
// Directory captures stay out. They are the one entry that is not a
|
|
112
|
+
// file in any encoding, and mounting one puts a second `lib` beside
|
|
113
|
+
// the directory it names.
|
|
114
|
+
export function bundleFilesAsMap(details) {
|
|
115
|
+
const key = details?.bundle ?? details?.json
|
|
116
|
+
if (key && filesCache.has(key)) return filesCache.get(key)
|
|
117
|
+
const files = new Map(bundleSourcesAsMap(details))
|
|
118
|
+
// An npm package's files that are not text (view/npm-package.js) carry
|
|
119
|
+
// their size and digest alone: enough for Compare to tell a changed one
|
|
120
|
+
// from an unchanged one, as `{ format: 'digest', size, digest }`.
|
|
121
|
+
for (const [path, file] of details?.npmBinaries ?? []) files.set(path, { format: 'digest', ...file })
|
|
122
|
+
if (details?.kind === 'stasis' && details.bundle) {
|
|
123
|
+
const formats = details.bundle.formats
|
|
124
|
+
for (const [file, content] of details.bundle.sources) {
|
|
125
|
+
if (typeof content !== 'string') continue
|
|
126
|
+
const format = formats?.get(file)
|
|
127
|
+
if (format === 'resource') files.set(file, content)
|
|
128
|
+
else if (format === 'resource:base64') files.set(file, { format: 'base64', data: content })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (key) filesCache.set(key, files)
|
|
132
|
+
return files
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
136
|
+
|
|
137
|
+
// The length of the bytes a base64 spelling decodes to — three for every
|
|
138
|
+
// four characters, less what the padding stands for — or null when it
|
|
139
|
+
// decodes to none. The terminal decodes strictly (`@exodus/bytes`): RFC
|
|
140
|
+
// 4648's alphabet, no whitespace, padding present or left off but never
|
|
141
|
+
// wrong, and no stray bits in the last character. A spelling it refuses is
|
|
142
|
+
// a file `wc -c` cannot read, so it gets no size here rather than an
|
|
143
|
+
// invented one. Checked without decoding: a bundle's images are not worth
|
|
144
|
+
// a buffer each just to be weighed.
|
|
145
|
+
function base64ByteLength(text) {
|
|
146
|
+
const padded = text.endsWith('=')
|
|
147
|
+
if (padded ? text.length % 4 !== 0 || text.at(-3) === '=' : text.length % 4 === 1) return null
|
|
148
|
+
const data = padded ? text.slice(0, text.endsWith('==') ? -2 : -1) : text
|
|
149
|
+
if (/[^A-Za-z0-9+/]/u.test(data)) return null
|
|
150
|
+
// A trailing group of 2 or 3 characters spells 1 or 2 bytes; the bits of
|
|
151
|
+
// its last character past those bytes must be zero.
|
|
152
|
+
const tail = data.length % 4
|
|
153
|
+
if (tail > 1 && BASE64_ALPHABET.indexOf(data.at(-1)) & (tail === 2 ? 0x0f : 0x03)) return null
|
|
154
|
+
return Math.floor(data.length * 3 / 4)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The byte size of one entry of `bundleFilesAsMap`, as `wc -c` reports it
|
|
158
|
+
// in the terminal: text weighs the UTF-8 it encodes to, and a base64
|
|
159
|
+
// declaration the bytes it decodes to — null when it decodes to none.
|
|
160
|
+
// Anything else is no file, and null too. The Compare tab sizes both
|
|
161
|
+
// bundles' files by this, so its totals are the Overview's.
|
|
162
|
+
export function bundleFileByteLength(content) {
|
|
163
|
+
if (typeof content === 'string') return utf8ByteLength(content)
|
|
164
|
+
if (content?.format === 'digest') return content.size
|
|
165
|
+
return content?.format === 'base64' ? base64ByteLength(content.data) : null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Every path the bundle records, keyed to the byte size of the file it
|
|
169
|
+
// holds — the size `wc -c` reports for it in the terminal, which is to
|
|
170
|
+
// say the size of the entry in `bundleFilesAsMap`: a source or `resource`
|
|
171
|
+
// weighs the UTF-8 its text encodes to, a `resource:base64` the bytes its
|
|
172
|
+
// base64 decodes to. Null is a path with no size to give: a directory
|
|
173
|
+
// capture, which is no file; a sourcemap source whose body was left out;
|
|
174
|
+
// or a `resource:base64` whose spelling does not decode.
|
|
175
|
+
//
|
|
176
|
+
// The Overview and the Treemap weigh a bundle by this, so what they show
|
|
177
|
+
// adds up to what `du` does. Neither the source-only view nor the base64
|
|
178
|
+
// text will do: the first drops every image and font, the second
|
|
179
|
+
// inflates them by a third.
|
|
180
|
+
//
|
|
181
|
+
// Metadata views need byte sizes and paths, never the bodies. The
|
|
182
|
+
// persistent index supplies this map directly; full parses compute it once.
|
|
183
|
+
export function bundleFileSizes(details) {
|
|
184
|
+
if (details?.fileSizes) return details.fileSizes
|
|
185
|
+
const key = details?.bundle ?? details?.json
|
|
186
|
+
if (key && sizesCache.has(key)) return sizesCache.get(key)
|
|
187
|
+
const sizes = new Map()
|
|
188
|
+
const files = bundleFilesAsMap(details)
|
|
189
|
+
const paths = details?.kind === 'stasis' ? details.bundle?.sources.keys() : details && sourcemapEntries(details).map(([path]) => path)
|
|
190
|
+
for (const path of paths ?? []) sizes.set(path, bundleFileByteLength(files.get(path)))
|
|
191
|
+
if (key) sizesCache.set(key, sizes)
|
|
192
|
+
return sizes
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The files the terminal mounts that have no size: a `resource:base64`
|
|
196
|
+
// whose spelling does not decode. The terminal lists such a file, and says
|
|
197
|
+
// why it cannot read it when asked, so the views list it too — but a null
|
|
198
|
+
// size alone cannot tell it from an entry that is no file at all, so it is
|
|
199
|
+
// named here. A metadata-only open reads the set from the index, which is
|
|
200
|
+
// the one thing a size and a format cannot say about a path.
|
|
201
|
+
export function bundleUnsizedFiles(details) {
|
|
202
|
+
if (details?.metadataOnly) return details.unsizedFiles ?? new Set()
|
|
203
|
+
const sizes = bundleFileSizes(details)
|
|
204
|
+
if (unsizedCache.has(sizes)) return unsizedCache.get(sizes)
|
|
205
|
+
const unsized = new Set()
|
|
206
|
+
for (const path of bundleFilesAsMap(details).keys()) if (sizes.get(path) === null) unsized.add(path)
|
|
207
|
+
unsizedCache.set(sizes, unsized)
|
|
208
|
+
return unsized
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// What each file the terminal mounts is, for a view that lists files:
|
|
212
|
+
// 'source', or 'resource' for an image, font or other asset — a file, sized
|
|
213
|
+
// like any other, but none the source viewer can show. Its keys are
|
|
214
|
+
// `bundleFilesAsMap`'s, and nothing else: a directory capture, or an entry
|
|
215
|
+
// with no body to mount (a sourcemap source whose `sourcesContent` was left
|
|
216
|
+
// out, a body that is no string), is no file and is absent. It reads sizes
|
|
217
|
+
// and formats, not bodies, so it answers for a metadata-only open as it
|
|
218
|
+
// does for a parsed one: a sized path is a mounted file, and so is one
|
|
219
|
+
// `bundleUnsizedFiles` names.
|
|
220
|
+
export function bundleFileKinds(details) {
|
|
221
|
+
const sizes = bundleFileSizes(details)
|
|
222
|
+
if (kindsCache.has(sizes)) return kindsCache.get(sizes)
|
|
223
|
+
const unsized = bundleUnsizedFiles(details)
|
|
224
|
+
const formats = details?.kind === 'stasis' ? details.bundle?.formats : null
|
|
225
|
+
const kinds = new Map()
|
|
226
|
+
for (const [path, size] of sizes) {
|
|
227
|
+
if (size === null && !unsized.has(path)) continue
|
|
228
|
+
// An npm package's file that is not text is a resource too: no source to view.
|
|
229
|
+
kinds.set(path, Bundle.isResourceFormat(formats?.get(path)) || details?.npmBinaries?.has(path) ? 'resource' : 'source')
|
|
230
|
+
}
|
|
231
|
+
kindsCache.set(sizes, kinds)
|
|
232
|
+
return kinds
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Source paths and their recorded byte sizes. Keep null for a source whose
|
|
236
|
+
// body is missing, but omit resources and directory captures entirely so
|
|
237
|
+
// graph consumers can distinguish unknown source bytes from non-source.
|
|
238
|
+
export function bundleSourceSizes(details) {
|
|
239
|
+
const sizes = bundleFileSizes(details)
|
|
240
|
+
const formats = details?.kind === 'stasis' ? details.bundle?.formats : null
|
|
241
|
+
if (!formats || formats.size === 0) return sizes
|
|
242
|
+
if (sourceSizesCache.has(sizes)) return sourceSizesCache.get(sizes)
|
|
243
|
+
const result = new Map()
|
|
244
|
+
for (const [path, size] of sizes) if (!Bundle.isResourceFormat(formats.get(path))) result.set(path, size)
|
|
245
|
+
sourceSizesCache.set(sizes, result)
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Map each stasis bundle source path to the package directory that
|
|
250
|
+
// owns it. A stasis `Bundle` already records authoritative package
|
|
251
|
+
// boundaries in `.modules` (a `Map<dir, { name, version, files }>`
|
|
252
|
+
// covering both `node_modules` deps and workspace packages — the
|
|
253
|
+
// PHP `vendor/<vendor>/<pkg>` case, monorepo workspaces, etc.); this
|
|
254
|
+
// mirrors the `Bundle.sources` getter's path construction (`dir/rel`,
|
|
255
|
+
// or just `rel` for the `.` root) so the returned keys line up exactly
|
|
256
|
+
// with `bundleSourcesAsMap`'s.
|
|
257
|
+
//
|
|
258
|
+
// The package views (overview, treemap, graph) otherwise classify
|
|
259
|
+
// paths with `bundlePkgOf`'s string heuristic, which only knows
|
|
260
|
+
// `node_modules/`/`dependencies/` and treats everything else as own
|
|
261
|
+
// source. Feeding each path's recorded module directory into
|
|
262
|
+
// `bundlePkgOf` (`packageDir` option) keeps those packages separate.
|
|
263
|
+
//
|
|
264
|
+
// Returns null for sourcemap bundles (and anything without parsed
|
|
265
|
+
// modules) — they carry no package metadata, so callers fall back to
|
|
266
|
+
// the path heuristic alone.
|
|
267
|
+
export function bundlePackageDirs(details) {
|
|
268
|
+
if (details?.kind !== 'stasis' || !details.bundle?.modules) return null
|
|
269
|
+
const map = new Map()
|
|
270
|
+
for (const [dir, info] of details.bundle.modules) {
|
|
271
|
+
for (const rel of Object.keys(info.files)) {
|
|
272
|
+
map.set(dir === '.' ? rel : `${dir}/${rel}`, dir)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return map
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Map each named npm dependency a (stasis) bundle carries to the set of
|
|
279
|
+
// concrete versions present for it: `Map<name, Set<version>>`. Only
|
|
280
|
+
// `node_modules/...` modules that carry both a name and a non-empty
|
|
281
|
+
// version string count — stasis v1 `scope: 'full'` bundles merge
|
|
282
|
+
// workspace / own-source entries into the same `Bundle.modules` Map
|
|
283
|
+
// (often `version: '0.0.0'` or null), and those aren't upstream
|
|
284
|
+
// dependencies, so they're filtered out by the directory key. A package
|
|
285
|
+
// can map to more than one version (pnpm keeps duplicate majors side by
|
|
286
|
+
// side), hence the Set.
|
|
287
|
+
//
|
|
288
|
+
// Shared by the Advisories tab (a bulk registry lookup keyed on these
|
|
289
|
+
// pairs) and the Compare slide (the dependency version-update diff).
|
|
290
|
+
// Returns an empty Map for sourcemaps and v0 stasis bundles — neither
|
|
291
|
+
// carries per-module version metadata. `keyOf(dir, info)` names each
|
|
292
|
+
// entry, the recorded name by default; Compare keys by install directory,
|
|
293
|
+
// as its sizes are, so an npm alias (`node_modules/alias` recording
|
|
294
|
+
// `actual-package`) joins its own sizes.
|
|
295
|
+
export function bundlePackageVersions(details, paths = null, keyOf = (_dir, info) => info.name) {
|
|
296
|
+
const versions = new Map()
|
|
297
|
+
if (details?.kind !== 'stasis' || !details.bundle?.modules) return versions
|
|
298
|
+
const packageDirs = paths === null ? null : bundlePackageDirs(details)
|
|
299
|
+
const selectedDirs = paths === null ? null : new Set([...paths].map(path => packageDirs?.get(path)))
|
|
300
|
+
for (const [dir, info] of details.bundle.modules) {
|
|
301
|
+
if (selectedDirs && !selectedDirs.has(dir)) continue
|
|
302
|
+
if (!dir.includes('node_modules')) continue
|
|
303
|
+
if (!info?.name || typeof info.version !== 'string' || !info.version) continue
|
|
304
|
+
const key = keyOf(dir, info)
|
|
305
|
+
let set = versions.get(key)
|
|
306
|
+
if (!set) { set = new Set(); versions.set(key, set) }
|
|
307
|
+
set.add(info.version)
|
|
308
|
+
}
|
|
309
|
+
return versions
|
|
310
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { langForPath } from './code-language.js'
|
|
2
|
+
|
|
3
|
+
const BUNDLE_LANGUAGE_LABELS = Object.freeze({
|
|
4
|
+
javascript: 'JavaScript', jsx: 'JSX', typescript: 'TypeScript', tsx: 'TSX',
|
|
5
|
+
json: 'JSON', css: 'CSS', markup: 'HTML', yaml: 'YAML', bash: 'Shell',
|
|
6
|
+
markdown: 'Markdown', solidity: 'Solidity', php: 'PHP', rust: 'Rust',
|
|
7
|
+
ruby: 'Ruby', java: 'Java', cpp: 'C++', c: 'C', objectivec: 'Objective-C',
|
|
8
|
+
python: 'Python', go: 'Go', kotlin: 'Kotlin', swift: 'Swift', dart: 'Dart',
|
|
9
|
+
sql: 'SQL', lua: 'Lua', csharp: 'C#', scala: 'Scala', vue: 'Vue', svelte: 'Svelte',
|
|
10
|
+
})
|
|
11
|
+
const BUNDLE_EXTENSION_LANGUAGES = Object.freeze({
|
|
12
|
+
py: 'python', pyw: 'python', go: 'go', kt: 'kotlin', kts: 'kotlin', swift: 'swift',
|
|
13
|
+
dart: 'dart', sql: 'sql', lua: 'lua', cs: 'csharp', scala: 'scala', sc: 'scala',
|
|
14
|
+
vue: 'vue', svelte: 'svelte',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function bundleLanguageOf(path) {
|
|
18
|
+
const basename = typeof path === 'string' ? path.slice(path.lastIndexOf('/') + 1) : ''
|
|
19
|
+
const dot = basename.lastIndexOf('.')
|
|
20
|
+
const ext = dot > 0 ? basename.slice(dot + 1).trim().toLowerCase() : ''
|
|
21
|
+
const lang = langForPath(path) ?? BUNDLE_EXTENSION_LANGUAGES[ext]
|
|
22
|
+
if (lang) return { key: lang, label: BUNDLE_LANGUAGE_LABELS[lang] ?? lang }
|
|
23
|
+
return ext ? { key: `extension:${ext}`, label: `.${ext}` } : { key: 'other', label: 'Other' }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function bundleCodeStats(lineCounts, fileSizes) {
|
|
27
|
+
const languages = new Map()
|
|
28
|
+
let bytes = 0, lines = 0
|
|
29
|
+
for (const [path, count] of lineCounts) {
|
|
30
|
+
const language = bundleLanguageOf(path)
|
|
31
|
+
const stat = languages.get(language.key) ?? { ...language, files: 0, lines: 0, bytes: 0 }
|
|
32
|
+
stat.files++
|
|
33
|
+
stat.lines += count
|
|
34
|
+
stat.bytes += fileSizes.get(path) ?? 0
|
|
35
|
+
languages.set(language.key, stat)
|
|
36
|
+
lines += count
|
|
37
|
+
bytes += fileSizes.get(path) ?? 0
|
|
38
|
+
}
|
|
39
|
+
return { files: lineCounts.size, lines, bytes, languages: [...languages.values()]
|
|
40
|
+
.toSorted((a, b) => b.lines - a.lines || a.label.localeCompare(b.label)) }
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const BUNDLE_TABS = new Set(['overview', 'graph', 'treemap', 'compare', 'advisories', 'issues', 'code', 'search', 'terminal'])
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const EXT_TO_LANG = {
|
|
2
|
+
__proto__: null,
|
|
3
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
|
4
|
+
jsx: 'jsx',
|
|
5
|
+
ts: 'typescript', mts: 'typescript', cts: 'typescript',
|
|
6
|
+
tsx: 'tsx',
|
|
7
|
+
json: 'json',
|
|
8
|
+
css: 'css',
|
|
9
|
+
html: 'markup', htm: 'markup', xml: 'markup', svg: 'markup',
|
|
10
|
+
yml: 'yaml', yaml: 'yaml',
|
|
11
|
+
sh: 'bash', bash: 'bash',
|
|
12
|
+
md: 'markdown', markdown: 'markdown',
|
|
13
|
+
sol: 'solidity',
|
|
14
|
+
php: 'php', phtml: 'php',
|
|
15
|
+
rs: 'rust',
|
|
16
|
+
rb: 'ruby',
|
|
17
|
+
py: 'python', pyw: 'python', pyi: 'python',
|
|
18
|
+
java: 'java',
|
|
19
|
+
// .h is claimed by C, C++ and Objective-C alike. The C++ grammar
|
|
20
|
+
// extends the C one, so it is the superset that colours a header
|
|
21
|
+
// from any of the three; the C grammar would leave a C++ header's
|
|
22
|
+
// class/template/namespace keywords plain.
|
|
23
|
+
cpp: 'cpp', cc: 'cpp', cxx: 'cpp', 'c++': 'cpp',
|
|
24
|
+
hpp: 'cpp', hh: 'cpp', hxx: 'cpp', 'h++': 'cpp', h: 'cpp',
|
|
25
|
+
c: 'c',
|
|
26
|
+
m: 'objectivec', mm: 'objectivec',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Stasis records loader formats as well as language names. Resolve those to
|
|
30
|
+
// the grammars we ship before consulting the filename.
|
|
31
|
+
const FORMAT_TO_LANG = {
|
|
32
|
+
__proto__: null,
|
|
33
|
+
module: 'javascript', commonjs: 'javascript',
|
|
34
|
+
'module-typescript': 'typescript', 'commonjs-typescript': 'typescript',
|
|
35
|
+
json: 'json', solidity: 'solidity', php: 'php', shell: 'bash', rust: 'rust',
|
|
36
|
+
java: 'java', objc: 'objectivec', objcpp: 'objectivec',
|
|
37
|
+
c: 'c', 'c-header': 'c', cpp: 'cpp', 'cpp-header': 'cpp',
|
|
38
|
+
ruby: 'ruby', podspec: 'ruby', podfile: 'ruby', fastlane: 'ruby',
|
|
39
|
+
'podfile-lock': 'yaml', xml: 'markup',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Whether a text is a JSON object by its ends: `{` first and `}` last,
|
|
43
|
+
// whitespace aside. Read from both ends, never the whole of a large file.
|
|
44
|
+
function objectText(content) {
|
|
45
|
+
if (typeof content !== 'string') return false
|
|
46
|
+
let end = content.length - 1, start = 0
|
|
47
|
+
while (start < end && /\s/u.test(content[start])) start++
|
|
48
|
+
while (end > start && /\s/u.test(content[end])) end--
|
|
49
|
+
return start < end && content[start] === '{' && content[end] === '}'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// `content`, where the caller has it, settles what the name alone cannot: a
|
|
53
|
+
// `.map` file is a source map, JSON, when its text is an object.
|
|
54
|
+
export function langForPath(path, format, content) {
|
|
55
|
+
if (typeof format === 'string' && FORMAT_TO_LANG[format]) return FORMAT_TO_LANG[format]
|
|
56
|
+
if (typeof path !== 'string') return null
|
|
57
|
+
// Only the filename can contribute an extension. A dotted directory such
|
|
58
|
+
// as `.abc/edf` is a path segment, not an extension on `edf`.
|
|
59
|
+
const basename = path.slice(path.lastIndexOf('/') + 1)
|
|
60
|
+
const dot = basename.lastIndexOf('.')
|
|
61
|
+
if (dot <= 0) return null
|
|
62
|
+
const ext = basename.slice(dot + 1).toLowerCase()
|
|
63
|
+
if (ext === 'map') return objectText(content) ? 'json' : null
|
|
64
|
+
return EXT_TO_LANG[ext] ?? null
|
|
65
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Shared starting catalogue for the UI and managed server until scan-service
|
|
2
|
+
// discovery supplies supported models and effort limits. Use undated aliases.
|
|
3
|
+
export function defaultScanModels(provider: string | null = null) {
|
|
4
|
+
const ids = [
|
|
5
|
+
'anthropic/claude-opus-5.5',
|
|
6
|
+
'anthropic/claude-fable-5.1',
|
|
7
|
+
'anthropic/claude-sonnet-5.5',
|
|
8
|
+
'anthropic/claude-haiku-5.5',
|
|
9
|
+
'openai/gpt-6-astra',
|
|
10
|
+
'openai/gpt-6-astra-pro',
|
|
11
|
+
'openai/gpt-6.1-sol',
|
|
12
|
+
'openai/gpt-6.1-sol-pro',
|
|
13
|
+
'openai/gpt-6-luna',
|
|
14
|
+
'openai/gpt-6-luna-pro',
|
|
15
|
+
'moonshotai/kimi-k3',
|
|
16
|
+
'google/gemini-3.8-flash',
|
|
17
|
+
'google/gemma-4-31b-it',
|
|
18
|
+
'x-ai/grok-4.7',
|
|
19
|
+
'z-ai/glm-5.3',
|
|
20
|
+
'qwen/qwen3.8-max',
|
|
21
|
+
'deepseek/deepseek-v4.1-flash',
|
|
22
|
+
'deepseek/deepseek-v4-pro',
|
|
23
|
+
'mistralai/mistral-large-4-0',
|
|
24
|
+
]
|
|
25
|
+
// OpenRouter routes all listed providers. Direct-provider credentials only
|
|
26
|
+
// expose that provider's models; no selection leaves the catalogue intact.
|
|
27
|
+
const prefix = provider === 'moonshot' ? 'moonshotai' : provider
|
|
28
|
+
const models = ids.filter(id => !['anthropic', 'openai', 'moonshot'].includes(provider ?? '') || id.startsWith(`${prefix}/`))
|
|
29
|
+
.map(id => ({ id, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] }))
|
|
30
|
+
return { models, defaultModel: models[0]!.id }
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ReviewVerdict = 'accept' | 'accept-with-nits' | 'request-changes' | 'reject'
|
|
2
|
+
export interface ReviewSummary {
|
|
3
|
+
coverage: 'full' | 'partial' | null
|
|
4
|
+
checkedFiles: number | null
|
|
5
|
+
totalFiles: number | null
|
|
6
|
+
verdictLimit: ReviewVerdict
|
|
7
|
+
}
|
|
8
|
+
export const REVIEW_VERDICTS: Record<ReviewVerdict, string>
|
|
9
|
+
export function isReviewableFile(before: unknown, after: unknown): boolean
|
|
10
|
+
export function summarizeReview(entries: { file: string; status: ReviewVerdict | null }[], files: string[] | null, nonTextFiles?: string[] | null): ReviewSummary
|
|
11
|
+
export function allowedReviewVerdicts(entries: { file: string; status: ReviewVerdict | null }[], limit?: ReviewVerdict): ReviewVerdict[]
|
|
12
|
+
interface ReviewNpmVersion { version: string; private?: boolean; manifest?: Record<string, unknown> }
|
|
13
|
+
export function npmTakenDown(entry: { npm: ReviewNpmVersion }): boolean
|
|
14
|
+
export function npmReviewVerdictLimit(npm: ReviewNpmVersion | null | undefined,
|
|
15
|
+
advisoryList?: { versions: string[]; advisories: { id?: string; title?: string; severity?: string; cwe?: string[]; affected: number[] }[] } | null,
|
|
16
|
+
socket?: { alerts: { type: string }[] } | null, risk?: { findings: { id: string }[] } | null): ReviewVerdict
|
|
17
|
+
export function needsPackageJsonReview(entries: { file: string; status: ReviewVerdict | null }[], files: string[], nonTextFiles?: string[] | null): boolean
|
|
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
|