@preventive/triage 1.0.0-alpha.33 → 1.0.0-alpha.34
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/client/linked-findings.js +5 -4
- package/common/bundle-metadata.js +4 -3
- package/common/bundle-source-links.d.ts +2 -1
- package/common/bundle-source-links.js +133 -6
- package/common/bundle-sources.d.ts +2 -0
- package/common/bundle-sources.js +2 -4
- package/common/managed/repository-alias.ts +65 -0
- package/common/managed/routes.js +20 -1
- package/common/managed/triage.ts +2 -2
- package/out/client-managed-import.js +51 -46
- package/out/client-managed.js +252 -72
- package/out/client-sync.js +14 -14
- package/out/graph.js +11 -11
- package/out/index.html +1 -0
- package/out/terminal.js +169 -169
- package/out/view.css +1 -1
- package/out/view.js +410 -202
- package/package.json +22 -17
- package/server-managed/activity.ts +15 -1
- package/server-managed/bundle-advisories.ts +8 -3
- package/server-managed/bundle-advisory-inventory.ts +27 -25
- package/server-managed/bundle-build-worker.js +45 -9
- package/server-managed/bundle-build.ts +35 -11
- package/server-managed/bundle-cache.ts +1 -1
- package/server-managed/bundle.ts +17 -1
- package/server-managed/config.ts +5 -0
- package/server-managed/db-methods.ts +168 -49
- package/server-managed/db-neon.ts +59 -4
- package/server-managed/db-schema.ts +12 -3
- package/server-managed/db.ts +9 -1
- package/server-managed/github-issue-links.ts +127 -0
- package/server-managed/github-issues.ts +1 -1
- package/server-managed/http-response.ts +7 -1
- package/server-managed/http.ts +248 -80
- package/server-managed/index.ts +4 -0
- package/server-managed/link-reports.ts +158 -0
- package/server-managed/managed-issues.ts +31 -3
- package/server-managed/management.ts +17 -4
- package/server-managed/public-workspace.ts +5 -2
- package/server-managed/report-sources.ts +6 -5
- package/server-managed/repository-aliases.ts +91 -0
- package/server-managed/revisions.ts +2 -2
- package/server-managed/sql.ts +3 -2
- package/server-managed/storage.ts +17 -0
- package/server-managed/team-feed.ts +3 -3
- package/server-managed/team-reports.ts +24 -15
- package/server-managed/upstream-cache.ts +73 -0
- package/server-managed/workspace-shares.ts +16 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Team content is filtered as one workspace, with separate report envelopes.
|
|
2
2
|
// Bounded snapshots retain filtered content and visibility for repeated reads.
|
|
3
|
+
import { mergeLinkGroups } from './link-reports.ts'
|
|
3
4
|
import { Buffer } from 'node:buffer'
|
|
4
5
|
import { backfillFindingIds, reportEntries, stampSecurityGroups } from '@preventive/report'
|
|
5
6
|
import { managedFindingSourcePaths, readManagedReport } from '../common/managed/report-content.ts'
|
|
@@ -7,6 +8,7 @@ import { filterReportData, projectFinding } from '../common/managed/report-filte
|
|
|
7
8
|
import type { BlobStore } from './blob-store.ts'
|
|
8
9
|
import type { ManagedDb, TeamReportAccessSnapshot } from './db.ts'
|
|
9
10
|
import { triageWireEntry } from './triage-response.ts'
|
|
11
|
+
import { visibleManagedIssues } from './managed-issues.ts'
|
|
10
12
|
import { MAX_REPORT_QUERY_BYTES, MAX_REPORT_QUERY_COUNT } from './report-query.ts'
|
|
11
13
|
|
|
12
14
|
type Finding = Record<string, unknown>
|
|
@@ -54,16 +56,17 @@ export class TeamReportsError extends Error {
|
|
|
54
56
|
constructor(status: number, error: string) { super(error); this.status = status }
|
|
55
57
|
}
|
|
56
58
|
export function teamSnapshotKey(snapshot: TeamReportAccessSnapshot): string {
|
|
57
|
-
return JSON.stringify([snapshot.user.id, snapshot.user.role, snapshot.teamId, snapshot.reports, snapshot.repositories])
|
|
59
|
+
return JSON.stringify([snapshot.user.id, snapshot.user.role, snapshot.teamId, snapshot.hiddenReportId ?? null, snapshot.reports, snapshot.repositories, snapshot.linkRevision])
|
|
58
60
|
}
|
|
59
|
-
export async function teamSnapshot(db: ManagedDb, sessionId: string, teamId: string): Promise<TeamReportAccessSnapshot> {
|
|
60
|
-
const snapshot = await db.getTeamReportAccessSnapshot(sessionId, Date.now(), teamId)
|
|
61
|
+
export async function teamSnapshot(db: ManagedDb, sessionId: string, teamId: string, reportId: string | null = null): Promise<TeamReportAccessSnapshot> {
|
|
62
|
+
const snapshot = await db.getTeamReportAccessSnapshot(sessionId, Date.now(), teamId, reportId)
|
|
61
63
|
if (!snapshot) throw new TeamReportsError(401, 'unauthenticated')
|
|
62
64
|
if (!snapshot.teamId) throw new TeamReportsError(404, 'no-team')
|
|
65
|
+
if (reportId !== null && !snapshot.reports.some(report => report.id === reportId)) throw new TeamReportsError(404, 'no-report')
|
|
63
66
|
return snapshot
|
|
64
67
|
}
|
|
65
68
|
export async function recheckTeam(db: ManagedDb, sessionId: string, snapshot: TeamReportAccessSnapshot): Promise<void> {
|
|
66
|
-
if (teamSnapshotKey(await teamSnapshot(db, sessionId, snapshot.teamId
|
|
69
|
+
if (teamSnapshotKey(await teamSnapshot(db, sessionId, snapshot.teamId!, snapshot.reportId)) !== teamSnapshotKey(snapshot)) throw new TeamReportsError(404, 'workspace-changed')
|
|
67
70
|
}
|
|
68
71
|
function groupsOf(data: unknown): Finding[][] {
|
|
69
72
|
return (reportEntries(data) ?? []).map(entry => (Array.isArray(entry) ? entry : [entry]).filter(
|
|
@@ -100,6 +103,7 @@ async function buildTeamWorkspace(db: ManagedDb, store: BlobStore, snapshot: Tea
|
|
|
100
103
|
}
|
|
101
104
|
}))
|
|
102
105
|
for (const result of results) if (result.status === 'rejected') throw result.reason
|
|
106
|
+
const globalLinks = snapshot.linkRevision ? await db.getEnabledLinkGroups(snapshot.linkRevision) : []
|
|
103
107
|
const edges = new Map<string, Set<string>>()
|
|
104
108
|
const connect = (a: string, b: string) => {
|
|
105
109
|
if (!edges.has(a)) edges.set(a, new Set())
|
|
@@ -107,10 +111,8 @@ async function buildTeamWorkspace(db: ManagedDb, store: BlobStore, snapshot: Tea
|
|
|
107
111
|
}
|
|
108
112
|
// A star is enough for transitive security propagation, without a quadratic
|
|
109
113
|
// expansion for large links. Unknown IDs can connect known findings.
|
|
110
|
-
for (const
|
|
111
|
-
for (const
|
|
112
|
-
for (const id of ids.slice(1)) { connect(ids[0]!, id); connect(id, ids[0]!) }
|
|
113
|
-
}
|
|
114
|
+
for (const ids of globalLinks.concat(reports.flatMap(report => linksOf(report.data) ?? []))) {
|
|
115
|
+
for (const id of ids.slice(1)) { connect(ids[0]!, id); connect(id, ids[0]!) }
|
|
114
116
|
}
|
|
115
117
|
const groups = reports.flatMap(report => groupsOf(report.data).map(group => group.map(f => projectFinding(f, report.data as Finding)!)))
|
|
116
118
|
stampSecurityGroups(groups, { linkedIds: id => edges.get(id) ?? [] })
|
|
@@ -130,7 +132,9 @@ async function buildTeamWorkspace(db: ManagedDb, store: BlobStore, snapshot: Tea
|
|
|
130
132
|
}
|
|
131
133
|
for (const report of reports) {
|
|
132
134
|
const links = linksOf(report.data)
|
|
133
|
-
|
|
135
|
+
// Hidden previews are independently authorized; preserve a hidden links
|
|
136
|
+
// document even though its referenced findings are outside this response.
|
|
137
|
+
if (links && !snapshot.hiddenReportId) report.data = { source: 'links', findings: [], links: links.map(ids => [...new Set(ids.filter(id => allIds.has(id)))]).filter(ids => ids.length >= 2) }
|
|
134
138
|
}
|
|
135
139
|
let outputBytes = 14
|
|
136
140
|
const parts = [Buffer.from('{"reports":[')]
|
|
@@ -141,7 +145,10 @@ async function buildTeamWorkspace(db: ManagedDb, store: BlobStore, snapshot: Tea
|
|
|
141
145
|
if (index) parts.push(Buffer.from(','))
|
|
142
146
|
parts.push(bytes)
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
const links = mergeLinkGroups(globalLinks, allIds)
|
|
149
|
+
const suffix = Buffer.from(links.length > 0 ? `],"links":${JSON.stringify(links)}}` : ']}')
|
|
150
|
+
if (outputBytes + suffix.length > MAX_REPORT_QUERY_BYTES) throw new TeamReportsError(413, 'batch-too-large')
|
|
151
|
+
parts.push(suffix)
|
|
145
152
|
const body = Buffer.concat(parts)
|
|
146
153
|
retainVisibility(db, teamSnapshotKey(snapshot), visible)
|
|
147
154
|
retainReports(db, teamSnapshotKey(snapshot), body)
|
|
@@ -190,13 +197,13 @@ export async function teamReportVisibility(db: ManagedDb, store: BlobStore, snap
|
|
|
190
197
|
return visible.get(reportId)!
|
|
191
198
|
}
|
|
192
199
|
export async function teamFindingIds(db: ManagedDb, store: BlobStore, sessionId: string, teamId: string, reportId: string): Promise<Set<string>> {
|
|
193
|
-
const snapshot = await teamSnapshot(db, sessionId, teamId)
|
|
200
|
+
const snapshot = await teamSnapshot(db, sessionId, teamId, reportId)
|
|
194
201
|
const visible = await teamReportVisibility(db, store, snapshot, reportId)
|
|
195
202
|
await recheckTeam(db, sessionId, snapshot)
|
|
196
203
|
return visible.ids
|
|
197
204
|
}
|
|
198
205
|
export async function teamSourcePaths(db: ManagedDb, store: BlobStore, sessionId: string, teamId: string, reportId: string): Promise<Set<string>> {
|
|
199
|
-
const snapshot = await teamSnapshot(db, sessionId, teamId)
|
|
206
|
+
const snapshot = await teamSnapshot(db, sessionId, teamId, reportId)
|
|
200
207
|
const visible = await teamReportVisibility(db, store, snapshot, reportId)
|
|
201
208
|
await recheckTeam(db, sessionId, snapshot)
|
|
202
209
|
return visible.sourcePaths
|
|
@@ -211,9 +218,11 @@ export async function loadTeamAnnotations(db: ManagedDb, store: BlobStore, snaps
|
|
|
211
218
|
: new Map([[reportId, await teamReportVisibility(db, store, snapshot, reportId)]])
|
|
212
219
|
const ids = new Set<string>()
|
|
213
220
|
for (const report of visible.values()) for (const id of report.ids) ids.add(id)
|
|
214
|
-
const { triage, comments } = await db.getAnnotations([...ids])
|
|
221
|
+
const { triage, comments, issues: savedIssues } = await db.getAnnotations([...ids])
|
|
222
|
+
const issues = Object.fromEntries(visibleManagedIssues(savedIssues, snapshot)
|
|
223
|
+
.map(issue => [issue.findingId, { url: issue.issueUrl!, autoFix: issue.autoFixUrl }]))
|
|
215
224
|
const entries = Object.fromEntries(triage.map(row => [row.findingId, triageWireEntry(row)]))
|
|
216
|
-
const annotated = new Set([...triage.map(row => row.findingId), ...comments.map(comment => comment.findingId)])
|
|
225
|
+
const annotated = new Set([...triage.map(row => row.findingId), ...comments.map(comment => comment.findingId), ...Object.keys(issues)])
|
|
217
226
|
const reports = Object.fromEntries([...visible].map(([id, report]) => [id, [...report.ids].filter(finding => annotated.has(finding))]))
|
|
218
|
-
return { reports, entries, comments }
|
|
227
|
+
return { reports, entries, comments, issues }
|
|
219
228
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CacheStore } from '@preventive/upstream/advisories.js'
|
|
2
|
+
import type { ManagedSql } from './sql.ts'
|
|
3
|
+
|
|
4
|
+
// Records @preventive/upstream would otherwise keep in its disk cache, shared
|
|
5
|
+
// by every instance: each repository's published advisory listing, written by
|
|
6
|
+
// dependency audits that ask repositories, keyed `github/advisories/owner/name`.
|
|
7
|
+
// Upstream stamps, validates and expires each value (90 minutes for a listing);
|
|
8
|
+
// a refresh replaces the row, and there is one row per repository, so rows are
|
|
9
|
+
// not evicted. Only public repositories publish advisories: rows are shared
|
|
10
|
+
// across viewers.
|
|
11
|
+
export const UPSTREAM_CACHE_SCHEMA = `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS managed_upstream_cache (
|
|
13
|
+
cache_key TEXT PRIMARY KEY,
|
|
14
|
+
value TEXT NOT NULL,
|
|
15
|
+
cached_at INTEGER NOT NULL
|
|
16
|
+
) STRICT;
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
export interface UpstreamCacheStore {
|
|
20
|
+
getUpstreamCacheEntry(key: string): Promise<string | null>
|
|
21
|
+
setUpstreamCacheEntry(key: string, value: string, cachedAt: number): Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function upstreamCacheMethods(db: ManagedSql): UpstreamCacheStore {
|
|
25
|
+
const get = db.prepare('SELECT value FROM managed_upstream_cache WHERE cache_key = ?')
|
|
26
|
+
// A slower concurrent audit cannot replace a newer listing with an older one.
|
|
27
|
+
const set = db.prepare(`INSERT INTO managed_upstream_cache (cache_key, value, cached_at) VALUES (?, ?, ?)
|
|
28
|
+
ON CONFLICT (cache_key) DO UPDATE SET value = excluded.value, cached_at = excluded.cached_at
|
|
29
|
+
WHERE managed_upstream_cache.cached_at <= excluded.cached_at`)
|
|
30
|
+
return {
|
|
31
|
+
async getUpstreamCacheEntry(key) {
|
|
32
|
+
const row = await get.get(key) as { value: string } | undefined
|
|
33
|
+
return row?.value ?? null
|
|
34
|
+
},
|
|
35
|
+
async setUpstreamCacheEntry(key, value, cachedAt) {
|
|
36
|
+
await set.run(key, value, cachedAt)
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// An audit's `cache`: where the server has an upstream disk cache (off Vercel,
|
|
42
|
+
// set at startup), that keeps everything; else this store keeps listings.
|
|
43
|
+
export function auditCache(cacheDir: string | null | undefined, db: UpstreamCacheStore, signal: AbortSignal, debug = false): CacheStore | undefined {
|
|
44
|
+
return cacheDir ? undefined : upstreamCache(db, signal, debug)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Only listings are kept: upstream's other records (npm version documents,
|
|
48
|
+
// registries' repository lookups) read as misses and are not written.
|
|
49
|
+
const CACHED_TYPE = 'github/advisories'
|
|
50
|
+
|
|
51
|
+
// The cache is an optimization: a database failure is a miss, never a failed
|
|
52
|
+
// audit. Upstream keeps listing after an abandoned audit; once `signal` aborts,
|
|
53
|
+
// the request's database work is over and the store is left alone.
|
|
54
|
+
export function upstreamCache(db: UpstreamCacheStore, signal: AbortSignal, debug = false): CacheStore {
|
|
55
|
+
return {
|
|
56
|
+
async read(type, key) {
|
|
57
|
+
if (type !== CACHED_TYPE || signal.aborted) return null
|
|
58
|
+
try {
|
|
59
|
+
const value = await db.getUpstreamCacheEntry(`${type}/${key}`)
|
|
60
|
+
return value === null ? null : JSON.parse(value) as unknown
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (debug) console.warn('managed: upstream cache read failed:', error)
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
async write(type, key, value) {
|
|
67
|
+
if (type !== CACHED_TYPE || signal.aborted) return
|
|
68
|
+
try { await db.setUpstreamCacheEntry(`${type}/${key}`, JSON.stringify(value), Date.now()) } catch (error) {
|
|
69
|
+
if (debug) console.warn('managed: upstream cache write failed:', error)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { linkRevision } from './link-reports.ts'
|
|
1
2
|
import type { ManagedSql } from './sql.ts'
|
|
2
|
-
import type { ManagedBundle, TeamReportAccessSnapshot, UserTeam } from './db.ts'
|
|
3
|
+
import type { BundleBuildConditions, ManagedBundle, TeamReportAccessSnapshot, UserTeam } from './db.ts'
|
|
3
4
|
import { type TeamUserPermissions, parseTeamUserPermissions } from '../common/managed/permissions.ts'
|
|
4
5
|
|
|
5
6
|
export const WORKSPACE_SHARE_SCHEMA = `
|
|
@@ -47,7 +48,7 @@ export interface WorkspaceShareStore {
|
|
|
47
48
|
function shareQueries(db: ManagedSql) {
|
|
48
49
|
const manager = db.prepare(`SELECT 1 FROM managed_session s
|
|
49
50
|
JOIN managed_user u ON u.id = s.user_id
|
|
50
|
-
JOIN managed_team t ON t.id = ?
|
|
51
|
+
JOIN managed_team t ON t.id = ? AND t.hidden = 0
|
|
51
52
|
WHERE s.id = ? AND s.expires_at > ? AND (u.role = 'admin' OR (u.role = 'manage'
|
|
52
53
|
AND EXISTS (SELECT 1 FROM managed_team_user tu WHERE tu.user_id = u.id AND tu.team_id = t.id)))`)
|
|
53
54
|
const insert = db.prepare(`INSERT INTO managed_workspace_share (token_hash, team_id, created_by, created_at, dependencies, security)
|
|
@@ -62,12 +63,12 @@ function shareQueries(db: ManagedSql) {
|
|
|
62
63
|
WHERE s.id = ? AND s.expires_at > ? AND u.role IN ('manage', 'admin')`)
|
|
63
64
|
const all = db.prepare(`SELECT s.token_hash AS id, s.created_at AS createdAt, u.login AS createdBy, s.dependencies, s.security,
|
|
64
65
|
t.id AS teamId, t.slug AS teamSlug, t.name AS teamName FROM managed_workspace_share s
|
|
65
|
-
JOIN managed_user u ON u.id = s.created_by JOIN managed_team t ON t.id = s.team_id
|
|
66
|
+
JOIN managed_user u ON u.id = s.created_by JOIN managed_team t ON t.id = s.team_id AND t.hidden = 0
|
|
66
67
|
WHERE ? = 'admin' OR EXISTS (SELECT 1 FROM managed_team_user tu WHERE tu.user_id = ? AND tu.team_id = s.team_id)
|
|
67
68
|
ORDER BY t.name, t.id, s.created_at DESC, s.token_hash`)
|
|
68
69
|
const shareGrant = `FROM managed_workspace_share s
|
|
69
70
|
JOIN managed_user u ON u.id = s.created_by
|
|
70
|
-
JOIN managed_team t ON t.id = s.team_id
|
|
71
|
+
JOIN managed_team t ON t.id = s.team_id AND t.hidden = 0
|
|
71
72
|
WHERE s.token_hash = ? AND (u.role = 'admin' OR (u.role = 'manage'
|
|
72
73
|
AND EXISTS (SELECT 1 FROM managed_team_user tu WHERE tu.user_id = u.id AND tu.team_id = s.team_id)))`
|
|
73
74
|
const share = db.prepare(`SELECT t.id, t.slug, t.name, s.dependencies, s.security ${shareGrant}`)
|
|
@@ -88,10 +89,10 @@ function shareQueries(db: ManagedSql) {
|
|
|
88
89
|
// A raw bundle is visible only when its declared root is inside the team scope.
|
|
89
90
|
// Root bundles remain hidden from directory-only grants.
|
|
90
91
|
const bundles = db.prepare(`SELECT DISTINCT b.id, b.slug, b.integrity, b.filename, b.kind, b.byte_size AS byteSize,
|
|
91
|
-
b.uploaded_by AS uploadedBy, b.repo_id AS repoId, b.repo_directory AS repoDirectory, b.uploaded_at AS uploadedAt, sr.full_name AS repoFullName
|
|
92
|
+
b.uploaded_by AS uploadedBy, b.repo_id AS repoId, b.repo_directory AS repoDirectory, b.uploaded_at AS uploadedAt, b.provenance, b.build_conditions AS buildConditions, sr.full_name AS repoFullName
|
|
92
93
|
FROM managed_team_repo tr JOIN managed_bundle b ON b.repo_id = tr.repo_id
|
|
93
94
|
JOIN managed_selected_repo sr ON sr.repo_id = b.repo_id
|
|
94
|
-
WHERE tr.team_id = ? AND (tr.path = '' OR b.repo_directory = tr.path
|
|
95
|
+
WHERE tr.team_id = ? AND b.visible = 1 AND (tr.path = '' OR b.repo_directory = tr.path
|
|
95
96
|
OR substr(b.repo_directory, 1, length(tr.path) + 1) = tr.path || '/') ORDER BY b.id`)
|
|
96
97
|
return { manager, insert, remove, update, list, session, all, share, feed, repositories, reports, bundles }
|
|
97
98
|
}
|
|
@@ -141,21 +142,24 @@ export function workspaceShareMethods(db: ManagedSql): WorkspaceShareStore {
|
|
|
141
142
|
const { dependencies, security, ...team } = grant
|
|
142
143
|
const permissions = { dependencies: dependencies === 1, security: security === 1 }
|
|
143
144
|
const rows = await q.reports.all(team.id) as { id: string; slug: string; filename: string; analyzer: string | null; byteSize: number; sha256: string; directory: string; github: string }[]
|
|
144
|
-
const
|
|
145
|
+
const links = await linkRevision(db)
|
|
146
|
+
const bundleRows = (await q.bundles.all(team.id) as (Omit<ManagedBundle, 'buildConditions'> & { buildConditions: string | null; repoFullName: string })[])
|
|
147
|
+
.map(row => ({ ...row, buildConditions: row.buildConditions == null ? null : JSON.parse(row.buildConditions) as BundleBuildConditions }))
|
|
145
148
|
return {
|
|
146
149
|
user: { id: `share:${tokenHash}`, login: 'public', name: 'Public workspace', avatarUrl: null, role: 'view' },
|
|
147
150
|
teamId: team.id,
|
|
151
|
+
linkRevision: links,
|
|
148
152
|
permissions,
|
|
149
153
|
repositories: await q.repositories.all(team.id) as WorkspaceShareSnapshot['repositories'],
|
|
150
154
|
reports: rows.map(row => ({ id: row.id, filename: row.filename, byteSize: row.byteSize, sha256: row.sha256,
|
|
151
155
|
repo: { github: row.github, directory: row.directory }, permissions })),
|
|
152
|
-
team: { ...team, permissions,
|
|
153
|
-
reports: rows.map(row => ({ id: row.id, slug: row.slug, filename: row.filename, analyzer: row.analyzer, repoFullName: row.github, repoDirectory: row.directory,
|
|
154
|
-
cacheKey: JSON.stringify([row.sha256, row.github, row.directory, row.filename, permissions]) })),
|
|
156
|
+
team: { ...team, permissions, cacheKey: links,
|
|
157
|
+
reports: rows.map(row => ({ id: row.id, slug: row.slug, filename: row.filename, visible: true, analyzer: row.analyzer, repoFullName: row.github, repoDirectory: row.directory,
|
|
158
|
+
cacheKey: JSON.stringify([row.sha256, row.github, row.directory, row.filename, permissions, links]) })),
|
|
155
159
|
bundles: bundleRows.map(row => ({ id: row.id, slug: row.slug, integrity: row.integrity,
|
|
156
|
-
filename: row.filename, kind: row.kind, byteSize: row.byteSize, repoId: row.repoId!, repoDirectory: row.repoDirectory, repoFullName: row.repoFullName })),
|
|
160
|
+
filename: row.filename, visible: true, kind: row.kind, byteSize: row.byteSize, repoId: row.repoId!, repoDirectory: row.repoDirectory, repoFullName: row.repoFullName, provenance: row.provenance })),
|
|
157
161
|
},
|
|
158
|
-
bundles: bundleRows,
|
|
162
|
+
bundles: bundleRows.map(row => ({ ...row, visible: true })),
|
|
159
163
|
}
|
|
160
164
|
},
|
|
161
165
|
}
|