@preventive/triage 1.0.0-alpha.43 → 1.0.0-alpha.44
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/managed/routes.js +3 -3
- package/common/server-info.ts +8 -0
- package/common/workspace-app.js +14 -1
- package/out/chunk-2ABLKCOY.js +46 -0
- package/out/chunk-5LSXOLUS.js +1 -0
- package/out/chunk-5RCM2J63.js +1 -0
- package/out/chunk-7B6HB4D7.js +1 -0
- package/out/{chunk-4F6JSF57.js → chunk-AJ4GTATZ.js} +1 -1
- package/out/chunk-CH4UN6WK.js +225 -0
- package/out/chunk-EDKLBQQN.js +17 -0
- package/out/{chunk-UXNUAIIC.js → chunk-F47DXCEV.js} +1 -1
- package/out/chunk-FK3DZEKL.js +1 -0
- package/out/chunk-G6AJWENO.js +1 -0
- package/out/chunk-GZWM7UQM.js +2 -0
- package/out/chunk-JCIIBLKR.js +1 -0
- package/out/chunk-LPCTJYNA.js +7 -0
- package/out/{chunk-LAAB5SZT.js → chunk-O4RJY5TP.js} +1 -1
- package/out/chunk-OD4LPN3H.js +1 -0
- package/out/{chunk-A7M4GV7M.js → chunk-QWDWTBM7.js} +1 -1
- package/out/chunk-QYWKC557.js +1 -0
- package/out/chunk-R75XOKUP.js +1 -0
- package/out/{chunk-RSHMVYNS.js → chunk-SWTLADW5.js} +1 -1
- package/out/chunk-T7OKH4VW.js +1 -0
- package/out/{chunk-ZMQQCGF6.js → chunk-UTR2LVME.js} +1 -1
- package/out/chunk-ZESLQCTF.js +6 -0
- package/out/client-managed-import.js +1 -1
- package/out/client-managed.js +20 -20
- package/out/client-sync.js +1 -1
- package/out/graph.js +1 -1
- package/out/index.html +14 -10
- package/out/terminal.js +1 -1
- package/out/view.css +1 -1
- package/out/view.js +48 -48
- package/package.json +7 -3
- package/risk/npm/assess.js +12 -7
- package/risk/npm/dependencies.js +11 -0
- package/server-managed/comparison.ts +41 -0
- package/server-managed/http.ts +99 -31
- package/server-managed/locked-packages.ts +11 -0
- package/server-managed/pretty-print.ts +14 -9
- package/server-managed/public-workspace.ts +26 -8
- package/server-managed/repository-risk.ts +11 -9
- package/server-managed/team-app.ts +38 -14
- package/out/chunk-7RU5EDRO.js +0 -226
- package/out/chunk-CLPKNNC7.js +0 -51
- package/out/chunk-NWZ4PVPN.js +0 -1
- package/out/chunk-OSIMGFUF.js +0 -1
- package/out/chunk-RFZQQXSG.js +0 -17
- package/out/chunk-VQJCXGIY.js +0 -7
- package/out/chunk-WZDTZE64.js +0 -1
- package/out/chunk-XOCAPDCM.js +0 -1
- package/out/chunk-YVZH5SHP.js +0 -1
- package/out/chunk-ZBBOBQ43.js +0 -1
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
// loads. Results are keyed by the catalog's own content keys: an unchanged
|
|
5
5
|
// catalog reuses them, and every change that can move a result also changes
|
|
6
6
|
// the catalog revision that clients refresh on.
|
|
7
|
+
//
|
|
8
|
+
// The same read names the reports a restricted reader would find nothing in:
|
|
9
|
+
// under -deps or -security the catalog leaves those out of the team's list.
|
|
10
|
+
// Readers with both permissions keep every report, empty or not.
|
|
11
|
+
import { reportEntries } from '@preventive/report'
|
|
7
12
|
import { type TeamAppMetadata, managedTeamAppMetadata } from '../common/managed/team-app.js'
|
|
8
13
|
import type { BlobStore } from './blob-store.ts'
|
|
9
14
|
import type { ManagedDb, TeamReportAccessSnapshot, UserTeam } from './db.ts'
|
|
@@ -11,12 +16,14 @@ import type { WorkspaceShareSnapshot } from './workspace-shares.ts'
|
|
|
11
16
|
import { TeamReportsError, loadTeamReportsResponse } from './team-reports.ts'
|
|
12
17
|
|
|
13
18
|
export type { TeamAppMetadata }
|
|
19
|
+
export interface TeamWorkspaceSummary { app: TeamAppMetadata; empty: readonly string[] }
|
|
20
|
+
const NO_APP: TeamWorkspaceSummary = { app: { appMode: false }, empty: [] }
|
|
14
21
|
|
|
15
22
|
const CACHE_MAX_ENTRIES = 4096
|
|
16
23
|
// Each workspace build already reads four reports at a time.
|
|
17
24
|
const CLASSIFY_CONCURRENCY = 2
|
|
18
|
-
const caches = new WeakMap<ManagedDb, Map<string,
|
|
19
|
-
const pendingJobs = new WeakMap<ManagedDb, Map<string, Promise<
|
|
25
|
+
const caches = new WeakMap<ManagedDb, Map<string, TeamWorkspaceSummary>>()
|
|
26
|
+
const pendingJobs = new WeakMap<ManagedDb, Map<string, Promise<TeamWorkspaceSummary | null>>>()
|
|
20
27
|
|
|
21
28
|
// Published reports with their content keys, and the team's own key (access,
|
|
22
29
|
// scopes and links). Equal keys describe the same filtered workspace, whoever
|
|
@@ -29,28 +36,36 @@ function published(team: UserTeam) {
|
|
|
29
36
|
return team.reports.filter(report => report.visible !== false)
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
function remember(db: ManagedDb, key: string,
|
|
39
|
+
function remember(db: ManagedDb, key: string, summary: TeamWorkspaceSummary): TeamWorkspaceSummary {
|
|
33
40
|
let cache = caches.get(db)
|
|
34
41
|
if (!cache) { cache = new Map(); caches.set(db, cache) }
|
|
35
42
|
cache.delete(key)
|
|
36
|
-
cache.set(key,
|
|
43
|
+
cache.set(key, summary)
|
|
37
44
|
for (const oldest of cache.keys()) {
|
|
38
45
|
if (cache.size <= CACHE_MAX_ENTRIES) break
|
|
39
46
|
cache.delete(oldest)
|
|
40
47
|
}
|
|
41
|
-
return
|
|
48
|
+
return summary
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A links document counts by its links, every other report by its findings.
|
|
52
|
+
function hasContent(data: unknown): boolean {
|
|
53
|
+
const record = data as { source?: unknown; links?: unknown } | null
|
|
54
|
+
if (record?.source === 'links' && Array.isArray(record.links)) return record.links.length > 0
|
|
55
|
+
return (reportEntries(data) ?? []).some(entry => !Array.isArray(entry) || entry.length > 0)
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
// Null when the team cannot be classified right now: its catalog went stale,
|
|
45
59
|
// the access snapshot is gone, or storage failed. The sidebar then keeps the
|
|
46
|
-
// team expanded, and a later catalog asks again.
|
|
47
|
-
// workspaces never have an App view, so they are
|
|
60
|
+
// team expanded and lists every report, and a later catalog asks again.
|
|
61
|
+
// Oversized and unreadable workspaces never have an App view, so they are
|
|
62
|
+
// remembered as such, hiding nothing.
|
|
48
63
|
async function classify(db: ManagedDb, store: BlobStore, key: string, team: UserTeam,
|
|
49
|
-
snapshot: () => Promise<TeamReportAccessSnapshot | null>): Promise<
|
|
64
|
+
snapshot: () => Promise<TeamReportAccessSnapshot | null>): Promise<TeamWorkspaceSummary | null> {
|
|
50
65
|
const cached = caches.get(db)?.get(key)
|
|
51
66
|
if (cached) return remember(db, key, cached)
|
|
52
67
|
const ids = new Set(published(team).map(report => report.id))
|
|
53
|
-
if (ids.size === 0) return remember(db, key,
|
|
68
|
+
if (ids.size === 0) return remember(db, key, NO_APP)
|
|
54
69
|
let jobs = pendingJobs.get(db)
|
|
55
70
|
if (!jobs) { jobs = new Map(); pendingJobs.set(db, jobs) }
|
|
56
71
|
const existing = jobs.get(key)
|
|
@@ -60,11 +75,13 @@ async function classify(db: ManagedDb, store: BlobStore, key: string, team: User
|
|
|
60
75
|
// A catalog read before a concurrent publication names another report set.
|
|
61
76
|
if (!access?.teamId || access.reports.length !== ids.size || access.reports.some(report => !ids.has(report.id))) return null
|
|
62
77
|
const body = JSON.parse((await loadTeamReportsResponse(db, store, access)).toString('utf8')) as {
|
|
63
|
-
reports: { filename: string; data: unknown }[]; links?: string[][]
|
|
78
|
+
reports: { id: string; filename: string; data: unknown }[]; links?: string[][]
|
|
64
79
|
}
|
|
65
|
-
|
|
80
|
+
const restricted = access.reports.some(report => !report.permissions.dependencies || !report.permissions.security)
|
|
81
|
+
const empty = restricted ? body.reports.filter(report => !hasContent(report.data)).map(report => report.id) : []
|
|
82
|
+
return remember(db, key, { app: managedTeamAppMetadata(Object.assign(body.reports, { links: body.links ?? [] })), empty })
|
|
66
83
|
})().catch((error: unknown) => {
|
|
67
|
-
if (error instanceof TeamReportsError && [413, 422].includes(error.status)) return remember(db, key,
|
|
84
|
+
if (error instanceof TeamReportsError && [413, 422].includes(error.status)) return remember(db, key, NO_APP)
|
|
68
85
|
if (!(error instanceof TeamReportsError)) console.warn('managed: team App classification failed:', error)
|
|
69
86
|
return null
|
|
70
87
|
})
|
|
@@ -72,10 +89,17 @@ async function classify(db: ManagedDb, store: BlobStore, key: string, team: User
|
|
|
72
89
|
try { return await job } finally { jobs.delete(key) }
|
|
73
90
|
}
|
|
74
91
|
|
|
92
|
+
// The catalog's view of a classified team: its App classification, and its
|
|
93
|
+
// reports without the ones its restricted reader would find empty.
|
|
94
|
+
export function withWorkspaceSummary<T extends { reports: readonly { id: string }[] }>(team: T, summary: TeamWorkspaceSummary | null | undefined) {
|
|
95
|
+
const empty = new Set(summary?.empty)
|
|
96
|
+
return { ...team, app: summary?.app ?? null, reports: team.reports.filter(report => !empty.has(report.id)) }
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
// Classify a user's catalog, keyed by teamAppKey. `known` carries results from
|
|
76
100
|
// an earlier read of the same request, so only changed teams are classified.
|
|
77
101
|
export async function teamAppStates(db: ManagedDb, store: BlobStore, sessionId: string, teams: readonly UserTeam[],
|
|
78
|
-
known: ReadonlyMap<string,
|
|
102
|
+
known: ReadonlyMap<string, TeamWorkspaceSummary | null> = new Map()): Promise<Map<string, TeamWorkspaceSummary | null>> {
|
|
79
103
|
const states = new Map(known)
|
|
80
104
|
const missing = [...new Map(teams.map(team => [teamAppKey(team), team])).entries()].filter(([key]) => !states.has(key))
|
|
81
105
|
let next = 0
|
|
@@ -89,6 +113,6 @@ export async function teamAppStates(db: ManagedDb, store: BlobStore, sessionId:
|
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
// A public link's workspace is its own access snapshot.
|
|
92
|
-
export function sharedTeamApp(db: ManagedDb, store: BlobStore, snapshot: WorkspaceShareSnapshot): Promise<
|
|
116
|
+
export function sharedTeamApp(db: ManagedDb, store: BlobStore, snapshot: WorkspaceShareSnapshot): Promise<TeamWorkspaceSummary | null> {
|
|
93
117
|
return classify(db, store, JSON.stringify(['share', teamAppKey(snapshot.team)]), snapshot.team, () => Promise.resolve(snapshot))
|
|
94
118
|
}
|