-
Notifications
You must be signed in to change notification settings - Fork 0
Develop #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Develop #2
Changes from all commits
bc44d2b
394ed3a
280364f
2fb45fb
8f05141
698db3b
ed8123b
b9a6d3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| import { readFile, writeFile, readdir } from "node:fs/promises" | ||
| import type { Dirent } from "node:fs" | ||
| import { join, basename } from "node:path" | ||
| import { exists } from "./detect.ts" | ||
| import { findStoreRoot } from "../../utils/store.ts" | ||
| import type { CheckResult, ExtensionKind, RunSummary } from "./types.ts" | ||
| import { exists } from "./detect" | ||
| import { findStoreRoot } from "../../utils/store" | ||
| import { ENGINE_CHALLENGES, type CheckResult, type ExtensionKind, type RunSummary } from "./types" | ||
|
|
||
| const readJson = async <T>(path: string): Promise<T | null> => { | ||
| try { | ||
|
|
@@ -182,6 +183,119 @@ const checkRouteConventions = async (dir: string, doFix: boolean): Promise<Check | |
| return results | ||
| } | ||
|
|
||
| const FAVICON_SOURCE_EXT = /\.(js|mjs|cjs|ts|html)$/ | ||
| const HAND_BUILT_FAVICON_RE = /\/api\/proxy\/favicon\?domain=/ | ||
| const FAVICON_SIGNER_RE = /\bsignFaviconUrl\b/ | ||
|
|
||
| const readPluginSources = async (dir: string, rel = ""): Promise<{ file: string; src: string }[]> => { | ||
| let entries: Dirent[] | ||
| try { | ||
| entries = await readdir(join(dir, rel), { withFileTypes: true }) | ||
| } catch { | ||
| return [] | ||
| } | ||
| const out: { file: string; src: string }[] = [] | ||
| for (const entry of entries) { | ||
| const file = rel ? `${rel}/${entry.name}` : entry.name | ||
| if (entry.isDirectory()) { | ||
| if (entry.name === "node_modules" || entry.name.startsWith(".")) continue | ||
| out.push(...(await readPluginSources(dir, file))) | ||
| } else if (FAVICON_SOURCE_EXT.test(entry.name)) { | ||
| try { | ||
| out.push({ file, src: await readFile(join(dir, file), "utf-8") }) | ||
| } catch { | ||
| continue | ||
| } | ||
| } | ||
| } | ||
| return out | ||
| } | ||
|
|
||
| export const checkFaviconUrls = async (dir: string): Promise<CheckResult[]> => { | ||
| const offenders = (await readPluginSources(dir)) | ||
| .filter(({ src }) => HAND_BUILT_FAVICON_RE.test(src)) | ||
| .map(({ file }) => file) | ||
| if (offenders.length === 0) { | ||
| return [{ label: "favicon URLs are signed by degoog", status: "pass" }] | ||
| } | ||
| return offenders.map((file) => ({ | ||
| label: `${file} favicon URLs`, | ||
| status: "fail", | ||
| detail: "hand-built /api/proxy/favicon URL gets a 403 - sign it on the server with ctx.signFaviconUrl(url)", | ||
| })) | ||
| } | ||
|
|
||
| export const usesFaviconSigner = async (dir: string): Promise<boolean> => | ||
| (await readPluginSources(dir)).some(({ src }) => FAVICON_SIGNER_RE.test(src)) | ||
|
|
||
| const ENTRY_FILES = ["index.ts", "index.js"] | ||
| const CHALLENGES_RE = /(?<![.\w$])challenges\s*[:=]\s*(\[[^\]]*\]|[^\s,;}]+)/ | ||
| const HANDLES_CHALLENGES_RE = /(?<![.\w$])handlesChallenges\s*[:=]\s*([^\s,;}]+)/ | ||
| const STRING_LITERAL_RE = /^(["'`])([^"'`]*)\1$/ | ||
|
Comment on lines
+232
to
+234
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Exclude comments and strings from challenge declaration detection. These regexes inspect raw source text. A valid transport with Parse actual property declarations, or use syntax-aware tokenization that excludes comments and strings. Apply the same detection to 🤖 Prompt for AI Agents |
||
|
|
||
| const readEntrySource = async (dir: string): Promise<string | null> => { | ||
| for (const file of ENTRY_FILES) { | ||
| try { | ||
| return await readFile(join(dir, file), "utf-8") | ||
| } catch { | ||
| continue | ||
| } | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| export const checkChallengesValue = (raw: string): CheckResult => { | ||
| const label = '"challenges" is valid' | ||
| if (!raw.startsWith("[")) { | ||
| return { label, status: "fail", detail: 'must be an array, e.g. ["anubis"]' } | ||
| } | ||
| const items = raw | ||
| .slice(1, -1) | ||
| .split(",") | ||
| .map((item) => item.trim()) | ||
| .filter(Boolean) | ||
| const unknown = items.filter((item) => { | ||
| const kind = STRING_LITERAL_RE.exec(item)?.[2] | ||
| return kind === undefined || !ENGINE_CHALLENGES.includes(kind) | ||
| }) | ||
| if (unknown.length > 0) { | ||
| return { | ||
| label, | ||
| status: "fail", | ||
| detail: `unknown kind ${unknown.join(", ")} - supported: ${ENGINE_CHALLENGES.map((k) => `"${k}"`).join(", ")}`, | ||
| } | ||
| } | ||
| return { label, status: "pass" } | ||
| } | ||
|
|
||
| export const checkHandlesChallengesValue = (raw: string): CheckResult => { | ||
| const label = '"handlesChallenges" is valid' | ||
| if (raw === "true" || raw === "false") return { label, status: "pass" } | ||
| return { label, status: "fail", detail: `must be true or false, got ${raw}` } | ||
| } | ||
|
|
||
| export const declaresChallenges = async (dir: string): Promise<boolean> => { | ||
| const src = await readEntrySource(dir) | ||
| return src !== null && CHALLENGES_RE.test(src) | ||
| } | ||
|
|
||
| const checkChallengeProps = async ( | ||
| dir: string, | ||
| kind: ExtensionKind, | ||
| ): Promise<CheckResult[]> => { | ||
| const src = await readEntrySource(dir) | ||
| if (src === null) return [] | ||
| if (kind === "engine") { | ||
| const raw = CHALLENGES_RE.exec(src)?.[1] | ||
| return raw === undefined ? [] : [checkChallengesValue(raw)] | ||
| } | ||
| if (kind === "transport") { | ||
| const raw = HANDLES_CHALLENGES_RE.exec(src)?.[1] | ||
| return raw === undefined ? [] : [checkHandlesChallengesValue(raw)] | ||
| } | ||
| return [] | ||
| } | ||
|
|
||
| const runThemeChecks = async (dir: string, doFix: boolean): Promise<RunSummary> => { | ||
| const results: CheckResult[] = [] | ||
| let failed = false | ||
|
|
@@ -287,10 +401,19 @@ export const runChecks = async ( | |
| if (authorRes.failed) failed = true | ||
| } | ||
|
|
||
| if (kind === "engine" || kind === "transport") { | ||
| const challengeChecks = await checkChallengeProps(dir, kind) | ||
| results.push(...challengeChecks) | ||
| if (challengeChecks.some((c) => c.status === "fail")) failed = true | ||
| } | ||
|
|
||
| if (kind === "plugin") { | ||
| const routeChecks = await checkRouteConventions(dir, doFix) | ||
| results.push(...routeChecks) | ||
| if (routeChecks.some((c) => c.status === "fail")) failed = true | ||
| const faviconChecks = await checkFaviconUrls(dir) | ||
| results.push(...faviconChecks) | ||
| if (faviconChecks.some((c) => c.status === "fail")) failed = true | ||
| } | ||
|
|
||
| return { results, failed } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: degoog-org/cli
Length of output: 8352
🏁 Script executed:
Repository: degoog-org/cli
Length of output: 11185
🏁 Script executed:
Repository: degoog-org/cli
Length of output: 21455
🏁 Script executed:
Repository: degoog-org/cli
Length of output: 21439
🏁 Script executed:
Repository: degoog-org/cli
Length of output: 9480
Make both favicon checks syntax-aware.
checkFaviconUrlssearches raw source, so a harmless comment containing/api/proxy/favicon?domain=can make Doctor fail.usesFaviconSigneralso searches raw source, so the generated slot’s comment and context type can trigger a signer-version check without a call. That check warns only whenminDegoogVersionis missing or below the minimum. Match URL construction and signer calls, not comments or type declarations.🤖 Prompt for AI Agents