diff --git a/deno.json b/deno.json index 4a79d1b793..c7fa1ab8eb 100644 --- a/deno.json +++ b/deno.json @@ -403,7 +403,7 @@ "tasks": { "setup": "deno run --allow-all scripts/setup.ts", "generate": "deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno run -A scripts/build/prebundle-bridge.ts && deno run -A scripts/build/prebundle-rsc-scripts.ts && deno run -A scripts/build/prebundle-hydration-runtime.ts", - "generate:manifests:check": "deno run -A scripts/build/generate-templates-manifest.ts --check && deno run -A scripts/build/generate-dev-ui-manifest.ts --check && deno run -A scripts/build/prebundle-hydration-runtime.ts --check", + "generate:manifests:check": "deno run -A scripts/build/generate-templates-manifest.ts --check && deno run -A scripts/build/generate-dev-ui-manifest.ts --check && deno run -A scripts/build/prebundle-hydration-runtime.ts --check && deno run -A scripts/build/prebundle-client-scripts.ts --check", "start": "deno task generate && deno run --allow-read --allow-write --allow-net --allow-env --allow-run --allow-sys --unstable-worker-options --unstable-net cli/main.ts", "start:headless": "deno task generate && deno run --allow-read --allow-write --allow-net --allow-env --allow-run --allow-sys --unstable-worker-options --unstable-net cli/main.ts --headless", "proxy": "deno task generate && deno run --allow-read --allow-write --allow-net --allow-env --allow-run --allow-sys --unstable-worker-options --unstable-net cli/main.ts serve --mode=proxy", diff --git a/scripts/build/prebundle-client-scripts.ts b/scripts/build/prebundle-client-scripts.ts index fba00e2128..b2f865f640 100644 --- a/scripts/build/prebundle-client-scripts.ts +++ b/scripts/build/prebundle-client-scripts.ts @@ -41,19 +41,64 @@ const candidatesPath = join( "framework-candidates.generated.ts", ); -async function writeFormattedTypeScriptFile(path: string, contents: string): Promise { - await Deno.writeTextFile(path, contents); - const result = await new Deno.Command("deno", { - args: ["fmt", path], - stdout: "null", +const checkMode = Deno.args.includes("--check"); +const staleFiles: string[] = []; + +/** + * Format through stdin rather than writing and formatting in place, so --check + * can compare against the same shape the write path lands on disk. + */ +async function formatTypeScript(source: string, path: string): Promise { + const fmt = new Deno.Command("deno", { + args: ["fmt", "-", "--ext", "ts"], + stdin: "piped", + stdout: "piped", stderr: "piped", - }).output(); + }).spawn(); + + const writer = fmt.stdin.getWriter(); + await writer.write(new TextEncoder().encode(source)); + await writer.close(); + + const result = await fmt.output(); if (!result.success) { const errorOutput = new TextDecoder().decode(result.stderr).trim(); + // This script generates two files; without the path the failure says + // nothing about which one to go and look at. throw new Error( `Failed to format generated file ${path}${errorOutput ? `: ${errorOutput}` : ""}`, ); } + return new TextDecoder().decode(result.stdout); +} + +/** + * Writes the generated file, or under --check reports whether the committed + * copy still matches. Stale files are collected rather than exited on, so one + * run tells you everything that needs regenerating. + */ +async function emitGeneratedFile( + path: string, + contents: string, + detail: string, +): Promise { + const formatted = await formatTypeScript(contents, path); + + if (checkMode) { + const existing = await Deno.readTextFile(path).catch(() => null); + if (existing !== formatted) { + console.error(`${path} is stale. Run deno task generate.`); + staleFiles.push(path); + return; + } + console.log(`${path} is current.`); + console.log(` ${detail}`); + return; + } + + await Deno.writeTextFile(path, formatted); + console.log(`✅ Generated ${path}`); + console.log(` ${detail}`); } console.log("[prebundle-client-scripts] Bundling client router..."); @@ -79,8 +124,7 @@ export const CLIENT_ROUTER_BUNDLE: string | undefined = ${JSON.stringify(routerB export const CLIENT_PREFETCH_BUNDLE: string | undefined = ${JSON.stringify(prefetchBundle)}; `; -await writeFormattedTypeScriptFile(templatesPath, output); -console.log(`[prebundle-client-scripts] Written to ${templatesPath}`); +await emitGeneratedFile(templatesPath, output, "client router + prefetch bundles"); // --- Extract framework component Tailwind candidates --- console.log("[prebundle-client-scripts] Extracting framework component candidates..."); @@ -133,7 +177,12 @@ const candidatesOutput = `/** export const FRAMEWORK_CANDIDATES: readonly string[] = ${JSON.stringify(sorted, null, 2).replace(/\n\]$/, ",\n]")}; `; -await writeFormattedTypeScriptFile(candidatesPath, candidatesOutput); -console.log( - `[prebundle-client-scripts] Extracted ${sorted.length} framework candidates to ${candidatesPath}`, +await emitGeneratedFile( + candidatesPath, + candidatesOutput, + `${sorted.length} framework candidates`, ); + +if (staleFiles.length > 0) { + Deno.exit(1); +} diff --git a/src/server/handlers/dev/framework-candidates.generated.ts b/src/server/handlers/dev/framework-candidates.generated.ts index 08580adc1b..65354cc904 100644 --- a/src/server/handlers/dev/framework-candidates.generated.ts +++ b/src/server/handlers/dev/framework-candidates.generated.ts @@ -168,6 +168,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "!value", "!visible)", "!w-auto", + "!warnedMissingAnchor)", "-1", "-1)", "-1,", @@ -489,6 +490,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "18a4", "18px", "19", + "19).", "19.32Q12", "19.78", "19.9502a4.4992", @@ -987,7 +989,6 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "Alternate", "Always-visible", "An", - "Anchor", "AnchorRendererProps", "AnchorRendererProps)", "Anchored", @@ -2285,6 +2286,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "DialogTrigger,", "Dims", "Disabled", + "Disclosure", "DisclosureOptions", "DisclosureOptions)", "DisclosureOptions,", @@ -3649,6 +3651,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "React.ReactNode>();", "React.ReactNode>,", "React.ReactNode>;", + "React.Ref", "React.Ref;", "React.Ref;", "React.Ref", @@ -4645,6 +4648,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "Waiting", "Walk", "Walking", + "Warn", "Was", "We", "Web", @@ -4963,6 +4967,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "[timeout]);", "[topFaded,", "[tracked,", + "[ui]", "[uncontrolledOpen,", "[update]);", "[uploadError,", @@ -5239,6 +5244,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "anchor:", "anchorRef", "anchorRef,", + "anchorRef.current", "anchorRef.current?.closest(UI_SCOPE_SELECTOR)", "anchorRef.current?.getBoundingClientRect();", "anchorRef:", @@ -5440,6 +5446,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "attachFilesLabel:", "attachFilesLabel?:", "attachFilesLabel}", + "attached", "attaching", "attachment", "attachment,", @@ -6019,6 +6026,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "cards)", "cards,", "cares", + "carried", "carries", "carry", "cascade", @@ -6443,6 +6451,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "composable", "compose", "composeRefs(forwardedRef,", + "composeRefs,", "composeRefs(", "composed", "composed).", @@ -6512,6 +6521,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "considered", "consistent", "console.error(", + "console.warn(", "const", "const,", "const;", @@ -6582,6 +6592,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "continue;", "contract", "contract):", + "contract:", "control", "control,", "control.", @@ -6767,6 +6778,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "ctx:", "ctx;", "ctx?.activeConversationId", + "ctx?.anchorRef", "ctx?.anchorRef;", "ctx?.conversations", "ctx?.open", @@ -7191,6 +7203,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "documents", "docx:", "does", + "does;", "doesn't", "domain", "don't", @@ -7353,6 +7366,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "el.value.length;", "element", "element)", + "element).", "element,", "element.", "element.getAttribute(", @@ -8477,6 +8491,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "include", "included", "includes", + "including", "increment", "indentation", "independent", @@ -9325,6 +9340,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "menu).", "menu,", "menu.", + "menu;", "menuOpen,", "menuOpen:", "menuOpen],", @@ -10212,6 +10228,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "openai", "openai/gpt-4o", "openai:", + "opened", "opening", "opens", "opens,", @@ -10629,6 +10646,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "popover).", "popover.", "popover.tsx", + "popover;", "populated", "portal", "portal,", @@ -10651,6 +10669,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "positioned", "positioning", "positioning,", + "positioning-anchor", "positions", "positive", "post-hydration", @@ -11117,6 +11136,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "ref.current?.querySelector(FOCUSABLE);", "ref:", "ref={anchorRef}", + "ref={composeRefs(", "ref={fieldContainerRef}", "ref={fileInputRef}", "ref={inputRef}", @@ -11225,6 +11245,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "render():", "render();", "render(id:", + "render,", "render-or-compose", "render.", "renderAnswerPart(", @@ -11730,6 +11751,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "serverItems]);", "session", "session's", + "session,", "session.", "sessionChat,", "sessionChat:", @@ -13171,6 +13193,8 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "ui/switch.tsx", "ui/tokens", "ui/tokens.tsx", + "unanchored", + "unanchored.", "unavailable", "unavailable).", "unavailable,", @@ -13765,7 +13789,9 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "waiting", "waits", "want", + "warnedMissingAnchor", "warning:", + "warns", "was", "way.", "way;",