@saastro/studio 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-S5K6H6MF.js → chunk-HEBSQKPT.js} +2 -2
- package/dist/{chunk-IPVMSMB4.js → chunk-MMUEPPBK.js} +2 -2
- package/dist/{chunk-5EXFBVC2.js → chunk-WTHLK3XQ.js} +202 -21
- package/dist/chunk-WTHLK3XQ.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/integration/index.d.ts +1 -1
- package/dist/integration/index.js +3 -3
- package/dist/middleware/index.js +1 -1
- package/dist/types-C9q8CVtQ.d.ts +38 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-5EXFBVC2.js.map +0 -1
- package/dist/types-DlrT0PDc.d.ts +0 -19
- /package/dist/{chunk-S5K6H6MF.js.map → chunk-HEBSQKPT.js.map} +0 -0
- /package/dist/{chunk-IPVMSMB4.js.map → chunk-MMUEPPBK.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite/index.ts","../../studio-core/src/component-schema/extract.ts","../../studio-core/src/component-schema/extract-bindings.ts","../src/vite/auto-wrap.ts","../src/vite/extract-props-schema.ts","../src/vite/inject-schema.ts","../src/vite/instrument-fields.ts"],"sourcesContent":["import { promises as fs } from 'node:fs'\nimport type { Plugin } from 'vite'\nimport {\n extractComponentSchemaFromTsx,\n extractPropsTypeBlock,\n} from '@saastro/studio-core/component-schema'\nimport { transformAstroSource } from './auto-wrap.js'\nimport { extractPropsSchema } from './extract-props-schema.js'\nimport { injectSchema } from './inject-schema.js'\nimport { instrumentFields, instrumentJsxFields } from './instrument-fields.js'\nimport { injectVersionMetaTag } from './meta-tag.js'\nimport {\n buildVisibilityStyleTag,\n injectVisibilityStyle,\n readVisibilityConfigSync,\n} from '../visibility-css.js'\nimport type { StudioAutoWrapOptions } from './types.js'\n\nexport type { StudioAutoWrapOptions } from './types.js'\n\n/**\n * Quick check: does this file declare `fieldPrefix` in its frontmatter\n * Props interface? Used as the entry-gate for the whole transform —\n * we only want to spin up ts-morph + run the regex pipeline on files\n * that are actually Studio-instrumentable. Anything else is skipped\n * with a cheap regex test.\n *\n * Conservative match: anchored on the `---` frontmatter fences and a\n * word-boundaried `fieldPrefix`. Comments mentioning `fieldPrefix`\n * trigger a false positive, but the downstream passes are no-ops on\n * files that don't actually use it.\n */\nconst FRONTMATTER_FIELD_PREFIX_RE = /^\\s*---\\s*\\n[\\s\\S]*?\\bfieldPrefix\\b[\\s\\S]*?\\n---\\s*\\n/\n\nfunction hasFieldPrefixReference(source: string): boolean {\n return FRONTMATTER_FIELD_PREFIX_RE.test(source)\n}\n\n/**\n * Splits an id into its file-path component and the query string (if any).\n * Vite ids look like `/abs/path/to/Hero.astro` or\n * `/abs/path/to/Hero.astro?astro&type=script&index=0&lang.ts`.\n */\nfunction splitId(id: string): { path: string; query: string } {\n const idx = id.indexOf('?')\n return idx === -1 ? { path: id, query: '' } : { path: id.slice(0, idx), query: id.slice(idx) }\n}\n\n/**\n * Whether this id is the main `.astro` module (NOT an Astro-virtual\n * sub-request like `?astro&type=style&index=0`). We only transform main\n * modules — sub-requests are emitted by Astro's own load hook with\n * already-compiled content and must not be touched.\n */\nfunction isMainAstroModule(id: string): boolean {\n const { path, query } = splitId(id)\n if (!path.endsWith('.astro')) return false\n if (query && /[?&]astro([&=]|$)/.test(query)) return false\n return true\n}\n\n/**\n * Whether this id is a `.tsx`/`.jsx` React component we should consider\n * for JSX field instrumentation. Excludes test files, type-only modules,\n * and storybook stories — they either don't render in the page DOM or\n * don't carry section props.\n */\nfunction isReactComponentModule(id: string): boolean {\n const { path, query } = splitId(id)\n if (query) return false\n if (!/\\.(tsx|jsx)$/.test(path)) return false\n if (/\\.(test|spec|stories)\\.[tj]sx$/.test(path)) return false\n return true\n}\n\n/**\n * Vite plugin factory for Saastro Studio instrumentation.\n *\n * Drop into `vite.plugins` in `astro.config.mjs`:\n *\n * import studioAutoWrap from '@saastro/studio/vite';\n * export default defineConfig({\n * vite: { plugins: [studioAutoWrap({ autoWrap: true })] },\n * });\n *\n * ## Why `load` and not `transform`?\n *\n * Astro 6's `astro:build` plugin compiles `.astro` source → JS inside its\n * own `transform` hook (`vite-plugin-astro/index.js`, line ~204). Both\n * plugins use `enforce: 'pre'`, so ordering falls back to insertion\n * order — and Astro's plugin is registered before any user\n * integration's plugins, so its transform always runs first. The result:\n * by the time our transform fires, `code` is already compiled JS and our\n * regex-on-frontmatter pipeline can't match anything.\n *\n * Vite runs all `load` hooks BEFORE any `transform`, regardless of\n * `enforce`. Astro's own `load` filters strictly to virtual sub-requests\n * (`/[?&]astro([&=]|$)/`), so the main `.astro` module falls through to\n * the default fs loader. We slot ourselves in there: read the file,\n * apply the regex pipeline to raw `.astro` source (with frontmatter\n * intact), return the modified raw source. Astro's transform then\n * compiles our edited source as if it were the original on disk.\n *\n * What the plugin does:\n * 1. (load) Scans `.astro` source for `fieldPrefix` prop and injects\n * `data-saastro=\"sec:${fieldPrefix}\"` on the root element.\n * 2. (load) Extracts a `ComponentSchema` from the file's `interface Props`\n * (via ts-morph in `@saastro/studio-core`) and bakes it into a\n * `<script type=\"application/saastro-schema\" data-prefix=\"…\">` placed\n * just before the section's root closing tag. The overlay reads it\n * at runtime so the Studio panel doesn't need a server round-trip.\n * 3. (load) Walks the template and stamps each rendered `Props` field\n * with `data-saastro-field=\"<propPath>\"` so the Studio overlay can\n * mutate the exact DOM node by name.\n * 4. (transformIndexHtml) Injects <meta name=\"saastro:studio-version\">\n * into every HTML page's <head> so the Hub can detect protocol version.\n */\nexport default function studioAutoWrap(opts: StudioAutoWrapOptions = {}): Plugin {\n const enabled = opts.autoWrap === true\n const include = opts.include ?? defaultInclude\n // Allow customers to opt out of TSX instrumentation entirely if it\n // ever misbehaves on their tree. Defaults to enabled — TSX scanning\n // is gated on the file containing a Props interface AND a `.map(`\n // call, so the cost on non-section components is one cheap regex.\n const instrumentTsx = opts.instrumentTsx !== false\n\n // Resolved Vite root — honors `--root` flag and any Astro adapter\n // overrides. Lazy because Vite calls `configResolved` before any\n // `transformIndexHtml`; until then we fall through to the helper's\n // default ('.' = CWD), which is the same value in every realistic\n // setup.\n let projectRoot: string | undefined\n\n return {\n name: '@saastro/studio:auto-wrap',\n enforce: 'pre',\n\n config() {\n // Build-time injection of the visibility `<style>` tag as a\n // `define` constant. The middleware reads this at runtime via\n // `__SAASTRO_STUDIO_HIDDEN_TAG__` and skips its `node:fs`\n // fallback — required on Cloudflare Workers where the\n // filesystem is a no-op. esbuild substitutes the identifier\n // with the JSON-stringified tag at customer-bundle time.\n //\n // Vite's `config` hook runs before `configResolved`, so we\n // read from `process.cwd()` here as a reasonable default —\n // honors `--root` because Vite invokes the CLI from that\n // directory. Inconsistent with `configResolved`'s richer\n // root, but for the visibility helper that's fine: it's a\n // best-effort build-time read.\n const config = readVisibilityConfigSync(process.cwd())\n const tag = buildVisibilityStyleTag(config)\n return {\n define: {\n __SAASTRO_STUDIO_HIDDEN_TAG__: JSON.stringify(tag),\n },\n }\n },\n\n configResolved(config) {\n projectRoot = config.root\n },\n\n async load(id) {\n if (!enabled) return null\n const { path } = splitId(id)\n\n if (isMainAstroModule(id)) {\n if (!include(path)) return null\n return loadAstro(path)\n }\n\n if (instrumentTsx && isReactComponentModule(id)) {\n if (!include(path)) return null\n return loadTsx(path)\n }\n\n return null\n },\n\n transformIndexHtml(html) {\n if (!enabled) return html\n // Layered: meta tags first (Hub-side overlay reads them to\n // detect protocol version), then the `studio.config.json`-driven\n // visibility CSS. Both helpers are idempotent so re-applying on\n // a HMR-rebuilt page does not double-inject.\n let next = injectVersionMetaTag(html)\n next = injectVisibilityStyle(next, projectRoot)\n return next\n },\n }\n}\n\nconst DEFAULT_INCLUDE_EXTS = /\\.(astro|tsx|jsx)$/\n\nfunction defaultInclude(id: string): boolean {\n return DEFAULT_INCLUDE_EXTS.test(id)\n}\n\nasync function loadAstro(path: string) {\n let code: string\n try {\n code = await fs.readFile(path, 'utf8')\n } catch {\n return null\n }\n\n // Trigger: the file declares a `fieldPrefix` prop. This catches\n // BOTH the auto-wrap path (we'll inject `data-saastro=\"sec:...\"`)\n // AND the hand-written path (the developer already put the marker\n // on the root tag — we still need to run schema injection +\n // per-field instrumentation, which transformAstroSource skips).\n if (!hasFieldPrefixReference(code)) return null\n\n const wrapped = transformAstroSource(code)\n const schema = extractPropsSchema(code, { componentPath: path })\n let next = wrapped\n if (schema) {\n next = injectSchema(next, schema)\n next = instrumentFields(next, schema, { componentPath: path })\n }\n if (next === code) return null\n return { code: next, map: null }\n}\n\n/**\n * Load + instrument a React (.tsx/.jsx) component. Gating is two-tier:\n *\n * 1. Cheap regex pre-filter — the file must contain a `Props`\n * interface/type and a `.map(` call. Skips library files, hooks,\n * and utility modules instantly.\n * 2. ts-morph parse + schema extraction — only runs when the cheap\n * filter passes. The schema tells `instrumentJsxFields` which\n * identifiers are array Props (so it knows which `.map()` chains\n * to rewrite).\n *\n * Any failure path returns the original source unchanged. The overlay\n * has DOM-walking fallbacks for unmarked nodes; partial coverage is\n * never wrong, only suboptimal.\n */\nasync function loadTsx(path: string) {\n let code: string\n try {\n code = await fs.readFile(path, 'utf8')\n } catch {\n return null\n }\n\n // Quick pre-filter: skip files without an array iteration. Studio's\n // contribution to TSX is array instrumentation; if there's no `.map(`\n // we have nothing to do. The check is cheap (string `includes`).\n if (!code.includes('.map(')) return null\n // The Props block must exist too — without it we can't know which\n // identifiers correspond to schema fields.\n if (extractPropsTypeBlock(code) === null) return null\n\n const schema = extractComponentSchemaFromTsx(code, { componentPath: path })\n if (!schema || !schema.fields.length) return null\n\n const next = instrumentJsxFields(code, schema, { componentPath: path })\n if (next === code) return null\n return { code: next, map: null }\n}\n","/**\n * Extract a `ComponentSchema` from a single `.astro` source file.\n *\n * The shape we care about lives in the frontmatter — the block between\n * the opening `---` and closing `---` lines. We feed the frontmatter into\n * ts-morph (TypeScript's AST library) so we get real type resolution\n * instead of the brittle regex matching the Vite plugin uses today for\n * the `fieldPrefix` marker injection.\n *\n * Why ts-morph and not a regex:\n * - The customer's `interface Props` may import types from sibling files\n * (`import type { StatItem } from './types'`).\n * - Property types may be unions of string literals (`'left' | 'center'`).\n * - Optional/required, nullable, default-bearing properties all need to\n * round-trip into the panel correctly.\n *\n * What we deliberately don't do here:\n * - Parse the Astro template body — we only care about the type contract.\n * Per-prop DOM markers (`data-saastro-field`) are a separate transform\n * in the Vite plugin (Phase 2 of the rollout plan).\n * - Run any I/O. The caller (Vite plugin or runtime fallback) is in\n * charge of locating the source and importing related .ts files into\n * the same in-memory ts-morph project.\n */\n\nimport {\n Project,\n SourceFile,\n Type,\n type InterfaceDeclaration,\n type JSDocableNode,\n type Node,\n type Symbol as TsSymbol,\n type TypeAliasDeclaration,\n} from 'ts-morph'\nimport type {\n ComponentField,\n ComponentSchema,\n FieldKind,\n} from './types.js'\n\n/**\n * Allowed kinds in `@studio:<kind>` JSDoc directives. The customer can\n * sprinkle these on individual `Props` properties to override the\n * name-based heuristic used by `pickStringKind` and friends. Compatibility\n * with the resolved type is enforced in `applyDirective` — a directive\n * that conflicts with the type is silently ignored (easier to debug than\n * a build-time throw, and the panel still shows something sensible).\n *\n * Shorthand: `@studio:image` is accepted as an alias for `image-url`,\n * since \"image\" reads more naturally on a property declaration.\n */\nconst STUDIO_DIRECTIVE_KINDS: ReadonlySet<FieldKind> = new Set<FieldKind>([\n 'string',\n 'textarea',\n 'image-url',\n 'enum',\n 'number',\n 'boolean',\n 'array',\n 'object',\n 'json',\n])\n\nconst STUDIO_DIRECTIVE_RE = /@studio:([A-Za-z][A-Za-z0-9-]*)/g\n\n/**\n * Property-name patterns we treat as image-shaped strings. The list is\n * intentionally conservative — adding \"hero\" here once caused EVERY\n * field on a hero section to flip to the image-url widget. Hints\n * describe the FIELD's role, not the section it lives in.\n */\nconst IMAGE_NAME_HINTS = /(^|_)(image|img|avatar|logo|icon|thumbnail|photo|picture|cover|background)($|_)/i\nconst IMAGE_SUFFIX_HINTS = /_(url|src)$/i\n\nconst FRONTMATTER_RE = /^\\s*---\\s*\\n([\\s\\S]*?)\\n---/\n\nexport interface ExtractOptions {\n /**\n * Path of the .astro file relative to the project root. Used only for\n * diagnostics and to populate `ComponentSchema.componentPath`.\n */\n componentPath?: string\n /**\n * Base directory the source file lives in. Affects how ts-morph\n * resolves relative imports (`import type { Foo } from './foo'`).\n * Defaults to the in-memory virtual root, which means imports won't\n * resolve unless the caller pre-loaded those files into the same\n * Project.\n */\n baseDir?: string\n /**\n * Caller-supplied ts-morph Project. Reusing one across extractions in\n * the same plugin run is cheaper because TypeScript can share the\n * type-checker cache. If omitted we spin up a fresh in-memory one.\n */\n project?: Project\n}\n\n/**\n * Pull the frontmatter out of a .astro source string. Returns null when\n * the file has no frontmatter block (a pure-template Astro component).\n */\nexport function extractFrontmatter(astroSource: string): string | null {\n const m = FRONTMATTER_RE.exec(astroSource)\n return m ? (m[1] ?? '') : null\n}\n\n/**\n * Pull the `interface Props { ... }` or `type Props = { ... }` block out\n * of a TSX/JSX source string. Returns the matched block (including the\n * `interface`/`type` keyword and outer braces) or null when the file has\n * no such declaration.\n *\n * Used by `extractComponentSchemaFromTsx` to feed only the type-shape\n * portion of a React component file into ts-morph, sidestepping the JSX\n * body entirely (which ts-morph would have to parse with `.tsx` script\n * kind, adding complexity).\n *\n * The matcher is intentionally simple: walk balanced braces starting at\n * the keyword to find the end of the block. Anything ts-morph rejects\n * downstream just returns null from `extractComponentSchemaFromTsx`.\n */\nexport function extractPropsTypeBlock(tsxSource: string): string | null {\n const interfaceRe = /\\b(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)\\s*\\{/g\n const typeRe = /\\b(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*\\{/g\n let match: RegExpExecArray | null\n while ((match = interfaceRe.exec(tsxSource)) !== null) {\n const name = match[1]!\n if (name === 'Props' || name.endsWith('Props')) {\n const braceOpenIdx = tsxSource.indexOf('{', match.index)\n if (braceOpenIdx === -1) continue\n const close = readBalancedBrace(tsxSource, braceOpenIdx)\n if (close === null) continue\n const block = tsxSource.slice(match.index, close + 1)\n return normalizeInterfaceName(block, name)\n }\n }\n while ((match = typeRe.exec(tsxSource)) !== null) {\n const name = match[1]!\n if (name === 'Props' || name.endsWith('Props')) {\n const braceOpenIdx = tsxSource.indexOf('{', match.index)\n if (braceOpenIdx === -1) continue\n const close = readBalancedBrace(tsxSource, braceOpenIdx)\n if (close === null) continue\n // Trailing `;` after `}` is fine — ts-morph swallows it.\n const block = tsxSource.slice(match.index, close + 1)\n return normalizeTypeAliasName(block, name)\n }\n }\n return null\n}\n\n/**\n * Rename a `<Name>Props` interface to `Props` so the downstream\n * `getInterface('Props')` lookup in extractComponentSchema succeeds.\n * No-op when the name is already `Props`.\n */\nfunction normalizeInterfaceName(block: string, name: string): string {\n if (name === 'Props') return block\n return block.replace(/^(\\s*(?:export\\s+)?interface\\s+)[A-Za-z_$][\\w$]*/, '$1Props')\n}\n\nfunction normalizeTypeAliasName(block: string, name: string): string {\n if (name === 'Props') return block\n return block.replace(/^(\\s*(?:export\\s+)?type\\s+)[A-Za-z_$][\\w$]*/, '$1Props')\n}\n\n/**\n * Walk balanced `{ … }` starting at the position of an opening brace.\n * Returns the index of the matching close, or null on imbalance.\n * Aware of string/template literals so braces inside strings don't\n * unbalance the count.\n */\nfunction readBalancedBrace(s: string, open: number): number | null {\n if (s[open] !== '{') return null\n let depth = 0\n let i = open\n while (i < s.length) {\n const ch = s[i]!\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n i = skipStringLiteral(s, i) + 1\n continue\n }\n if (ch === '/' && s[i + 1] === '/') {\n // Line comment\n const eol = s.indexOf('\\n', i + 2)\n i = eol === -1 ? s.length : eol + 1\n continue\n }\n if (ch === '/' && s[i + 1] === '*') {\n const end = s.indexOf('*/', i + 2)\n i = end === -1 ? s.length : end + 2\n continue\n }\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n i++\n }\n return null\n}\n\nfunction skipStringLiteral(s: string, start: number): number {\n const quote = s[start]!\n let i = start + 1\n while (i < s.length) {\n const ch = s[i]!\n if (ch === '\\\\') {\n i += 2\n continue\n }\n if (quote === '`' && ch === '$' && s[i + 1] === '{') {\n // Walk the template-literal expression as balanced braces.\n const close = readBalancedBrace(s, i + 1)\n if (close === null) return s.length - 1\n i = close + 1\n continue\n }\n if (ch === quote) return i\n i++\n }\n return s.length - 1\n}\n\n/**\n * Collect ALL type-shape declarations (interfaces + type aliases) from\n * a TSX/JSX source. Used to feed referenced types (e.g. `Capability`\n * declared next to `Props`) into the synthetic ts-morph project so the\n * type checker can resolve nested array item shapes correctly.\n *\n * Returns an empty array when no type declarations are present.\n */\nexport function extractAllTypeBlocks(tsxSource: string): string[] {\n const blocks: string[] = []\n const seen = new Set<number>()\n const decl = /\\b(?:export\\s+)?(?:interface|type)\\s+[A-Za-z_$][\\w$]*/g\n let m: RegExpExecArray | null\n while ((m = decl.exec(tsxSource)) !== null) {\n const start = m.index\n if (seen.has(start)) continue\n seen.add(start)\n const isType = /\\btype\\b/.test(m[0])\n // Find the opening `{`. For interfaces it's after the name; for\n // type aliases it's after `=`. We just look for the next `{` on\n // the same logical line (skipping whitespace + `=`).\n const braceOpen = tsxSource.indexOf('{', start)\n if (braceOpen < 0) continue\n // Sanity: the `{` must be on the declaration line, no statement\n // breakers between the keyword and the `{`.\n const between = tsxSource.slice(start + m[0].length, braceOpen)\n if (!/^[\\s=]*(?:extends\\s+[^{]+)?$/.test(between)) continue\n const close = readBalancedBrace(tsxSource, braceOpen)\n if (close === null) continue\n let block = tsxSource.slice(start, close + 1)\n if (isType) {\n // ts-morph wants a trailing `;` on type aliases; harmless if it\n // already has one.\n if (!block.trimEnd().endsWith(';')) block = block + ';'\n }\n blocks.push(block)\n // Skip past this declaration so the outer regex doesn't re-walk it\n // for nested matches.\n decl.lastIndex = close + 1\n }\n return blocks\n}\n\n/**\n * Extract a `ComponentSchema` from a React TSX/JSX source file by\n * locating its `interface Props` (or `interface <Name>Props`) block and\n * reusing the existing extractor. Returns null when no recognizable\n * Props declaration is found.\n *\n * Why not parse the full TSX with ts-morph + scriptKind=tsx? The Props\n * block is the only thing we need — the JSX body is for the separate\n * `instrument-fields` transform. Slicing the type block and feeding it\n * as plain TypeScript keeps the existing ts-morph setup intact and\n * dodges JSX-parsing edge cases.\n *\n * To resolve nested array item shapes (`Capability[]` referencing a\n * `Capability` interface declared next to `Props`), we also pull in\n * every other interface / type declaration from the file. Imports are\n * dropped — the synthetic compile unit can't resolve relative paths\n * anyway, and an unresolved type falls through to the `'json'` field\n * kind which the panel renders as a generic textarea.\n */\nexport function extractComponentSchemaFromTsx(\n tsxSource: string,\n options: ExtractOptions = {},\n): ComponentSchema | null {\n const propsBlock = extractPropsTypeBlock(tsxSource)\n if (!propsBlock) return null\n // Collect every type/interface declaration so referenced types\n // resolve. Strip the Props block out of the \"siblings\" set — we'll\n // re-emit the normalised version separately to avoid the\n // `interface Foo` vs `interface Props` name collision.\n const allBlocks = extractAllTypeBlocks(tsxSource)\n const siblings = allBlocks.filter(b => !looksLikeProps(b))\n const synthetic =\n `---\\n` +\n [...siblings, propsBlock].join('\\n\\n') +\n `\\n---\\n`\n return extractComponentSchema(synthetic, options)\n}\n\nfunction looksLikeProps(block: string): boolean {\n return /^\\s*(?:export\\s+)?(?:interface|type)\\s+(?:Props\\b|[A-Za-z_$][\\w$]*Props\\b)/.test(block)\n}\n\n/**\n * Main entry point. Takes the raw `.astro` source, returns a schema\n * (or null when the file has no `interface Props`). Errors during\n * parsing are caught and returned as null — the caller decides whether\n * to fall back to the catalog.\n */\nexport function extractComponentSchema(\n astroSource: string,\n options: ExtractOptions = {},\n): ComponentSchema | null {\n const frontmatter = extractFrontmatter(astroSource)\n if (frontmatter == null) return null\n\n const project = options.project ?? createProject()\n // Virtual filename — ts-morph ignores extensions for lookup, but using\n // .ts here keeps any error message readable.\n const virtualName = options.componentPath\n ? `${options.componentPath}.frontmatter.ts`\n : 'component.frontmatter.ts'\n\n let sourceFile: SourceFile\n try {\n sourceFile = project.createSourceFile(virtualName, frontmatter, {\n overwrite: true,\n })\n } catch {\n return null\n }\n\n let propsDecl: InterfaceDeclaration | TypeAliasDeclaration | undefined =\n sourceFile.getInterface('Props')\n if (!propsDecl) {\n propsDecl = sourceFile.getTypeAlias('Props')\n }\n if (!propsDecl) {\n sourceFile.delete()\n return null\n }\n\n const propsType = propsDecl.getType()\n const fields: ComponentField[] = []\n for (const prop of propsType.getProperties()) {\n const field = describeProperty(prop, propsDecl)\n if (field) fields.push(field)\n }\n\n // Parse default values from the destructuring assignment if present.\n // Pattern matched: `const { title = 'foo', stats = [] } = Astro.props`.\n applyDefaultsFromDestructuring(frontmatter, fields)\n\n sourceFile.delete()\n\n return {\n fields,\n componentPath: options.componentPath,\n version: 1,\n }\n}\n\nfunction createProject(): Project {\n return new Project({\n useInMemoryFileSystem: true,\n skipFileDependencyResolution: true,\n compilerOptions: {\n strict: true,\n // Match the customer's typical Astro tsconfig — ESNext + node16\n // resolution covers most setups. Misses are non-fatal: unresolved\n // imports surface as `'json'` kind with a description.\n target: 99,\n module: 199,\n moduleResolution: 100,\n allowSyntheticDefaultImports: true,\n esModuleInterop: true,\n skipLibCheck: true,\n },\n })\n}\n\ninterface PropertyParent {\n getSourceFile(): SourceFile\n}\n\nfunction describeProperty(\n prop: TsSymbol,\n parent: PropertyParent,\n): ComponentField | null {\n const name = prop.getName()\n const declarations = prop.getDeclarations()\n const decl = declarations[0]\n if (!decl) return null\n\n // Optional flag: `prop?: T` OR `prop: T | undefined`\n const isQuestionToken = (\n decl as unknown as { hasQuestionToken?: () => boolean }\n ).hasQuestionToken?.() ?? false\n\n const propsSourceFile = parent.getSourceFile()\n const type = prop.getTypeAtLocation(propsSourceFile)\n\n const optional =\n isQuestionToken || typeIncludes(type, t => t.isUndefined())\n\n const directive = readStudioDirective(decl)\n\n // ts-morph models `boolean` as the union `true | false`, so the\n // generic union path below would mis-classify it as heterogeneous.\n // Short-circuit primitives that survive a `T | undefined` wrapper.\n if (type.isBoolean() || stripUndefined(type).isBoolean()) {\n return applyDirective({ name, kind: 'boolean', optional }, directive, type)\n }\n\n // Strip undefined / null while preserving the rest of the union so\n // string-literal enums like `'left' | 'center' | undefined` keep all\n // their literals.\n const nonNullishParts = type.isUnion()\n ? type.getUnionTypes().filter(t => !t.isUndefined() && !t.isNull())\n : [type]\n\n // 1. String literal union → enum\n if (\n nonNullishParts.length >= 1 &&\n nonNullishParts.every(t => t.isStringLiteral())\n ) {\n const enumValues = nonNullishParts.map(t =>\n String(t.getLiteralValue()),\n )\n if (enumValues.length === 1) {\n // Single literal isn't an enum — describe as a normal string.\n const f = describeType(name, nonNullishParts[0]!)\n return applyDirective(\n { ...f, optional: f.optional || optional },\n directive,\n type,\n )\n }\n return applyDirective(\n { name, kind: 'enum', optional, enumValues },\n directive,\n type,\n )\n }\n\n // 2. Single non-nullish branch → describe that branch.\n if (nonNullishParts.length === 1) {\n const f = describeType(name, nonNullishParts[0]!)\n return applyDirective(\n { ...f, optional: f.optional || optional },\n directive,\n type,\n )\n }\n\n // 3. Heterogeneous union (string | number, etc.) — too ambiguous to\n // pick a control. Fall back to json with the raw type text.\n return applyDirective(\n {\n name,\n kind: 'json',\n optional,\n description: `unparsed: ${type.getText()}`,\n },\n directive,\n type,\n )\n}\n\n/**\n * Read the first `@studio:<kind>` JSDoc directive on a node. Returns\n * the canonicalised `FieldKind` or null if none/unknown. Walks ts-morph\n * `getJsDocs()` plus the leading-comment fallback (older ts-morph\n * versions don't always thread JSDoc onto every PropertySignature).\n */\nfunction readStudioDirective(decl: Node): FieldKind | null {\n const sources: string[] = []\n\n const jsDocable = decl as Node & Partial<JSDocableNode>\n if (typeof jsDocable.getJsDocs === 'function') {\n for (const doc of jsDocable.getJsDocs()) {\n sources.push(doc.getText())\n }\n }\n\n // Fallback: scan leading trivia in case JSDoc isn't attached as a\n // first-class node (happens for some property signature shapes).\n if (sources.length === 0) {\n const ranges =\n (decl as unknown as {\n getLeadingCommentRanges?: () => { getText(): string }[]\n }).getLeadingCommentRanges?.() ?? []\n for (const range of ranges) {\n const text = range.getText()\n if (text.includes('@studio:')) sources.push(text)\n }\n }\n\n for (const source of sources) {\n STUDIO_DIRECTIVE_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = STUDIO_DIRECTIVE_RE.exec(source)) !== null) {\n const raw = match[1]\n if (!raw) continue\n // Shorthand: `@studio:image` → `image-url`.\n const kind = (raw === 'image' ? 'image-url' : raw) as FieldKind\n if (STUDIO_DIRECTIVE_KINDS.has(kind)) return kind\n // Unknown kind — skip and keep looking. First valid match wins.\n }\n }\n\n return null\n}\n\n/**\n * Override `field.kind` when the directive is compatible with the\n * already-resolved type. Incompatible directives are dropped silently —\n * the panel renders the heuristic-picked control instead, which is far\n * easier to debug than a build-time throw.\n */\nfunction applyDirective(\n field: ComponentField,\n directive: FieldKind | null,\n type: Type,\n): ComponentField {\n if (!directive) return field\n if (!isDirectiveCompatible(directive, type)) return field\n if (field.kind === directive) return field\n return { ...field, kind: directive }\n}\n\nfunction isDirectiveCompatible(directive: FieldKind, type: Type): boolean {\n // `json` is the universal escape hatch.\n if (directive === 'json') return true\n\n const stripped = stripUndefined(type)\n const nonNullishParts = type.isUnion()\n ? type.getUnionTypes().filter(t => !t.isUndefined() && !t.isNull())\n : [stripped]\n\n switch (directive) {\n case 'string':\n case 'textarea':\n case 'image-url':\n return nonNullishParts.every(t => t.isString() || t.isStringLiteral())\n case 'enum':\n return nonNullishParts.every(t => t.isString() || t.isStringLiteral())\n case 'number':\n return nonNullishParts.every(t => t.isNumber() || t.isNumberLiteral())\n case 'boolean':\n return type.isBoolean() || stripped.isBoolean()\n case 'array':\n return nonNullishParts.every(t => t.isArray())\n case 'object':\n return nonNullishParts.every(t => isDescendableObject(t))\n default:\n return false\n }\n}\n\n/**\n * Built-in classes / brand types we never want to descend into. They\n * have member properties (e.g. RegExp.lastIndex, Date.getTime) that\n * would confuse the panel, so we surface them as a json fallback\n * instead of an object form.\n */\nconst NON_DESCENDABLE_TYPE_NAMES = new Set([\n 'RegExp',\n 'Date',\n 'Map',\n 'Set',\n 'WeakMap',\n 'WeakSet',\n 'Promise',\n 'Error',\n 'URL',\n 'URLSearchParams',\n 'ArrayBuffer',\n 'Uint8Array',\n])\n\nfunction isCallable(type: Type): boolean {\n return type.getCallSignatures().length > 0\n}\n\nfunction isDescendableObject(type: Type): boolean {\n if (!type.isObject() && !type.isInterface()) return false\n if (isCallable(type)) return false\n const symbol = type.getSymbol() ?? type.getAliasSymbol()\n const symbolName = symbol?.getName()\n if (symbolName && NON_DESCENDABLE_TYPE_NAMES.has(symbolName)) return false\n return type.getProperties().length > 0\n}\n\n/**\n * Maximum descent depth when walking a type tree. Self-referential types\n * (e.g. `interface MenuItem { items?: MenuItem[] }`) would otherwise\n * recurse forever — the type-graph cycle isn't visible until ts-morph\n * returns the same property-type twice. We bail to a `json` fallback at\n * the cap, which the runtime catalog/overlay treats as opaque.\n *\n * 8 is comfortably above anything real (the deepest field on the studio\n * fixture suite is 3) while still short-circuiting cycles within a few\n * ms instead of blowing the call stack.\n */\nconst MAX_DESCENT_DEPTH = 8\n\nfunction describeType(\n name: string,\n type: Type,\n depth: number = 0,\n): ComponentField {\n // Order matters: literal first (single literal kept as enum-of-1 might\n // be useful but we treat as string for sanity), then primitives,\n // arrays, descendable objects, fallback.\n\n if (type.isStringLiteral()) {\n return { name, kind: 'string', optional: false }\n }\n\n if (\n type.isUnion() &&\n type.getUnionTypes().every(t => t.isStringLiteral())\n ) {\n const values = type\n .getUnionTypes()\n .map(t => String(t.getLiteralValue()))\n return { name, kind: 'enum', optional: false, enumValues: values }\n }\n\n if (type.isString()) {\n return { name, kind: pickStringKind(name), optional: false }\n }\n if (type.isNumber()) return { name, kind: 'number', optional: false }\n if (type.isBoolean()) return { name, kind: 'boolean', optional: false }\n\n // Before descending further, check the depth cap. Primitives above\n // are always safe (no recursion), so we only gate the array/object\n // branches.\n if (depth >= MAX_DESCENT_DEPTH) {\n return {\n name,\n kind: 'json',\n optional: false,\n description: `unparsed: ${type.getText()} (depth ${depth})`,\n }\n }\n\n if (type.isArray()) {\n const elementType = type.getArrayElementType()\n if (elementType) {\n const itemDescriptor = describeAnyType('__item__', elementType, depth + 1)\n return { name, kind: 'array', optional: false, items: itemDescriptor }\n }\n return { name, kind: 'array', optional: false }\n }\n\n if (isDescendableObject(type)) {\n const fields: ComponentField[] = []\n for (const sub of type.getProperties()) {\n const subDecl = sub.getDeclarations()[0]\n if (!subDecl) continue\n const subType = sub.getTypeAtLocation(subDecl)\n const subOptional =\n ((subDecl as unknown as { hasQuestionToken?: () => boolean })\n .hasQuestionToken?.() ?? false) ||\n typeIncludes(subType, t => t.isUndefined())\n const sf = describeAnyType(sub.getName(), subType, depth + 1)\n const subDirective = readStudioDirective(subDecl)\n fields.push(\n applyDirective(\n { ...sf, optional: sf.optional || subOptional },\n subDirective,\n subType,\n ),\n )\n }\n if (fields.length > 0) {\n return { name, kind: 'object', optional: false, fields }\n }\n }\n\n return {\n name,\n kind: 'json',\n optional: false,\n description: `unparsed: ${type.getText()}`,\n }\n}\n\n/**\n * Like `describeType` but also handles the optional / null-stripping\n * pre-pass — used when descending into array items or nested object\n * properties (where we don't have a question-token to look at).\n */\nfunction describeAnyType(\n name: string,\n type: Type,\n depth: number = 0,\n): ComponentField {\n const optional = typeIncludes(type, t => t.isUndefined())\n const nonNullishParts = type.isUnion()\n ? type.getUnionTypes().filter(t => !t.isUndefined() && !t.isNull())\n : [type]\n\n if (\n nonNullishParts.length >= 1 &&\n nonNullishParts.every(t => t.isStringLiteral())\n ) {\n if (nonNullishParts.length === 1) {\n return { ...describeType(name, nonNullishParts[0]!, depth), optional }\n }\n return {\n name,\n kind: 'enum',\n optional,\n enumValues: nonNullishParts.map(t => String(t.getLiteralValue())),\n }\n }\n\n if (nonNullishParts.length === 1) {\n const f = describeType(name, nonNullishParts[0]!, depth)\n return { ...f, optional: f.optional || optional }\n }\n\n return {\n name,\n kind: 'json',\n optional,\n description: `unparsed: ${type.getText()}`,\n }\n}\n\nfunction pickStringKind(name: string): FieldKind {\n if (IMAGE_NAME_HINTS.test(name) || IMAGE_SUFFIX_HINTS.test(name)) {\n return 'image-url'\n }\n // Long-form heuristic — only the name is available without runtime\n // length info. Matches the existing zod-introspect behavior.\n if (/description|body|content|copy|summary/i.test(name)) {\n return 'textarea'\n }\n return 'string'\n}\n\nfunction typeIncludes(type: Type, predicate: (t: Type) => boolean): boolean {\n if (predicate(type)) return true\n if (type.isUnion()) {\n return type.getUnionTypes().some(predicate)\n }\n return false\n}\n\nfunction stripUndefined(type: Type): Type {\n if (!type.isUnion()) return type\n const remaining = type\n .getUnionTypes()\n .filter(t => !t.isUndefined() && !t.isNull())\n // We just need a representative type for primitive checks — return any\n // of the surviving members. The full extraction path uses\n // `nonNullishParts` (an array) for completeness.\n return remaining[0] ?? type\n}\n\n/**\n * Best-effort: parse `const { foo = X, bar = Y } = Astro.props` from the\n * frontmatter and stamp `defaultValue` on matching fields. Pure regex —\n * we don't try to evaluate the RHS, just record it as a string for the\n * panel's placeholder.\n */\nfunction applyDefaultsFromDestructuring(\n frontmatter: string,\n fields: ComponentField[],\n): void {\n const match = /const\\s*\\{([\\s\\S]*?)\\}\\s*=\\s*Astro\\.props/.exec(frontmatter)\n if (!match) return\n const body = match[1] ?? ''\n // Naive split on top-level commas — sufficient for `name = 'foo'`,\n // `count = 0`, `flag = false`. Object/array defaults will fall through.\n const entries = splitTopLevel(body, ',')\n for (const entry of entries) {\n const eq = entry.indexOf('=')\n if (eq < 0) continue\n const lhs = entry.slice(0, eq).trim()\n const rhs = entry.slice(eq + 1).trim()\n if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(lhs)) continue\n const target = fields.find(f => f.name === lhs)\n if (!target) continue\n target.defaultValue = parseLiteral(rhs)\n }\n}\n\nfunction splitTopLevel(s: string, sep: string): string[] {\n const out: string[] = []\n let depth = 0\n let current = ''\n for (const ch of s) {\n if (ch === '{' || ch === '[' || ch === '(') depth++\n else if (ch === '}' || ch === ']' || ch === ')') depth--\n if (ch === sep && depth === 0) {\n out.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current.trim()) out.push(current)\n return out\n}\n\nfunction parseLiteral(rhs: string): unknown {\n const trimmed = rhs.trim()\n if (trimmed === 'true') return true\n if (trimmed === 'false') return false\n if (trimmed === 'null') return null\n if (/^-?\\d+(\\.\\d+)?$/.test(trimmed)) return Number(trimmed)\n // Strip wrapping quotes.\n const quoted = /^(['\"`])([\\s\\S]*)\\1$/.exec(trimmed)\n if (quoted) return quoted[2]\n // Anything else (object, array, identifier reference) — keep the raw\n // source text so the panel can show it as a placeholder string.\n return trimmed\n}\n","/**\n * Extract per-prop call-site bindings from an Astro PAGE file.\n *\n * Where component-schema (./extract.ts) parses the COMPONENT file\n * (`Hero.astro`) for its `interface Props`, this extractor parses the\n * PAGE that uses the component (`src/pages/[locale]/index.astro`) and\n * inspects each attribute on the JSX-like element marked with\n * `fieldPrefix=\"<prefix>\"`.\n *\n * The output is what the panel needs to decide which change op to stage\n * when the user edits a field:\n *\n * <Hero\n * title={hero.title} ← i18n: keyPath = 'hero.home.title'\n * image=\"/img/hero.jpg\" ← literal\n * stats={getStats()} ← unknown\n * fieldPrefix=\"hero.home\"\n * />\n *\n * Frontmatter trace:\n * const { t } = Astro.locals;\n * const hero = t.hero.home;\n *\n * The trace builds a map `hero → 'hero.home'`, then the attribute\n * `title={hero.title}` resolves to keyPath `hero.home.title`.\n *\n * Supported call-site shapes:\n * prop=\"literal\"\n * prop={'literal'}\n * prop={`literal`} (no expressions)\n * prop={IDENT} (where IDENT was assigned to t.<path>)\n * prop={IDENT.sub.path}\n * prop={IDENT['sub']['path']}\n * prop={t.foo.bar}\n * prop={t('foo.bar')}\n * prop={t['foo.bar']}\n *\n * Unsupported (graded as `unknown`):\n * prop={fn()}, prop={a + b}, prop={cond ? x : y}, etc.\n *\n * The extractor is intentionally conservative — anything ambiguous lands\n * as `unknown` rather than silently misclassifying. The panel's behaviour\n * for `unknown` is the same as before this extractor existed\n * (`section_props_update`), so misclassification is degrade-only.\n */\n\nimport { Project } from 'ts-morph'\nimport type { PropBinding } from './types.js'\n\nconst FRONTMATTER_RE = /^\\s*---\\s*\\n([\\s\\S]*?)\\n---/\n\n/**\n * Strict identifier (TS rules — letters, digits, _, $; cannot start with\n * digit). Conservative on purpose: looser parsing can match attribute\n * fragments inside HTML strings.\n */\nconst IDENT_RE = '[A-Za-z_$][A-Za-z0-9_$]*'\n\nexport interface ExtractBindingsOptions {\n /** Path of the page file relative to repo root — included in the result for debugging. */\n pageFile?: string\n}\n\nexport interface ExtractBindingsResult {\n /**\n * Map from prop name → binding info. Props whose call-site value we\n * couldn't classify are still included as `kind: 'unknown'` so the\n * panel can render them (and stage as `section_props_update`).\n *\n * Props missing from the call site entirely (e.g. relying on a\n * component-side default) are absent from this map.\n */\n bindings: Record<string, PropBinding>\n /** Echo of pageFile from options, for debugging. */\n pageFile?: string\n}\n\nexport function extractCallSiteBindings(\n pageSource: string,\n fieldPrefix: string,\n options: ExtractBindingsOptions = {},\n): ExtractBindingsResult {\n const empty: ExtractBindingsResult = { bindings: {}, pageFile: options.pageFile }\n\n const fmMatch = pageSource.match(FRONTMATTER_RE)\n const frontmatter = fmMatch?.[1] ?? ''\n const template = fmMatch ? pageSource.slice(fmMatch[0].length) : pageSource\n\n // 1. Build identifier → keyPath map from the frontmatter. Only\n // top-level `const X = t.<path>` (or `let`/`var`) declarations\n // count. Nested scopes are out of scope (rare in Astro pages).\n const identMap = buildIdentifierMap(frontmatter)\n\n // 2. Locate the JSX-like element with the matching fieldPrefix and\n // pull out its other attributes.\n const attrs = findElementAttributes(template, fieldPrefix)\n if (!attrs) return empty\n\n // 3. Classify each attribute.\n const bindings: Record<string, PropBinding> = {}\n for (const [name, raw] of Object.entries(attrs)) {\n if (name === 'fieldPrefix') continue\n bindings[name] = classifyAttributeValue(raw, identMap)\n }\n\n return { bindings, pageFile: options.pageFile }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Frontmatter: identifier → key path */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Walk the page's frontmatter and build a map from identifier name to\n * the translation key path it was assigned. Examples:\n *\n * const hero = t.hero.home; → hero → 'hero.home'\n * const homeAbout = t.homeAbout; → homeAbout → 'homeAbout'\n * const x = t['foo']['bar']; → x → 'foo.bar'\n *\n * Only direct assignments rooted at `t` are tracked — chained traces\n * through other identifiers (`const a = hero.foo`) are ignored to keep\n * this conservative. dorjoiers does flat one-level binding so this\n * covers the canonical case.\n */\nfunction buildIdentifierMap(frontmatter: string): Map<string, string> {\n const map = new Map<string, string>()\n if (!frontmatter) return map\n\n const project = new Project({\n useInMemoryFileSystem: true,\n skipAddingFilesFromTsConfig: true,\n skipFileDependencyResolution: true,\n skipLoadingLibFiles: true,\n compilerOptions: {\n allowJs: true,\n noResolve: true,\n jsx: 4 /* Preserve */,\n noEmit: true,\n },\n })\n let sf\n try {\n sf = project.createSourceFile('__page-frontmatter.ts', frontmatter)\n } catch {\n return map\n }\n\n for (const v of sf.getVariableDeclarations()) {\n const name = v.getName()\n const init = v.getInitializer()\n if (!init) continue\n const path = traceTPath(init.getText())\n if (path !== null) map.set(name, path)\n }\n return map\n}\n\n/**\n * Convert a member-access chain rooted at `t` into a dotted key path.\n * Returns `null` if the expression isn't a recognized t.<...> chain.\n */\nfunction traceTPath(expr: string): string | null {\n // Strip outer parens (rare): `(t.hero.home)` → `t.hero.home`.\n let e = expr.trim()\n while (e.startsWith('(') && e.endsWith(')')) {\n e = e.slice(1, -1).trim()\n }\n if (!/^t([.[(]|$)/.test(e)) return null\n\n // Tokenize: split into dot-segments and ['x']/[\"x\"] segments.\n const segments: string[] = []\n let i = 1 // skip leading 't'\n while (i < e.length) {\n const ch = e[i]\n if (ch === '.') {\n const m = e.slice(i + 1).match(new RegExp('^' + IDENT_RE))\n if (!m) return null\n segments.push(m[0])\n i += 1 + m[0].length\n } else if (ch === '[') {\n const close = e.indexOf(']', i + 1)\n if (close === -1) return null\n const inner = e.slice(i + 1, close).trim()\n const litMatch = inner.match(/^(['\"])(.+)\\1$/)\n if (!litMatch || litMatch[2] === undefined) return null\n segments.push(litMatch[2])\n i = close + 1\n } else if (ch === '(') {\n // t('foo.bar') — only valid as the very first segment.\n if (segments.length !== 0) return null\n const close = matchingClose(e, i)\n if (close === -1) return null\n const inner = e.slice(i + 1, close).trim()\n const litMatch = inner.match(/^(['\"`])(.+)\\1$/)\n if (!litMatch || litMatch[2] === undefined) return null\n // Split the dotted key inside the call.\n for (const seg of litMatch[2].split('.')) {\n if (seg) segments.push(seg)\n }\n i = close + 1\n // After a call, only a member access may follow (rare). Bail to\n // keep this deterministic.\n if (i < e.length) return null\n } else {\n return null\n }\n }\n return segments.length === 0 ? null : segments.join('.')\n}\n\nfunction matchingClose(s: string, openIdx: number): number {\n let depth = 0\n let inStr: string | null = null\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (inStr) {\n if (ch === '\\\\') { i++; continue }\n if (ch === inStr) inStr = null\n continue\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inStr = ch\n continue\n }\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/* -------------------------------------------------------------------------- */\n/* Template: locate element with fieldPrefix and extract attributes */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Find the JSX-like element in the template that has\n * `fieldPrefix=\"<prefix>\"` (or `fieldPrefix={`<prefix>`}` /\n * `fieldPrefix={'<prefix>'}`) and return ALL its attributes as a\n * `name → rawValueText` map. RawValueText preserves the original\n * surrounding form (\"...\" / '...' / {...}) so the classifier can\n * distinguish literals from expressions.\n *\n * Returns `null` if no matching element is found.\n *\n * The scanner is purpose-built for Astro/JSX-style attribute lists. It\n * tracks balanced braces inside expression values and respects\n * string/template literals to avoid being fooled by `{` or `}` inside\n * string content.\n */\nfunction findElementAttributes(\n template: string,\n fieldPrefix: string,\n): Record<string, string> | null {\n const escaped = escapeRegExp(fieldPrefix)\n // Match `<Tag` … `fieldPrefix=\"...\"` … `>` or `/>`. We don't insist on\n // tag identity — Studio prefixes are unique so the attribute alone\n // pins us. We DO need to know where the tag begins so we can scan\n // its full attribute list both before and after fieldPrefix.\n const fpRegexes = [\n new RegExp(`fieldPrefix\\\\s*=\\\\s*\"${escaped}\"`),\n new RegExp(`fieldPrefix\\\\s*=\\\\s*'${escaped}'`),\n new RegExp(`fieldPrefix\\\\s*=\\\\s*\\\\{\\\\s*\\`${escaped}\\`\\\\s*\\\\}`),\n new RegExp(`fieldPrefix\\\\s*=\\\\s*\\\\{\\\\s*'${escaped}'\\\\s*\\\\}`),\n new RegExp(`fieldPrefix\\\\s*=\\\\s*\\\\{\\\\s*\"${escaped}\"\\\\s*\\\\}`),\n ]\n let fpIdx = -1\n for (const re of fpRegexes) {\n const m = re.exec(template)\n if (m) { fpIdx = m.index; break }\n }\n if (fpIdx === -1) return null\n\n // Walk backwards to the opening `<Tag` of the element containing\n // this attribute. We bail if we cross a closing `>` or another `<`,\n // which would mean fieldPrefix lives outside the tag we expected.\n const tagStart = findTagStartBefore(template, fpIdx)\n if (tagStart === -1) return null\n\n const tagEnd = findTagEndAfter(template, fpIdx)\n if (tagEnd === -1) return null\n\n const tagBody = template.slice(tagStart, tagEnd + 1)\n return parseAttributeList(tagBody)\n}\n\nfunction findTagStartBefore(s: string, idx: number): number {\n // Scan back looking for `<` that starts a JSX tag (component or HTML).\n // We assume tag content has no embedded `<` outside expressions; an\n // unbalanced `>` before our `<` invalidates the parse.\n let depth = 0\n for (let i = idx; i >= 0; i--) {\n const ch = s[i]\n if (ch === '}') depth++\n else if (ch === '{') {\n if (depth > 0) depth--\n } else if (ch === '<' && depth === 0) {\n return i\n } else if (ch === '>' && depth === 0) {\n // Crossed a tag boundary — fieldPrefix isn't inside an open tag.\n return -1\n }\n }\n return -1\n}\n\nfunction findTagEndAfter(s: string, idx: number): number {\n let depth = 0\n let inStr: string | null = null\n for (let i = idx; i < s.length; i++) {\n const ch = s[i]\n if (inStr) {\n if (ch === '\\\\') { i++; continue }\n if (ch === inStr) inStr = null\n continue\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inStr = ch\n continue\n }\n if (ch === '{') depth++\n else if (ch === '}') {\n if (depth > 0) depth--\n } else if (ch === '>' && depth === 0) {\n return i\n }\n }\n return -1\n}\n\n/**\n * Parse the attributes of a single tag body (e.g. `<Hero a=\"1\" b={x} c />`).\n * Returns `name → rawValueText` (without the surrounding quotes/braces\n * stripped — the classifier needs the syntax).\n *\n * Boolean attributes (no `=`) get value `'true'` (literal `'true'`,\n * not the boolean primitive — the classifier treats it as a literal\n * to avoid surprising behaviour).\n */\nfunction parseAttributeList(tagBody: string): Record<string, string> {\n const out: Record<string, string> = {}\n // Skip the leading `<TagName` (or `<TagName>`).\n const tagNameMatch = tagBody.match(new RegExp(`^<${IDENT_RE}(?:\\\\.${IDENT_RE})*`))\n if (!tagNameMatch) return out\n let i = tagNameMatch[0].length\n\n while (i < tagBody.length) {\n // Skip whitespace.\n while (i < tagBody.length && /\\s/.test(tagBody[i] ?? '')) i++\n if (i >= tagBody.length) break\n const ch = tagBody[i]\n if (ch === '/' || ch === '>') break\n\n // Read attribute name. Astro accepts hyphens (`data-foo`) but\n // Component prop names are TS identifiers — we stick to that.\n const nameMatch = tagBody.slice(i).match(new RegExp(`^${IDENT_RE}`))\n if (!nameMatch) {\n // Not a name we can parse — skip to next whitespace-or-end.\n i++\n continue\n }\n const name = nameMatch[0]\n i += name.length\n\n // Skip whitespace before potential `=`.\n while (i < tagBody.length && /\\s/.test(tagBody[i] ?? '')) i++\n\n if (tagBody[i] !== '=') {\n // Boolean attribute (no `=`). Quote it so the classifier\n // resolves it as a string literal `\"true\"`.\n out[name] = '\"true\"'\n continue\n }\n i++ // consume `=`\n while (i < tagBody.length && /\\s/.test(tagBody[i] ?? '')) i++\n\n const valStart = i\n const after = tagBody[i]\n if (after === '\"' || after === \"'\") {\n const close = findStringClose(tagBody, i)\n if (close === -1) break\n out[name] = tagBody.slice(valStart, close + 1)\n i = close + 1\n } else if (after === '{') {\n const close = findExprClose(tagBody, i)\n if (close === -1) break\n out[name] = tagBody.slice(valStart, close + 1)\n i = close + 1\n } else {\n // Unquoted value — uncommon in JSX; consume to whitespace.\n const m = tagBody.slice(i).match(/^[^\\s>]+/)\n if (!m) break\n out[name] = m[0]\n i += m[0].length\n }\n }\n return out\n}\n\nfunction findStringClose(s: string, openIdx: number): number {\n const quote = s[openIdx]\n for (let i = openIdx + 1; i < s.length; i++) {\n const ch = s[i]\n if (ch === '\\\\') { i++; continue }\n if (ch === quote) return i\n }\n return -1\n}\n\nfunction findExprClose(s: string, openIdx: number): number {\n let depth = 0\n let inStr: string | null = null\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (inStr) {\n if (ch === '\\\\') { i++; continue }\n if (ch === inStr) inStr = null\n continue\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inStr = ch\n continue\n }\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/* -------------------------------------------------------------------------- */\n/* Classification */\n/* -------------------------------------------------------------------------- */\n\nfunction classifyAttributeValue(\n raw: string,\n identMap: Map<string, string>,\n): PropBinding {\n if (!raw) return { kind: 'unknown' }\n const trimmed = raw.trim()\n\n // String literal: \"...\" or '...'\n const strLit = matchStringLiteral(trimmed)\n if (strLit !== null) return { kind: 'literal', value: strLit }\n\n // Expression: { ... }\n if (trimmed.startsWith('{') && trimmed.endsWith('}')) {\n const inner = trimmed.slice(1, -1).trim()\n\n // Literal-inside-expression: {\"foo\"} / {'foo'} / {`foo`} (no $)\n const innerStr = matchStringLiteral(inner)\n if (innerStr !== null) return { kind: 'literal', value: innerStr }\n const tplLit = matchTemplateLiteralNoExpr(inner)\n if (tplLit !== null) return { kind: 'literal', value: tplLit }\n\n // Member access: hero.title / homeAbout.eyebrow / hero['title']\n const path = traceMemberPath(inner, identMap)\n if (path !== null) return { kind: 'i18n', keyPath: path }\n\n return { kind: 'unknown', expression: inner }\n }\n\n return { kind: 'unknown', expression: trimmed }\n}\n\nfunction matchStringLiteral(s: string): string | null {\n if (s.length < 2) return null\n const q = s[0]\n if ((q !== '\"' && q !== \"'\") || s[s.length - 1] !== q) return null\n // Reject anything with an unescaped quote in the middle.\n for (let i = 1; i < s.length - 1; i++) {\n if (s[i] === '\\\\') { i++; continue }\n if (s[i] === q) return null\n }\n // Unescape minimally — \\\" \\' \\\\ \\n \\t.\n return s.slice(1, -1).replace(/\\\\(.)/g, (_, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n return c\n })\n}\n\nfunction matchTemplateLiteralNoExpr(s: string): string | null {\n if (s.length < 2 || s[0] !== '`' || s[s.length - 1] !== '`') return null\n if (s.includes('${')) return null\n return s.slice(1, -1)\n}\n\n/**\n * Trace a member-access expression rooted at a known identifier or `t`.\n * Returns dotted key path or `null`.\n *\n * Examples (with identMap = { hero: 'hero.home' }):\n * hero.title → 'hero.home.title'\n * hero['title'] → 'hero.home.title'\n * t.hero.home.title → 'hero.home.title'\n * t('hero.home.title') → 'hero.home.title'\n * t.hero['home'].title → 'hero.home.title'\n */\nfunction traceMemberPath(\n expr: string,\n identMap: Map<string, string>,\n): string | null {\n const e = expr.trim()\n // Direct t(...) / t.<...>\n const direct = traceTPath(e)\n if (direct !== null) return direct\n\n // Identifier-rooted\n const m = e.match(new RegExp(`^${IDENT_RE}`))\n if (!m) return null\n const root = m[0]\n const prefix = identMap.get(root)\n if (!prefix) return null\n\n let i = root.length\n const segments: string[] = []\n while (i < e.length) {\n const ch = e[i]\n if (ch === '.') {\n const sub = e.slice(i + 1).match(new RegExp(`^${IDENT_RE}`))\n if (!sub) return null\n segments.push(sub[0])\n i += 1 + sub[0].length\n } else if (ch === '[') {\n const close = e.indexOf(']', i + 1)\n if (close === -1) return null\n const inner = e.slice(i + 1, close).trim()\n const lit = inner.match(/^(['\"])(.+)\\1$/)\n if (!lit || lit[2] === undefined) return null\n segments.push(lit[2])\n i = close + 1\n } else {\n return null\n }\n }\n if (segments.length === 0) return prefix\n return prefix + '.' + segments.join('.')\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","/**\n * Studio auto-wrap transform — Studio v5.2 §M1.2.\n *\n * Scans `.astro` files for a `fieldPrefix` prop and injects\n * `data-saastro=\"sec:${fieldPrefix}\"` on the template's root element.\n *\n * Approach: regex-based source transformation (enforce: 'pre', runs before\n * Astro compiles the template). Covers 95%+ of real-world Astro components.\n *\n * ## Limitations\n * - Component roots (PascalCase tags) are skipped — the marker must land\n * on a rendered HTML element so the overlay can hit-test it.\n * - Top-level branching (`{cond ? <A/> : <B/>}`) is skipped; users can\n * opt-in manually with `editableSection()`.\n */\n\n// Matches the frontmatter block at the very top of the file.\nconst FRONTMATTER_RE = /^\\s*---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/\n\n// Matches both HTML attribute form (`data-saastro=`) and the JSX spread\n// form we inject (`\"data-saastro\":`). Plain substring check is enough —\n// `data-saastro` is unique to this instrumentation.\nconst SAASTRO_ATTR_RE = /data-saastro/\n\n/**\n * Detects any mention of `fieldPrefix` in the frontmatter.\n * Conservative: a comment mentioning `fieldPrefix` is a false-positive,\n * but the runtime guard (`fieldPrefix ? ... : {}`) makes it safe.\n */\nconst FIELD_PREFIX_REFERENCE_RE = /\\bfieldPrefix\\b/\n\n/**\n * Matches the first tag opening after the frontmatter. Group 1 = tag name\n * (either HTML lowercase or component PascalCase), group 2 = existing\n * attributes, group 3 = self-closing slash. The lowercase check in\n * `transformAstroSource` decides whether to inject or skip.\n */\nconst FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\\b([^<>]*?)\\s*(\\/?)>/\n\n/**\n * Attribute spread injected on the root element. Uses conditional form\n * so no `data-saastro` attribute appears in production HTML when the\n * prop is falsy (avoids `data-saastro=\"sec:\"` partial matches).\n *\n * H9 — also emit `draggable=\"true\"` so HTML5 drag-and-drop fires\n * `dragstart` on the section root. Spec v5.2 §M1.6 promised section-level\n * drag reorder; the overlay's drag listeners (overlay-script.ts) were\n * wired but never received events because the wrappers weren't draggable.\n */\nconst INJECTION =\n ' {...(fieldPrefix ? { \"data-saastro\": `sec:${fieldPrefix}`, draggable: \"true\" } : {})}'\n\n/**\n * Transform a single .astro source string. Exported for unit tests;\n * the plugin wraps this with file-system + Vite metadata.\n */\nexport function transformAstroSource(source: string): string {\n const fm = FRONTMATTER_RE.exec(source)\n if (!fm) return source\n\n if (!FIELD_PREFIX_REFERENCE_RE.test(fm[1]!)) return source\n\n const template = source.slice(fm[0].length)\n const tagMatch = FIRST_OPEN_TAG_RE.exec(template)\n if (!tagMatch) return source\n\n const tagName = tagMatch[1]!\n if (tagName[0]! !== tagName[0]!.toLowerCase()) return source\n\n const existingAttrs = tagMatch[2]!\n if (SAASTRO_ATTR_RE.test(existingAttrs)) return source\n\n const selfClose = tagMatch[3]!\n const replacement = `<${tagName}${existingAttrs}${INJECTION}${selfClose ? ' ' + selfClose : ''}>`\n const templateInjected =\n template.slice(0, tagMatch.index) +\n replacement +\n template.slice(tagMatch.index + tagMatch[0]!.length)\n\n return source.slice(0, fm[0].length) + templateInjected\n}\n","/**\n * Studio component-schema extractor — Studio v5.3 Phase 1.2.\n *\n * Thin wrapper around `extractComponentSchema` from `@saastro/studio-core`\n * that memoises results keyed by source-file content. Each `.astro` file\n * is parsed at most once per content hash; HMR replaces a hash with a new\n * one on every edit, which naturally evicts stale entries.\n *\n * The extractor itself is heavy (spins up an in-memory ts-morph project),\n * so caching matters even for small sites — `pnpm dev` restarts and full\n * production builds otherwise re-extract every section on every transform\n * pass.\n */\n\nimport {\n extractComponentSchema,\n type ComponentSchema,\n} from '@saastro/studio-core/component-schema'\n\n/**\n * Cache keyed by `${componentPath} ${hash(source)}`. The componentPath\n * is part of the key because the same source can legitimately live at\n * two paths during dev (a copied file rename), and we want the cached\n * schema to carry the right `componentPath` field.\n *\n * LRU cap so long-running `pnpm dev` sessions don't leak memory: every\n * HMR edit inserts a new (path, hash) entry — without a bound the Map\n * grows monotonically and never frees the old schemas. JS Map preserves\n * insertion order, so we evict the oldest key when we hit the ceiling\n * and re-insert on hits so the recency heuristic survives.\n */\nconst CACHE_MAX = 500\nconst cache = new Map<string, ComponentSchema | null>()\n\n/**\n * djb2-style content hash. Plenty of entropy for a per-file cache key —\n * collisions just mean a stale schema for one transform pass, which the\n * next edit will refresh. Avoids pulling in `node:crypto` here so the\n * module stays lean and bundles cleanly under tsup.\n */\nfunction hashSource(source: string): string {\n let h = 5381\n for (let i = 0; i < source.length; i++) {\n h = ((h << 5) + h + source.charCodeAt(i)) | 0\n }\n // Force unsigned hex so the key doesn't carry a leading `-`.\n return (h >>> 0).toString(16)\n}\n\nexport interface ExtractPropsSchemaOptions {\n /** Path used for diagnostics + populated on the returned schema. */\n componentPath?: string\n}\n\n/**\n * Memoised wrapper around `extractComponentSchema`. Returns null when the\n * file has no `interface Props` (or the parser bails) — same contract as\n * the underlying extractor.\n */\nexport function extractPropsSchema(\n source: string,\n options: ExtractPropsSchemaOptions = {},\n): ComponentSchema | null {\n const key = `${options.componentPath ?? ''} ${hashSource(source)}`\n if (cache.has(key)) {\n // LRU bump: re-insert so the entry moves to the back of the\n // insertion-order list and isn't the next eviction victim.\n const cached = cache.get(key) ?? null\n cache.delete(key)\n cache.set(key, cached)\n return cached\n }\n const schema = extractComponentSchema(source, {\n componentPath: options.componentPath,\n })\n if (cache.size >= CACHE_MAX) {\n // Evict the oldest (first inserted) key — Map iteration is in\n // insertion order, so `.keys().next().value` is the LRU.\n const oldest = cache.keys().next().value\n if (oldest !== undefined) cache.delete(oldest)\n }\n cache.set(key, schema)\n return schema\n}\n\n/**\n * Test-only escape hatch — clears the in-memory cache. Production code\n * should never need to call this; the content-hash key already handles\n * HMR by producing a new key per edit.\n */\nexport function __resetPropsSchemaCache(): void {\n cache.clear()\n}\n\nexport type { ComponentSchema }\n","/**\n * Inject a `<script type=\"application/saastro-schema\">` next to an\n * instrumented section root.\n *\n * Runs AFTER `transformAstroSource` (the auto-wrap pass). Given an\n * `.astro` source whose root element already carries the\n * `data-saastro=\"sec:${fieldPrefix}\"` marker, this transform inserts a\n * single inline `<script>` carrying the JSON-encoded `ComponentSchema`\n * just before the root's closing tag. The overlay (`overlay-script.ts`\n * in hub-react) reads that script at runtime to discover prop fields\n * without making an extra network round-trip.\n *\n * ## Output shape\n *\n * For an auto-wrapped section with prop `fieldPrefix`:\n *\n * ```html\n * <section ... data-saastro=\"sec:...\" ...>\n * ...original body...\n * <script type=\"application/saastro-schema\" is:inline data-prefix={fieldPrefix}>\n * {\"version\":1,\"fields\":[…]}\n * </script>\n * </section>\n * ```\n *\n * The script body is a JSON-string literal (NOT an Astro JS expression)\n * because Astro's compiler must treat it as text — anything wrapped in\n * `{}` would be evaluated at SSR time and produce a runtime expression.\n * Astro preserves `<script is:inline>` content verbatim regardless of\n * `{` characters, so plain JSON works.\n *\n * ## Constraints\n *\n * - Idempotent — re-running on output that already has a\n * `<script type=\"application/saastro-schema\">` is a no-op.\n * - Self-closing roots (`<img />`, `<input />`) are skipped: there is no\n * place to put a child node. The overlay's document-wide fallback\n * query covers those if/when we add a sibling-emission path later.\n * - Non-HTML roots (PascalCase components) are skipped — `auto-wrap`\n * already declines to instrument those.\n * - Escapes any `</` inside the JSON body to `<\\/` so a default value\n * containing the literal `</script>` cannot break the script tag.\n */\n\nimport type { ComponentSchema } from '@saastro/studio-core/component-schema'\n\nconst FRONTMATTER_RE = /^\\s*---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/\n\n/** Substring check is enough — the marker is unique to our instrumentation. */\nconst SCHEMA_SCRIPT_MARKER = 'type=\"application/saastro-schema\"'\n\n/**\n * Match the first opening tag after the frontmatter. Mirrors the regex\n * `auto-wrap` uses but with a simpler attribute capture (we don't care\n * about the existing attribute text here, only positions).\n */\nconst FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\\b([^<>]*?)\\s*(\\/?)>/\n\n/**\n * Extract the literal default for `fieldPrefix` from a destructuring\n * assignment, if any. Examples that yield `'hero.home'`:\n *\n * - `const { fieldPrefix = 'hero.home' } = Astro.props`\n * - `const { fieldPrefix = \"hero.home\", title } = Astro.props`\n *\n * Returns null when the default is missing, non-string, or too complex\n * to parse with a single regex (object literal, function call, etc.).\n */\nfunction extractFieldPrefixDefault(frontmatter: string): string | null {\n const destruct = /const\\s*\\{([\\s\\S]*?)\\}\\s*=\\s*Astro\\.props/.exec(frontmatter)\n if (!destruct) return null\n const body = destruct[1] ?? ''\n // Find `fieldPrefix = <literal>` — accept single, double, or backtick\n // quotes. `[^'\"\\n]` keeps the match conservative; templated strings\n // (`\\`hero.${section}\\``) deliberately do NOT match — we'd lose the\n // expression by treating it as a literal.\n const m = /\\bfieldPrefix\\s*=\\s*(['\"])([^'\"\\n]*)\\1/.exec(body)\n return m ? (m[2] ?? null) : null\n}\n\n/**\n * Detect the literal default embedded in a `data-saastro` attribute that\n * was hand-written rather than auto-wrap-injected. Pattern matched:\n *\n * data-saastro={`sec:${fieldPrefix ?? 'hero.home'}`}\n *\n * Used as a fallback when the destructuring regex above misses (the\n * dorjoiers Hero, for example, destructures `fieldPrefix` with NO\n * default and folds the fallback into the JSX expression).\n */\nfunction extractFieldPrefixFromDataAttr(template: string): string | null {\n const m = /data-saastro=\\{\\s*`\\s*sec:\\$\\{\\s*fieldPrefix\\s*\\?\\?\\s*(['\"])([^'\"`]*)\\1\\s*\\}/.exec(\n template,\n )\n return m ? (m[2] ?? null) : null\n}\n\n/**\n * Find the position of the matching closing tag for the root element at\n * `openTagEnd`. Naive bracket counting on `<TAG` openers and `</TAG>`\n * closers — sufficient for well-formed Astro templates where the root\n * is a single HTML element (the only shape `auto-wrap` instruments).\n *\n * HTML comments (`<!-- ... -->`) are skipped so a commented-out\n * `<section>` inside the root doesn't desynchronise the depth counter.\n * Astro JS expression comments (`{/* ... */}`) don't carry HTML tags so\n * they never confuse the bracket walker; we leave them alone.\n *\n * Returns the index of the `<` in the closing tag, or -1 when no match\n * is found (in which case the caller bails out without emitting).\n */\nfunction findClosingTag(\n haystack: string,\n tagName: string,\n searchFrom: number,\n): number {\n // Match either an HTML comment OR an opener / closer of `tagName`.\n // Putting comments in the same alternation guarantees we skip them\n // atomically — a tag inside a comment can't sneak into the depth\n // counter.\n const re = new RegExp(\n `<!--[\\\\s\\\\S]*?-->|<(/?)${tagName}\\\\b[^<>]*?(/?)>`,\n 'gi',\n )\n re.lastIndex = searchFrom\n let depth = 1\n while (true) {\n const m = re.exec(haystack)\n if (!m) return -1\n // Comments don't have capture groups for the opener/closer marks,\n // so `m[1]` and `m[2]` are undefined for the comment branch. Skip.\n if (m[0]!.startsWith('<!--')) continue\n const isClose = m[1] === '/'\n const isSelfClose = m[2] === '/'\n if (isClose) {\n depth--\n if (depth === 0) return m.index\n } else if (!isSelfClose) {\n depth++\n }\n }\n}\n\n/**\n * JSON-encode a schema and escape any character sequences that could\n * accidentally close the host `<script>` tag. The replacement is safe\n * for `JSON.parse` to round-trip because `\\/` is a valid JSON escape.\n */\nfunction encodeSchemaBody(schema: ComponentSchema): string {\n return JSON.stringify(schema).replace(/<\\//g, '<\\\\/')\n}\n\nexport interface InjectSchemaOptions {\n /**\n * Override for the `data-prefix` attribute. The plugin normally lets\n * `injectSchema` derive this from the frontmatter; tests use this to\n * pin the value explicitly.\n */\n prefixOverride?: string\n}\n\n/**\n * Inject the schema script into `source`. Returns the source unchanged\n * when the file lacks a frontmatter / lacks an instrumented root /\n * already has a schema script.\n */\nexport function injectSchema(\n source: string,\n schema: ComponentSchema,\n options: InjectSchemaOptions = {},\n): string {\n // Idempotency — never emit a second script tag.\n if (source.includes(SCHEMA_SCRIPT_MARKER)) return source\n\n const fm = FRONTMATTER_RE.exec(source)\n if (!fm) return source\n\n const frontmatter = fm[1] ?? ''\n const templateStart = fm[0].length\n const template = source.slice(templateStart)\n\n const tagMatch = FIRST_OPEN_TAG_RE.exec(template)\n if (!tagMatch) return source\n const tagName = tagMatch[1]!\n // Component (PascalCase) roots are out of scope — auto-wrap skips them\n // for the same reason: the marker has to land on a real DOM element.\n if (tagName[0]! !== tagName[0]!.toLowerCase()) return source\n\n const isSelfClose = tagMatch[3] === '/'\n if (isSelfClose) return source\n\n // Where does the opening tag END (just past the `>`)?\n const openTagEnd = tagMatch.index + tagMatch[0].length\n // Find the matching closing tag.\n const closeIdx = findClosingTag(template, tagName, openTagEnd)\n if (closeIdx < 0) return source\n\n // Determine the data-prefix expression. Priority order:\n // 1. Caller-provided override (tests).\n // 2. Default literal from destructuring (`{ fieldPrefix = 'x' } = ...`).\n // 3. Default literal from a hand-written data-saastro attribute.\n // 4. Bare `{fieldPrefix}` reference — overlay falls back to the\n // sibling-by-data-prefix lookup.\n const prefixDefault =\n options.prefixOverride ??\n extractFieldPrefixDefault(frontmatter) ??\n extractFieldPrefixFromDataAttr(template)\n\n const dataPrefixAttr =\n prefixDefault != null\n ? `data-prefix={fieldPrefix ?? ${JSON.stringify(prefixDefault)}}`\n : `data-prefix={fieldPrefix}`\n\n const scriptTag =\n `<script type=\"application/saastro-schema\" is:inline ${dataPrefixAttr}>` +\n encodeSchemaBody(schema) +\n `</script>`\n\n const newTemplate =\n template.slice(0, closeIdx) + scriptTag + template.slice(closeIdx)\n\n return source.slice(0, templateStart) + newTemplate\n}\n","/**\n * Per-prop DOM instrumentation transform — Phase 2.\n *\n * Walks the Astro template and adds `data-saastro-field=\"<propPath>\"`\n * markers on the elements that render each `Props` field, so the\n * Studio overlay's `applySectionProps` can mutate the right node by\n * exact match instead of guessing via role-based heuristics.\n *\n * Markers we emit:\n *\n * <h1 data-saastro-field=\"title\">{title}</h1>\n * Welcome <span data-saastro-field=\"appName\">{appName}</span>!\n * <img data-saastro-field=\"image\" src={image} alt={imageAlt} />\n * {stats.map((s, __i) => (\n * <div data-saastro-field={`stats[${__i}]`}>\n * <p data-saastro-field={`stats[${__i}].value`}>{s.value}</p>\n * </div>\n * ))}\n *\n * Implementation choice: regex / string-walk, NOT `@astrojs/compiler`.\n * We deliberately keep the dependency surface light because:\n *\n * - This package ships into customer Astro builds; pulling in a\n * ~3 MB compiler dep is hard to justify for a small set of patterns.\n * - The patterns we need to handle are constrained to plain prop\n * references (`{ident}`, `{ident.member}`) and one well-known\n * iterator shape (`{ident.map((x[, i]) => <jsx/>)}`).\n * - The existing `auto-wrap.ts` is regex-based and ships fine.\n * - Anything we cannot recognize is left untouched — the bridge has a\n * heuristic fallback for unmarked nodes, so partial coverage is\n * never wrong, only suboptimal.\n *\n * Idempotency:\n *\n * - If an element already has `data-saastro-field`, we leave it alone\n * (developer's manual marker wins).\n * - The schema `<script type=\"application/saastro-schema\">` body is\n * skipped — its content is JSON, not template.\n * - The section root carries `data-saastro=\"sec:…\"` and we never\n * stamp it again.\n */\n\nimport type {\n ComponentField,\n ComponentSchema,\n} from '@saastro/studio-core/component-schema/types'\n\nexport interface InstrumentFieldsOptions {\n /** Component file path — used for diagnostics only. */\n componentPath?: string\n}\n\nconst FRONTMATTER_RE = /^\\s*---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/\n\n/**\n * Pure transform. Takes auto-wrapped Astro source + the schema (so we\n * know which identifiers are Props fields and what their nested shapes\n * are) and returns the source with `data-saastro-field` markers\n * injected.\n *\n * No-op when:\n * - schema has no fields (nothing to instrument)\n * - source has no `data-saastro=\"sec:` marker (auto-wrap didn't\n * instrument this file)\n */\nexport function instrumentFields(\n source: string,\n schema: ComponentSchema,\n options: InstrumentFieldsOptions = {},\n): string {\n if (!schema.fields.length) return source\n if (!source.includes('data-saastro=')) return source\n\n const fmMatch = FRONTMATTER_RE.exec(source)\n if (!fmMatch) return source\n\n const templateStart = fmMatch[0].length\n const template = source.slice(templateStart)\n\n // Carve out the schema script body so we never poke around inside\n // its JSON. The simplest and safest approach is to split on the\n // marker, transform the head only, and rejoin.\n const schemaScriptStart = template.indexOf(\n '<script type=\"application/saastro-schema\"',\n )\n\n let head: string\n let tail: string\n if (schemaScriptStart >= 0) {\n head = template.slice(0, schemaScriptStart)\n tail = template.slice(schemaScriptStart)\n } else {\n head = template\n tail = ''\n }\n\n const transformed = transformTemplate(\n head,\n schema.fields,\n options.componentPath,\n source,\n )\n return source.slice(0, templateStart) + transformed + tail\n}\n\n/**\n * TSX/JSX entry point — `.tsx`/`.jsx` components don't have Astro\n * frontmatter or a `data-saastro=\"sec:...\"` section root. The Studio\n * overlay walks the section subtree at runtime, so `data-saastro-field`\n * markers emitted ANYWHERE inside the section's DOM tree get resolved\n * against that section's prefix — regardless of which file rendered\n * them. We can therefore reuse the same array/scalar stamping engine\n * the Astro path uses; we just skip the frontmatter slicing and the\n * schema-script carve-out.\n *\n * Used for React-island components whose contents are part of an Astro\n * section. The schema source is whatever `interface Props` lives in\n * the TSX file itself — see `extractComponentSchemaFromTsx` in\n * `@saastro/studio-core`.\n */\nexport function instrumentJsxFields(\n source: string,\n schema: ComponentSchema,\n options: InstrumentFieldsOptions = {},\n): string {\n if (!schema.fields.length) return source\n // Cheap rejection: no JSX at all means nothing to stamp. Saves the\n // ts-morph round-trip for components that are pure logic (hooks /\n // helpers / non-rendering exports).\n if (!/<[A-Za-z]/.test(source)) return source\n return transformTemplate(source, schema.fields, options.componentPath, source)\n}\n\n/**\n * Top-level transform of the template body (everything between\n * frontmatter and the schema script). Order matters:\n *\n * 1. Array fields first — their `.map()` block is rewritten as a\n * whole, after which the inner JSX gets per-item markers.\n * 2. Plain (non-array) fields — `{ident}` references in the\n * remaining template.\n */\nfunction transformTemplate(\n template: string,\n fields: readonly ComponentField[],\n componentPath?: string,\n source: string = template,\n): string {\n let out = template\n\n // Step 1 — arrays with `.map()`.\n for (const field of fields) {\n if (field.kind !== 'array') continue\n const res = transformArrayField(out, field)\n out = res.out\n // Silent-failure guard: an array prop whose `.map()` the regex couldn't\n // find IN THE TEMPLATE (it's iterated via a derived variable, a chained\n // method, or a destructured param — e.g. `const cols = xs.map(...)` in the\n // frontmatter, then `cols.map(...)` in the body) gets NO per-item markers,\n // so its items are invisible / uneditable in the Studio. Warn so the author\n // can add the `data-saastro-field` escape-hatch (which the Hub reads).\n // Only fires when the prop IS mapped somewhere in the source (so we don't\n // warn about data-only array props) and carries no hand-written marker yet.\n if (\n !res.matched &&\n hasMapReference(source, field.name) &&\n !hasHandWrittenFieldMarker(template, field.name)\n ) {\n warnUninstrumentedArray(componentPath, field.name)\n }\n }\n\n // Step 2 — scalar / object / enum / etc. references.\n for (const field of fields) {\n if (field.kind === 'array') continue\n out = transformScalarField(out, field.name)\n }\n\n return out\n}\n\n/**\n * True when the template already has a hand-written `data-saastro-field` (or\n * legacy `data-saastro=\"img:…\"`) marker referencing `<name>` — i.e. the author\n * instrumented this array's items manually because autoWrap couldn't. Used to\n * suppress the \"uninstrumented array\" warning when it's already solved.\n */\nfunction hasHandWrittenFieldMarker(template: string, name: string): boolean {\n const n = escapeReg(name)\n // data-saastro-field={`…name[…`} or data-saastro={`…img:…name.…`}\n return new RegExp(\n 'data-saastro(?:-field)?\\\\s*=\\\\s*\\\\{\\\\s*`[^`]*\\\\b' + n + '[[.]',\n ).test(template)\n}\n\n/**\n * Build-time DX warning: an array prop couldn't be auto-instrumented. Points\n * the author at the hand-written escape-hatch. Fires once per uninstrumented\n * array field per transform (i.e. per file change in dev).\n */\nfunction warnUninstrumentedArray(\n componentPath: string | undefined,\n name: string,\n): void {\n const where = componentPath ? ` in ${componentPath}` : ''\n // eslint-disable-next-line no-console\n console.warn(\n `[saastro/studio] array prop \"${name}\"${where} was not auto-instrumented: ` +\n `no \\`${name}.map(...)\\` found in the template (it may be iterated via a ` +\n `derived variable, a chained method, or a destructured param). Its items ` +\n `won't be editable in the Studio until you hand-write data-saastro-field ` +\n `markers — e.g. <img src={item.image} data-saastro-field={\\`${name}[\\${i}].image\\`} />.`,\n )\n}\n\n/**\n * True when `<name>.map(...)` appears anywhere in the source (frontmatter OR\n * template) — the array IS iterated, even if no per-item markers got stamped\n * (it was mapped to a derived variable, or chained). Distinguishes a\n * rendered-but-uninstrumented array (warn) from a data-only prop that is never\n * iterated (don't warn).\n */\nfunction hasMapReference(source: string, name: string): boolean {\n return new RegExp('\\\\b' + escapeReg(name) + '\\\\s*\\\\.\\\\s*map\\\\s*\\\\(').test(source)\n}\n\n/* ─────────────────────── Array .map() rewriting ─────────────────────── */\n\n/**\n * Rewrite `{<name>.map((item[, i]) => <jsx/>)}` so:\n *\n * - The iterator gains a second parameter `__i` (or reuses an existing\n * one, renaming it to `__i`).\n * - The first JSX element inside the body gets\n * `data-saastro-field={\\`<name>[\\${__i}]\\`}`.\n * - Each `{item.<sub>}` inside gets a wrapper / parent marker of\n * `<name>[\\${__i}].<sub>` (depending on single-child / multi-child).\n */\nfunction transformArrayField(\n template: string,\n field: ComponentField,\n): { out: string; matched: boolean } {\n const name = field.name\n const subFields = field.items?.fields ?? []\n\n // Anchor on `<name> . map ( ( <param> [, <idx>] ) =>`. We deliberately\n // do NOT require a leading `{` here — the iterator is sometimes guarded\n // by a conditional shortcut (e.g. `{xs && xs.map(...)}`) and matching\n // the bare method call is enough to find every site we care about.\n const re = new RegExp(\n `\\\\b${escapeReg(name)}\\\\s*\\\\.\\\\s*map\\\\s*\\\\(\\\\s*\\\\(\\\\s*([A-Za-z_$][\\\\w$]*)\\\\s*(?:,\\\\s*([A-Za-z_$][\\\\w$]*)\\\\s*)?\\\\)\\\\s*=>\\\\s*`,\n 'g',\n )\n\n let result = ''\n let cursor = 0\n let matchCount = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(template)) !== null) {\n matchCount++\n const headerStart = m.index\n const paramName = m[1]!\n const existingIdx = m[2] ?? null\n const arrowEnd = headerStart + m[0].length\n\n const body = readArrowBody(template, arrowEnd)\n if (!body) continue\n\n let innerJsx = body.bodyText\n if (existingIdx && existingIdx !== '__i') {\n const renameRe = new RegExp(`\\\\b${escapeReg(existingIdx)}\\\\b`, 'g')\n innerJsx = innerJsx.replace(renameRe, '__i')\n }\n const transformedJsx = stampArrayItem(innerJsx, name, paramName, subFields)\n\n const headerNew = `${name}.map((${paramName}, __i) => `\n\n result += template.slice(cursor, headerStart) + headerNew\n if (body.parens) {\n result += '(' + transformedJsx + ')'\n } else {\n result += transformedJsx\n }\n // Preserve text from the end of the body (or its closing paren) up\n // to the close of the `.map(` call — that range carries any\n // user-authored whitespace / comments. Note we use `bodyCloseRel`\n // (which always points PAST the body's terminator, including the\n // wrapping `)` when present) rather than `bodyOffset + bodyText.length`\n // because the latter is whitespace-trimmed and would leak the\n // body's close paren back into the slice.\n const afterBody = arrowEnd + body.bodyCloseRel\n const callCloseAbsEnd = arrowEnd + body.totalEndRel\n result += template.slice(afterBody, callCloseAbsEnd)\n\n cursor = callCloseAbsEnd\n re.lastIndex = cursor\n }\n result += template.slice(cursor)\n return { out: result, matched: matchCount > 0 }\n}\n\n/**\n * Read the arrow function body starting at `start` (immediately after\n * `=>`). Two shapes supported:\n *\n * a) `( <jsx> )` — paren-wrapped expression. Walk balanced parens.\n * b) `<jsx />` — unwrapped JSX. Walk one element.\n *\n * Returns positional info so callers can slice and rebuild.\n */\nfunction readArrowBody(\n template: string,\n start: number,\n): null | {\n parens: boolean\n bodyOffset: number // start of bodyText relative to `start`\n bodyText: string\n /**\n * Offset past the close of the arrow body itself, relative to `start`.\n * For `parens: true` this is one past the `)` that wraps the body.\n * For `parens: false` this equals `bodyOffset + bodyText.length`.\n */\n bodyCloseRel: number\n totalEndRel: number // offset past the closing `)` of the .map call, relative to `start`\n} {\n // Skip whitespace\n let i = start\n while (i < template.length && /\\s/.test(template[i]!)) i++\n if (i >= template.length) return null\n\n let bodyStart: number\n let bodyEnd: number\n let parens: boolean\n\n if (template[i] === '(') {\n parens = true\n // Walk balanced parens to find the body's wrapping `(...)`.\n const inner = readBalanced(template, i, '(', ')')\n if (inner == null) return null\n bodyStart = i + 1\n bodyEnd = inner // index of matching ')'\n // Trim whitespace inside the parens.\n while (bodyStart < bodyEnd && /\\s/.test(template[bodyStart]!)) bodyStart++\n let trimEnd = bodyEnd\n while (trimEnd > bodyStart && /\\s/.test(template[trimEnd - 1]!)) trimEnd--\n bodyEnd = trimEnd\n\n // After the close paren, find the closing `)` of the `.map(` call.\n let j = inner + 1\n while (j < template.length && /\\s/.test(template[j]!)) j++\n if (template[j] !== ')') return null\n j++\n\n return {\n parens,\n bodyOffset: bodyStart - start,\n bodyText: template.slice(bodyStart, bodyEnd),\n bodyCloseRel: inner + 1 - start,\n totalEndRel: j - start,\n }\n }\n\n // Unwrapped JSX — read one element.\n if (template[i] === '<') {\n parens = false\n const elEnd = readJsxElement(template, i)\n if (elEnd == null) return null\n bodyStart = i\n bodyEnd = elEnd\n\n let j = bodyEnd\n while (j < template.length && /\\s/.test(template[j]!)) j++\n if (template[j] !== ')') return null\n j++\n\n return {\n parens,\n bodyOffset: bodyStart - start,\n bodyText: template.slice(bodyStart, bodyEnd),\n bodyCloseRel: bodyEnd - start,\n totalEndRel: j - start,\n }\n }\n\n return null\n}\n\n/**\n * Walk balanced delimiters starting at `i` (which points at `open`).\n * Returns the index of the matching close, or null if unbalanced.\n *\n * Aware of strings (single, double, template) and JSX expression braces.\n */\nfunction readBalanced(\n s: string,\n i: number,\n open: string,\n close: string,\n): number | null {\n if (s[i] !== open) return null\n let depth = 0\n let j = i\n while (j < s.length) {\n const ch = s[j]!\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n j = skipString(s, j) + 1\n continue\n }\n if (ch === open) depth++\n else if (ch === close) {\n depth--\n if (depth === 0) return j\n }\n j++\n }\n return null\n}\n\nfunction skipString(s: string, i: number): number {\n const quote = s[i]!\n let j = i + 1\n while (j < s.length) {\n const ch = s[j]!\n if (ch === '\\\\') {\n j += 2\n continue\n }\n // Template literals can hold `${...}` — walk through them as balanced\n // braces. Strings inside the expression are themselves handled\n // recursively by the outer walker, but for our simple needs the\n // greedy approach is enough.\n if (quote === '`' && ch === '$' && s[j + 1] === '{') {\n const close = readBalanced(s, j + 1, '{', '}')\n if (close == null) return s.length - 1\n j = close + 1\n continue\n }\n if (ch === quote) return j\n j++\n }\n return s.length - 1\n}\n\n/**\n * Read one JSX element starting at `<`. Returns the index just past the\n * matching close tag (or self-close). Handles nested same-named tags.\n *\n * This is conservative — anything tricky causes a return of null, which\n * makes the caller skip the rewrite and leave the original text alone.\n */\nfunction readJsxElement(s: string, start: number): number | null {\n if (s[start] !== '<') return null\n // Read tag name\n let i = start + 1\n const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(i))\n if (!nameMatch) return null\n const tag = nameMatch[0]\n i += tag.length\n\n // Walk attributes until we hit `>` or `/>`\n let inString: string | null = null\n let braceDepth = 0\n while (i < s.length) {\n const ch = s[i]!\n if (inString) {\n if (ch === '\\\\') {\n i += 2\n continue\n }\n if (inString === '`' && ch === '$' && s[i + 1] === '{') {\n const close = readBalanced(s, i + 1, '{', '}')\n if (close == null) return null\n i = close + 1\n continue\n }\n if (ch === inString) inString = null\n i++\n continue\n }\n if (braceDepth > 0) {\n if (ch === '{') braceDepth++\n else if (ch === '}') braceDepth--\n else if (ch === '\"' || ch === \"'\" || ch === '`') inString = ch\n i++\n continue\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n i++\n continue\n }\n if (ch === '{') {\n braceDepth = 1\n i++\n continue\n }\n if (ch === '/' && s[i + 1] === '>') {\n // Self-closing — done.\n return i + 2\n }\n if (ch === '>') {\n i++\n // Now walk children until matching `</tag>`.\n let depth = 1\n const re = new RegExp(`<\\\\/?${tag}\\\\b`, 'g')\n re.lastIndex = i\n while (true) {\n const m = re.exec(s)\n if (!m) return null\n const isClose = s[m.index + 1] === '/'\n // Find the `>` that ends this tag, accounting for attributes.\n let k = m.index + (isClose ? 2 + tag.length : 1 + tag.length)\n let str: string | null = null\n let bd = 0\n while (k < s.length) {\n const c = s[k]!\n if (str) {\n if (c === '\\\\') {\n k += 2\n continue\n }\n if (c === str) str = null\n k++\n continue\n }\n if (bd > 0) {\n if (c === '{') bd++\n else if (c === '}') bd--\n k++\n continue\n }\n if (c === '\"' || c === \"'\" || c === '`') {\n str = c\n k++\n continue\n }\n if (c === '{') {\n bd = 1\n k++\n continue\n }\n if (c === '/' && s[k + 1] === '>') {\n k += 2\n break\n }\n if (c === '>') {\n k++\n break\n }\n k++\n }\n if (isClose) {\n depth--\n if (depth === 0) return k\n } else {\n // Self-closing siblings don't increment depth; check explicitly.\n // Heuristic — if `s[k - 2] === '/'` it was self-closing.\n if (s[k - 2] !== '/') depth++\n }\n re.lastIndex = k\n }\n }\n i++\n }\n return null\n}\n\n/**\n * Stamp markers inside the body of an array `.map()` callback:\n *\n * - The outer iteration element gets `data-saastro-field={\\`name[\\${__i}]\\`}`\n * - For each sub-field declared in the schema, find `{paramName.<sub>}`\n * and stamp `name[\\${__i}].<sub>` on the appropriate element (parent\n * for single-child, span wrap for multi-child).\n */\nfunction stampArrayItem(\n jsx: string,\n fieldName: string,\n paramName: string,\n subFields: readonly ComponentField[],\n): string {\n // Stamp the outer element first.\n let out = stampOuterElement(\n jsx,\n `{\\`${fieldName}[\\${__i}]\\`}`,\n )\n\n // Then per-sub-field references. The attribute VALUE we emit is a\n // JSX expression: `{`<path>[${__i}].<sub>`}` — caller passes the\n // full wrapped value so `injectAttr` can paste it as-is. The value\n // is always a literal `{…}` expression here (never a JSON-quoted\n // string) because we need to interpolate `${__i}` per iteration.\n for (const sub of subFields) {\n const propPath = `{\\`${fieldName}[\\${__i}].${sub.name}\\`}`\n out = transformDottedReference(out, paramName, sub.name, propPath)\n }\n return out\n}\n\n/**\n * Inject `data-saastro-field={…}` on the FIRST element of a JSX\n * fragment — used for the `.map()` iteration root. Skips elements that\n * already have a marker.\n *\n * JSX-attribute aware: walks the opening tag with brace + string\n * tracking so attribute values like `onClick={() => setActive(i)}` —\n * which contain a `>` inside an arrow expression — don't fool a naive\n * `/<tag[^>]*>/` regex into closing the tag early. The Astro template\n * path was tolerant of the simpler regex because Astro components\n * rarely embed arrow callbacks in attributes; TSX does it constantly.\n */\nfunction stampOuterElement(jsx: string, valueExpr: string): string {\n const start = jsx.search(/<[A-Za-z]/)\n if (start < 0) return jsx\n const tagInfo = readOpeningTag(jsx, start)\n if (!tagInfo) return jsx\n if (/data-saastro-field/.test(tagInfo.attrs)) return jsx\n const inject = ` data-saastro-field=${valueExpr}`\n // Insert the marker right after the tag NAME so the diff stays stable\n // and the rendered HTML has the marker as the first attribute. The\n // remainder of the opening tag (attrs + optional self-close + `>`) is\n // preserved verbatim — important because the regex would otherwise\n // mangle JSX expressions inside attributes.\n const insertAt = start + 1 + tagInfo.tag.length // past `<tag`\n return jsx.slice(0, insertAt) + inject + jsx.slice(insertAt)\n}\n\n/**\n * Read a JSX opening tag starting at `start` (which must point at `<`).\n * Tracks single/double/template strings AND JSX expression braces so\n * arbitrary expressions inside attribute values don't terminate the tag\n * prematurely.\n *\n * Returns:\n * - `tag`: the tag name (e.g. `button`)\n * - `attrs`: the raw attribute substring (everything after the name,\n * up to but not including the closing `>` or `/>`)\n * - `end`: index of the character AFTER the closing `>` or `/>`\n * - `selfClose`: whether the tag is self-closing (`<img … />`)\n */\nfunction readOpeningTag(\n s: string,\n start: number,\n): null | { tag: string; attrs: string; end: number; selfClose: boolean } {\n if (s[start] !== '<') return null\n const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(start + 1))\n if (!nameMatch) return null\n const tag = nameMatch[0]\n let i = start + 1 + tag.length\n const attrsStart = i\n let inString: string | null = null\n let braceDepth = 0\n while (i < s.length) {\n const ch = s[i]!\n if (inString) {\n if (ch === '\\\\') {\n i += 2\n continue\n }\n if (inString === '`' && ch === '$' && s[i + 1] === '{') {\n const close = readBalanced(s, i + 1, '{', '}')\n if (close == null) return null\n i = close + 1\n continue\n }\n if (ch === inString) inString = null\n i++\n continue\n }\n if (braceDepth > 0) {\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n i++\n continue\n }\n if (ch === '{') braceDepth++\n else if (ch === '}') braceDepth--\n i++\n continue\n }\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n i++\n continue\n }\n if (ch === '{') {\n braceDepth = 1\n i++\n continue\n }\n if (ch === '/' && s[i + 1] === '>') {\n return {\n tag,\n attrs: s.slice(attrsStart, i),\n end: i + 2,\n selfClose: true,\n }\n }\n if (ch === '>') {\n return {\n tag,\n attrs: s.slice(attrsStart, i),\n end: i + 1,\n selfClose: false,\n }\n }\n i++\n }\n return null\n}\n\n/* ─────────────────────── Scalar prop rewriting ─────────────────────── */\n\n/**\n * Find `{name}` references in the template and stamp the appropriate\n * element. Skips:\n *\n * - Compound expressions (`{name.foo}`, `{name || 'x'}`, `{name + 'y'}`)\n * - References inside attributes (those are handled separately for img)\n * - References on elements that already have `data-saastro-field`\n */\nfunction transformScalarField(template: string, name: string): string {\n return transformDottedReference(template, name, null, JSON.stringify(name))\n}\n\n/**\n * Generic per-reference rewriter shared between scalar props and array\n * sub-props.\n *\n * `valueAttr` is whatever string the caller wants pasted after the `=`\n * sign of the emitted attribute. The caller decides the quoting style:\n *\n * - scalar/static field name → caller passes `JSON.stringify(name)`,\n * yielding `data-saastro-field=\"title\"`.\n * - array sub-resource with interpolated index → caller passes a JSX\n * expression like `{`stats[${__i}].value`}`, yielding\n * `data-saastro-field={`stats[${__i}].value`}`.\n *\n * Both shapes are valid Astro template literals, so a single pasting\n * path covers both cases.\n *\n * @param paramOrName the identifier whose reference we look for. If\n * `subProp` is given, we look for `paramOrName.subProp`.\n * If `subProp` is null, we look for the bare\n * identifier `paramOrName`.\n * @param valueAttr the attribute value to emit (caller-quoted).\n */\nfunction transformDottedReference(\n template: string,\n paramOrName: string,\n subProp: string | null,\n valueAttr: string,\n): string {\n const refPattern = subProp\n ? new RegExp(\n `\\\\{\\\\s*${escapeReg(paramOrName)}\\\\s*\\\\.\\\\s*${escapeReg(subProp)}\\\\s*\\\\}`,\n 'g',\n )\n : new RegExp(`\\\\{\\\\s*${escapeReg(paramOrName)}\\\\s*\\\\}`, 'g')\n\n // Special-case: img tags with `src={ident}` get marked on the img\n // itself. We handle this before the text-position pass so the image\n // attribute case wins over a hypothetical surrounding-element pass.\n //\n // Both shapes are covered:\n // - scalar: `<img src={image} />` → match `image`\n // - array sub-prop: `<img src={c.image} />` → match `c.image`\n //\n // Without this, an `image: string` sub-field of an array iterated by\n // `.map((c) => ...)` would never get a `data-saastro-field` marker\n // and the overlay couldn't hit-test it.\n let out = template\n const imgSrcRef = subProp ? `${paramOrName}.${subProp}` : paramOrName\n out = stampImgSrc(out, imgSrcRef, valueAttr)\n\n // Walk every plain `{ident}` (or `{param.sub}`) reference and stamp\n // the parent element.\n let result = ''\n let cursor = 0\n let m: RegExpExecArray | null\n refPattern.lastIndex = 0\n while ((m = refPattern.exec(out)) !== null) {\n const refStart = m.index\n const refEnd = refStart + m[0].length\n\n // Skip if this `{ident}` is inside an attribute value (e.g. src={image}).\n // Heuristic: walk backwards from refStart to the most recent `<` or `>`.\n // If we hit `<` first WITHOUT crossing a `>`, we're inside a tag.\n if (isInsideTag(out, refStart)) {\n result += out.slice(cursor, refEnd)\n cursor = refEnd\n continue\n }\n\n // Skip if `{name}` is preceded by `.` or followed by `.`/`(` —\n // signals a member access or call (e.g. `{name.toUpperCase()}`).\n // The regex itself captures only `{ \\s* name \\s* }` so this is\n // already guarded, but we double-check defensively.\n if (out[refEnd] === '.' || out[refStart - 1] === '.') {\n result += out.slice(cursor, refEnd)\n cursor = refEnd\n continue\n }\n\n // Find the enclosing element for this expression.\n const parent = findEnclosingElement(out, refStart)\n if (!parent) {\n result += out.slice(cursor, refEnd)\n cursor = refEnd\n continue\n }\n\n // Skip the section root — already has `data-saastro=\"sec:…\"`.\n if (parent.attrs.includes('data-saastro=')) {\n result += out.slice(cursor, refEnd)\n cursor = refEnd\n continue\n }\n // Skip if already marked.\n if (parent.attrs.includes('data-saastro-field')) {\n result += out.slice(cursor, refEnd)\n cursor = refEnd\n continue\n }\n\n // Single-child vs multi-child: count meaningful \"tokens\" of children.\n const childContent = out.slice(parent.bodyStart, parent.bodyEnd)\n const onlyChild = isOnlyMeaningfulExpression(childContent, refStart - parent.bodyStart, refEnd - parent.bodyStart)\n\n if (onlyChild) {\n // Inject `data-saastro-field=...` into parent's opening tag.\n const newOpen = injectAttr(parent.openTag, valueAttr)\n const replaced = out.slice(0, parent.openStart) + newOpen + out.slice(parent.openEnd)\n // Recompute cursor / refEnd offsets — easiest is to advance based\n // on length delta and continue from after the original ref.\n const delta = newOpen.length - parent.openTag.length\n result += replaced.slice(cursor, refEnd + delta)\n cursor = refEnd + delta\n out = replaced\n refPattern.lastIndex = cursor\n } else {\n // Wrap in `<span data-saastro-field=…>{name}</span>`.\n const wrapped = `<span data-saastro-field=${valueAttr}>${m[0]}</span>`\n result += out.slice(cursor, refStart) + wrapped\n cursor = refEnd\n // Update refPattern.lastIndex relative to current `out`.\n refPattern.lastIndex = cursor\n }\n }\n result += out.slice(cursor)\n return result\n}\n\n/**\n * Stamp `<img>` tags whose `src={name}` (or templated `src={\\`…${name}…\\`}`)\n * matches our prop name. The marker goes ON the img element itself.\n */\nfunction stampImgSrc(\n template: string,\n name: string,\n valueAttr: string,\n): string {\n // Match an <img …> tag with `src={...name...}` — we keep this loose so\n // helper-wrapped src values still get caught (`src={imageUrl(image)}`).\n const re = /<img\\b([^<>]*?)\\/?>/g\n let result = ''\n let cursor = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(template)) !== null) {\n const attrs = m[1]!\n if (/data-saastro-field/.test(attrs)) continue\n const srcMatch = /\\bsrc\\s*=\\s*\\{([\\s\\S]*?)\\}/.exec(attrs)\n if (!srcMatch) continue\n const srcExpr = srcMatch[1]!\n if (!new RegExp(`\\\\b${escapeReg(name)}\\\\b`).test(srcExpr)) continue\n // Insert the marker.\n const newOpen = injectAttr(m[0], valueAttr)\n result += template.slice(cursor, m.index) + newOpen\n cursor = m.index + m[0].length\n re.lastIndex = cursor\n }\n result += template.slice(cursor)\n return result\n}\n\n/** Escape a string for use inside a RegExp. */\nfunction escapeReg(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Inject a `data-saastro-field=…` attribute into an opening tag.\n * Idempotent (returns the tag unchanged when the attribute is already\n * present). `valueAttr` is pasted verbatim — the caller chooses the\n * quoting style (JSON-quoted string vs JSX expression) before passing.\n */\nfunction injectAttr(tag: string, valueAttr: string): string {\n if (/data-saastro-field/.test(tag)) return tag\n // We want the attribute right after the tag name for stable test diffs.\n const m = /^<([A-Za-z][A-Za-z0-9]*)/.exec(tag)\n if (!m) return tag\n const insertAt = m[0].length\n return tag.slice(0, insertAt) + ` data-saastro-field=${valueAttr}` + tag.slice(insertAt)\n}\n\n/** True when the position is inside a tag (between `<` and the next `>`). */\nfunction isInsideTag(s: string, pos: number): boolean {\n for (let i = pos - 1; i >= 0; i--) {\n const ch = s[i]!\n if (ch === '>') return false\n if (ch === '<') return true\n }\n return false\n}\n\n/**\n * Find the smallest element that fully contains `pos`. Returns the\n * positions of the open tag, the body, and the open tag's attribute\n * string. Returns null when no enclosing element can be located (the\n * caller leaves the reference unmarked in that case).\n */\nfunction findEnclosingElement(\n s: string,\n pos: number,\n): null | {\n openStart: number\n openEnd: number\n openTag: string\n attrs: string\n bodyStart: number\n bodyEnd: number\n} {\n // Walk every tag in the source. Brace-aware so JSX attribute values\n // containing `>` (e.g. `onClick={() => …}`) don't truncate a tag.\n type TagInfo = {\n isClose: boolean\n selfClose: boolean\n tagName: string\n attrs: string\n start: number\n end: number\n }\n const tags: TagInfo[] = []\n for (let scan = 0; scan < s.length; ) {\n const lt = s.indexOf('<', scan)\n if (lt < 0) break\n const next = s[lt + 1]\n if (next === '/') {\n const nm = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(lt + 2))\n if (!nm) {\n scan = lt + 1\n continue\n }\n // Close tags are simple; no attribute parsing needed.\n const gt = s.indexOf('>', lt + 2 + nm[0].length)\n if (gt < 0) break\n tags.push({\n isClose: true,\n selfClose: false,\n tagName: nm[0],\n attrs: '',\n start: lt,\n end: gt + 1,\n })\n scan = gt + 1\n continue\n }\n if (typeof next === 'string' && /[A-Za-z]/.test(next)) {\n const ot = readOpeningTag(s, lt)\n if (!ot) {\n scan = lt + 1\n continue\n }\n tags.push({\n isClose: false,\n selfClose: ot.selfClose,\n tagName: ot.tag,\n attrs: ot.attrs,\n start: lt,\n end: ot.end,\n })\n scan = ot.end\n continue\n }\n scan = lt + 1\n }\n\n // Find the open tag whose body covers `pos`. Strategy: walk tags in\n // order; maintain a stack of open tags. When a close tag pops, if\n // pos is between the popped open's end and the close's start, we\n // have our parent — but we want the SMALLEST such element, so keep\n // a candidate and replace it whenever a deeper one is found.\n const stack: TagInfo[] = []\n let bestOpen: TagInfo | null = null\n let bestClose: TagInfo | null = null\n for (const t of tags) {\n if (t.selfClose) continue\n if (t.isClose) {\n // pop matching\n for (let i = stack.length - 1; i >= 0; i--) {\n if (stack[i]!.tagName === t.tagName) {\n const opener = stack[i]!\n // Drop everything above this index (mismatched, skip).\n stack.splice(i, stack.length - i)\n // Element runs from opener.end .. t.start.\n if (opener.end <= pos && pos < t.start) {\n // smallest: prefer the one with latest opener.end / earliest t.start\n if (\n !bestOpen ||\n opener.end > bestOpen.end ||\n (opener.end === bestOpen.end && t.start < bestClose!.start)\n ) {\n bestOpen = opener\n bestClose = t\n }\n }\n break\n }\n }\n } else {\n stack.push(t)\n }\n }\n\n if (!bestOpen || !bestClose) return null\n\n return {\n openStart: bestOpen.start,\n openEnd: bestOpen.end,\n openTag: s.slice(bestOpen.start, bestOpen.end),\n attrs: bestOpen.attrs,\n bodyStart: bestOpen.end,\n bodyEnd: bestClose.start,\n }\n}\n\n/**\n * Within the BODY of an element, decide whether the `{name}` expression\n * (located at relStart..relEnd within `body`) is the only meaningful\n * content. Whitespace and comments don't count. Anything else (text,\n * other expressions, sibling elements) makes it a multi-child case.\n */\nfunction isOnlyMeaningfulExpression(\n body: string,\n relStart: number,\n relEnd: number,\n): boolean {\n const before = body.slice(0, relStart)\n const after = body.slice(relEnd)\n return /^\\s*$/.test(before) && /^\\s*$/.test(after)\n}\n"],"mappings":"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","names":["Project","FRONTMATTER_RE","FRONTMATTER_RE","FIRST_OPEN_TAG_RE","FRONTMATTER_RE"]}
|
package/dist/types-DlrT0PDc.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
interface StudioAutoWrapOptions {
|
|
2
|
-
/** Turn on the auto-wrap transform. Off by default. */
|
|
3
|
-
autoWrap?: boolean;
|
|
4
|
-
/**
|
|
5
|
-
* Only transform files whose path matches this predicate. Default:
|
|
6
|
-
* every `.astro`, `.tsx`, `.jsx` file (further filtered by per-file
|
|
7
|
-
* heuristics — fieldPrefix on Astro, Props + `.map(` on TSX).
|
|
8
|
-
*/
|
|
9
|
-
include?: (id: string) => boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Instrument React components (.tsx/.jsx) whose Props match field
|
|
12
|
-
* names. When enabled, `.map((item, i) => <jsx/>)` patterns get
|
|
13
|
-
* `data-saastro-field` markers on the iterated element and on
|
|
14
|
-
* `{item.<sub>}` references. Defaults to `true`.
|
|
15
|
-
*/
|
|
16
|
-
instrumentTsx?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type { StudioAutoWrapOptions as S };
|
|
File without changes
|
|
File without changes
|