@preventive/triage 1.0.0-alpha.43 → 1.0.0-alpha.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/bundle-compare-diff.js +2 -167
- package/common/bundle-compare-versions.js +172 -0
- package/common/bundle-comparison.d.ts +8 -0
- package/common/bundle-comparison.js +62 -0
- package/common/bundle-sources.js +20 -1
- package/common/default-scan-models.ts +1 -1
- package/common/dependency-reviews.d.ts +0 -5
- package/common/dependency-reviews.js +0 -17
- package/common/managed/agent-tasks.ts +292 -0
- package/common/managed/github-agents.ts +92 -0
- package/common/managed/github-logins.ts +49 -0
- package/common/managed/roles.ts +7 -0
- package/common/managed/routes.js +91 -10
- package/common/managed/team-app.d.ts +5 -2
- package/common/managed/team-app.js +2 -2
- package/common/server-info.ts +11 -0
- package/common/workspace-app.js +19 -3
- package/out/brotli-fallback.js +3 -3
- package/out/chunk-2XUEZHIP.js +18 -18
- package/out/chunk-2ZDZWLFY.js +1 -0
- package/out/chunk-3PS4KWD2.js +10 -0
- package/out/chunk-5DHL5NG3.js +2 -0
- package/out/chunk-6HNXNIQY.js +52 -0
- package/out/chunk-7C7DWL7I.js +1 -0
- package/out/chunk-AFLZH7WG.js +1 -0
- package/out/chunk-BC2UCLFJ.js +1 -0
- package/out/chunk-C4Z6ZBXV.js +6 -6
- package/out/chunk-GDPCLSOS.js +2 -0
- package/out/chunk-GO7FT4OD.js +1 -0
- package/out/chunk-HMAJDVXN.js +1 -0
- package/out/chunk-I2MDGSQX.js +3 -0
- package/out/chunk-IYPY47MC.js +1 -0
- package/out/chunk-IZ7POWI3.js +18 -0
- package/out/chunk-JCIIBLKR.js +1 -0
- package/out/chunk-NPW52BM2.js +17 -0
- package/out/chunk-NRPC74AM.js +1 -1
- package/out/chunk-NV7ES4EW.js +1 -0
- package/out/chunk-OKRLLE5I.js +17 -0
- package/out/chunk-OOJM4CTU.js +1 -1
- package/out/chunk-PNHBZGDD.js +1 -1
- package/out/chunk-PTGDYLNS.js +36 -0
- package/out/chunk-PXK7EU4V.js +1 -0
- package/out/chunk-Q6MVYGYV.js +3 -0
- package/out/chunk-QB4CBOX2.js +1 -0
- package/out/chunk-QRD6GIG5.js +229 -0
- package/out/chunk-R3NZFXVR.js +1 -0
- package/out/chunk-R5O6FAW4.js +1 -1
- package/out/chunk-SXV2T5C4.js +80 -0
- package/out/chunk-TUII34K2.js +1 -1
- package/out/chunk-VFWMQ3LG.js +2 -0
- package/out/chunk-VH4XMZIW.js +2 -0
- package/out/chunk-WFYLMF6Z.js +10 -0
- package/out/chunk-WI7Z5A2T.js +6 -0
- package/out/chunk-WQ74ZBZF.js +7 -0
- package/out/chunk-XVBEOGOS.js +3 -0
- package/out/client-managed-import.js +3 -3
- package/out/client-managed.js +31 -35
- package/out/client-sync.js +11 -11
- package/out/compare.js +35 -0
- package/out/dialogs.js +51 -0
- package/out/github-issue-authorized.html +1 -1
- package/out/github-issue-authorized.js +1 -1
- package/out/graph.js +17 -17
- package/out/index.html +31 -31
- package/out/prism.js +14 -14
- package/out/scan.js +2 -0
- package/out/terminal.js +294 -293
- package/out/view.css +1 -1
- package/out/view.js +16 -107
- package/package.json +55 -6
- package/risk/actions/findings.js +156 -0
- package/risk/actions/resolve.js +219 -0
- package/risk/actions/scripts/commands.js +182 -0
- package/risk/actions/scripts/grade.js +113 -0
- package/risk/actions/scripts/packages.js +123 -0
- package/risk/actions/scripts/shell.js +165 -0
- package/risk/actions/uses.js +76 -0
- package/risk/actions/workflow.js +110 -0
- package/risk/files/hosts.js +6 -6
- package/risk/files/readability.js +23 -17
- package/risk/files/runs-code.js +18 -6
- package/risk/files/script.js +7 -3
- package/risk/files/stamps.js +8 -7
- package/risk/index.d.ts +68 -1
- package/risk/index.js +6 -2
- package/risk/npm/assess.js +13 -8
- package/risk/npm/compare.js +34 -10
- package/risk/npm/dependencies.js +11 -0
- package/risk/npm/files.js +413 -2
- package/risk/npm/tarball.js +56 -73
- package/server-managed/agent-merges.ts +198 -0
- package/server-managed/app-agents.ts +30 -0
- package/server-managed/avatar-store.ts +1 -0
- package/server-managed/commit-coauthors.ts +83 -0
- package/server-managed/commit-lockfiles.ts +62 -0
- package/server-managed/comparison.ts +41 -0
- package/server-managed/db-methods.ts +29 -10
- package/server-managed/db-neon.ts +30 -2
- package/server-managed/db-schema.ts +9 -2
- package/server-managed/db.ts +7 -0
- package/server-managed/dependabot-access.ts +60 -0
- package/server-managed/dependabot-alerts.ts +185 -0
- package/server-managed/dependency-review-comments.ts +97 -0
- package/server-managed/dependency-reviews.ts +65 -15
- package/server-managed/github-accounts.ts +33 -0
- package/server-managed/github-actions.ts +71 -0
- package/server-managed/github-app.ts +23 -9
- package/server-managed/github-errors.ts +2 -0
- package/server-managed/github-link-oauth.ts +69 -0
- package/server-managed/github-links.ts +204 -0
- package/server-managed/github-oauth.ts +5 -2
- package/server-managed/github-users.ts +30 -0
- package/server-managed/http.ts +1219 -81
- package/server-managed/index.ts +6 -2
- package/server-managed/locked-packages.ts +48 -16
- package/server-managed/lockfile-scope.ts +49 -0
- package/server-managed/npm-delta-worker.js +23 -0
- package/server-managed/npm-delta.ts +173 -0
- package/server-managed/npm-insights.ts +20 -11
- package/server-managed/npm-kept.ts +51 -4
- package/server-managed/npm-loads.ts +20 -13
- package/server-managed/npm-packages.ts +24 -64
- package/server-managed/pretty-print.ts +14 -9
- package/server-managed/public-workspace.ts +26 -8
- package/server-managed/pull-comment-reviews.ts +161 -0
- package/server-managed/pull-dependencies.ts +280 -0
- package/server-managed/pull-dependency-reviews.ts +318 -0
- package/server-managed/pull-reviews.ts +205 -0
- package/server-managed/release-commits.ts +84 -0
- package/server-managed/repository-actions.ts +209 -0
- package/server-managed/repository-agents.ts +220 -0
- package/server-managed/repository-browser.ts +54 -13
- package/server-managed/repository-changes.ts +284 -0
- package/server-managed/repository-code.ts +52 -0
- package/server-managed/repository-dependency-reviews.ts +135 -0
- package/server-managed/repository-history.ts +445 -0
- package/server-managed/repository-issues.ts +181 -0
- package/server-managed/repository-pulse.ts +432 -0
- package/server-managed/repository-risk-read.ts +165 -0
- package/server-managed/repository-risk.ts +116 -106
- package/server-managed/reviewed-dependencies.ts +149 -0
- package/server-managed/session.ts +9 -5
- package/server-managed/sql.ts +1 -1
- package/server-managed/storage-stores.ts +5 -0
- package/server-managed/team-app.ts +54 -14
- package/server-managed/workspace-shares.ts +4 -1
- package/server.ts +11 -0
- package/out/chunk-2KBF5TZ7.js +0 -2
- package/out/chunk-35VE6CDH.js +0 -2
- package/out/chunk-3BZ7JERB.js +0 -1
- package/out/chunk-4F6JSF57.js +0 -3
- package/out/chunk-4XCDFGDP.js +0 -10
- package/out/chunk-7RU5EDRO.js +0 -226
- package/out/chunk-A7M4GV7M.js +0 -1
- package/out/chunk-CLPKNNC7.js +0 -51
- package/out/chunk-CMQA4DM6.js +0 -1
- package/out/chunk-CVVJAXVY.js +0 -2
- package/out/chunk-DIJJNTRH.js +0 -10
- package/out/chunk-GJ4IUCNS.js +0 -1
- package/out/chunk-GNXOKIGP.js +0 -80
- package/out/chunk-GZ4MS3TE.js +0 -13
- package/out/chunk-IU3CCINR.js +0 -3
- package/out/chunk-K3X5EJBQ.js +0 -2
- package/out/chunk-KEX2ATEW.js +0 -2
- package/out/chunk-KYCNJVB7.js +0 -1
- package/out/chunk-LAAB5SZT.js +0 -2
- package/out/chunk-NWZ4PVPN.js +0 -1
- package/out/chunk-OSIMGFUF.js +0 -1
- package/out/chunk-RFZQQXSG.js +0 -17
- package/out/chunk-RSHMVYNS.js +0 -1
- package/out/chunk-TZ5KB2Y5.js +0 -10
- package/out/chunk-UNTRGE26.js +0 -2
- package/out/chunk-UXNUAIIC.js +0 -33
- package/out/chunk-UYAXVRVL.js +0 -1
- package/out/chunk-V3ME6FI5.js +0 -2
- package/out/chunk-V5O7M2YT.js +0 -1
- package/out/chunk-VIHGO77Y.js +0 -1
- package/out/chunk-VQJCXGIY.js +0 -7
- package/out/chunk-W4J3MGLB.js +0 -1
- package/out/chunk-WGG7KQUH.js +0 -1
- package/out/chunk-WZDTZE64.js +0 -1
- package/out/chunk-XOCAPDCM.js +0 -1
- package/out/chunk-XYFXXB2G.js +0 -10
- package/out/chunk-YVZH5SHP.js +0 -1
- package/out/chunk-ZBBOBQ43.js +0 -1
- package/out/chunk-ZMQQCGF6.js +0 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// The reviewed dependencies a host keeps beyond the app, as it registers
|
|
2
|
+
// them before the server is assembled (server.ts registerReviewedDependencies):
|
|
3
|
+
// an async method telling whether a dependency, as a lockfile locks it, is
|
|
4
|
+
// reviewed there (`lookup`), and, optionally, one reading the reviews of
|
|
5
|
+
// dependencies an open pull request's comments make (`parseComments`,
|
|
6
|
+
// pull-comment-reviews.ts), and one formatting one's own dependency reviews
|
|
7
|
+
// as a comment, for a copy button to give (`formatDependencyReviews`,
|
|
8
|
+
// dependency-review-comments.ts). Registered, they replace the commit a repository
|
|
9
|
+
// is reviewed since (pull-dependency-reviews.ts): a version passes by the
|
|
10
|
+
// reviews its teams publish, and those its pull request's comments make, as
|
|
11
|
+
// many as the repository requires, or by `lookup`'s saying so, and is
|
|
12
|
+
// otherwise unreviewed. A patched version (pnpm's patchedDependencies)
|
|
13
|
+
// passes by `lookup`, or reviews its comments make of its patch, alone, the
|
|
14
|
+
// app reviewing no patch yet. Those comments' reviews count for the pull
|
|
15
|
+
// request alone, as it is open: the host's to keep, as it is merged, among
|
|
16
|
+
// those `lookup` tells of.
|
|
17
|
+
//
|
|
18
|
+
// Each version is asked of `lookup` once a request, as many at once as
|
|
19
|
+
// ASK_AT_ONCE, its answer kept in this instance KEPT_MS; one it fails to
|
|
20
|
+
// answer is unreviewed, and counted so (`unasked`).
|
|
21
|
+
import { type Lru, lru } from './lru.ts'
|
|
22
|
+
|
|
23
|
+
export type ReviewedLockfile = 'npm' | 'pnpm' | 'yarn1'
|
|
24
|
+
// A dependency as a lockfile locks it: npm's always with its integrity (one
|
|
25
|
+
// with none is never reviewed, nor asked of), its shasum where the lockfile
|
|
26
|
+
// has one (Yarn 1's, after a tarball's `#`), and the hash of its patch where
|
|
27
|
+
// it is patched (pnpm's).
|
|
28
|
+
export interface ReviewedDependency {
|
|
29
|
+
ecosystem: 'npm'
|
|
30
|
+
lockfile: ReviewedLockfile
|
|
31
|
+
name: string
|
|
32
|
+
version: string
|
|
33
|
+
integrity?: string
|
|
34
|
+
shasum?: string
|
|
35
|
+
patchHash?: string
|
|
36
|
+
}
|
|
37
|
+
export type ReviewVerdict = 'accept' | 'accept-with-nits' | 'request-changes' | 'reject'
|
|
38
|
+
// A comment of an open pull request's, as `parseComments` is given it: one
|
|
39
|
+
// on the pull request (`comment`), a review's own (`review`, whatever its
|
|
40
|
+
// state, a comment's alone too), or one on a line of its diff
|
|
41
|
+
// (`review-comment`); by its author's GitHub login; when it was written, and
|
|
42
|
+
// last edited (`editedAt`) where it was.
|
|
43
|
+
export interface ReviewComment {
|
|
44
|
+
kind: 'comment' | 'review' | 'review-comment'
|
|
45
|
+
id: string
|
|
46
|
+
author: string | null
|
|
47
|
+
body: string
|
|
48
|
+
state?: string
|
|
49
|
+
createdAt: string
|
|
50
|
+
editedAt?: string
|
|
51
|
+
url: string
|
|
52
|
+
}
|
|
53
|
+
// What `parseComments` reads: the pull request, its comments, and the
|
|
54
|
+
// dependencies: every one the lockfiles its review holds to add that can be
|
|
55
|
+
// reviewed and `lookup` doesn't pass, and the others still wanting reviews.
|
|
56
|
+
export interface ParseCommentsInput { repository: string; number: number; comments: ReviewComment[]; dependencies: ReviewedDependency[] }
|
|
57
|
+
// A review its comments make, by `user` (a GitHub login, one of their
|
|
58
|
+
// authors'), of each of `dependencies`, with its verdict, as of `time` (when
|
|
59
|
+
// it was made, an ISO date or milliseconds; that user's latest comment's
|
|
60
|
+
// where none is told).
|
|
61
|
+
export interface CommentReview {
|
|
62
|
+
user: string
|
|
63
|
+
time?: string | number
|
|
64
|
+
dependencies: { ecosystem?: 'npm'; name: string; version: string; integrity: string; patchHash?: string; status: ReviewVerdict }[]
|
|
65
|
+
}
|
|
66
|
+
// A version a dependency review is of, or compared it with: its package's
|
|
67
|
+
// name, its version and integrity, and its tarball's shasum and its patch's
|
|
68
|
+
// hash where they are known (a lockfile's, pnpm's patched dependencies').
|
|
69
|
+
export interface ReviewedVersion { name: string; version: string; integrity: string; shasum?: string; patchHash?: string }
|
|
70
|
+
// One of one's own published dependency reviews, as `formatDependencyReviews`
|
|
71
|
+
// is given it: the version it is of (`new`) and the one it compared it with
|
|
72
|
+
// (`old`, none where it read its package whole); its verdict (`status`);
|
|
73
|
+
// whether it read every file (`coverage`, `full`, else `partial`); its final
|
|
74
|
+
// comment; each file's verdict and comment (`files`, of those with either);
|
|
75
|
+
// by whom (`reviewer`, their GitHub login), and when it was published.
|
|
76
|
+
export interface DependencyReviewEntry {
|
|
77
|
+
ecosystem: 'npm'
|
|
78
|
+
old: ReviewedVersion | null
|
|
79
|
+
new: ReviewedVersion
|
|
80
|
+
status: ReviewVerdict
|
|
81
|
+
coverage: 'full' | 'partial'
|
|
82
|
+
comment: string
|
|
83
|
+
files: { path: string; status: ReviewVerdict | null; comment: string }[]
|
|
84
|
+
reviewer: string
|
|
85
|
+
publishedAt: number
|
|
86
|
+
}
|
|
87
|
+
// What `formatDependencyReviews` formats: one's own reviews, never another's,
|
|
88
|
+
// and the pull request whose versions they are of, where its copy asks.
|
|
89
|
+
export interface FormatDependencyReviewsInput { reviews: DependencyReviewEntry[]; pullRequest?: { repository: string; number: number } }
|
|
90
|
+
export interface ReviewedDependencies {
|
|
91
|
+
lookup(dependency: ReviewedDependency): Promise<boolean>
|
|
92
|
+
parseComments?(input: ParseCommentsInput): Promise<CommentReview[]>
|
|
93
|
+
// The comment to copy, none where it is empty, null or undefined.
|
|
94
|
+
formatDependencyReviews?(input: FormatDependencyReviewsInput): Promise<string | null | undefined>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ASK_AT_ONCE = 16
|
|
98
|
+
const KEPT_MS = 60_000
|
|
99
|
+
|
|
100
|
+
let registered: ReviewedDependencies | null = null
|
|
101
|
+
let assembled = false
|
|
102
|
+
// Each lookup's answers, apart.
|
|
103
|
+
const answers = new WeakMap<ReviewedDependencies['lookup'], Lru<{ at: number; reviewed: boolean }>>()
|
|
104
|
+
|
|
105
|
+
// Registers the host's reviewed dependencies, once, before the server is
|
|
106
|
+
// assembled (init()): a server already assembled keeps what it was given.
|
|
107
|
+
export function registerReviewedDependencies(reviewed: ReviewedDependencies): void {
|
|
108
|
+
const optional = (method: unknown) => method === undefined || typeof method === 'function'
|
|
109
|
+
if (typeof reviewed?.lookup !== 'function' || !optional(reviewed.parseComments) || !optional(reviewed.formatDependencyReviews)) {
|
|
110
|
+
throw new TypeError('registerReviewedDependencies: expected { lookup, parseComments?, formatDependencyReviews? }, async methods')
|
|
111
|
+
}
|
|
112
|
+
if (assembled) throw new Error('registerReviewedDependencies: call it before init(), as the server takes them as it is assembled')
|
|
113
|
+
if (registered) throw new Error('registerReviewedDependencies: reviewed dependencies are registered already')
|
|
114
|
+
registered = { lookup: reviewed.lookup.bind(reviewed), ...reviewed.parseComments ? { parseComments: reviewed.parseComments.bind(reviewed) } : {},
|
|
115
|
+
...reviewed.formatDependencyReviews ? { formatDependencyReviews: reviewed.formatDependencyReviews.bind(reviewed) } : {} }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The reviewed dependencies registered, as a server takes them once it is
|
|
119
|
+
// assembled; none registers after.
|
|
120
|
+
export function assembledReviewedDependencies(): ReviewedDependencies | null {
|
|
121
|
+
assembled = true
|
|
122
|
+
return registered
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Which of `dependencies`, by their keys, `lookup` passes, and how many it
|
|
126
|
+
// failed to answer.
|
|
127
|
+
export async function askReviewed(lookup: ReviewedDependencies['lookup'], dependencies: Map<string, ReviewedDependency>): Promise<{ passed: Set<string>; unasked: number }> {
|
|
128
|
+
const passed = new Set<string>()
|
|
129
|
+
let unasked = 0
|
|
130
|
+
const pending = [...dependencies]
|
|
131
|
+
const kept = answers.get(lookup) ?? answers.set(lookup, lru(100_000)).get(lookup)!
|
|
132
|
+
await Promise.all(Array.from({ length: Math.min(ASK_AT_ONCE, pending.length) }, async () => {
|
|
133
|
+
for (let next = pending.pop(); next; next = pending.pop()) {
|
|
134
|
+
const [key, dependency] = next
|
|
135
|
+
const known = kept.get(key)
|
|
136
|
+
let answer = known && Date.now() - known.at < KEPT_MS ? known.reviewed : null
|
|
137
|
+
if (answer === null) {
|
|
138
|
+
try { answer = await lookup({ ...dependency }) === true } catch (error) {
|
|
139
|
+
unasked++
|
|
140
|
+
console.warn('reviewed dependencies: failed to answer', `${dependency.name}@${dependency.version}`, error instanceof Error ? error.message : error)
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
kept.set(key, { at: Date.now(), reviewed: answer })
|
|
144
|
+
}
|
|
145
|
+
if (answer) passed.add(key)
|
|
146
|
+
}
|
|
147
|
+
}))
|
|
148
|
+
return { passed, unasked }
|
|
149
|
+
}
|
|
@@ -13,13 +13,15 @@ import { hashToken, randomToken } from './crypto.ts'
|
|
|
13
13
|
// `cookieName` when cookies are Secure.
|
|
14
14
|
export const STATE_COOKIE = 'dvstate'
|
|
15
15
|
|
|
16
|
-
// Base names for GitHub issue authorization state (github-issue-oauth.ts)
|
|
17
|
-
//
|
|
16
|
+
// Base names for GitHub issue authorization state (github-issue-oauth.ts),
|
|
17
|
+
// linking another GitHub account (github-link-oauth.ts) and an
|
|
18
|
+
// administrator's read-only view as another user (view-as.ts).
|
|
18
19
|
export const ISSUE_STATE_COOKIE = 'dvissuestate'
|
|
20
|
+
export const LINK_STATE_COOKIE = 'dvlinkstate'
|
|
19
21
|
export const VIEW_COOKIE = 'dvview'
|
|
20
22
|
// The session cookie cannot share a name with these, or one would overwrite
|
|
21
23
|
// or be read as the other.
|
|
22
|
-
export const RESERVED_COOKIES: readonly string[] = [STATE_COOKIE, ISSUE_STATE_COOKIE, VIEW_COOKIE]
|
|
24
|
+
export const RESERVED_COOKIES: readonly string[] = [STATE_COOKIE, ISSUE_STATE_COOKIE, LINK_STATE_COOKIE, VIEW_COOKIE]
|
|
23
25
|
|
|
24
26
|
// `__Host-`-prefix the base name when cookies are Secure (the prefix mandates
|
|
25
27
|
// Secure + Path=/ + no Domain, which `buildCookie` already emits).
|
|
@@ -55,13 +57,15 @@ export function parseCookies(header: string | undefined): Map<string, string> {
|
|
|
55
57
|
|
|
56
58
|
// Mint a session for a freshly-authenticated user. Upserts the identity, then
|
|
57
59
|
// stores the session keyed by the cookie token's hash. Returns the cookie to
|
|
58
|
-
// set and the CSRF token (also persisted on the row)
|
|
60
|
+
// set and the CSRF token (also persisted on the row), or null for an account
|
|
61
|
+
// a user linked to theirs, which can neither sign in nor register.
|
|
59
62
|
export async function createSession(
|
|
60
63
|
config: ManagedConfig, db: ManagedDb, user: ManagedUser, now: number,
|
|
61
|
-
): Promise<{ setCookie: string; csrfToken: string; userId: string }> {
|
|
64
|
+
): Promise<{ setCookie: string; csrfToken: string; userId: string } | null> {
|
|
62
65
|
const token = randomToken()
|
|
63
66
|
const csrfToken = randomToken()
|
|
64
67
|
const userId = await db.upsertUser(user, now, config.initialAdminGithubId)
|
|
68
|
+
if (userId == null) return null
|
|
65
69
|
await db.createSession({ id: hashToken(token), userId, csrfToken, expiresAt: now + config.sessionTtlMs }, now)
|
|
66
70
|
const setCookie = buildCookie(config.sessionCookieName, token, {
|
|
67
71
|
maxAgeS: Math.floor(config.sessionTtlMs / 1000), secure: config.cookieSecure, sameSite: 'Lax',
|
package/server-managed/sql.ts
CHANGED
|
@@ -23,7 +23,7 @@ const SINGLE_STATEMENTS = new Set(['getReport', 'getBundle', 'getStorageRow', 'g
|
|
|
23
23
|
'listReports', 'listBundles', 'listReadableBundleIds', 'listAllRepos', 'listSelectedRepos', 'listRepoScopesForUser', 'listTriage',
|
|
24
24
|
'getFeedState', 'getWorkspaceShareFeedState', 'claimMaintenanceLease', 'finishMaintenanceLease', 'cacheRepoDefaultBranch', 'releaseBundleBuildLease',
|
|
25
25
|
'getUpstreamCacheEntry', 'getUpstreamCacheEntries', 'setUpstreamCacheEntry', 'setUpstreamCacheEntries', 'ensureSessionUploadKey',
|
|
26
|
-
'listGithubCommits', 'listGithubCommitTags', 'setGithubCommits'])
|
|
26
|
+
'listGithubCommits', 'listGithubCommitTags', 'setGithubCommits', 'getAvatarKey', 'listUsersByAccount'])
|
|
27
27
|
// Session presence and upload keys change only their own rows.
|
|
28
28
|
const UNLOCKED_WRITES = new Set(['sessionWithUser', 'viewSessionWithUser', 'ensureSessionUploadKey'])
|
|
29
29
|
|
|
@@ -61,6 +61,11 @@ function avatarStore(objects: ObjectStorage, sidecar: boolean): AvatarStore {
|
|
|
61
61
|
if (split < 0 || split > 100) throw new Error('Invalid cached avatar')
|
|
62
62
|
return { contentType: bytes.subarray(0, split).toString(), bytes: bytes.subarray(split + 1) }
|
|
63
63
|
},
|
|
64
|
+
async delete(value) {
|
|
65
|
+
const path = `avatars/${id(value)}`
|
|
66
|
+
await objects.delete(path)
|
|
67
|
+
if (sidecar) await objects.delete(`${path}.type`)
|
|
68
|
+
},
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -4,6 +4,18 @@
|
|
|
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
|
+
//
|
|
12
|
+
// A team that is not App also says how many of its reports open in App mode
|
|
13
|
+
// on their own, and its findings for the badge it collapses to. None is as
|
|
14
|
+
// uniform as an App team, and collapses like one. A team mixing App and non-App
|
|
15
|
+
// reports stays expanded, and so does one whose reports each open as App but
|
|
16
|
+
// whose combined workspace does not (contradicting verdicts, say): it has App
|
|
17
|
+
// findings, and only expanded does it show that they fail to combine.
|
|
18
|
+
import { reportEntries } from '@preventive/report'
|
|
7
19
|
import { type TeamAppMetadata, managedTeamAppMetadata } from '../common/managed/team-app.js'
|
|
8
20
|
import type { BlobStore } from './blob-store.ts'
|
|
9
21
|
import type { ManagedDb, TeamReportAccessSnapshot, UserTeam } from './db.ts'
|
|
@@ -11,12 +23,14 @@ import type { WorkspaceShareSnapshot } from './workspace-shares.ts'
|
|
|
11
23
|
import { TeamReportsError, loadTeamReportsResponse } from './team-reports.ts'
|
|
12
24
|
|
|
13
25
|
export type { TeamAppMetadata }
|
|
26
|
+
export interface TeamWorkspaceSummary { app: TeamAppMetadata; empty: readonly string[] }
|
|
27
|
+
const NO_APP: TeamWorkspaceSummary = { app: { appMode: false }, empty: [] }
|
|
14
28
|
|
|
15
29
|
const CACHE_MAX_ENTRIES = 4096
|
|
16
30
|
// Each workspace build already reads four reports at a time.
|
|
17
31
|
const CLASSIFY_CONCURRENCY = 2
|
|
18
|
-
const caches = new WeakMap<ManagedDb, Map<string,
|
|
19
|
-
const pendingJobs = new WeakMap<ManagedDb, Map<string, Promise<
|
|
32
|
+
const caches = new WeakMap<ManagedDb, Map<string, TeamWorkspaceSummary>>()
|
|
33
|
+
const pendingJobs = new WeakMap<ManagedDb, Map<string, Promise<TeamWorkspaceSummary | null>>>()
|
|
20
34
|
|
|
21
35
|
// Published reports with their content keys, and the team's own key (access,
|
|
22
36
|
// scopes and links). Equal keys describe the same filtered workspace, whoever
|
|
@@ -29,28 +43,40 @@ function published(team: UserTeam) {
|
|
|
29
43
|
return team.reports.filter(report => report.visible !== false)
|
|
30
44
|
}
|
|
31
45
|
|
|
32
|
-
function remember(db: ManagedDb, key: string,
|
|
46
|
+
function remember(db: ManagedDb, key: string, summary: TeamWorkspaceSummary): TeamWorkspaceSummary {
|
|
33
47
|
let cache = caches.get(db)
|
|
34
48
|
if (!cache) { cache = new Map(); caches.set(db, cache) }
|
|
35
49
|
cache.delete(key)
|
|
36
|
-
cache.set(key,
|
|
50
|
+
cache.set(key, summary)
|
|
37
51
|
for (const oldest of cache.keys()) {
|
|
38
52
|
if (cache.size <= CACHE_MAX_ENTRIES) break
|
|
39
53
|
cache.delete(oldest)
|
|
40
54
|
}
|
|
41
|
-
return
|
|
55
|
+
return summary
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isLinks(data: unknown): boolean {
|
|
59
|
+
const record = data as { source?: unknown; links?: unknown } | null
|
|
60
|
+
return record?.source === 'links' && Array.isArray(record.links)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A links document counts by its links, every other report by its findings.
|
|
64
|
+
function hasContent(data: unknown): boolean {
|
|
65
|
+
if (isLinks(data)) return (data as { links: unknown[] }).links.length > 0
|
|
66
|
+
return (reportEntries(data) ?? []).some(entry => !Array.isArray(entry) || entry.length > 0)
|
|
42
67
|
}
|
|
43
68
|
|
|
44
69
|
// Null when the team cannot be classified right now: its catalog went stale,
|
|
45
70
|
// 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
|
|
71
|
+
// team expanded and lists every report, and a later catalog asks again.
|
|
72
|
+
// Oversized and unreadable workspaces never have an App view, so they are
|
|
73
|
+
// remembered as such, hiding nothing.
|
|
48
74
|
async function classify(db: ManagedDb, store: BlobStore, key: string, team: UserTeam,
|
|
49
|
-
snapshot: () => Promise<TeamReportAccessSnapshot | null>): Promise<
|
|
75
|
+
snapshot: () => Promise<TeamReportAccessSnapshot | null>): Promise<TeamWorkspaceSummary | null> {
|
|
50
76
|
const cached = caches.get(db)?.get(key)
|
|
51
77
|
if (cached) return remember(db, key, cached)
|
|
52
78
|
const ids = new Set(published(team).map(report => report.id))
|
|
53
|
-
if (ids.size === 0) return remember(db, key,
|
|
79
|
+
if (ids.size === 0) return remember(db, key, NO_APP)
|
|
54
80
|
let jobs = pendingJobs.get(db)
|
|
55
81
|
if (!jobs) { jobs = new Map(); pendingJobs.set(db, jobs) }
|
|
56
82
|
const existing = jobs.get(key)
|
|
@@ -60,11 +86,18 @@ async function classify(db: ManagedDb, store: BlobStore, key: string, team: User
|
|
|
60
86
|
// A catalog read before a concurrent publication names another report set.
|
|
61
87
|
if (!access?.teamId || access.reports.length !== ids.size || access.reports.some(report => !ids.has(report.id))) return null
|
|
62
88
|
const body = JSON.parse((await loadTeamReportsResponse(db, store, access)).toString('utf8')) as {
|
|
63
|
-
reports: { filename: string; data: unknown }[]; links?: string[][]
|
|
89
|
+
reports: { id: string; filename: string; data: unknown }[]; links?: string[][]
|
|
64
90
|
}
|
|
65
|
-
|
|
91
|
+
const restricted = access.reports.some(report => !report.permissions.dependencies || !report.permissions.security)
|
|
92
|
+
const empty = restricted ? body.reports.filter(report => !hasContent(report.data)).map(report => report.id) : []
|
|
93
|
+
const links = body.links ?? []
|
|
94
|
+
const app = managedTeamAppMetadata(Object.assign(body.reports, { links }), { countFindings: true })
|
|
95
|
+
if (app.appMode) return remember(db, key, { app, empty })
|
|
96
|
+
const appReports = body.reports.filter(report => !isLinks(report.data)
|
|
97
|
+
&& managedTeamAppMetadata(Object.assign([report], { links })).appMode).length
|
|
98
|
+
return remember(db, key, { app: { ...app, appReports }, empty })
|
|
66
99
|
})().catch((error: unknown) => {
|
|
67
|
-
if (error instanceof TeamReportsError && [413, 422].includes(error.status)) return remember(db, key,
|
|
100
|
+
if (error instanceof TeamReportsError && [413, 422].includes(error.status)) return remember(db, key, NO_APP)
|
|
68
101
|
if (!(error instanceof TeamReportsError)) console.warn('managed: team App classification failed:', error)
|
|
69
102
|
return null
|
|
70
103
|
})
|
|
@@ -72,10 +105,17 @@ async function classify(db: ManagedDb, store: BlobStore, key: string, team: User
|
|
|
72
105
|
try { return await job } finally { jobs.delete(key) }
|
|
73
106
|
}
|
|
74
107
|
|
|
108
|
+
// The catalog's view of a classified team: its App classification, and its
|
|
109
|
+
// reports without the ones its restricted reader would find empty.
|
|
110
|
+
export function withWorkspaceSummary<T extends { reports: readonly { id: string }[] }>(team: T, summary: TeamWorkspaceSummary | null | undefined) {
|
|
111
|
+
const empty = new Set(summary?.empty)
|
|
112
|
+
return { ...team, app: summary?.app ?? null, reports: team.reports.filter(report => !empty.has(report.id)) }
|
|
113
|
+
}
|
|
114
|
+
|
|
75
115
|
// Classify a user's catalog, keyed by teamAppKey. `known` carries results from
|
|
76
116
|
// an earlier read of the same request, so only changed teams are classified.
|
|
77
117
|
export async function teamAppStates(db: ManagedDb, store: BlobStore, sessionId: string, teams: readonly UserTeam[],
|
|
78
|
-
known: ReadonlyMap<string,
|
|
118
|
+
known: ReadonlyMap<string, TeamWorkspaceSummary | null> = new Map()): Promise<Map<string, TeamWorkspaceSummary | null>> {
|
|
79
119
|
const states = new Map(known)
|
|
80
120
|
const missing = [...new Map(teams.map(team => [teamAppKey(team), team])).entries()].filter(([key]) => !states.has(key))
|
|
81
121
|
let next = 0
|
|
@@ -89,6 +129,6 @@ export async function teamAppStates(db: ManagedDb, store: BlobStore, sessionId:
|
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
// A public link's workspace is its own access snapshot.
|
|
92
|
-
export function sharedTeamApp(db: ManagedDb, store: BlobStore, snapshot: WorkspaceShareSnapshot): Promise<
|
|
132
|
+
export function sharedTeamApp(db: ManagedDb, store: BlobStore, snapshot: WorkspaceShareSnapshot): Promise<TeamWorkspaceSummary | null> {
|
|
93
133
|
return classify(db, store, JSON.stringify(['share', teamAppKey(snapshot.team)]), snapshot.team, () => Promise.resolve(snapshot))
|
|
94
134
|
}
|
|
@@ -145,15 +145,18 @@ export function workspaceShareMethods(db: ManagedSql): WorkspaceShareStore {
|
|
|
145
145
|
const links = await linkRevision(db)
|
|
146
146
|
const bundleRows = (await q.bundles.all(team.id) as (Omit<ManagedBundle, 'buildConditions'> & { buildConditions: string | null; repoFullName: string })[])
|
|
147
147
|
.map(row => ({ ...row, buildConditions: row.buildConditions == null ? null : JSON.parse(row.buildConditions) as BundleBuildConditions }))
|
|
148
|
+
const repositories = await q.repositories.all(team.id) as WorkspaceShareSnapshot['repositories']
|
|
148
149
|
return {
|
|
149
150
|
user: { id: `share:${tokenHash}`, login: 'public', name: 'Public workspace', avatarUrl: null, role: 'view' },
|
|
150
151
|
teamId: team.id,
|
|
151
152
|
linkRevision: links,
|
|
152
153
|
permissions,
|
|
153
|
-
repositories
|
|
154
|
+
repositories,
|
|
154
155
|
reports: rows.map(row => ({ id: row.id, filename: row.filename, byteSize: row.byteSize, sha256: row.sha256,
|
|
155
156
|
repo: { github: row.github, directory: row.directory }, permissions })),
|
|
156
157
|
team: { ...team, permissions, cacheKey: links,
|
|
158
|
+
repos: repositories.map(repo => ({ repoId: repo.repoId, fullName: repo.github, path: repo.path || null }))
|
|
159
|
+
.toSorted((a, b) => a.fullName.localeCompare(b.fullName) || (a.path ?? '').localeCompare(b.path ?? '')),
|
|
157
160
|
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
161
|
cacheKey: JSON.stringify([row.sha256, row.github, row.directory, row.filename, permissions, links]) })),
|
|
159
162
|
bundles: bundleRows.map(row => ({ id: row.id, slug: row.slug, integrity: row.integrity,
|
package/server.ts
CHANGED
|
@@ -2,6 +2,17 @@ import { parseArgs } from 'node:util'
|
|
|
2
2
|
import type { Server } from 'node:http'
|
|
3
3
|
import { startServer } from './server-common/standalone.ts'
|
|
4
4
|
|
|
5
|
+
// The reviewed dependencies a host keeps beyond the app, registered before
|
|
6
|
+
// init() (server-managed/reviewed-dependencies.ts): an async method telling
|
|
7
|
+
// whether a dependency, as a lockfile locks it, is reviewed there
|
|
8
|
+
// (`lookup`), and optionally one reading the reviews an open pull request's
|
|
9
|
+
// comments make (`parseComments`), and one formatting one's own dependency
|
|
10
|
+
// reviews as a comment to copy (`formatDependencyReviews`). They replace a
|
|
11
|
+
// repository's commit its dependencies are reviewed since.
|
|
12
|
+
export { registerReviewedDependencies } from './server-managed/reviewed-dependencies.ts'
|
|
13
|
+
export type { CommentReview, DependencyReviewEntry, FormatDependencyReviewsInput, ParseCommentsInput, ReviewComment, ReviewedDependencies, ReviewedDependency,
|
|
14
|
+
ReviewedLockfile, ReviewedVersion, ReviewVerdict } from './server-managed/reviewed-dependencies.ts'
|
|
15
|
+
|
|
5
16
|
const modes = ['e2e', 'managed', 'managed-e2e', 'e2e-managed'] as const
|
|
6
17
|
export type ServerMode = typeof modes[number]
|
|
7
18
|
|
package/out/chunk-2KBF5TZ7.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var n=`.managed-comment{container:managed-comment / inline-size;white-space:normal}.managed-comment-row{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr) auto;align-items:start;gap:.35rem .65rem;line-height:1.5}.managed-comment-author{grid-column:1;grid-row:1;display:flex;align-items:center;gap:.45rem;min-width:0;max-width:14rem;& strong{overflow:hidden;text-overflow:ellipsis;font-weight:600}}.managed-comment-avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:1.3rem;height:1.3rem;overflow:hidden;border-radius:50%;background:rgb(from var(--muted) r g b / .15);color:var(--muted);font-size:.65rem;font-weight:600;& img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}& img[hidden]{display:none}}.managed-comment-body{grid-column:2;grid-row:1;min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;& a{color:var(--accent);text-decoration:none}& a:hover{text-decoration:underline}& a.comment-self-ref{text-decoration:underline dotted;text-underline-offset:2px}& a.comment-self-ref:hover{text-decoration:underline solid}}.managed-comment-time{color:var(--muted);font-size:.7rem;font-weight:400;white-space:nowrap;text-align:right}.managed-comment-meta{grid-column:3;grid-row:1;justify-self:end;display:flex;align-items:center;gap:.5rem;min-height:1.3rem}.managed-comment-actions{display:flex;align-items:center;gap:.15rem}.managed-comment-row:not(:has(.managed-comment-author)){grid-template-columns:minmax(0,1fr) auto;.managed-comment-body{grid-column:1}.managed-comment-meta{grid-column:2}}@container managed-comment (max-width: 28rem){.managed-comment-row:has(.managed-comment-meta){grid-template-columns:minmax(0,max-content) minmax(0,1fr);.managed-comment-meta{grid-column:1 / -1;grid-row:1}.managed-comment-author,.managed-comment-body{grid-row:2}}.managed-comment-row:not(:has(.managed-comment-author)){grid-template-columns:minmax(0,1fr)}.managed-comment-author{max-width:9rem}}
|
|
2
|
-
`;export{n as a};
|
package/out/chunk-35VE6CDH.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as n}from"./chunk-DIJJNTRH.js";import{c as t,g as i}from"./chunk-2XUEZHIP.js";import{c as d,l as s,m}from"./chunk-V5O7M2YT.js";var c=[["manage","Overview"],["manage-bundles","Bundles"],["manage-scans","Scans"],["manage-reports","Reports"],["manage-deduplication","Deduplication","admin"],["manage-repos","Repositories","admin"],["admin-users","Users","admin"],["manage-teams","Teams","admin"],["manage-import","Import","admin"],["manage-links","Links"],["manage-history","History"]];function h(e,r,l=!1){return t`<header class="manage-header"><nav class="manage-nav" aria-label="Management pages">${c.filter(([a,,o])=>(o==null||o===r)&&(a!=="manage-links"||l)).map(([a,o])=>t`<button type="button" aria-current="${e===a?"page":i}" @click="${()=>{document.dispatchEvent(new CustomEvent("managed-admin-navigate",{detail:{view:a},bubbles:!0,composed:!0}))}}">${o}</button>`)}</nav></header>`}function v(e){if(e==="bundle")return n(d);if(e==="report"||e==="preview")return n(m);if(e==="scan")return n(s);let r={users:"M10.75 4.75a2.75 2.75 0 1 1-5.5 0 2.75 2.75 0 0 1 5.5 0ZM3 14v-1.5A3.5 3.5 0 0 1 6.5 9h3a3.5 3.5 0 0 1 3.5 3.5V14",team:"M8.5 5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0ZM1.5 14v-1.5A3.5 3.5 0 0 1 5 9h2a3.5 3.5 0 0 1 3.5 3.5V14M11 2.75a2.5 2.5 0 0 1 0 4.5m1 2a3 3 0 0 1 2.5 3V14",repo:"M3 12.5V3a1.5 1.5 0 0 1 1.5-1.5H13v13H4.5a1.5 1.5 0 0 1 0-3H13M7 1.5v6l2-1.5 2 1.5v-6",history:"M2 5a6.25 6.25 0 1 1-.25 5M1.5 1.5V5H5M8 4.5V8l3 1.5",arrow:"M3 8h10M9 4l4 4-4 4",show:"M1 8s2.5-4.5 7-4.5S15 8 15 8s-2.5 4.5-7 4.5S1 8 1 8Zm9 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0",hide:"m2 2 12 12M6 3.75A8 8 0 0 1 8 3.5c4.5 0 7 4.5 7 4.5a12 12 0 0 1-2 2.5M3.5 5A13 13 0 0 0 1 8s2.5 4.5 7 4.5a8 8 0 0 0 3-.6",download:"M8 2v8M5 7l3 3 3-3M2 11v3h12v-3",upload:"M8 11V2m-3 3 3-3 3 3M2 10v4h12v-4"};return t`<svg class="${e==="repo"?"repo-icon":i}" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${r[e]??r.repo}"></path></svg>`}var w=`:host{display:flex;flex-direction:column;position:relative;padding:0 clamp(1rem,3vw,3rem) 3rem;color:var(--text);container-type:inline-size;font-family:var(--sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);font-size:.875rem;line-height:1.5;--panel:color-mix(in srgb, var(--bg) 50%, var(--surface));--panel-hover:color-mix(in srgb, var(--text) 3%, var(--panel))}*,*:before,*:after{box-sizing:border-box}button,summary,select,label,legend,h1,h2,h3,.section-heading,.ui-hint,.page-intro>.intro,.count,.result-count,.repository-count,.breadcrumb-separator,.list-head,.report-list-head,.history-head,.bundle-group-head,.drop-copy,.local-import-copy,.location-field>span,.empty:not(.error,[role=alert]),.msg:not(.error,[role=alert]),.local-import-message:not(.error,[role=status]),.data-empty,.revision-empty{cursor:default;user-select:none;-webkit-user-select:none}input:not([type=checkbox],[type=radio]),textarea,[contenteditable],pre,code,.mono{cursor:text;user-select:text;-webkit-user-select:text}.wrap{width:100%;max-width:76rem;margin:0 auto;flex:1;min-height:0;overflow-y:auto;overscroll-behavior:none;scrollbar-gutter:stable}.manage-header{position:sticky;top:0;z-index:2}.wrap:has(>.manage-list){display:flex;flex-direction:column;overflow:hidden;scrollbar-gutter:auto}.wrap:has(>.manage-list)>:not(.manage-list){flex:none}.manage-list{position:relative;flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable}.manage-nav button{flex:0 0 auto;position:relative;min-height:3.7rem;padding:.9rem 0;border:0;border-radius:0;color:var(--muted);background:none;font-size:.8rem;font-weight:500;white-space:nowrap}.manage-nav button:hover,.manage-nav button[aria-current=page]{color:var(--text)}.manage-nav button[aria-current=page]:after{content:"";position:absolute;bottom:.6rem;inset-inline:0;height:2px;background:var(--accent);border-radius:var(--ui-radius, 2px)}.manage-nav button:focus-visible{outline-offset:-3px}.head{display:flex;align-items:center;flex-wrap:wrap;gap:.7rem;margin:0 0 .5rem}h1,.head h1{margin:0;font-size:1.9rem;font-weight:600;letter-spacing:-.045em;line-height:1.2}.count{display:inline-flex;align-items:center;justify-content:center;min-width:1.6rem;padding:.12rem .5rem;border:1px solid var(--border);border-radius:6px;color:var(--muted);background:var(--surface);font-size:.73rem;font-weight:500;font-variant-numeric:tabular-nums}button,input,select{font:inherit}button,select,input[type=checkbox]{cursor:default}button,a,input,select{-webkit-app-region:no-drag}button,a{transition:background .15s,color .15s,border-color .15s}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:3px}button:disabled,select:disabled{opacity:.45}input:not([type=checkbox]),select{min-width:0;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:var(--ui-radius, 7px);background:var(--bg);color:var(--text);font-size:.8rem}input::placeholder{color:var(--muted)}input[type=checkbox]{accent-color:var(--accent)}.btn,.drop-browse{display:inline-flex;align-items:center;justify-content:center;gap:.45rem;min-height:2.25rem;height:auto;padding:.45rem .8rem;border:1px solid var(--border);border-radius:var(--ui-radius, 7px);background:var(--surface);color:var(--text);font-size:.8rem;font-weight:500;text-decoration:none;white-space:nowrap}.btn:hover:not(:disabled),.drop-browse:hover:not(:disabled){background:var(--surface-active);border-color:var(--muted)}.btn.primary,.drop-browse{color:var(--bg);background:var(--accent);border-color:var(--accent)}.btn.primary:hover:not(:disabled),.drop-browse:hover:not(:disabled){background:var(--accent);filter:brightness(1.08)}.btn svg,.icon-btn svg{width:1rem;height:1rem}.icon-btn{width:2rem;height:2rem}.avatar{color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,var(--bg));border:1px solid color-mix(in srgb,var(--accent) 16%,var(--border))}.breadcrumb{display:flex;align-items:center;flex-basis:100%;gap:.4rem;margin-bottom:.8rem}.breadcrumb-manage{padding:.1rem 0;border:0;background:transparent;color:var(--muted);font-size:.78rem}.breadcrumb-manage:hover{color:var(--text)}.breadcrumb-separator{color:var(--muted)}.msg{margin:1rem 0;padding:1.1rem 1.25rem;border:1px solid var(--border);border-radius:var(--ui-radius, 9px);color:var(--muted);font-size:.85rem}.msg.error{color:var(--critical);border-color:color-mix(in srgb,var(--critical) 35%,var(--border))}.empty{padding:3.5rem 1.5rem;border:1px dashed var(--border);border-radius:var(--ui-radius, 10px);color:var(--muted);text-align:center}.empty strong{display:block;margin-bottom:.4rem;color:var(--text);font-size:.95rem;font-weight:500}.empty p{margin:0;font-size:.85rem}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@container (max-width: 35rem){.drop-copy>span{display:none}.manage-nav{gap:1.2rem;margin-bottom:1.75rem}.manage-nav button{min-height:3.25rem}h1,.head h1{font-size:1.65rem}.intro{margin-bottom:1.5rem}}@media(prefers-reduced-motion:reduce){button,a{transition:none}}.collection-toolbar input{flex:1;max-width:26rem;min-width:10rem}.collection-toolbar select{min-width:10rem}.result-count{text-align:right;white-space:nowrap;margin-left:auto;color:var(--muted);font-size:.75rem;font-variant-numeric:tabular-nums}@container (max-width: 35rem){.collection-toolbar input{flex-basis:100%;max-width:none}}.manage-nav{display:flex;gap:1.35rem;align-items:stretch;overflow-x:auto;scrollbar-width:thin;border-bottom:1px solid var(--border);margin-bottom:1.5rem;padding:0 .1rem}.wrap>:not(.manage-header),.manage-nav{-webkit-app-region:no-drag;app-region:no-drag}@media(display-mode:window-controls-overlay){.manage-header{display:flow-root}.manage-header:before{content:"";float:right;width:var(--wco-right-toolbar, 0px);height:var(--wco-titlebar-height)}.manage-nav{width:max-content;max-width:100%;border-bottom:0}.manage-nav button{min-height:var(--wco-titlebar-height);padding-block:.35rem}.manage-nav button[aria-current=page]:after{bottom:0}}.intro{margin:0 0 1.25rem;max-width:46rem;color:var(--muted);font-size:.875rem;line-height:1.65}.collection-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:.75rem;margin-bottom:.85rem}.page-intro{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem 1rem;margin-bottom:1.25rem}.page-intro .intro{margin:0}.page-intro>.btn{margin-block:calc(-.45rem - 1px)}a{cursor:default}.loading-rows{border:1px solid var(--border);border-radius:var(--ui-radius, 10px);overflow:hidden}.loading-row{display:grid;align-content:center;gap:.6rem;min-height:4.25rem;padding:.85rem 1rem}.loading-row+.loading-row{border-top:1px solid var(--border)}.loading-row span{width:40%;height:.65rem;border-radius:3px;background:color-mix(in srgb,var(--muted) 15%,transparent)}.loading-row span+span{width:65%;height:.5rem}.drop-card{display:flex;align-items:center;gap:.75rem;margin-bottom:1rem;padding:.7rem .85rem;border:1px dashed color-mix(in srgb,var(--muted) 35%,var(--border));border-radius:var(--ui-radius, 8px);background:var(--panel)}.drop-icon{display:grid;place-items:center;flex-shrink:0;width:1.8rem;height:1.8rem;color:var(--muted)}.drop-icon svg{width:1.1rem;height:1.1rem}.drop-copy{min-width:0;display:flex;align-items:baseline;flex-wrap:wrap;gap:.2rem .6rem}.drop-copy strong{font-size:.8rem;font-weight:500}.drop-copy>span{color:var(--muted);font-size:.75rem}.drop-browse{min-width:7rem;margin-left:auto;flex-shrink:0}.content-hidden .filename,.content-hidden .report-name,.content-hidden .report-uploader{color:var(--muted)}.status{display:inline-flex;align-items:center;gap:.3rem;width:fit-content;padding:.1rem .4rem;border:1px solid var(--border);border-radius:5px;color:var(--muted);font-size:.65rem}.status:before{content:"";width:.25rem;height:.25rem;border-radius:50%;background:currentcolor}.status.visible{color:var(--green);border-color:rgb(from var(--green) r g b / .2);background:rgb(from var(--green) r g b / .06)}
|
|
2
|
-
`;export{h as a,v as b,w as c};
|
package/out/chunk-3BZ7JERB.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function n(t){return typeof t=="string"&&/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(t)?t:null}export{n as a};
|
package/out/chunk-4F6JSF57.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{b as w,c as f,j as m,l as k}from"./chunk-WZDTZE64.js";import{a as h}from"./chunk-TUII34K2.js";var x=3,y=4,H=/^[\w-]{4}$/u,_=/^[\w-]{8}$/u,u="AAAA",R={report:`deepview/finding-link/report/v1
|
|
2
|
-
`,workspace:`deepview/finding-link/workspace/v1
|
|
3
|
-
`},p=new Map;function $(t,e){return`${t}\0${e}`}function I(t){return typeof t=="string"&&H.test(t)}async function T(t,e){let n=R[t];if(!n||typeof e!="string"||!e)return null;let i=$(t,e),o=p.get(i);if(o!==void 0)return o;let r;try{let s=await crypto.subtle.digest("SHA-256",h(n+e));r=new Uint8Array(s).slice(0,x).toBase64({alphabet:"base64url",omitPadding:!0})}catch{return null}return p.set(i,r),r}function E(t,e){return typeof e!="string"||!e?null:p.get($(t,e))??null}function N(t,e){let n=[];for(let[i,o]of[["report",t],["workspace",e]]){if(o==null||o===""){n.push(u);continue}if(!I(o))throw new TypeError(`encodeFindingRef: ${i} must be a link hint token, not a name`);n.push(o)}return n.every(i=>i===u)?null:n[0]===u?`w${n[1]}`:n.join("")}function b(t){let e=[t.slice(0,y),t.slice(y)].map(n=>n===u?null:n);return{report:e[0],workspace:e[1]}}function a({id:t,report:e,workspace:n}={}){if(!f(t))throw new TypeError("encodeFindingRef: a persistent finding id is required");let i=[`finding=${encodeURIComponent(t)}`],o=N(e,n);return o&&i.push(`v=${o}`),i.join("&")}function O(t,e){let n=a(t);return typeof location>"u"?`${e??""}#${n}`:`${location.origin}${e??location.pathname}#${n}`}function A(t){let e=typeof t=="string"?t:typeof location>"u"?"":location.hash;if(!e)return null;let n=e.replace(/^#/u,"");if(!n)return null;let i={id:null,report:null,workspace:null};for(let o of n.split("&")){let r=o.indexOf("=");if(r<0)continue;let s=o.slice(0,r),c=o.slice(r+1);if(!c)continue;if(s==="v"){if(c.length===5&&c[0]==="w"&&I(c.slice(1))){let g=c.slice(1);Object.assign(i,{report:null,workspace:g===u?null:g});continue}if(!_.test(c))continue;Object.assign(i,b(c));continue}if(s!=="finding"||c.length>w*3)continue;let d;try{d=decodeURIComponent(c)}catch{continue}f(d)&&(i.id=d)}return i.id?i:null}function U(t,{managed:e=!1}={}){if(typeof location>"u")return null;let n;try{n=new URL(t)}catch{return null}if(n.href!==t||n.protocol!==location.protocol||n.host!==location.host||n.username||n.password)return null;let i=k(n);if(i?.finding)return{id:i.finding.id,fragment:a(i.finding),path:m(i)};if(e&&i?.view!=="home"&&!/\/(?:index|view)\.html$/u.test(n.pathname))return null;let o=A(n.hash);if(!o)return null;let r=a(o);return{id:o.id,fragment:r,path:`/#${r}`}}function l(t,e){if(typeof t!="string")return!1;let n=t.replaceAll("\\","/");return n.startsWith(`${e}/`)||n.includes(`/${e}/`)}function C(t){let e=!1;for(let n of t){for(let i of n.groups??[])for(let o of i){if(l(o.file,"node_modules"))return"node_modules";l(o.file,"vendor")&&(e=!0)}for(let i of Object.keys(n.tree??{})){if(l(i,"node_modules"))return"node_modules";l(i,"vendor")&&(e=!0)}}return e?"vendor":"dependencies"}export{l as a,C as b,T as c,E as d,a as e,O as f,A as g,U as h};
|
package/out/chunk-4XCDFGDP.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import{d as o}from"./chunk-XYFXXB2G.js";import{b as s,c as i}from"./chunk-C4Z6ZBXV.js";import{g as r}from"./chunk-2XUEZHIP.js";var u=s(class extends i{constructor(){super(...arguments),this.key=r}render(t,e){return this.key=t,e}update(t,[e,m]){return e!==this.key&&(o(t),this.key=e),m}});export{u as a};
|
|
2
|
-
/*! Bundled license information:
|
|
3
|
-
|
|
4
|
-
lit-html/directives/keyed.js:
|
|
5
|
-
(**
|
|
6
|
-
* @license
|
|
7
|
-
* Copyright 2021 Google LLC
|
|
8
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
9
|
-
*)
|
|
10
|
-
*/
|