From 9971b95afacdcdfde61fbc9cab912b3165023e9c Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Sat, 16 May 2026 17:55:52 +1000 Subject: [PATCH 1/4] fix(build-report): parse route exports with OXC AST Route report classification relied on regexes and a hand-written return-object scanner. That made comments look like exports and missed valid static syntax such as no-substitution template literals and numeric separators. The report now parses route modules with oxc-parser, reads top-level static exports from the AST, and keeps getStaticProps revalidate extraction scoped to the exported function body. Tests cover the prior false positives, supported literal forms, TypeScript generic syntax fallback, and Next.js-style export specifier handling. --- packages/vinext/package.json | 1 + packages/vinext/src/build/report.ts | 718 ++++++++++------------------ pnpm-lock.yaml | 231 +++++++++ pnpm-workspace.yaml | 1 + tests/build-report.test.ts | 51 +- 5 files changed, 542 insertions(+), 460 deletions(-) diff --git a/packages/vinext/package.json b/packages/vinext/package.json index c65794edf..040239212 100644 --- a/packages/vinext/package.json +++ b/packages/vinext/package.json @@ -69,6 +69,7 @@ "image-size": "catalog:", "ipaddr.js": "catalog:", "magic-string": "catalog:", + "oxc-parser": "catalog:", "vite-plugin-commonjs": "catalog:", "vite-tsconfig-paths": "catalog:", "web-vitals": "catalog:" diff --git a/packages/vinext/src/build/report.ts b/packages/vinext/src/build/report.ts index e26ef3a48..ceda7930c 100644 --- a/packages/vinext/src/build/report.ts +++ b/packages/vinext/src/build/report.ts @@ -8,9 +8,9 @@ * ? Unknown — no explicit config; likely dynamic but not confirmed * λ API — API route handler * - * Classification uses regex-based static source analysis (no module - * execution). Vite's parseAst() is NOT used because it doesn't handle - * TypeScript syntax. + * Classification uses AST-based static source analysis (no module execution). + * Runtime/prerender results are still treated as stronger evidence where + * available; AST analysis only reads top-level static exports. * * Limitation: without running the build, we cannot detect dynamic API usage * (headers(), cookies(), connection(), etc.) that implicitly forces a route @@ -21,6 +21,21 @@ import fs from "node:fs"; import path from "node:path"; +import { parseSync } from "oxc-parser"; +import type { + ArrowFunctionExpression, + BindingPattern, + BlockStatement, + Expression, + Function as OxcFunction, + FunctionBody, + ModuleExportName, + ObjectExpression, + PropertyKey, + Program, + Statement, + VariableDeclarator, +} from "oxc-parser"; import type { Route } from "../routing/pages-router.js"; import type { AppRoute } from "../routing/app-router.js"; import type { LayoutBuildClassification } from "./layout-classification-types.js"; @@ -60,44 +75,130 @@ export function getAppRouteRenderEntryPath(route: AppRouteRenderEntry): string | return null; } -// ─── Regex-based export detection ──────────────────────────────────────────── +// ─── Static export analysis ────────────────────────────────────────────────── + +type FunctionLike = OxcFunction | ArrowFunctionExpression; +type StaticNumberValue = number | false; + +function parseRouteModuleWithLang(code: string, lang: "ts" | "tsx"): Program | null { + try { + const result = parseSync(`vinext-route.${lang}`, code, { + astType: "ts", + lang, + sourceType: "module", + }); + + return result.errors.some((error) => error.severity === "Error") ? null : result.program; + } catch { + return null; + } +} + +function parseRouteModule(code: string): Program | null { + return parseRouteModuleWithLang(code, "tsx") ?? parseRouteModuleWithLang(code, "ts"); +} + +function moduleExportNameValue(name: ModuleExportName): string | null { + if (name.type === "Identifier") return name.name; + if (name.type === "Literal" && typeof name.value === "string") return name.value; + return null; +} + +function bindingName(pattern: BindingPattern): string | null { + return pattern.type === "Identifier" ? pattern.name : null; +} + +function declarationHasBindingName(declaration: Statement | null, name: string): boolean { + if (declaration === null) return false; + + if (declaration.type === "FunctionDeclaration") { + return declaration.id?.name === name; + } + + if (declaration.type !== "VariableDeclaration") return false; + + return declaration.declarations.some((declaration) => bindingName(declaration.id) === name); +} /** - * Returns true if the source code contains a named export with the given name. + * Returns true if the source code contains an export declaration with the given name. + * For re-export specifiers, this intentionally follows Next.js' static analyzer + * and checks the local/original binding name. * Handles all three common export forms: * export function foo() {} * export const foo = ... * export { foo } */ export function hasNamedExport(code: string, name: string): boolean { - // Function / generator / async function declaration - const fnRe = new RegExp(`(?:^|\\n)\\s*export\\s+(?:async\\s+)?function\\s+${name}\\b`); - if (fnRe.test(code)) return true; + const program = parseRouteModule(code); + if (!program) return false; - // Variable declaration (const / let / var) - const varRe = new RegExp(`(?:^|\\n)\\s*export\\s+(?:const|let|var)\\s+${name}\\s*[=:]`); - if (varRe.test(code)) return true; + for (const node of program.body) { + if (node.type !== "ExportNamedDeclaration") continue; - // Re-export specifier: export { foo } or export { foo as bar } - const reRe = new RegExp(`export\\s*\\{[^}]*\\b${name}\\b[^}]*\\}`); - if (reRe.test(code)) return true; + if (declarationHasBindingName(node.declaration, name)) return true; + for (const specifier of node.specifiers) { + if (moduleExportNameValue(specifier.local) === name) { + return true; + } + } + } return false; } +function unwrapStaticExpression(expression: Expression): Expression { + let current = expression; + while ( + current.type === "ParenthesizedExpression" || + current.type === "TSAsExpression" || + current.type === "TSSatisfiesExpression" || + current.type === "TSTypeAssertion" || + current.type === "TSNonNullExpression" + ) { + current = current.expression; + } + return current; +} + +function findExportedConstInitializer(code: string, name: string): Expression | null { + const program = parseRouteModule(code); + if (!program) return null; + + for (const node of program.body) { + if (node.type !== "ExportNamedDeclaration") continue; + const declaration = node.declaration; + if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") continue; + + for (const declarator of declaration.declarations) { + if (bindingName(declarator.id) === name) { + return declarator.init; + } + } + } + + return null; +} + /** * Extracts the string value of `export const = "value"`. - * Handles optional TypeScript type annotations: - * export const dynamic: string = "force-dynamic" + * Handles TypeScript annotations/assertions and no-substitution template literals. * Returns null if the export is absent or not a string literal. */ export function extractExportConstString(code: string, name: string): string | null { - const re = new RegExp( - `^\\s*export\\s+const\\s+${name}\\s*(?::[^=]+)?\\s*=\\s*['"]([^'"]+)['"]`, - "m", - ); - const m = re.exec(code); - return m ? m[1] : null; + const initializer = findExportedConstInitializer(code, name); + if (initializer === null) return null; + + const expression = unwrapStaticExpression(initializer); + if (expression.type === "Literal" && typeof expression.value === "string") { + return expression.value; + } + + if (expression.type === "TemplateLiteral" && expression.expressions.length === 0) { + return expression.quasis[0]?.value.cooked ?? expression.quasis[0]?.value.raw ?? null; + } + + return null; } /** @@ -105,18 +206,16 @@ export function extractExportConstString(code: string, name: string): string | n * Supports integers, decimals, negative values, `Infinity`, and `false`. * `false` is returned as `Infinity` because `export const revalidate = false` * means "cache indefinitely" in Next.js segment config. - * Handles optional TypeScript type annotations. + * Handles TypeScript annotations/assertions and JavaScript numeric separators. * Returns null if the export is absent or not a number/`false`. */ export function extractExportConstNumber(code: string, name: string): number | null { - const re = new RegExp( - `^\\s*export\\s+const\\s+${name}\\s*(?::[^=]+)?\\s*=\\s*(-?\\d+(?:\\.\\d+)?|Infinity|false)(?![\\w$])`, - "m", - ); - const m = re.exec(code); - if (!m) return null; - if (m[1] === "Infinity" || m[1] === "false") return Infinity; - return parseFloat(m[1]); + const initializer = findExportedConstInitializer(code, name); + if (initializer === null) return null; + + const value = extractStaticNumberValue(initializer); + if (value === null) return null; + return value === false ? Infinity : value; } /** @@ -131,501 +230,202 @@ export function extractExportConstNumber(code: string, name: string): number | n * null — no `revalidate` key found (fully static) */ export function extractGetStaticPropsRevalidate(code: string): number | false | null { - const returnObjects = extractGetStaticPropsReturnObjects(code); + const program = parseRouteModule(code); + if (!program) return extractWrappedGetStaticPropsRevalidate(code); - if (returnObjects) { - for (const searchSpace of returnObjects) { - const revalidate = extractTopLevelRevalidateValue(searchSpace); - if (revalidate !== null) return revalidate; - } - return null; - } + const getStaticProps = findExportedGetStaticProps(program); + if (getStaticProps === "external") return null; + if (getStaticProps === null) return extractWrappedGetStaticPropsRevalidate(code); - const m = /\brevalidate\s*:\s*(-?\d+(?:\.\d+)?|Infinity|false)\b/.exec(code); - if (!m) return null; - if (m[1] === "false") return false; - if (m[1] === "Infinity") return Infinity; - return parseFloat(m[1]); + return extractFunctionRevalidate(getStaticProps); } -function extractTopLevelRevalidateValue(code: string): number | false | null { - let braceDepth = 0; - let parenDepth = 0; - let bracketDepth = 0; - let quote: '"' | "'" | "`" | null = null; - let inLineComment = false; - let inBlockComment = false; - - for (let i = 0; i < code.length; i++) { - const char = code[i]; - const next = code[i + 1]; - - if (inLineComment) { - if (char === "\n") inLineComment = false; - continue; - } - - if (inBlockComment) { - if (char === "*" && next === "/") { - inBlockComment = false; - i++; - } - continue; - } - - if (quote) { - if (char === "\\") { - i++; - continue; - } - if (char === quote) quote = null; - continue; - } - - if (char === "/" && next === "/") { - inLineComment = true; - i++; - continue; - } - - if (char === "/" && next === "*") { - inBlockComment = true; - i++; - continue; - } - - if (char === '"' || char === "'" || char === "`") { - quote = char; - continue; - } - - if (char === "{") { - braceDepth++; - continue; - } - - if (char === "}") { - braceDepth--; - continue; - } - - if (char === "(") { - parenDepth++; - continue; - } - - if (char === ")") { - parenDepth--; - continue; - } - - if (char === "[") { - bracketDepth++; - continue; - } - - if (char === "]") { - bracketDepth--; - continue; - } - - if ( - braceDepth === 1 && - parenDepth === 0 && - bracketDepth === 0 && - matchesKeywordAt(code, i, "revalidate") - ) { - const colonIndex = findNextNonWhitespaceIndex(code, i + "revalidate".length); - if (colonIndex === -1 || code[colonIndex] !== ":") continue; - - const valueStart = findNextNonWhitespaceIndex(code, colonIndex + 1); - if (valueStart === -1) return null; +function extractStaticNumberValue(expression: Expression): StaticNumberValue | null { + const unwrapped = unwrapStaticExpression(expression); - const valueMatch = /^(-?\d+(?:\.\d+)?|Infinity|false)\b/.exec(code.slice(valueStart)); - if (!valueMatch) return null; - if (valueMatch[1] === "false") return false; - if (valueMatch[1] === "Infinity") return Infinity; - return parseFloat(valueMatch[1]); - } - } - - return null; -} - -function extractGetStaticPropsReturnObjects(code: string): string[] | null { - const declarationMatch = - /(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+getStaticProps\b|(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+getStaticProps\b/.exec( - code, - ); - if (!declarationMatch) { - // A file can re-export getStaticProps from another module without defining - // it locally. In that case we can't safely infer revalidate from this file, - // so skip the whole-file fallback to avoid unrelated false positives. - if (/(?:^|\n)\s*export\s*\{[^}]*\bgetStaticProps\b[^}]*\}\s*from\b/.test(code)) { - return []; - } + if (unwrapped.type === "Literal") { + if (typeof unwrapped.value === "number") return unwrapped.value; + if (unwrapped.value === false) return false; return null; } - const declaration = extractGetStaticPropsDeclaration(code, declarationMatch); - if (declaration === null) return []; - - const returnObjects = declaration.trimStart().startsWith("{") - ? collectReturnObjectsFromFunctionBody(declaration) - : []; - - if (returnObjects.length > 0) return returnObjects; - - const arrowMatch = declaration.search(/=>\s*\(\s*\{/); - // getStaticProps was found but contains no return objects — return empty - // (non-null signals the caller to skip the whole-file fallback). - if (arrowMatch === -1) return []; - - const braceStart = declaration.indexOf("{", arrowMatch); - if (braceStart === -1) return []; - - const braceEnd = findMatchingBrace(declaration, braceStart); - if (braceEnd === -1) return []; - - return [declaration.slice(braceStart, braceEnd + 1)]; -} - -function extractGetStaticPropsDeclaration( - code: string, - declarationMatch: RegExpExecArray, -): string | null { - const declarationStart = declarationMatch.index; - const declarationText = declarationMatch[0]; - const declarationTail = code.slice(declarationStart); - - if (declarationText.includes("function getStaticProps")) { - return extractFunctionBody(code, declarationStart + declarationText.length); + if (unwrapped.type === "Identifier" && unwrapped.name === "Infinity") { + return Infinity; } - const functionExpressionMatch = /(?:async\s+)?function\b/.exec(declarationTail); - if (functionExpressionMatch) { - return extractFunctionBody(declarationTail, functionExpressionMatch.index); + if (unwrapped.type === "UnaryExpression") { + const argument = extractStaticNumberValue(unwrapped.argument); + if (typeof argument !== "number") return null; + if (unwrapped.operator === "-") return -argument; + if (unwrapped.operator === "+") return argument; } - const blockBodyMatch = /=>\s*\{/.exec(declarationTail); - if (blockBodyMatch) { - const braceStart = declarationTail.indexOf("{", blockBodyMatch.index); - if (braceStart === -1) return null; - - const braceEnd = findMatchingBrace(declarationTail, braceStart); - if (braceEnd === -1) return null; - - return declarationTail.slice(braceStart, braceEnd + 1); - } - - const implicitArrowMatch = declarationTail.search(/=>\s*\(\s*\{/); - if (implicitArrowMatch === -1) return null; - - const implicitBraceStart = declarationTail.indexOf("{", implicitArrowMatch); - if (implicitBraceStart === -1) return null; - - const implicitBraceEnd = findMatchingBrace(declarationTail, implicitBraceStart); - if (implicitBraceEnd === -1) return null; - - return declarationTail.slice(0, implicitBraceEnd + 1); -} - -function extractFunctionBody(code: string, functionStart: number): string | null { - const bodyEnd = findFunctionBodyEnd(code, functionStart); - if (bodyEnd === -1) return null; - - const paramsStart = code.indexOf("(", functionStart); - if (paramsStart === -1) return null; - - const paramsEnd = findMatchingParen(code, paramsStart); - if (paramsEnd === -1) return null; - - const bodyStart = code.indexOf("{", paramsEnd + 1); - if (bodyStart === -1) return null; - - return code.slice(bodyStart, bodyEnd + 1); + return null; } -function collectReturnObjectsFromFunctionBody(code: string): string[] { - const returnObjects: string[] = []; - let quote: '"' | "'" | "`" | null = null; - let inLineComment = false; - let inBlockComment = false; +function findExportedGetStaticProps(program: Program): FunctionLike | "external" | null { + const localExportNames = new Set(); - for (let i = 0; i < code.length; i++) { - const char = code[i]; - const next = code[i + 1]; - - if (inLineComment) { - if (char === "\n") inLineComment = false; - continue; - } - - if (inBlockComment) { - if (char === "*" && next === "/") { - inBlockComment = false; - i++; - } - continue; - } + for (const node of program.body) { + if (node.type !== "ExportNamedDeclaration") continue; - if (quote) { - if (char === "\\") { - i++; - continue; - } - if (char === quote) quote = null; - continue; + const declaration = node.declaration; + if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "getStaticProps") { + return declaration; } - if (char === "/" && next === "/") { - inLineComment = true; - i++; - continue; + if (declaration?.type === "VariableDeclaration") { + const direct = findFunctionLikeVariable(declaration.declarations, "getStaticProps"); + if (direct) return direct; } - if (char === "/" && next === "*") { - inBlockComment = true; - i++; - continue; + for (const specifier of node.specifiers) { + const localName = moduleExportNameValue(specifier.local); + if (localName !== "getStaticProps") continue; + if (node.source !== null) return "external"; + localExportNames.add(localName); } + } - if (char === '"' || char === "'" || char === "`") { - quote = char; - continue; - } + if (localExportNames.size === 0) return null; - if (matchesKeywordAt(code, i, "function")) { - const nestedBodyEnd = findFunctionBodyEnd(code, i); - if (nestedBodyEnd !== -1) { - i = nestedBodyEnd; - } - continue; + for (const node of program.body) { + if (node.type === "FunctionDeclaration" && node.id && localExportNames.has(node.id.name)) { + return node; } - if (matchesKeywordAt(code, i, "class")) { - const classBodyEnd = findClassBodyEnd(code, i); - if (classBodyEnd !== -1) { - i = classBodyEnd; + if (node.type === "VariableDeclaration") { + for (const localName of localExportNames) { + const local = findFunctionLikeVariable(node.declarations, localName); + if (local) return local; } - continue; } + } - if (char === "=" && next === ">") { - const nestedBodyEnd = findArrowFunctionBodyEnd(code, i); - if (nestedBodyEnd !== -1) { - i = nestedBodyEnd; - } - continue; - } + return null; +} +function findFunctionLikeVariable( + declarations: readonly VariableDeclarator[], + name: string, +): FunctionLike | null { + for (const declaration of declarations) { + if (bindingName(declaration.id) !== name || declaration.init === null) continue; + const initializer = unwrapStaticExpression(declaration.init); if ( - (char >= "A" && char <= "Z") || - (char >= "a" && char <= "z") || - char === "_" || - char === "$" || - char === "*" + initializer.type === "FunctionExpression" || + initializer.type === "ArrowFunctionExpression" ) { - const methodBodyEnd = findObjectMethodBodyEnd(code, i); - if (methodBodyEnd !== -1) { - i = methodBodyEnd; - continue; - } - } - - if (matchesKeywordAt(code, i, "return")) { - const braceStart = findNextNonWhitespaceIndex(code, i + "return".length); - if (braceStart === -1 || code[braceStart] !== "{") continue; - - const braceEnd = findMatchingBrace(code, braceStart); - if (braceEnd === -1) continue; - - returnObjects.push(code.slice(braceStart, braceEnd + 1)); - i = braceEnd; + return initializer; } } - return returnObjects; + return null; } -function findFunctionBodyEnd(code: string, functionStart: number): number { - const paramsStart = code.indexOf("(", functionStart); - if (paramsStart === -1) return -1; +function extractWrappedGetStaticPropsRevalidate(code: string): number | false | null { + const program = parseRouteModule(`function __vinextGetStaticProps() {\n${code}\n}`); + if (!program) return null; - const paramsEnd = findMatchingParen(code, paramsStart); - if (paramsEnd === -1) return -1; - - const bodyStart = code.indexOf("{", paramsEnd + 1); - if (bodyStart === -1) return -1; + for (const node of program.body) { + if (node.type === "FunctionDeclaration" && node.id?.name === "__vinextGetStaticProps") { + return extractFunctionRevalidate(node); + } + } - return findMatchingBrace(code, bodyStart); + return null; } -function findClassBodyEnd(code: string, classStart: number): number { - const bodyStart = code.indexOf("{", classStart + "class".length); - if (bodyStart === -1) return -1; +function extractFunctionRevalidate(fn: FunctionLike): number | false | null { + if (fn.type === "ArrowFunctionExpression" && fn.body.type !== "BlockStatement") { + const expression = unwrapStaticExpression(fn.body); + return expression.type === "ObjectExpression" ? extractObjectRevalidate(expression) : null; + } - return findMatchingBrace(code, bodyStart); + if (!fn.body || fn.body.type !== "BlockStatement") return null; + return extractBlockRevalidate(fn.body); } -function findArrowFunctionBodyEnd(code: string, arrowIndex: number): number { - const bodyStart = findNextNonWhitespaceIndex(code, arrowIndex + 2); - if (bodyStart === -1 || code[bodyStart] !== "{") return -1; +function extractBlockRevalidate(block: BlockStatement | FunctionBody): number | false | null { + for (const statement of block.body) { + const result = extractStatementRevalidate(statement); + if (result !== null) return result; + } - return findMatchingBrace(code, bodyStart); + return null; } -function findObjectMethodBodyEnd(code: string, start: number): number { - let i = start; - - if (matchesKeywordAt(code, i, "async")) { - const afterAsync = findNextNonWhitespaceIndex(code, i + "async".length); - if (afterAsync === -1) return -1; - if (code[afterAsync] !== "(") { - i = afterAsync; - } +function extractStatementRevalidate(statement: Statement): number | false | null { + if (statement.type === "ReturnStatement") { + if (!statement.argument) return null; + const argument = unwrapStaticExpression(statement.argument); + return argument.type === "ObjectExpression" ? extractObjectRevalidate(argument) : null; } - if (code[i] === "*") { - i = findNextNonWhitespaceIndex(code, i + 1); - if (i === -1) return -1; + if (statement.type === "BlockStatement") { + return extractBlockRevalidate(statement); } - if (!/[A-Za-z_$]/.test(code[i] ?? "")) return -1; - - const nameStart = i; - while (/[A-Za-z0-9_$]/.test(code[i] ?? "")) i++; - const name = code.slice(nameStart, i); + if (statement.type === "IfStatement") { + return ( + extractStatementRevalidate(statement.consequent) ?? + (statement.alternate ? extractStatementRevalidate(statement.alternate) : null) + ); + } if ( - name === "if" || - name === "for" || - name === "while" || - name === "switch" || - name === "catch" || - name === "function" || - name === "return" || - name === "const" || - name === "let" || - name === "var" || - name === "new" + statement.type === "ForStatement" || + statement.type === "ForInStatement" || + statement.type === "ForOfStatement" || + statement.type === "WhileStatement" || + statement.type === "DoWhileStatement" || + statement.type === "WithStatement" || + statement.type === "LabeledStatement" ) { - return -1; + return extractStatementRevalidate(statement.body); } - if (name === "get" || name === "set") { - const afterAccessor = findNextNonWhitespaceIndex(code, i); - if (afterAccessor === -1) return -1; - if (code[afterAccessor] !== "(") { - i = afterAccessor; - if (!/[A-Za-z_$]/.test(code[i] ?? "")) return -1; - while (/[A-Za-z0-9_$]/.test(code[i] ?? "")) i++; + if (statement.type === "SwitchStatement") { + for (const switchCase of statement.cases) { + for (const consequent of switchCase.consequent) { + const result = extractStatementRevalidate(consequent); + if (result !== null) return result; + } } + return null; } - const paramsStart = findNextNonWhitespaceIndex(code, i); - if (paramsStart === -1 || code[paramsStart] !== "(") return -1; - - const paramsEnd = findMatchingParen(code, paramsStart); - if (paramsEnd === -1) return -1; - - const bodyStart = findNextNonWhitespaceIndex(code, paramsEnd + 1); - if (bodyStart === -1 || code[bodyStart] !== "{") return -1; - - return findMatchingBrace(code, bodyStart); -} - -function findNextNonWhitespaceIndex(code: string, start: number): number { - for (let i = start; i < code.length; i++) { - if (!/\s/.test(code[i])) return i; + if (statement.type === "TryStatement") { + return ( + extractBlockRevalidate(statement.block) ?? + (statement.handler ? extractBlockRevalidate(statement.handler.body) : null) ?? + (statement.finalizer ? extractBlockRevalidate(statement.finalizer) : null) + ); } - return -1; -} -function matchesKeywordAt(code: string, index: number, keyword: string): boolean { - const before = index === 0 ? "" : code[index - 1]; - const after = code[index + keyword.length] ?? ""; - return ( - code.startsWith(keyword, index) && - (before === "" || !/[A-Za-z0-9_$]/.test(before)) && - (after === "" || !/[A-Za-z0-9_$]/.test(after)) - ); -} - -function findMatchingBrace(code: string, start: number): number { - return findMatchingToken(code, start, "{", "}"); -} - -function findMatchingParen(code: string, start: number): number { - return findMatchingToken(code, start, "(", ")"); + return null; } -function findMatchingToken( - code: string, - start: number, - openToken: string, - closeToken: string, -): number { - let depth = 0; - let quote: '"' | "'" | "`" | null = null; - let inLineComment = false; - let inBlockComment = false; - - for (let i = start; i < code.length; i++) { - const char = code[i]; - const next = code[i + 1]; - - if (inLineComment) { - if (char === "\n") inLineComment = false; - continue; - } - - if (inBlockComment) { - if (char === "*" && next === "/") { - inBlockComment = false; - i++; - } - continue; - } - - if (quote) { - if (char === "\\") { - i++; - continue; - } - if (char === quote) quote = null; - continue; - } - - if (char === "/" && next === "/") { - inLineComment = true; - i++; - continue; - } - - if (char === "/" && next === "*") { - inBlockComment = true; - i++; - continue; - } - - if (char === '"' || char === "'" || char === "`") { - quote = char; - continue; - } - - if (char === openToken) { - depth++; +function extractObjectRevalidate(object: ObjectExpression): number | false | null { + for (const property of object.properties) { + if ( + property.type !== "Property" || + property.computed || + propertyName(property.key) !== "revalidate" + ) { continue; } - if (char === closeToken) { - depth--; - if (depth === 0) return i; - } + return extractStaticNumberValue(property.value); } - return -1; + return null; +} + +function propertyName(key: PropertyKey): string | null { + if (key.type === "Identifier") return key.name; + if (key.type === "Literal" && typeof key.value === "string") return key.value; + return null; } // ─── Layout segment config classification ──────────────────────────────────── diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6df84775f..598f4a348 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,6 +162,9 @@ catalogs: nuqs: specifier: ^2.8.8 version: 2.8.8 + oxc-parser: + specifier: 0.130.0 + version: 0.130.0 playwright: specifier: ^1.52.0 version: 1.58.2 @@ -902,6 +905,9 @@ importers: magic-string: specifier: 'catalog:' version: 0.30.21 + oxc-parser: + specifier: 'catalog:' + version: 0.130.0 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.21 version: '@voidzero-dev/vite-plus-core@0.1.21(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.8.3)' @@ -2434,42 +2440,84 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.130.0': + resolution: {integrity: sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm64@0.127.0': resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.130.0': + resolution: {integrity: sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-darwin-arm64@0.127.0': resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.130.0': + resolution: {integrity: sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.127.0': resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.130.0': + resolution: {integrity: sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-freebsd-x64@0.127.0': resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.130.0': + resolution: {integrity: sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': + resolution: {integrity: sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': + resolution: {integrity: sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2477,6 +2525,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.130.0': + resolution: {integrity: sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-musl@0.127.0': resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2484,6 +2539,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.130.0': + resolution: {integrity: sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2491,6 +2553,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': + resolution: {integrity: sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2498,6 +2567,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': + resolution: {integrity: sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2505,6 +2581,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.130.0': + resolution: {integrity: sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2512,6 +2595,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.130.0': + resolution: {integrity: sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.127.0': resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2519,6 +2609,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.130.0': + resolution: {integrity: sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-musl@0.127.0': resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2526,35 +2623,71 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.130.0': + resolution: {integrity: sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-openharmony-arm64@0.127.0': resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.130.0': + resolution: {integrity: sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-wasm32-wasi@0.127.0': resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.130.0': + resolution: {integrity: sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.130.0': + resolution: {integrity: sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.130.0': + resolution: {integrity: sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.127.0': resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.130.0': + resolution: {integrity: sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/runtime@0.129.0': resolution: {integrity: sha512-0+S67blQakgeNqoKGozOUp5rQBrz2ynXZ2QIINXZPiafsD0YL0UogB9hAWc1S7k6VSNwKYC/N7MqT0V6IzpHkQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2565,6 +2698,9 @@ packages: '@oxc-project/types@0.129.0': resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -5741,6 +5877,10 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.130.0: + resolution: {integrity: sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} @@ -7325,51 +7465,99 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.130.0': + optional: true + '@oxc-parser/binding-android-arm64@0.127.0': optional: true + '@oxc-parser/binding-android-arm64@0.130.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.127.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.130.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.127.0': optional: true + '@oxc-parser/binding-darwin-x64@0.130.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.127.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.130.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.130.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.130.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.130.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.130.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.130.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.130.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.127.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.130.0': + optional: true + '@oxc-parser/binding-wasm32-wasi@0.127.0': dependencies: '@emnapi/core': 1.9.2 @@ -7377,21 +7565,39 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true + '@oxc-parser/binding-wasm32-wasi@0.130.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.130.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.130.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.130.0': + optional: true + '@oxc-project/runtime@0.129.0': {} '@oxc-project/types@0.127.0': {} '@oxc-project/types@0.129.0': {} + '@oxc-project/types@0.130.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -10344,6 +10550,31 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + oxc-parser@0.130.0: + dependencies: + '@oxc-project/types': 0.130.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.130.0 + '@oxc-parser/binding-android-arm64': 0.130.0 + '@oxc-parser/binding-darwin-arm64': 0.130.0 + '@oxc-parser/binding-darwin-x64': 0.130.0 + '@oxc-parser/binding-freebsd-x64': 0.130.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.130.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.130.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.130.0 + '@oxc-parser/binding-linux-arm64-musl': 0.130.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.130.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.130.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.130.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.130.0 + '@oxc-parser/binding-linux-x64-gnu': 0.130.0 + '@oxc-parser/binding-linux-x64-musl': 0.130.0 + '@oxc-parser/binding-openharmony-arm64': 0.130.0 + '@oxc-parser/binding-wasm32-wasi': 0.130.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.130.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.130.0 + '@oxc-parser/binding-win32-x64-msvc': 0.130.0 + oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.19.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 35af793d9..6a475cace 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,6 +67,7 @@ catalog: next-view-transitions: ^0.3.5 nitro: npm:nitro-nightly@latest nuqs: ^2.8.8 + oxc-parser: 0.130.0 playwright: ^1.52.0 postcss: 8.5.10 react: ^19.2.6 diff --git a/tests/build-report.test.ts b/tests/build-report.test.ts index f753da79a..987e0276a 100644 --- a/tests/build-report.test.ts +++ b/tests/build-report.test.ts @@ -1,7 +1,7 @@ /** * Build report tests — verifies route classification, formatting, and sorting. * - * Tests the regex-based export detection helpers and the classification + * Tests the static export detection helpers and the classification * logic for both Pages Router and App Router routes, using real fixture files * where integration testing is needed. */ @@ -58,6 +58,10 @@ describe("hasNamedExport", () => { expect(hasNamedExport("export { getStaticProps as gsp };", "getStaticProps")).toBe(true); }); + it("does not detect alias exported under the searched name", () => { + expect(hasNamedExport("export { gsp as getStaticProps };", "getStaticProps")).toBe(false); + }); + it("returns false when export is absent", () => { expect(hasNamedExport("export default function Page() {}", "getStaticProps")).toBe(false); }); @@ -77,6 +81,13 @@ describe("hasNamedExport", () => { it("detects TypeScript-annotated const", () => { expect(hasNamedExport("export const dynamic: string = 'force-dynamic';", "dynamic")).toBe(true); }); + + it("ignores export-shaped text inside block comments", () => { + const code = `/* +export function getServerSideProps() {} +*/`; + expect(hasNamedExport(code, "getServerSideProps")).toBe(false); + }); }); // ─── extractExportConstString ───────────────────────────────────────────────── @@ -100,6 +111,19 @@ describe("extractExportConstString", () => { ); }); + it("extracts no-substitution template literal values", () => { + expect(extractExportConstString("export const dynamic = `force-dynamic`;", "dynamic")).toBe( + "force-dynamic", + ); + }); + + it("ignores export-shaped string values inside block comments", () => { + const code = `/* +export const dynamic = "force-dynamic"; +*/`; + expect(extractExportConstString(code, "dynamic")).toBeNull(); + }); + it("returns null when export is absent", () => { expect(extractExportConstString("export const revalidate = 60;", "dynamic")).toBeNull(); }); @@ -142,6 +166,16 @@ describe("extractExportConstNumber", () => { ); }); + it("extracts numeric separators", () => { + expect(extractExportConstNumber("export const revalidate = 60_000;", "revalidate")).toBe(60000); + }); + + it("extracts config from TypeScript files with generic arrow syntax", () => { + const code = `const identity = (value: T) => value; +export const revalidate = 60;`; + expect(extractExportConstNumber(code, "revalidate")).toBe(60); + }); + it("returns null when export is absent", () => { expect(extractExportConstNumber("export const dynamic = 'auto';", "revalidate")).toBeNull(); }); @@ -324,12 +358,27 @@ export { getStaticProps } from "./shared"; expect(extractGetStaticPropsRevalidate(code)).toBeNull(); }); + it("ignores an alias exported under the getStaticProps name", () => { + const code = `const gsp = async () => ({ props: {}, revalidate: 60 }); + +export { gsp as getStaticProps }; +`; + expect(extractGetStaticPropsRevalidate(code)).toBeNull(); + }); + it("handles inline comment after value (fixture file style)", () => { // From tests/fixtures/pages-basic/pages/isr-test.tsx: // revalidate: 1, // Revalidate every 1 second const code = `return { props: {}, revalidate: 1, // comment\n};`; expect(extractGetStaticPropsRevalidate(code)).toBe(1); }); + + it("extracts revalidate from numeric separators in getStaticProps", () => { + const code = `export async function getStaticProps() { + return { props: {}, revalidate: 60_000 }; +}`; + expect(extractGetStaticPropsRevalidate(code)).toBe(60000); + }); }); // ─── classifyPagesRoute (integration — real fixture files) ──────────────────── From 803f6eb36b6cdd97259d61202a8f44ea8ecb2629 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Sat, 16 May 2026 21:53:15 +1000 Subject: [PATCH 2/4] fix(cli): lazy-load build report generation The CLI imported the route report module at startup, which also loaded the native parser dependency added for build report classification. That widened the parser and native binding path to non-build commands. Load the build report module inside buildApp() immediately before printing the report so dev, start, check, lint, deploy, and help paths do not touch the parser-backed report code. --- packages/vinext/src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vinext/src/cli.ts b/packages/vinext/src/cli.ts index e64efb2a7..fac7702f8 100644 --- a/packages/vinext/src/cli.ts +++ b/packages/vinext/src/cli.ts @@ -14,7 +14,6 @@ */ import vinext from "./index.js"; -import { printBuildReport } from "./build/report.js"; import { runPrerender } from "./build/run-prerender.js"; import path from "node:path"; import fs from "node:fs"; @@ -591,6 +590,7 @@ async function buildApp() { // Opt-in via --precompress CLI flag or `precompress: true` in plugin options. process.stdout.write("\x1b[0m"); + const { printBuildReport } = await import("./build/report.js"); await printBuildReport({ root: process.cwd(), pageExtensions: resolvedNextConfig.pageExtensions, From 088fef5c5b976d61a9dd5b154570c4b7700a1b25 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Sun, 17 May 2026 03:28:36 +1000 Subject: [PATCH 3/4] perf(build-report): reuse parsed route modules Route classification reparsed the same source for each static export query. With the TSX-to-TS fallback, a single route could invoke the parser several times before producing one report row. Thread the parsed OXC Program through internal route-report helpers so classifier paths parse each source once while preserving the public string-based helper signatures. Also clarifies the synthetic getStaticProps wrapper path and the unsupported unary-operator branch. --- packages/vinext/src/build/report.ts | 70 ++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/packages/vinext/src/build/report.ts b/packages/vinext/src/build/report.ts index ceda7930c..cc05e84e8 100644 --- a/packages/vinext/src/build/report.ts +++ b/packages/vinext/src/build/report.ts @@ -132,7 +132,10 @@ function declarationHasBindingName(declaration: Statement | null, name: string): export function hasNamedExport(code: string, name: string): boolean { const program = parseRouteModule(code); if (!program) return false; + return hasNamedExportInProgram(program, name); +} +function hasNamedExportInProgram(program: Program, name: string): boolean { for (const node of program.body) { if (node.type !== "ExportNamedDeclaration") continue; @@ -164,7 +167,10 @@ function unwrapStaticExpression(expression: Expression): Expression { function findExportedConstInitializer(code: string, name: string): Expression | null { const program = parseRouteModule(code); if (!program) return null; + return findExportedConstInitializerInProgram(program, name); +} +function findExportedConstInitializerInProgram(program: Program, name: string): Expression | null { for (const node of program.body) { if (node.type !== "ExportNamedDeclaration") continue; const declaration = node.declaration; @@ -187,6 +193,14 @@ function findExportedConstInitializer(code: string, name: string): Expression | */ export function extractExportConstString(code: string, name: string): string | null { const initializer = findExportedConstInitializer(code, name); + return extractStringFromConstInitializer(initializer); +} + +function extractExportConstStringFromProgram(program: Program, name: string): string | null { + return extractStringFromConstInitializer(findExportedConstInitializerInProgram(program, name)); +} + +function extractStringFromConstInitializer(initializer: Expression | null): string | null { if (initializer === null) return null; const expression = unwrapStaticExpression(initializer); @@ -211,6 +225,14 @@ export function extractExportConstString(code: string, name: string): string | n */ export function extractExportConstNumber(code: string, name: string): number | null { const initializer = findExportedConstInitializer(code, name); + return extractNumberFromConstInitializer(initializer); +} + +function extractExportConstNumberFromProgram(program: Program, name: string): number | null { + return extractNumberFromConstInitializer(findExportedConstInitializerInProgram(program, name)); +} + +function extractNumberFromConstInitializer(initializer: Expression | null): number | null { if (initializer === null) return null; const value = extractStaticNumberValue(initializer); @@ -232,10 +254,16 @@ export function extractExportConstNumber(code: string, name: string): number | n export function extractGetStaticPropsRevalidate(code: string): number | false | null { const program = parseRouteModule(code); if (!program) return extractWrappedGetStaticPropsRevalidate(code); + return extractGetStaticPropsRevalidateFromProgram(program, code); +} +function extractGetStaticPropsRevalidateFromProgram( + program: Program, + fallbackCode: string, +): number | false | null { const getStaticProps = findExportedGetStaticProps(program); if (getStaticProps === "external") return null; - if (getStaticProps === null) return extractWrappedGetStaticPropsRevalidate(code); + if (getStaticProps === null) return extractWrappedGetStaticPropsRevalidate(fallbackCode); return extractFunctionRevalidate(getStaticProps); } @@ -258,13 +286,14 @@ function extractStaticNumberValue(expression: Expression): StaticNumberValue | n if (typeof argument !== "number") return null; if (unwrapped.operator === "-") return -argument; if (unwrapped.operator === "+") return argument; + return null; } return null; } function findExportedGetStaticProps(program: Program): FunctionLike | "external" | null { - const localExportNames = new Set(); + let hasLocalGetStaticPropsExport = false; for (const node of program.body) { if (node.type !== "ExportNamedDeclaration") continue; @@ -283,22 +312,20 @@ function findExportedGetStaticProps(program: Program): FunctionLike | "external" const localName = moduleExportNameValue(specifier.local); if (localName !== "getStaticProps") continue; if (node.source !== null) return "external"; - localExportNames.add(localName); + hasLocalGetStaticPropsExport = true; } } - if (localExportNames.size === 0) return null; + if (!hasLocalGetStaticPropsExport) return null; for (const node of program.body) { - if (node.type === "FunctionDeclaration" && node.id && localExportNames.has(node.id.name)) { + if (node.type === "FunctionDeclaration" && node.id?.name === "getStaticProps") { return node; } if (node.type === "VariableDeclaration") { - for (const localName of localExportNames) { - const local = findFunctionLikeVariable(node.declarations, localName); - if (local) return local; - } + const local = findFunctionLikeVariable(node.declarations, "getStaticProps"); + if (local) return local; } } @@ -324,6 +351,8 @@ function findFunctionLikeVariable( } function extractWrappedGetStaticPropsRevalidate(code: string): number | false | null { + // Exported helpers are also used by tests with bare `return { ... }` fragments, + // which are not valid module source until wrapped in a synthetic function. const program = parseRouteModule(`function __vinextGetStaticProps() {\n${code}\n}`); if (!program) return null; @@ -443,7 +472,8 @@ function propertyName(key: PropertyKey): string | null { * (`revalidate = 0` → dynamic, `revalidate = Infinity` → static) are decisive. */ export function classifyLayoutSegmentConfig(code: string): LayoutBuildClassification { - const dynamicValue = extractExportConstString(code, "dynamic"); + const program = parseRouteModule(code); + const dynamicValue = program ? extractExportConstStringFromProgram(program, "dynamic") : null; if (dynamicValue === "force-dynamic") { return { kind: "dynamic", @@ -457,7 +487,9 @@ export function classifyLayoutSegmentConfig(code: string): LayoutBuildClassifica }; } - const revalidateValue = extractExportConstNumber(code, "revalidate"); + const revalidateValue = program + ? extractExportConstNumberFromProgram(program, "revalidate") + : null; if (revalidateValue === Infinity) { return { kind: "static", @@ -499,12 +531,14 @@ export function classifyPagesRoute(filePath: string): { return { type: "unknown" }; } - if (hasNamedExport(code, "getServerSideProps")) { + const program = parseRouteModule(code); + + if (program && hasNamedExportInProgram(program, "getServerSideProps")) { return { type: "ssr" }; } - if (hasNamedExport(code, "getStaticProps")) { - const revalidate = extractGetStaticPropsRevalidate(code); + if (program && hasNamedExportInProgram(program, "getStaticProps")) { + const revalidate = extractGetStaticPropsRevalidateFromProgram(program, code); if (revalidate === null || revalidate === false || revalidate === Infinity) { return { type: "static" }; @@ -546,8 +580,10 @@ export function classifyAppRoute( return { type: "unknown" }; } + const program = parseRouteModule(code); + // Check `export const dynamic` - const dynamicValue = extractExportConstString(code, "dynamic"); + const dynamicValue = program ? extractExportConstStringFromProgram(program, "dynamic") : null; if (dynamicValue === "force-dynamic") { return { type: "ssr" }; } @@ -558,7 +594,9 @@ export function classifyAppRoute( } // Check `export const revalidate` - const revalidateValue = extractExportConstNumber(code, "revalidate"); + const revalidateValue = program + ? extractExportConstNumberFromProgram(program, "revalidate") + : null; if (revalidateValue !== null) { if (revalidateValue === Infinity) return { type: "static" }; if (revalidateValue === 0) return { type: "ssr" }; From c7e009ad38f49b454e631f4242856ef16d0019cf Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Sun, 17 May 2026 14:16:37 +1000 Subject: [PATCH 4/4] fix(build-report): use Vite parser export Route report parsing now uses Vite's OXC-backed parser export instead of depending on oxc-parser directly. Adding the parser as a package dependency duplicated the toolchain parser and exposed native optional bindings through vinext itself. Import parseSync and ESTree types from Vite, keep the existing parser options and AST analysis unchanged, and remove the direct package/catalog entries so the lockfile only retains transitive parser usage from dev tooling. --- packages/vinext/package.json | 1 - packages/vinext/src/build/report.ts | 30 ++-- pnpm-lock.yaml | 231 ---------------------------- pnpm-workspace.yaml | 1 - 4 files changed, 14 insertions(+), 249 deletions(-) diff --git a/packages/vinext/package.json b/packages/vinext/package.json index 040239212..c65794edf 100644 --- a/packages/vinext/package.json +++ b/packages/vinext/package.json @@ -69,7 +69,6 @@ "image-size": "catalog:", "ipaddr.js": "catalog:", "magic-string": "catalog:", - "oxc-parser": "catalog:", "vite-plugin-commonjs": "catalog:", "vite-tsconfig-paths": "catalog:", "web-vitals": "catalog:" diff --git a/packages/vinext/src/build/report.ts b/packages/vinext/src/build/report.ts index cc05e84e8..418f0a910 100644 --- a/packages/vinext/src/build/report.ts +++ b/packages/vinext/src/build/report.ts @@ -21,21 +21,8 @@ import fs from "node:fs"; import path from "node:path"; -import { parseSync } from "oxc-parser"; -import type { - ArrowFunctionExpression, - BindingPattern, - BlockStatement, - Expression, - Function as OxcFunction, - FunctionBody, - ModuleExportName, - ObjectExpression, - PropertyKey, - Program, - Statement, - VariableDeclarator, -} from "oxc-parser"; +import { parseSync } from "vite"; +import type { ESTree } from "vite"; import type { Route } from "../routing/pages-router.js"; import type { AppRoute } from "../routing/app-router.js"; import type { LayoutBuildClassification } from "./layout-classification-types.js"; @@ -59,6 +46,18 @@ export type RouteRow = { }; type AppRouteRenderEntry = Pick; +type ArrowFunctionExpression = ESTree.ArrowFunctionExpression; +type BindingPattern = ESTree.BindingPattern; +type BlockStatement = ESTree.BlockStatement; +type Expression = ESTree.Expression; +type FunctionBody = ESTree.FunctionBody; +type FunctionLike = ESTree.Function | ArrowFunctionExpression; +type ModuleExportName = ESTree.ModuleExportName; +type ObjectExpression = ESTree.ObjectExpression; +type Program = ESTree.Program; +type PropertyKey = ESTree.PropertyKey; +type Statement = ESTree.Statement; +type VariableDeclarator = ESTree.VariableDeclarator; export function getAppRouteRenderEntryPath(route: AppRouteRenderEntry): string | null { if (route.pagePath) return route.pagePath; @@ -77,7 +76,6 @@ export function getAppRouteRenderEntryPath(route: AppRouteRenderEntry): string | // ─── Static export analysis ────────────────────────────────────────────────── -type FunctionLike = OxcFunction | ArrowFunctionExpression; type StaticNumberValue = number | false; function parseRouteModuleWithLang(code: string, lang: "ts" | "tsx"): Program | null { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 598f4a348..6df84775f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,9 +162,6 @@ catalogs: nuqs: specifier: ^2.8.8 version: 2.8.8 - oxc-parser: - specifier: 0.130.0 - version: 0.130.0 playwright: specifier: ^1.52.0 version: 1.58.2 @@ -905,9 +902,6 @@ importers: magic-string: specifier: 'catalog:' version: 0.30.21 - oxc-parser: - specifier: 'catalog:' - version: 0.130.0 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.21 version: '@voidzero-dev/vite-plus-core@0.1.21(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.8.3)' @@ -2440,84 +2434,42 @@ packages: cpu: [arm] os: [android] - '@oxc-parser/binding-android-arm-eabi@0.130.0': - resolution: {integrity: sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - '@oxc-parser/binding-android-arm64@0.127.0': resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-android-arm64@0.130.0': - resolution: {integrity: sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@oxc-parser/binding-darwin-arm64@0.127.0': resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-arm64@0.130.0': - resolution: {integrity: sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - '@oxc-parser/binding-darwin-x64@0.127.0': resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.130.0': - resolution: {integrity: sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.127.0': resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-freebsd-x64@0.130.0': - resolution: {integrity: sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': - resolution: {integrity: sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': - resolution: {integrity: sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.127.0': resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2525,13 +2477,6 @@ packages: os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-arm64-gnu@0.130.0': - resolution: {integrity: sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@oxc-parser/binding-linux-arm64-musl@0.127.0': resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2539,13 +2484,6 @@ packages: os: [linux] libc: [musl] - '@oxc-parser/binding-linux-arm64-musl@0.130.0': - resolution: {integrity: sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2553,13 +2491,6 @@ packages: os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': - resolution: {integrity: sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2567,13 +2498,6 @@ packages: os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': - resolution: {integrity: sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@oxc-parser/binding-linux-riscv64-musl@0.127.0': resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2581,13 +2505,6 @@ packages: os: [linux] libc: [musl] - '@oxc-parser/binding-linux-riscv64-musl@0.130.0': - resolution: {integrity: sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@oxc-parser/binding-linux-s390x-gnu@0.127.0': resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2595,13 +2512,6 @@ packages: os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-s390x-gnu@0.130.0': - resolution: {integrity: sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.127.0': resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2609,13 +2519,6 @@ packages: os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.130.0': - resolution: {integrity: sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - '@oxc-parser/binding-linux-x64-musl@0.127.0': resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2623,71 +2526,35 @@ packages: os: [linux] libc: [musl] - '@oxc-parser/binding-linux-x64-musl@0.130.0': - resolution: {integrity: sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - '@oxc-parser/binding-openharmony-arm64@0.127.0': resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-openharmony-arm64@0.130.0': - resolution: {integrity: sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - '@oxc-parser/binding-wasm32-wasi@0.127.0': resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@oxc-parser/binding-wasm32-wasi@0.130.0': - resolution: {integrity: sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - '@oxc-parser/binding-win32-arm64-msvc@0.127.0': resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-arm64-msvc@0.130.0': - resolution: {integrity: sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.127.0': resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.130.0': - resolution: {integrity: sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.127.0': resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.130.0': - resolution: {integrity: sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@oxc-project/runtime@0.129.0': resolution: {integrity: sha512-0+S67blQakgeNqoKGozOUp5rQBrz2ynXZ2QIINXZPiafsD0YL0UogB9hAWc1S7k6VSNwKYC/N7MqT0V6IzpHkQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2698,9 +2565,6 @@ packages: '@oxc-project/types@0.129.0': resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} - '@oxc-project/types@0.130.0': - resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -5877,10 +5741,6 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-parser@0.130.0: - resolution: {integrity: sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw==} - engines: {node: ^20.19.0 || >=22.12.0} - oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} @@ -7465,99 +7325,51 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true - '@oxc-parser/binding-android-arm-eabi@0.130.0': - optional: true - '@oxc-parser/binding-android-arm64@0.127.0': optional: true - '@oxc-parser/binding-android-arm64@0.130.0': - optional: true - '@oxc-parser/binding-darwin-arm64@0.127.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.130.0': - optional: true - '@oxc-parser/binding-darwin-x64@0.127.0': optional: true - '@oxc-parser/binding-darwin-x64@0.130.0': - optional: true - '@oxc-parser/binding-freebsd-x64@0.127.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.130.0': - optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': - optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': - optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.127.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.130.0': - optional: true - '@oxc-parser/binding-linux-arm64-musl@0.127.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.130.0': - optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': - optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': - optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.127.0': optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.130.0': - optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.127.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.130.0': - optional: true - '@oxc-parser/binding-linux-x64-gnu@0.127.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.130.0': - optional: true - '@oxc-parser/binding-linux-x64-musl@0.127.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.130.0': - optional: true - '@oxc-parser/binding-openharmony-arm64@0.127.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.130.0': - optional: true - '@oxc-parser/binding-wasm32-wasi@0.127.0': dependencies: '@emnapi/core': 1.9.2 @@ -7565,39 +7377,21 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true - '@oxc-parser/binding-wasm32-wasi@0.130.0': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.127.0': optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.130.0': - optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.127.0': optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.130.0': - optional: true - '@oxc-parser/binding-win32-x64-msvc@0.127.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.130.0': - optional: true - '@oxc-project/runtime@0.129.0': {} '@oxc-project/types@0.127.0': {} '@oxc-project/types@0.129.0': {} - '@oxc-project/types@0.130.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -10550,31 +10344,6 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 - oxc-parser@0.130.0: - dependencies: - '@oxc-project/types': 0.130.0 - optionalDependencies: - '@oxc-parser/binding-android-arm-eabi': 0.130.0 - '@oxc-parser/binding-android-arm64': 0.130.0 - '@oxc-parser/binding-darwin-arm64': 0.130.0 - '@oxc-parser/binding-darwin-x64': 0.130.0 - '@oxc-parser/binding-freebsd-x64': 0.130.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.130.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.130.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.130.0 - '@oxc-parser/binding-linux-arm64-musl': 0.130.0 - '@oxc-parser/binding-linux-ppc64-gnu': 0.130.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.130.0 - '@oxc-parser/binding-linux-riscv64-musl': 0.130.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.130.0 - '@oxc-parser/binding-linux-x64-gnu': 0.130.0 - '@oxc-parser/binding-linux-x64-musl': 0.130.0 - '@oxc-parser/binding-openharmony-arm64': 0.130.0 - '@oxc-parser/binding-wasm32-wasi': 0.130.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.130.0 - '@oxc-parser/binding-win32-ia32-msvc': 0.130.0 - '@oxc-parser/binding-win32-x64-msvc': 0.130.0 - oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.19.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6a475cace..35af793d9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,7 +67,6 @@ catalog: next-view-transitions: ^0.3.5 nitro: npm:nitro-nightly@latest nuqs: ^2.8.8 - oxc-parser: 0.130.0 playwright: ^1.52.0 postcss: 8.5.10 react: ^19.2.6