import fs from "node:fs/promises"; import path from "node:path"; import fg from "fast-glob"; import { type CommandResult, success } from "../../../lib/command-result"; import { APP_PATHS } from "../../../lib/paths"; export interface ResolverMeasure { count: number; avgLoC: number; maxLoC: number; filesOverThreshold: { path: string; loc: number }[]; } export interface CommandMeasure { count: number; avgLoC: number; handWrittenRatio: number; libNonTypesFiles: string[]; } export interface FrontendMeasure { pages: { count: number }; appShellCatalog: { localVersion: string | null; source: "local" | "unresolved"; }; jsxComposition: { appShellTags: number; replaceableTags: number; candidateTags: number; structuralTags: number; otherLibraryTags: number; unclassifiedTags: number; appShellRatio: number; }; appShellGaps: { tag: string; count: number }[]; suspectedHandRolled: { path: string; appShellTags: number; structuralTags: number; replaceableTags: number; }[]; rawHtmlHeavyPages: { path: string; appShellRatio: number; replaceableTags: number }[]; customComponents: number; localComponentRisks: { path: string; exportedNames: string[]; reasons: string[]; appShellTags: number; replaceableTags: number; structuralTags: number; }[]; } export interface MeasureThinResult { resolver?: ResolverMeasure; command?: CommandMeasure; frontend?: FrontendMeasure; } const RESOLVER_LOC_THRESHOLD = 200; const APP_SHELL_PACKAGE = "@tailor-platform/app-shell"; const RAW_HTML_HEAVY_RATIO = 0.5; const RAW_HTML_HEAVY_MIN_TAGS = 5; const HANDROLLED_MIN_STRUCTURAL = 20; const HANDROLLED_MAX_APPSHELL_RATIO = 0.4; const STRUCTURAL_TAGS = new Set([ "div", "span", "p", "br", "hr", "ul", "ol", "li", "dl", "dt", "dd", "section", "article", "nav", "header", "footer", "main", "aside", "figure", "figcaption", "hgroup", "address", "small", "strong", "em", "b", "i", "u", "s", "mark", "code", "pre", "blockquote", "abbr", "cite", "q", "sub", "sup", "kbd", "samp", "var", "wbr", "h1", "h2", "h3", "h4", "h5", "h6", ]); const TS_TYPE_KEYWORDS = new Set([ "string", "number", "boolean", "void", "typeof", "null", "undefined", "any", "unknown", "never", "bigint", "keyof", "infer", "readonly", ]); // SVG drawing primitives. Not something app-shell provides a replacement for // (they are the raw material of a drawing, not UI parts), so they belong in // neither the ratio nor appShellGaps — treat as neutral and skip. const SVG_TAGS = new Set([ "svg", "g", "defs", "use", "symbol", "marker", "mask", "clipPath", "pattern", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "text", "tspan", "textPath", "image", "foreignObject", "linearGradient", "radialGradient", "stop", "filter", "feGaussianBlur", "feOffset", "feBlend", "feColorMatrix", "feMerge", "feMergeNode", ]); const IMPORT_RE = /import\s+(type\s+)?(?:(?:([A-Za-z_$][\w$]*)\s*,\s*)?(?:\{([^}]+)\}|\*\s+as\s+([A-Za-z_$][\w$]*))|([A-Za-z_$][\w$]*))\s+from\s+["']([^"']+)["']/g; const JSX_TAG_OPEN_RE = /<([A-Za-z][A-Za-z0-9_.]*)/g; const COMPONENT_DECL_RE = /^export declare (?:function|const) ([A-Z][A-Za-z0-9]*)/gm; // Namespace/compound members, e.g. `Label` in // `export declare const Field: { Label: typeof Label; ... }`. These expose // components like `` that the top-level COMPONENT_DECL_RE misses, // so a raw `