meno-astro 0.1.13 → 0.1.14
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/chunks/{chunk-YC3WSC6M.js → chunk-ENCYNJWV.js} +2 -2
- package/dist/chunks/{chunk-LEJMZHGE.js → chunk-GITHFAZG.js} +2 -1
- package/dist/chunks/{chunk-YJMWK222.js → chunk-MCIBOYQT.js} +3 -3
- package/dist/chunks/{chunk-4ICB3SZG.js → chunk-P2SFVSXG.js} +2 -2
- package/dist/chunks/{chunk-ANGVNS5N.js → chunk-Q43GCLMA.js} +2 -2
- package/dist/chunks/{chunk-5HBRX7IZ.js → chunk-SYOR6LP4.js} +2 -2
- package/dist/lib/components/Embed.astro +9 -4
- package/dist/lib/components/Link.astro +10 -1
- package/dist/lib/dialect/index.js +3 -3
- package/dist/lib/index.js +102 -61
- package/dist/lib/index.js.map +4 -4
- package/dist/lib/integration/index.js +139 -11
- package/dist/lib/integration/index.js.map +2 -2
- package/dist/lib/runtime/localeMiddleware.js +3 -3
- package/package.json +1 -1
- /package/dist/chunks/{chunk-YC3WSC6M.js.map → chunk-ENCYNJWV.js.map} +0 -0
- /package/dist/chunks/{chunk-LEJMZHGE.js.map → chunk-GITHFAZG.js.map} +0 -0
- /package/dist/chunks/{chunk-YJMWK222.js.map → chunk-MCIBOYQT.js.map} +0 -0
- /package/dist/chunks/{chunk-4ICB3SZG.js.map → chunk-P2SFVSXG.js.map} +0 -0
- /package/dist/chunks/{chunk-ANGVNS5N.js.map → chunk-Q43GCLMA.js.map} +0 -0
- /package/dist/chunks/{chunk-5HBRX7IZ.js.map → chunk-SYOR6LP4.js.map} +0 -0
package/dist/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../lib/runtime/
|
|
4
|
-
"sourcesContent": ["/**\n * meno-astro \u2014 locale route enumeration + locale-aware link helpers (pure).\n *\n * The fs-free half of meno-astro's i18n routing. `loadSlugMappings(projectRoot)`\n * (server) collects each page's `meta.slugs`; the functions here turn that `SlugMap[]`\n * into:\n *\n * - the `getStaticPaths()` entries for the injected `/[locale]/[...path]` route\n * (`LocaleRoute.astro`) \u2014 one path per (non-default locale \u00D7 page), using the page's\n * localized slug when it has one (SSR parity: every page exists in every locale),\n * - hreflang alternate links for `BaseLayout` (`buildHreflangLinks` \u2014 the dialect twin\n * of meno-core's `metaTagGenerator` hreflang block),\n * - slug-translated locale-switcher items for `LocaleList` (`localeListItems`).\n *\n * Everything composes meno-core's slug translator (`buildSlugIndex`/`getLocaleLinks`/\n * `resolveSlugToPageId`) \u2014 no translation logic is reinvented here. Pure functions, no\n * filesystem, no Astro coupling: unit-testable under bun:test (this package has no Astro\n * toolchain; the `.astro` consumers stay thin shells over these).\n */\n\nimport {\n buildSlugIndex,\n getLocaleLinks,\n resolveSlugToPageId,\n extractLocaleFromPath,\n} from 'meno-core/shared';\nimport type { I18nConfig } from 'meno-core/shared/types';\nimport type { SlugMap, LocaleLink } from 'meno-core/shared';\n\n/**\n * One `getStaticPaths()` entry of the injected locale route. `params.path` is the rest\n * segment after `/[locale]/` \u2014 `undefined` encodes the empty rest param (Astro's required\n * shape for `/pl/`). `props.pageId` tells `LocaleRoute.astro` which page module to render,\n * so the component never reverse-looks-up slugs.\n */\nexport interface LocaleStaticPath {\n params: { locale: string; path: string | undefined };\n props: { pageId: string };\n}\n\n/**\n * Enumerate every (non-default locale \u00D7 page) route the injected `/[locale]/[...path]`\n * route serves. Slug preference per page+locale mirrors `translatePath`'s chain:\n * the locale's own slug \u2192 the default locale's slug \u2192 `_default` (slugless pages) \u2192\n * the pageId itself. The default locale is never enumerated (it lives un-prefixed,\n * `prefixDefaultLocale: false`). Single-locale configs yield `[]` (the integration also\n * skips injecting the route then \u2014 defense in depth).\n */\nexport function enumerateLocaleStaticPaths(\n mappings: SlugMap[],\n config: I18nConfig,\n): LocaleStaticPath[] {\n if (!config.locales || config.locales.length <= 1) return [];\n const paths: LocaleStaticPath[] = [];\n for (const { code } of config.locales) {\n if (code === config.defaultLocale) continue;\n for (const { pageId, slugs } of mappings) {\n const slug = (\n slugs[code] ??\n slugs[config.defaultLocale] ??\n slugs._default ??\n (pageId === 'index' ? '' : pageId)\n ).replace(/^\\/+/, '');\n paths.push({\n params: { locale: code, path: slug === '' ? undefined : slug },\n props: { pageId },\n });\n }\n }\n return paths;\n}\n\n/**\n * The `import.meta.glob('/src/pages/**\\/*.astro')` key for a pageId \u2014 the glob-key\n * normalization seam between `loadSlugMappings`' pageIds and `LocaleRoute.astro`'s\n * page-module map.\n */\nexport function pageModuleKey(pageId: string): string {\n return `/src/pages/${pageId}.astro`;\n}\n\n/** One hreflang alternate for BaseLayout's `<head>`. */\nexport interface HreflangLink {\n hreflang: string;\n href: string;\n}\n\n/**\n * Strip a trailing slash (keeping the root `/`). Astro's rendered pathnames carry one\n * under its default `build.format: 'directory'` (`/pl/o-nas/`), but meno-core's\n * `translatePath` extracts slugs by bare string surgery \u2014 `o-nas/` would miss the slug\n * index and silently degrade every link to the prefix-swap fallback.\n */\nfunction normalizePathname(pathname: string): string {\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') || '/' : pathname;\n}\n\n/**\n * Resolve the locale-stripped `pathname` to a known pageId, or undefined. The slug-index\n * lookup is tried for the current locale then the default locale (the same fallback\n * `translatePath` uses); `_default` entries never match a locale lookup, so slugless\n * pages resolve via the bare path-as-pageId check.\n */\nfunction resolveCurrentPage(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n index: Map<string, { pageId: string; slugs: Record<string, string> }>,\n): string | undefined {\n const { pathWithoutLocale } = extractLocaleFromPath(pathname, config);\n const slug = pathWithoutLocale.replace(/^\\/+|\\/+$/g, '');\n const byLocale =\n resolveSlugToPageId(slug, currentLocale, index) ??\n resolveSlugToPageId(slug, config.defaultLocale, index);\n if (byLocale) return byLocale;\n const asPageId = slug === '' ? 'index' : slug;\n return mappings.some((m) => m.pageId === asPageId) ? asPageId : undefined;\n}\n\n/**\n * Hreflang alternates for the page at `pathname` \u2014 the dialect twin of meno-core's\n * `metaTagGenerator` hreflang block (one `<link rel=\"alternate\">` per locale, `langTag`\n * values, relative hrefs, plus `x-default` \u2192 the default-locale path).\n *\n * Returns `[]` when the project is single-locale, `mappings` is empty, or the current\n * path doesn't resolve to a known page. The last guard is a deliberate (small) deviation\n * from SSR: pages this module can't route (e.g. CMS items, until the CMS i18n follow-up)\n * must not advertise `/pl/\u2026` URLs that 404 in static output.\n */\nexport function buildHreflangLinks(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n): HreflangLink[] {\n if (!config.locales || config.locales.length <= 1 || mappings.length === 0) return [];\n const path = normalizePathname(pathname);\n const index = buildSlugIndex(mappings);\n if (!resolveCurrentPage(path, currentLocale, config, mappings, index)) return [];\n\n const links = getLocaleLinks(path, currentLocale, config, index);\n const out: HreflangLink[] = links.map((l) => ({ hreflang: l.langTag, href: l.path }));\n const defaultLink = links.find((l) => l.locale === config.defaultLocale);\n if (defaultLink) out.push({ hreflang: 'x-default', href: defaultLink.path });\n return out;\n}\n\n/** One LocaleList switcher item (pre-translated href; label = nativeName ?? code). */\nexport interface LocaleListItem {\n locale: string;\n href: string;\n label: string;\n langTag: string;\n isCurrent: boolean;\n icon?: string;\n}\n\n/**\n * Slug-translated locale-switcher items for `LocaleList.astro` \u2014 `getLocaleLinks` over\n * the slug index (so `/pl/o-nas`, not a naive `/pl/about` prefix swap), with the\n * component's `showCurrent` filter applied. For unknown paths `translatePath` degrades to\n * the locale-prefix swap, which matches the previous LocaleList behavior.\n */\nexport function localeListItems(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n showCurrent: boolean,\n): LocaleListItem[] {\n const index = buildSlugIndex(mappings);\n const byCode = new Map(config.locales.map((l) => [l.code, l]));\n return getLocaleLinks(normalizePathname(pathname), currentLocale, config, index)\n .filter((l: LocaleLink) => showCurrent || !l.isCurrent)\n .map((l: LocaleLink) => ({\n locale: l.locale,\n href: l.path,\n label: l.nativeName || l.locale,\n langTag: l.langTag,\n isCurrent: l.isCurrent,\n icon: byCode.get(l.locale)?.icon,\n }));\n}\n", "/**\n * meno-astro/server \u2014 `loadSlugMappings`.\n *\n * Scans a converted project's `src/pages/**\\/*.astro` files and collects each page's\n * `meta.slugs` (per-locale URL slugs, e.g. `{ en: \"about\", pl: \"o-nas\" }`) into the\n * `SlugMap[]` shape meno-core's slug translator consumes. This is the astro-format twin of\n * `PageService.getSlugMappings()` (meno-core), and it mirrors that method's conventions\n * exactly so `buildSlugIndex`/`translatePath`/`getLocaleLinks` behave identically:\n *\n * - `pageId` is the route path without the leading slash (`/` \u2192 `\"index\"`).\n * - Pages WITHOUT `meta.slugs` contribute `{ _default: <route path> }` (`\"\"` for the\n * index page). `_default` entries never match a locale lookup directly \u2014 they flow\n * through `translatePath`'s no-entry fallback (locale-prefix swap, same slug), which\n * is the SSR behavior for slugless pages.\n *\n * Consumed at build/render time (cwd = project root during `astro dev`/`build`, same\n * contract as `loadI18nConfig`/`loadFontCss`) by:\n * - the injected `[locale]/[...path]` route's `getStaticPaths` (locale route enumeration),\n * - `LocaleList.astro` (slug-translated switcher links),\n * - `BaseLayout.astro` (hreflang alternates).\n *\n * Excluded from the scan:\n * - dynamic routes (any path segment containing `[`) \u2014 this is deliberately also the\n * CMS-template exclusion (`src/pages/<collection>/[slug].astro`); CMS i18n is a\n * follow-up phase,\n * - `404.astro` / `500.astro` (error routes have no locale variants).\n *\n * A module-level per-file mtime cache keeps repeated calls cheap (LocaleList + BaseLayout\n * call this on every render): each call re-stats every page file but re-parses only\n * changed/new ones, so editor saves (AstroPageProvider rewrites the `.astro` file) take\n * effect on the next render without any explicit invalidation. Never throws \u2014 an\n * unreadable/unparseable page degrades to its `_default` entry.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { join, relative, sep } from 'path';\nimport { readPageMeta } from '../dialect/parse/parseFrontmatter';\nimport type { SlugMap } from 'meno-core/shared';\n\ninterface CacheEntry {\n mtimeMs: number;\n map: SlugMap;\n}\n\n/** projectRoot \u2192 (absolute page file path \u2192 cached entry). */\nconst cache = new Map<string, Map<string, CacheEntry>>();\n\n/** Reset the cache (test seam). */\nexport function clearSlugMappingsCache(): void {\n cache.clear();\n}\n\n/** `meta.slugs` is usable when it is a non-empty plain record of string slugs. */\nfunction readSlugs(meta: Record<string, unknown> | undefined): Record<string, string> | undefined {\n const slugs = meta?.slugs;\n if (!slugs || typeof slugs !== 'object' || Array.isArray(slugs)) return undefined;\n const entries = Object.entries(slugs as Record<string, unknown>);\n if (entries.length === 0 || entries.some(([, v]) => typeof v !== 'string')) return undefined;\n return slugs as Record<string, string>;\n}\n\n/** Page files under `pagesDir`, excluding dynamic routes (`[`) and 404/500. */\nfunction collectPageFiles(pagesDir: string): string[] {\n const files: string[] = [];\n const walk = (dir: string) => {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n if (e.name.includes('[')) continue; // dynamic route (CMS templates, user catch-alls)\n const p = join(dir, e.name);\n if (e.isDirectory()) walk(p);\n else if (e.name.endsWith('.astro') && !/^(404|500)\\.astro$/.test(e.name)) files.push(p);\n }\n };\n walk(pagesDir);\n return files;\n}\n\n/**\n * Collect the slug mappings for the project rooted at `projectRoot`.\n * Deterministic order (sorted by pageId); never throws.\n */\nexport function loadSlugMappings(projectRoot: string): SlugMap[] {\n const mappings: SlugMap[] = [];\n try {\n const pagesDir = join(projectRoot, 'src', 'pages');\n if (!existsSync(pagesDir)) return mappings;\n\n let fileCache = cache.get(projectRoot);\n if (!fileCache) {\n fileCache = new Map();\n cache.set(projectRoot, fileCache);\n }\n\n const files = collectPageFiles(pagesDir);\n const seen = new Set<string>();\n\n for (const file of files) {\n seen.add(file);\n // `about.astro` \u2192 \"about\", `index.astro` \u2192 \"index\", `docs/intro.astro` \u2192 \"docs/intro\".\n const pageId = relative(pagesDir, file).split(sep).join('/').replace(/\\.astro$/, '');\n try {\n const mtimeMs = statSync(file).mtimeMs;\n const cached = fileCache.get(file);\n if (cached && cached.mtimeMs === mtimeMs) {\n mappings.push(cached.map);\n continue;\n }\n const slugs = readSlugs(readPageMeta(readFileSync(file, 'utf8')));\n const map: SlugMap = slugs\n ? { pageId, slugs }\n : { pageId, slugs: { _default: pageId === 'index' ? '' : pageId } };\n fileCache.set(file, { mtimeMs, map });\n mappings.push(map);\n } catch {\n // Unreadable/unparseable page \u2192 SSR-parity default entry (uncached).\n mappings.push({ pageId, slugs: { _default: pageId === 'index' ? '' : pageId } });\n }\n }\n\n // Drop cache entries for deleted files.\n for (const file of fileCache.keys()) {\n if (!seen.has(file)) fileCache.delete(file);\n }\n } catch {\n // No pages dir / unreadable tree \u2192 empty mappings (single-locale-like degradation).\n }\n return mappings.sort((a, b) => a.pageId.localeCompare(b.pageId));\n}\n", "/**\n * meno-astro/server \u2014 `loadFontCss`.\n *\n * Reads a project's `project.config.json`, pulls its `fonts` array, and renders the\n * `@font-face` rules + `<link rel=\"preload\">` tags for `BaseLayout.astro` to drop into\n * `<head>`. This is the dialect twin's counterpart to what meno-core's SSR\n * (`htmlGenerator`) and JSON\u2192Astro export (`build-astro`) already emit \u2014 without it the\n * font files ship but are never *defined*, so any `font-family: 'Inter'` declared via\n * `style()` silently falls back to a system font.\n *\n * Formatting is delegated wholesale to meno-core's pure `fontCss` helpers so all three\n * renderers stay byte-identical. Any failure (missing file, bad JSON, no `fonts`)\n * degrades to empty strings \u2014 a project always renders, just without custom fonts.\n *\n * Server/build-only (touches the filesystem); BaseLayout's frontmatter runs at\n * build/SSR time, never in the browser.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n// Import from the `meno-core/shared` barrel (not the deep `./fontCss` path): the\n// published meno-core bundles shared modules into the barrel and does not emit\n// `dist/lib/shared/fontCss.js`, so the deep path would 404 in a consumer project.\nimport { fontFaceCss, fontPreloadLinks, type FontConfig } from 'meno-core/shared';\n\nexport interface FontHeadAssets {\n /** `@font-face` rules for a `<style>` tag (empty string when no fonts). */\n css: string;\n /** `<link rel=\"preload\">` tags (empty string when no fonts). */\n preloads: string;\n}\n\nconst EMPTY: FontHeadAssets = { css: '', preloads: '' };\n\n/**\n * Load font `<head>` assets for the project rooted at `projectRoot`.\n *\n * Resolution: read `<projectRoot>/project.config.json`, take its `.fonts` array, and\n * format it. Never throws \u2014 every failure path returns empty strings.\n */\nexport function loadFontCss(projectRoot: string): FontHeadAssets {\n try {\n const cfgPath = join(projectRoot, 'project.config.json');\n if (!existsSync(cfgPath)) return EMPTY;\n const parsed = JSON.parse(readFileSync(cfgPath, 'utf8')) as { fonts?: FontConfig[] };\n const fonts = Array.isArray(parsed.fonts) ? parsed.fonts : [];\n if (fonts.length === 0) return EMPTY;\n return { css: fontFaceCss(fonts), preloads: fontPreloadLinks(fonts) };\n } catch {\n return EMPTY;\n }\n}\n", "/**\n * meno-astro/server \u2014 `loadLibraries`.\n *\n * Reads a project's `project.config.json`, merges its three library tiers (global\n * `libraries` \u2192 component tier \u2192 the page's `meta.libraries`), and renders the `<link>` /\n * `<script>` / inline `<style>` tags for `BaseLayout.astro` to drop into `<head>` (and\n * before `</body>` for body-end scripts). Without it the libraries config ships in the\n * project but is never *loaded* \u2014 the converter's gap this closes.\n *\n * The component tier is collected LIVE from `src/components/**\u2215*.astro` (each component's\n * `const __meno = {\u2026}` literal) on every render \u2014 the dialect twin of meno-core's SSR,\n * which runs `collectComponentLibraries` over the live registry per render. A project being\n * edited bidirectionally rewrites its component files continuously; the convert-time\n * `__componentLibraries` snapshot in `project.config.json` goes stale the moment that\n * happens (nothing refreshes it), which made component-tier libraries vanish from real\n * Astro renders (`astro dev` play mode, `astro build`). The snapshot remains the fallback\n * for output without a components dir.\n *\n * This is the dialect twin of meno-core's SSR (`htmlGenerator.ts`): same merge order, same\n * URL-dedupe, same local-CSS inlining, so the two runtimes stay byte-identical. The Astro\n * output is always the **build** context (`disableBuild` libs dropped, `disableEditor` kept;\n * no dev cache-busting). The pure tag/merge logic is reused wholesale from meno-core's\n * `libraryLoader` (exposed on the `meno-core/shared` barrel).\n *\n * Any failure (missing file, bad JSON, unreadable local CSS) degrades to empty strings \u2014 a\n * project always renders, just without its libraries.\n *\n * Server/build-only (touches the filesystem); BaseLayout's frontmatter runs at build/SSR\n * time, never in the browser.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\n// Import from the `meno-core/shared` barrel (not a deep `./libraryLoader` path): the\n// published meno-core bundles shared modules into the barrel and does not emit\n// `dist/lib/shared/libraryLoader.js`, so the deep path would 404 in a consumer project.\n// Same constraint that drives loadFontCss's import.\nimport {\n mergeLibraries,\n filterLibrariesByContext,\n generateLibraryTags,\n type LibrariesConfig,\n type PageLibrariesConfig,\n type JSLibraryConfig,\n type CSSLibraryConfig,\n type LibraryTags,\n} from 'meno-core/shared';\nimport { stripImportedLibraries } from '../dialect/libraryImports';\nimport { readComponentMeta } from '../dialect/parse/parseFrontmatter';\n\nconst EMPTY: LibraryTags = { headCSS: '', headJS: '', bodyEndJS: '' };\n\n/**\n * A page's meta as far as libraries are concerned. Structural (not `MenoPageMeta`) to avoid\n * a type cycle with the root barrel, which re-exports `loadLibraries`. BaseLayout passes the\n * full `meta`, which is assignable.\n */\nexport interface PageLibrariesMeta {\n libraries?: PageLibrariesConfig;\n}\n\n/**\n * Normalize a raw `libraries` block (string URLs OR object configs) to object form \u2014 mirrors\n * `ConfigService.getLibraries` so author-written `\"js\": [\"https://\u2026\"]` shorthand still works.\n */\nfunction normalizeLibraries(raw: unknown): LibrariesConfig {\n if (!raw || typeof raw !== 'object') return { js: [], css: [] };\n const libs = raw as { js?: unknown; css?: unknown };\n const js = Array.isArray(libs.js)\n ? (libs.js.map((l) => (typeof l === 'string' ? { url: l } : l)) as JSLibraryConfig[])\n : [];\n const css = Array.isArray(libs.css)\n ? (libs.css.map((l) => (typeof l === 'string' ? { url: l } : l)) as CSSLibraryConfig[])\n : [];\n return { js, css };\n}\n\n/** Deduplicate JS + CSS libraries by URL (first occurrence wins). */\nfunction dedupeByUrl(libs: LibrariesConfig): LibrariesConfig {\n const seenJS = new Set<string>();\n const seenCSS = new Set<string>();\n return {\n js: (libs.js || []).filter((l) => (seenJS.has(l.url) ? false : (seenJS.add(l.url), true))),\n css: (libs.css || []).filter((l) => (seenCSS.has(l.url) ? false : (seenCSS.add(l.url), true))),\n };\n}\n\n/**\n * Per-file cache for component-library extraction, keyed by absolute path and reused while\n * the file's mtime is unchanged. `astro dev` calls loadLibraries on every page render \u2014\n * re-parsing every component each time would be wasted work \u2014 while an editor save (the\n * workdir mirror rewrites the file, bumping its mtime) invalidates naturally.\n */\nconst componentLibsCache = new Map<string, { mtimeMs: number; libs: LibrariesConfig | null }>();\n\n/** A single component file's tag-tier libraries (mtime-cached), or null when it has none. */\nfunction componentLibsOf(absPath: string): LibrariesConfig | null {\n let mtimeMs: number;\n try {\n mtimeMs = statSync(absPath).mtimeMs;\n } catch {\n return null;\n }\n const cached = componentLibsCache.get(absPath);\n if (cached && cached.mtimeMs === mtimeMs) return cached.libs;\n\n let libs: LibrariesConfig | null = null;\n try {\n const raw = readComponentMeta(readFileSync(absPath, 'utf8'))?.libraries;\n if (raw) {\n // Vite-imported locals (local CSS / module JS) load via the component's own emitted\n // `import` \u2014 strip them so they aren't ALSO rendered as tags (same rule as\n // convertProject's snapshot). What remains (external + local classic JS) is the tag tier.\n const normalized = stripImportedLibraries(normalizeLibraries(raw));\n if (normalized.js?.length || normalized.css?.length) libs = normalized;\n }\n } catch {\n // Unreadable/unparseable component \u2014 contributes nothing.\n }\n\n componentLibsCache.set(absPath, { mtimeMs, libs });\n return libs;\n}\n\n/**\n * Collect the component tier the way meno-core SSR does (`collectComponentLibraries` over\n * the LIVE registry on every render), but from the dialect's canonical store: each\n * `src/components/**\u2215*.astro` carries its `libraries` inside the `const __meno = {\u2026}`\n * literal. Deduped by URL (first wins, files walked in sorted order for determinism).\n * Returns null when the project has no `src/components` dir, so the caller can fall back\n * to the convert-time `__componentLibraries` snapshot.\n */\nexport function collectAstroComponentLibraries(projectRoot: string): LibrariesConfig | null {\n const dir = join(projectRoot, 'src', 'components');\n if (!existsSync(dir)) return null;\n\n const collected: LibrariesConfig = { js: [], css: [] };\n const seenJS = new Set<string>();\n const seenCSS = new Set<string>();\n\n const visit = (d: string) => {\n let entries;\n try {\n entries = readdirSync(d, { withFileTypes: true });\n } catch {\n return;\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n for (const e of entries) {\n const abs = join(d, e.name);\n if (e.isDirectory()) {\n visit(abs);\n continue;\n }\n if (!e.isFile() || !e.name.endsWith('.astro')) continue;\n const libs = componentLibsOf(abs);\n if (!libs) continue;\n for (const js of libs.js || [])\n if (!seenJS.has(js.url)) {\n seenJS.add(js.url);\n collected.js!.push(js);\n }\n for (const css of libs.css || [])\n if (!seenCSS.has(css.url)) {\n seenCSS.add(css.url);\n collected.css!.push(css);\n }\n }\n };\n visit(dir);\n return collected;\n}\n\n/**\n * Load library `<head>`/body tags for the project rooted at `projectRoot`, for the given page.\n *\n * Merge order (mirrors htmlGenerator): global \u2192 component \u2192 page. The page tier may `extend`\n * (default) or `replace` global+component. Never throws \u2014 every failure returns empty strings.\n */\nexport function loadLibraries(projectRoot: string, pageMeta?: PageLibrariesMeta): LibraryTags {\n try {\n const cfgPath = join(projectRoot, 'project.config.json');\n if (!existsSync(cfgPath)) return EMPTY;\n const cfg = JSON.parse(readFileSync(cfgPath, 'utf8')) as {\n libraries?: unknown;\n __componentLibraries?: unknown;\n };\n\n const global = normalizeLibraries(cfg.libraries);\n // Component tier \u2014 collected LIVE from src/components/*.astro (mtime-cached) so editor\n // saves are reflected without re-converting; the convert-time `__componentLibraries`\n // snapshot is only the fallback for output without a components dir. Either way the tier\n // always extends, matching meno-core which injects every component's libs on every page.\n const component =\n collectAstroComponentLibraries(projectRoot) ?? normalizeLibraries(cfg.__componentLibraries);\n // Page tier: drop locals the page emits as Vite imports (local CSS / module JS) so they\n // aren't ALSO rendered as tags here. Global-tier locals stay (no per-page import exists for\n // them) \u2014 they're inlined (CSS) / tagged (JS) below, as in Phase 1.\n const page = pageMeta?.libraries ? stripImportedLibraries(pageMeta.libraries) : undefined;\n\n const globalPlusComponent = mergeLibraries(global, component);\n const merged = mergeLibraries(globalPlusComponent, page);\n const deduped = dedupeByUrl(merged);\n const filtered = filterLibrariesByContext(deduped, 'build');\n\n // Inline local CSS (default for `/\u2026` paths unless `inline:false`) \u2014 byte-parity with SSR.\n // External URLs and `inline:false` stay as <link>. Local files live at <projectRoot>/<path>\n // (the meno() integration serves /libraries/* etc. from the project root).\n const inlineContents = new Map<string, string>();\n for (const css of filtered.css || []) {\n if (css.inline === false || !css.url.startsWith('/')) continue;\n try {\n const filePath = join(projectRoot, css.url.split('?')[0].slice(1));\n if (filePath.startsWith(projectRoot)) {\n inlineContents.set(css.url, readFileSync(filePath, 'utf8'));\n }\n } catch {\n // Unreadable local file \u2014 fall back to a <link> tag (skip inlining).\n }\n }\n\n return generateLibraryTags(filtered, inlineContents);\n } catch {\n return EMPTY;\n }\n}\n", "/**\n * meno-astro \u2014 `href()` / `embedHtml()` runtime resolvers.\n *\n * Emitted markup binds a Link's `href` and an Embed's `html` through these helpers when\n * the model value is a prop-`_mapping` (`{ _mapping: true, prop, values? }`) rather than a\n * literal string/template:\n *\n * <Link href={href(mapping, __props)} />\n * <Embed html={embedHtml(mapping, __props)} />\n *\n * `__props` is the host component's resolved props (threaded by the emitter, exactly like\n * `style()`); pages have no props scope, so the emitter calls the 1-arg form and the\n * mapping degrades to a safe default.\n *\n * \u2500\u2500 Parity with meno-core \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * These mirror meno-core's canonical `resolveLinkMapping` / `resolveHtmlMapping`\n * (`client/templateEngine.ts`), re-implemented locally so this Node/SSR package keeps its\n * narrow `meno-core/shared`-only import graph \u2014 the `meno-core/client` barrel re-exports\n * the whole React renderer (and browser globals), which must not enter an Astro build.\n * `style.ts` mirrors core's style-mapping resolver for the same reason.\n *\n * Both mapping modes are supported, matching core:\n * - value mapping: `mapping.values[String(props[prop])]`\n * - passthrough : `props[prop]` used directly (an object link `{ href, target? }` or a\n * bare string URL / HTML string) when `values` is omitted/empty.\n * The resolved link is flattened to its URL the same way the JSON\u2192Astro exporter does\n * (`(typeof v === 'string' ? v : v?.href) ?? '#'`) \u2014 the dialect `Link.astro` takes a\n * string `href`. Anything that can't resolve (no props, prop unset, missing key) degrades\n * to a safe default \u2014 `\"#\"` for href, `\"\"` for html \u2014 and never throws.\n */\n\n/** A prop-binding `_mapping` of either link or html flavour. */\ninterface RefMapping {\n _mapping: true;\n prop: string;\n values?: Record<string, unknown>;\n}\n\nfunction isMapping(value: unknown): value is RefMapping {\n return (\n !!value &&\n typeof value === 'object' &&\n (value as { _mapping?: unknown })._mapping === true &&\n typeof (value as { prop?: unknown }).prop === 'string'\n );\n}\n\n/** Flatten a resolved link value (object `{ href }` or bare string) to its URL, else `\"#\"`. */\nfunction toHrefString(value: unknown): string {\n if (typeof value === 'string') return value;\n if (value && typeof value === 'object' && 'href' in value) {\n const h = (value as { href?: unknown }).href;\n return typeof h === 'string' ? h : '#';\n }\n return '#';\n}\n\n/**\n * Resolve a Link node's `href` to a URL string.\n *\n * @param value A `LinkMapping` (`{ _mapping, prop, values? }`) for a prop-bound link, or\n * an already-literal value (passed straight through).\n * @param props The host component's resolved props (`__props`); absent for pages.\n * @returns The URL the dialect `Link.astro` renders; `\"#\"` when unresolvable.\n */\nexport function href(value: unknown, props?: Record<string, unknown>): string {\n if (!isMapping(value)) return toHrefString(value);\n const propValue = props?.[value.prop];\n if (propValue === undefined || propValue === null) return '#';\n // Passthrough: the prop value is already a link object \u2014 use it directly (mirrors\n // resolveLinkMapping, which prefers passthrough even when a `values` table exists).\n if (typeof propValue === 'object' && propValue !== null && 'href' in propValue) {\n return toHrefString(propValue);\n }\n // Value-mapping mode: look the prop value up in the mapping table.\n if (value.values) return toHrefString(value.values[String(propValue)]);\n // Passthrough of a coerced string URL.\n return toHrefString(propValue);\n}\n\n/**\n * Resolve an Embed node's `html` to an HTML string.\n *\n * @param value An `HtmlMapping` (`{ _mapping, prop, values? }`) for a prop-bound embed, or\n * an already-literal string (passed straight through).\n * @param props The host component's resolved props (`__props`); absent for pages.\n * @returns The HTML string to inject; `\"\"` when unresolvable.\n */\nexport function embedHtml(value: unknown, props?: Record<string, unknown>): string {\n if (!isMapping(value)) return typeof value === 'string' ? value : '';\n const propValue = props?.[value.prop];\n if (propValue === undefined || propValue === null) return '';\n // Value-mapping mode (non-empty table); else passthrough of a string prop.\n if (value.values && Object.keys(value.values).length > 0) {\n const mapped = value.values[String(propValue)];\n return typeof mapped === 'string' ? mapped : '';\n }\n return typeof propValue === 'string' ? propValue : '';\n}\n\n/**\n * Resolve a node's `if` condition when it is a `BooleanMapping`\n * (`{ _mapping, prop, values }`) \u2014 the emitter wraps that form in `when(...)`. Mirrors\n * meno-core's `resolveConditionalValue` (nodeUtils.ts): look the host prop's value up in\n * the mapping's table and coerce to a boolean.\n *\n * Defaults to `true` (render) when the mapping can't be resolved \u2014 no props, or the prop\n * value isn't in the table \u2014 matching meno-core, so a misconfigured condition shows the\n * node rather than silently hiding it. A non-mapping argument is coerced directly.\n *\n * @param value A `BooleanMapping`, or an already-evaluated condition value.\n * @param props The host component's resolved props (`__props`); absent for pages.\n */\nexport function when(value: unknown, props?: Record<string, unknown>): boolean {\n if (!isMapping(value)) return Boolean(value);\n if (!props) return true;\n const propValue = props[value.prop];\n const mapped = value.values?.[String(propValue)];\n return mapped !== undefined ? Boolean(mapped) : true;\n}\n", "/**\n * Runtime helper for CMS list nodes (`sourceType: 'collection'`) in the meno-astro\n * dialect. The emitter generates:\n *\n * const blogList = await getCollectionList(\"blog\", { sort, filter, limit, \u2026 }, Astro);\n * blogList.map((blog, blogIndex) => ( \u2026 ))\n *\n * It pulls the collection's items from Astro's content layer (`astro:content`\n * `getCollection`) and applies the same query semantics as the JSON runtime's\n * `CMSService.queryItems` / `getItemsByIds` (mirrored here so both render the same\n * lists). Each returned item is the raw stored JSON (`entry.data`) \u2014 the converter\n * writes items with a permissive `z.record(z.string(), z.any())` schema, so `_id`,\n * `_createdAt`, and all fields sit at the top level, exactly as the emitted child\n * template expects (`blog.title`, `blog._createdAt`, \u2026).\n *\n * `astro:content`'s `getCollection` is passed IN by the calling page (which lives in\n * the host Astro project where the virtual module resolves natively). meno-astro never\n * imports `astro:content` itself \u2014 doing so from inside node_modules both breaks\n * non-Astro loaders (tooling/tests) and corrupts Astro's generated content module.\n */\n\ntype GetCollection = (name: string) => Promise<Array<{ data: Record<string, any> }>>;\n\ntype Item = Record<string, any>;\ntype SortConfig = { field: string; order?: 'asc' | 'desc' };\ntype FilterCondition = { field: string; operator?: string; value?: unknown };\n\nexport interface CollectionListQuery {\n filter?: FilterCondition | FilterCondition[] | Record<string, unknown>;\n sort?: SortConfig | SortConfig[];\n limit?: number;\n offset?: number;\n /** Explicit id/filename list (reference fields) \u2014 preserves the given order. */\n items?: string[] | string;\n /** Drop the current page's CMS item (for related-item lists on template routes). */\n excludeCurrentItem?: boolean;\n /** Emit-only hint; unused at runtime. */\n emitTemplate?: unknown;\n}\n\ninterface AstroLike {\n props?: { cms?: { _id?: string } | undefined } & Record<string, unknown>;\n}\n\n/**\n * Resolve a CMS collection list to its items, applying the node's query. `getCollection`\n * is the host project's `astro:content` export, passed by the emitted page.\n */\nexport async function getCollectionList(\n source: string,\n query: CollectionListQuery = {},\n astro?: AstroLike,\n getCollection?: GetCollection,\n): Promise<Item[]> {\n if (typeof getCollection !== 'function') return [];\n let entries: Array<{ data: Item }>;\n try {\n entries = await getCollection(source);\n } catch {\n // No such collection \u2192 empty list (matches the SSR fallback).\n return [];\n }\n\n let items: Item[] = entries.map((e) => e.data);\n\n if (query.items !== undefined) {\n // Reference-field path: fetch specific ids in order (match _id or _filename),\n // skipping missing \u2014 mirrors CMSService.getItemsByIds.\n const ids = (Array.isArray(query.items) ? query.items : [query.items]).filter(Boolean).map(String);\n const byId = new Map(items.map((i) => [i._id, i]));\n const byFilename = new Map(items.filter((i) => i._filename).map((i) => [i._filename, i]));\n items = ids.map((id) => byId.get(id) ?? byFilename.get(id)).filter((i): i is Item => i !== undefined);\n } else {\n if (query.filter) items = applyFilters(items, query.filter);\n if (query.sort) items = applySorting(items, query.sort);\n if (query.offset !== undefined && query.offset > 0) items = items.slice(query.offset);\n if (query.limit !== undefined && query.limit > 0) items = items.slice(0, query.limit);\n }\n\n // Exclude the current CMS item (template [slug] pages pass it on Astro.props.cms).\n if (query.excludeCurrentItem) {\n const currentId = astro?.props?.cms?._id;\n if (currentId) items = items.filter((i) => i._id !== currentId);\n }\n\n return items;\n}\n\n// --- filter/sort, ported verbatim from CMSService for identical semantics ---\n\nfunction isFilterCondition(obj: unknown): obj is FilterCondition {\n return typeof obj === 'object' && obj !== null && 'field' in obj;\n}\n\nfunction applyFilters(\n items: Item[],\n filter: FilterCondition | FilterCondition[] | Record<string, unknown>,\n): Item[] {\n // Simple object filter: { featured: true } \u2192 equality on every key.\n if (!Array.isArray(filter) && !isFilterCondition(filter)) {\n const entries = Object.entries(filter);\n return items.filter((item) => entries.every(([key, value]) => item[key] === value));\n }\n const conditions = Array.isArray(filter) ? filter : [filter as FilterCondition];\n return items.filter((item) => conditions.every((cond) => matchCondition(item, cond)));\n}\n\nfunction matchCondition(item: Item, condition: FilterCondition): boolean {\n const value = item[condition.field];\n switch (condition.operator || 'eq') {\n case 'eq': return value === condition.value;\n case 'neq': return value !== condition.value;\n case 'gt': return (value as number) > (condition.value as number);\n case 'gte': return (value as number) >= (condition.value as number);\n case 'lt': return (value as number) < (condition.value as number);\n case 'lte': return (value as number) <= (condition.value as number);\n case 'contains': return String(value).includes(String(condition.value));\n case 'in': return Array.isArray(condition.value) && condition.value.includes(value);\n default: return false;\n }\n}\n\nfunction applySorting(items: Item[], sort: SortConfig | SortConfig[]): Item[] {\n const sorts = Array.isArray(sort) ? sort : [sort];\n return [...items].sort((a, b) => {\n for (const s of sorts) {\n const aVal = a[s.field];\n const bVal = b[s.field];\n const isDesc = s.order === 'desc';\n if (typeof aVal === 'boolean' && typeof bVal === 'boolean') {\n if (aVal === bVal) continue;\n return isDesc ? (aVal ? -1 : 1) : (aVal ? 1 : -1);\n }\n let result = 0;\n if ((aVal as string | number) < (bVal as string | number)) result = -1;\n else if ((aVal as string | number) > (bVal as string | number)) result = 1;\n if (result !== 0) return isDesc ? -result : result;\n }\n return 0;\n });\n}\n", "/**\n * meno-astro \u2014 runtime entry point.\n *\n * This is the package that Meno-generated Astro projects import at runtime so the\n * `.astro` source stays thin and consistent (one shared resolver for styles, i18n,\n * and CMS queries \u2014 the same code paths the Studio React preview uses).\n *\n * Design note: `meno-astro` depends on `meno-core` (single direction). The heavy,\n * battle-tested logic (style/utility-class generation, i18n resolution, CMS query\n * parsing, the template engine) lives in `meno-core` and is composed here behind a\n * curated, stable surface. No `meno-core` files move, so the existing JSON runtime is\n * untouched by construction. Slimming the published bundle (so generated projects do\n * not transitively pull all of `meno-core`) is a later optimization.\n */\n\nimport type {\n JSONPage,\n PageData,\n ComponentNode,\n StructuredComponentDefinition,\n PropDefinition,\n StyleObject,\n ResponsiveStyleObject,\n} from 'meno-core/shared/types';\n\n// ---------------------------------------------------------------------------\n// Dialect version \u2014 written into generated projects so a project can be\n// migrated forward if the on-disk `.astro` dialect format evolves. Part of the\n// package's semver contract.\n// ---------------------------------------------------------------------------\nexport const dialectVersion = '0.1.0' as const;\n\n// ---------------------------------------------------------------------------\n// Model types re-exported for the dialect + generated projects. These are the\n// Meno in-memory model the emitter serializes and the parser reconstructs.\n// ---------------------------------------------------------------------------\nexport type {\n JSONPage,\n PageData,\n ComponentNode,\n StructuredComponentDefinition,\n PropDefinition,\n StyleObject,\n ResponsiveStyleObject,\n};\n\n/** A responsive style payload as carried verbatim in `style({...})` calls. */\nexport type MenoStyle = ResponsiveStyleObject | StyleObject;\n\n/** A component's prop definitions, as carried by the `resolveProps(Astro, {\u2026})` call. */\nexport type MenoProps = Record<string, PropDefinition>;\n\n/** A page's `export const meta` payload. */\nexport type MenoPageMeta = NonNullable<JSONPage['meta']>;\n\n/**\n * A component's non-interface metadata, carried by the `__meno` frontmatter const.\n * `defineVars` is intentionally not here: it is emitted as the script's native\n * `<script define:vars={{\u2026}}>` directive and reconstructed on parse, not via `__meno`.\n */\nexport type MenoComponentMeta = Pick<\n StructuredComponentDefinition,\n 'category' | 'acceptsStyles' | 'libraries'\n>;\n\n// ---------------------------------------------------------------------------\n// Curated runtime helpers, composed from meno-core. The dialect-specific wrappers\n// the emitter targets: `i18n()`, `list()`, `style()`, `getCollectionList()`,\n// `href()`, and `embedHtml()` are implemented (below / `runtime/*.ts`). The primitives\n// re-exported here are the unambiguous, dependency-free foundations they build on.\n// ---------------------------------------------------------------------------\n\n// i18n resolution (pure primitives; only type-imports inside meno-core).\nexport {\n isI18nValue,\n resolveI18nValue,\n extractLocaleFromPath,\n buildLocalizedPath,\n} from 'meno-core/shared';\n\n// Slug translation primitives (pure; meno-core's slug translator). Re-exported here so\n// the published components (LocaleList/BaseLayout/LocaleRoute) can reach them alongside\n// `loadSlugMappings`; the locale-route helpers in `runtime/localeRoutes.ts` build on them.\nexport {\n buildSlugIndex,\n getLocaleLinks,\n resolveSlugToPageId,\n} from 'meno-core/shared';\nexport type { SlugMap, LocaleLink } from 'meno-core/shared';\n\n// Locale route helpers (pure) \u2014 enumerate the injected `/[locale]/[...path]` route's\n// getStaticPaths entries from the slug map, plus the slug-translated link builders the\n// published components consume (LocaleRoute / LocaleList / BaseLayout hreflang). See\n// `runtime/localeRoutes.ts`.\nexport {\n enumerateLocaleStaticPaths,\n pageModuleKey,\n buildHreflangLinks,\n localeListItems,\n} from './runtime/localeRoutes';\nexport type {\n LocaleStaticPath,\n HreflangLink,\n LocaleListItem,\n} from './runtime/localeRoutes';\n\n// i18n runtime \u2014 the emitter-facing `i18n()` resolver + its locale-context seam.\n// Emitted markup calls `i18n({\u2026})`; `runWithLocale` is how BaseLayout/middleware will\n// set the active locale per route. See `runtime/i18n.ts`.\nexport {\n i18n,\n runWithLocale,\n getLocaleContext,\n localeFromAstro,\n} from './runtime/i18n';\nexport type {\n LocaleContextValue,\n AstroLike,\n I18nOverride,\n} from './runtime/i18n';\n\n// Locale middleware factory \u2014 wraps each page render in `runWithLocale(...)` so `i18n()`\n// resolves per locale. `createLocaleMiddleware(config)` is the pure, testable factory;\n// `deriveLocale` is its locale-selection policy. The injected middleware module (the one\n// the integration points Astro at) lives at the `meno-astro/runtime/localeMiddleware`\n// subpath. See `runtime/middleware.ts`.\nexport {\n createLocaleMiddleware,\n deriveLocale,\n} from './runtime/middleware';\nexport type {\n LocaleMiddleware,\n LocaleMiddlewareContext,\n} from './runtime/middleware';\n\n// loadI18nConfig \u2014 read + migrate a converted project's i18n config (server/build helper).\n// Also available from `meno-astro/server`; re-exported here as the natural root surface\n// for the middleware/integration story. Touches the filesystem (server/build only).\nexport { loadI18nConfig } from './server/loadI18nConfig';\n\n// loadSlugMappings \u2014 scan a project's `src/pages` for `meta.slugs` \u2192 the SlugMap[] that\n// drives locale routing (LocaleRoute's getStaticPaths), LocaleList links, and BaseLayout\n// hreflang. Re-exported here (alongside loadI18nConfig) so the published components can\n// reach it; `meno-astro/server` is workspace-only and not shipped. Touches the filesystem\n// (build/SSR only \u2014 runs in route/component frontmatter; mtime-cached per file).\nexport { loadSlugMappings } from './server/loadSlugMappings';\n\n// loadFontCss \u2014 read a project's `fonts` config \u2192 `@font-face` CSS + preload tags for\n// BaseLayout's <head>. Re-exported here (alongside loadI18nConfig) so the published\n// BaseLayout component can reach it; `meno-astro/server` is workspace-only and not\n// shipped. Touches the filesystem (build/SSR only \u2014 runs in BaseLayout frontmatter).\nexport { loadFontCss } from './server/loadFontCss';\nexport type { FontHeadAssets } from './server/loadFontCss';\n\n// loadLibraries \u2014 merge a converted project's library tiers (global + component +\n// page `meta.libraries`) \u2192 external <link>/<script>/inline <style> tags for BaseLayout's\n// <head>/body. Re-exported here (alongside loadFontCss) so the published BaseLayout can\n// reach it; `meno-astro/server` is workspace-only and not shipped. Touches the filesystem\n// (build/SSR only \u2014 runs in BaseLayout frontmatter).\nexport { loadLibraries } from './server/loadLibraries';\nexport type { PageLibrariesMeta } from './server/loadLibraries';\n\n// style() runtime \u2014 the emitter-facing style resolver (class-name only). Emitted markup\n// calls `style(styleObject[, props][, meta])` and `style()` returns just the `class={...}`\n// value; the matching utility/interactive CSS is generated at BUILD time by the meno()\n// integration (`virtual:meno-utilities.css`), not collected at render time. See\n// `runtime/style.ts`.\nexport { style } from './runtime/style';\nexport type { StyleMeta } from './runtime/style';\n\n// href() / embedHtml() / when() runtime \u2014 resolve a Link's href / an Embed's html / a\n// node's `if` condition when the model value is a prop-`_mapping`. Emitted markup calls\n// `href(mapping, __props)` / `embedHtml(mapping, __props)` / `when(mapping, __props)`;\n// each mirrors meno-core's resolver (resolveLinkMapping / resolveHtmlMapping /\n// resolveConditionalValue). See `runtime/refs.ts`.\nexport { href, embedHtml, when } from './runtime/refs';\n\n/**\n * Prop-list helper used by generated `.astro`: `list(items, { limit, offset }).map(\u2026)`.\n * Tolerates null/undefined sources and applies offset/limit. (The scope-aware\n * `when()` / `href()` runtime + the CSS-injection integration are the remaining,\n * Astro-toolchain-verified part of Phase 0b.)\n */\nexport function list<T>(\n source: T[] | null | undefined,\n opts?: { offset?: number; limit?: number },\n): T[] {\n let out = Array.isArray(source) ? source : [];\n if (opts?.offset) out = out.slice(opts.offset);\n if (opts?.limit != null) out = out.slice(0, opts.limit);\n return out;\n}\n\n// CMS collection list helper used by generated `.astro`:\n// `const blogList = await getCollectionList(\"blog\", { sort, limit, \u2026 }, Astro)`.\n// Pulls items from astro:content and applies the JSON runtime's query semantics.\nexport { getCollectionList } from './runtime/collectionList';\nexport type { CollectionListQuery } from './runtime/collectionList';\n\n// CMS filter/sort expression parsing + serialization (pure).\nexport {\n parseFilterExpression,\n serializeFilterExpression,\n parseSortExpression,\n serializeSortExpression,\n} from 'meno-core/shared';\n\n// ---------------------------------------------------------------------------\n// resolveProps \u2014 the single authoritative prop block for `.astro` components.\n//\n// A component's frontmatter declares its props exactly once:\n//\n// const { text, isMarginTop, class: className } = resolveProps(Astro, {\n// text: { type: \"string\", default: \"Link\" },\n// isMarginTop: { type: \"boolean\", default: false },\n// });\n//\n// The `{\u2026}` literal is the authoritative prop definition the dialect parser reads\n// back into `def.interface`. At runtime this merges `Astro.props` over each def's\n// `default` (and always provides `class`, defaulting to \"\"). The destructured locals\n// are typed by inferring each prop's value type from its definition (mirroring the\n// old `interface Props` mapping), so editing the file stays fully type-checked.\n// ---------------------------------------------------------------------------\n\n/** Map a single prop definition to the runtime value type of that prop. */\ntype InferMenoProp<D> =\n D extends { type: 'number' } ? number :\n D extends { type: 'boolean' } ? boolean :\n D extends { type: 'link' } ? { href: string; target?: string } :\n D extends { type: 'list' } ? unknown[] :\n D extends { type: 'select'; options: infer O }\n ? (O extends readonly (infer U)[] ? U : string)\n : string; // string, rich-text, image, embed, file \u2192 string\n\n/** Map a whole prop-definition record to the destructured locals' value types. */\ntype InferMenoProps<T extends MenoProps> = { [K in keyof T]: InferMenoProp<T[K]> };\n\n/**\n * Resolve a component's props from `Astro.props`, merging each provided value over\n * its declared `default` and always supplying `class` (defaulting to \"\").\n *\n * `defs` is the authoritative prop definition (the same literal the dialect parser\n * reads). The `const` type parameter preserves literal `type`/`options` so the\n * returned locals are precisely typed (e.g. `select` options become a string union).\n */\nexport function resolveProps<const T extends MenoProps>(\n astro: { props: Record<string, unknown> },\n defs: T,\n): InferMenoProps<T> & { class: string } {\n const props = astro.props ?? {};\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(defs)) {\n if (key === 'children') continue;\n const provided = props[key];\n out[key] = provided !== undefined ? provided : (defs[key] as PropDefinition).default;\n }\n out.class = typeof props.class === 'string' ? props.class : '';\n return out as InferMenoProps<T> & { class: string };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["existsSync", "readFileSync", "join", "join", "existsSync", "readFileSync", "existsSync", "readFileSync", "readdirSync", "statSync", "join", "EMPTY", "statSync", "readFileSync", "join", "existsSync", "readdirSync"]
|
|
3
|
+
"sources": ["../../lib/server/loadSlugMappings.ts", "../../lib/runtime/localizeHref.ts", "../../lib/runtime/localeRoutes.ts", "../../lib/server/loadFontCss.ts", "../../lib/server/loadLibraries.ts", "../../lib/runtime/refs.ts", "../../lib/runtime/collectionList.ts", "../../lib/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * meno-astro/server \u2014 `loadSlugMappings`.\n *\n * Scans a converted project's `src/pages/**\\/*.astro` files and collects each page's\n * `meta.slugs` (per-locale URL slugs, e.g. `{ en: \"about\", pl: \"o-nas\" }`) into the\n * `SlugMap[]` shape meno-core's slug translator consumes. This is the astro-format twin of\n * `PageService.getSlugMappings()` (meno-core), and it mirrors that method's conventions\n * exactly so `buildSlugIndex`/`translatePath`/`getLocaleLinks` behave identically:\n *\n * - `pageId` is the route path without the leading slash (`/` \u2192 `\"index\"`).\n * - Pages WITHOUT `meta.slugs` contribute `{ _default: <route path> }` (`\"\"` for the\n * index page). `_default` entries never match a locale lookup directly \u2014 they flow\n * through `translatePath`'s no-entry fallback (locale-prefix swap, same slug), which\n * is the SSR behavior for slugless pages.\n * - The DEFAULT locale's slug is always the filename-derived route path, overriding\n * any `meta.slugs[defaultLocale]`. In the astro format the file name IS the\n * default-locale URL (file-based routing, `prefixDefaultLocale: false`) \u2014 a meta\n * entry that disagrees is stale data (e.g. a slug edit that predates the\n * rename-on-default-slug flow) and honoring it would break every lookup keyed by\n * the authored href (`/about` would stop resolving the moment `slugs.en` said\n * anything else, degrading all localized links to the bare prefix fallback).\n *\n * Consumed at build/render time (cwd = project root during `astro dev`/`build`, same\n * contract as `loadI18nConfig`/`loadFontCss`) by:\n * - the injected `[locale]/[...path]` route's `getStaticPaths` (locale route enumeration),\n * - `LocaleList.astro` (slug-translated switcher links),\n * - `BaseLayout.astro` (hreflang alternates).\n *\n * Excluded from the scan:\n * - dynamic routes (any path segment containing `[`) \u2014 this is deliberately also the\n * CMS-template exclusion (`src/pages/<collection>/[slug].astro`); CMS i18n is a\n * follow-up phase,\n * - `404.astro` / `500.astro` (error routes have no locale variants).\n *\n * A module-level per-file mtime cache keeps repeated calls cheap (LocaleList + BaseLayout\n * call this on every render): each call re-stats every page file but re-parses only\n * changed/new ones, so editor saves (AstroPageProvider rewrites the `.astro` file) take\n * effect on the next render without any explicit invalidation. Never throws \u2014 an\n * unreadable/unparseable page degrades to its `_default` entry.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { join, relative, sep } from 'path';\nimport { readPageMeta } from '../dialect/parse/parseFrontmatter';\nimport { loadI18nConfig } from './loadI18nConfig';\nimport type { SlugMap } from 'meno-core/shared';\n\ninterface CacheEntry {\n mtimeMs: number;\n map: SlugMap;\n}\n\n/** projectRoot \u2192 (absolute page file path \u2192 cached entry). */\nconst cache = new Map<string, Map<string, CacheEntry>>();\n\n/** Reset the cache (test seam). */\nexport function clearSlugMappingsCache(): void {\n cache.clear();\n}\n\n/** `meta.slugs` is usable when it is a non-empty plain record of string slugs. */\nfunction readSlugs(meta: Record<string, unknown> | undefined): Record<string, string> | undefined {\n const slugs = meta?.slugs;\n if (!slugs || typeof slugs !== 'object' || Array.isArray(slugs)) return undefined;\n const entries = Object.entries(slugs as Record<string, unknown>);\n if (entries.length === 0 || entries.some(([, v]) => typeof v !== 'string')) return undefined;\n return slugs as Record<string, string>;\n}\n\n/** Page files under `pagesDir`, excluding dynamic routes (`[`) and 404/500. */\nfunction collectPageFiles(pagesDir: string): string[] {\n const files: string[] = [];\n const walk = (dir: string) => {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n if (e.name.includes('[')) continue; // dynamic route (CMS templates, user catch-alls)\n const p = join(dir, e.name);\n if (e.isDirectory()) walk(p);\n else if (e.name.endsWith('.astro') && !/^(404|500)\\.astro$/.test(e.name)) files.push(p);\n }\n };\n walk(pagesDir);\n return files;\n}\n\n/**\n * Collect the slug mappings for the project rooted at `projectRoot`.\n * Deterministic order (sorted by pageId); never throws.\n */\nexport function loadSlugMappings(projectRoot: string): SlugMap[] {\n const mappings: SlugMap[] = [];\n try {\n const pagesDir = join(projectRoot, 'src', 'pages');\n if (!existsSync(pagesDir)) return mappings;\n\n let fileCache = cache.get(projectRoot);\n if (!fileCache) {\n fileCache = new Map();\n cache.set(projectRoot, fileCache);\n }\n\n const files = collectPageFiles(pagesDir);\n const seen = new Set<string>();\n\n for (const file of files) {\n seen.add(file);\n // `about.astro` \u2192 \"about\", `index.astro` \u2192 \"index\", `docs/intro.astro` \u2192 \"docs/intro\".\n const pageId = relative(pagesDir, file).split(sep).join('/').replace(/\\.astro$/, '');\n try {\n const mtimeMs = statSync(file).mtimeMs;\n const cached = fileCache.get(file);\n if (cached && cached.mtimeMs === mtimeMs) {\n mappings.push(cached.map);\n continue;\n }\n const slugs = readSlugs(readPageMeta(readFileSync(file, 'utf8')));\n const map: SlugMap = slugs\n ? { pageId, slugs }\n : { pageId, slugs: { _default: pageId === 'index' ? '' : pageId } };\n fileCache.set(file, { mtimeMs, map });\n mappings.push(map);\n } catch {\n // Unreadable/unparseable page \u2192 SSR-parity default entry (uncached).\n mappings.push({ pageId, slugs: { _default: pageId === 'index' ? '' : pageId } });\n }\n }\n\n // Drop cache entries for deleted files.\n for (const file of fileCache.keys()) {\n if (!seen.has(file)) fileCache.delete(file);\n }\n } catch {\n // No pages dir / unreadable tree \u2192 empty mappings (single-locale-like degradation).\n }\n\n // Filename wins for the default locale (see module doc). Applied OUTSIDE the per-file\n // cache because it depends on the i18n config (the default can change at runtime);\n // `_default` entries (slugless pages) already carry the filename and stay untouched.\n const { defaultLocale } = loadI18nConfig(projectRoot);\n const normalized = mappings.map((m) =>\n '_default' in m.slugs\n ? m\n : { ...m, slugs: { ...m.slugs, [defaultLocale]: m.pageId === 'index' ? '' : m.pageId } },\n );\n return normalized.sort((a, b) => a.pageId.localeCompare(b.pageId));\n}\n", "/**\n * meno-astro \u2014 locale-aware href rewriting (the dialect twin of meno-core SSR's\n * `localizeHref` / `localizeRichTextLinks`, ssrRenderer.ts).\n *\n * Authored hrefs in a Meno model are default-locale paths (`/about`). When a page\n * renders under a non-default locale (`/pl/o-nas`), its internal links must point at\n * the SAME locale's URLs \u2014 slug-translated when the target page has localized slugs\n * (`/about` \u2192 `/pl/o-nas`), locale-prefixed otherwise (`/contact` \u2192 `/pl/contact`).\n * Meno's SSR did this at render time; in the astro format the equivalent seam is the\n * runtime components every link flows through (`Link.astro` for `{type:\"link\"}` nodes,\n * `Embed.astro` for anchors inside raw HTML).\n *\n * Split pure-core / context-wrapper like the rest of the runtime: `localizeHrefFor`\n * is fully parameterized (unit-testable, no fs / no ALS), while `localizeHref` reads\n * the active locale from the middleware-opened context and the slug map from the\n * project (`loadSlugMappings`, mtime-cached) \u2014 callable straight from component\n * frontmatter.\n */\n\nimport { buildSlugIndex, translatePath, buildLocalizedPath } from 'meno-core/shared';\nimport type { SlugMap } from 'meno-core/shared';\nimport type { I18nConfig } from 'meno-core/shared/types';\nimport { getLocaleContext } from './i18n';\nimport { loadSlugMappings } from '../server/loadSlugMappings';\n\n/** Internal app paths only: `/x` yes; external/protocol-relative/anchors/mail no. */\nfunction isInternalHref(href: unknown): href is string {\n return typeof href === 'string' && href.startsWith('/') && !href.startsWith('//');\n}\n\n/**\n * Pure core: rewrite an authored (default-locale) internal `href` for `locale`.\n * Mirrors SSR's chain \u2014 slug map present \u2192 `translatePath` (prefix + slug\n * translation, no-op for the default locale); no map \u2192 bare prefix for\n * non-default locales. Non-internal hrefs pass through untouched.\n */\nexport function localizeHrefFor(\n href: string,\n locale: string | null | undefined,\n config: I18nConfig,\n mappings: SlugMap[],\n): string {\n if (!isInternalHref(href) || !locale) return href;\n if (mappings.length > 0) {\n return translatePath(href, locale, config.defaultLocale, config.defaultLocale, buildSlugIndex(mappings));\n }\n if (locale !== config.defaultLocale) return buildLocalizedPath(href, locale);\n return href;\n}\n\n/**\n * Localize an internal href to the active render locale (middleware context).\n * No context / default locale / non-internal href \u2192 unchanged. Reads the slug map\n * from the project root (cwd during astro dev/build \u2014 loadFontCss precedent).\n */\nexport function localizeHref(href: unknown): unknown {\n if (!isInternalHref(href)) return href;\n const ctx = getLocaleContext();\n if (!ctx?.locale || ctx.locale === ctx.config.defaultLocale) return href;\n return localizeHrefFor(href, ctx.locale, ctx.config, loadSlugMappings(process.cwd()));\n}\n\n/**\n * Localize every internal `<a href=\"\u2026\">` inside a raw HTML string (Embed nodes /\n * rich text) \u2014 same regex + skip rules as SSR's `localizeRichTextLinks`.\n */\nexport function localizeRichTextLinks(html: string): string {\n if (typeof html !== 'string' || !html) return html;\n const ctx = getLocaleContext();\n if (!ctx?.locale || ctx.locale === ctx.config.defaultLocale) return html;\n const mappings = loadSlugMappings(process.cwd());\n return html.replace(\n /<a\\b([^>]*?)href=([\"'])([^\"']*?)\\2([^>]*?)>/gi,\n (match, before, quote, href, after) => {\n if (!isInternalHref(href)) return match;\n const localized = localizeHrefFor(href, ctx.locale, ctx.config, mappings);\n return localized === href ? match : `<a${before}href=${quote}${localized}${quote}${after}>`;\n },\n );\n}\n", "/**\n * meno-astro \u2014 locale route enumeration + locale-aware link helpers (pure).\n *\n * The fs-free half of meno-astro's i18n routing. `loadSlugMappings(projectRoot)`\n * (server) collects each page's `meta.slugs`; the functions here turn that `SlugMap[]`\n * into:\n *\n * - the `getStaticPaths()` entries for the injected `/[locale]/[...path]` route\n * (`LocaleRoute.astro`) \u2014 one path per (non-default locale \u00D7 page), using the page's\n * localized slug when it has one (SSR parity: every page exists in every locale),\n * - hreflang alternate links for `BaseLayout` (`buildHreflangLinks` \u2014 the dialect twin\n * of meno-core's `metaTagGenerator` hreflang block),\n * - slug-translated locale-switcher items for `LocaleList` (`localeListItems`).\n *\n * Everything composes meno-core's slug translator (`buildSlugIndex`/`getLocaleLinks`/\n * `resolveSlugToPageId`) \u2014 no translation logic is reinvented here. Pure functions, no\n * filesystem, no Astro coupling: unit-testable under bun:test (this package has no Astro\n * toolchain; the `.astro` consumers stay thin shells over these).\n */\n\nimport {\n buildSlugIndex,\n getLocaleLinks,\n resolveSlugToPageId,\n extractLocaleFromPath,\n} from 'meno-core/shared';\nimport type { I18nConfig } from 'meno-core/shared/types';\nimport type { SlugMap, LocaleLink } from 'meno-core/shared';\n\n/**\n * One `getStaticPaths()` entry of the injected locale route. `params.path` is the rest\n * segment after `/[locale]/` \u2014 `undefined` encodes the empty rest param (Astro's required\n * shape for `/pl/`). `props.pageId` tells `LocaleRoute.astro` which page module to render,\n * so the component never reverse-looks-up slugs.\n */\nexport interface LocaleStaticPath {\n params: { locale: string; path: string | undefined };\n props: { pageId: string };\n}\n\n/**\n * Enumerate every (non-default locale \u00D7 page) route the injected `/[locale]/[...path]`\n * route serves. Slug preference per page+locale mirrors `translatePath`'s chain:\n * the locale's own slug \u2192 the default locale's slug \u2192 `_default` (slugless pages) \u2192\n * the pageId itself. The default locale is never enumerated (it lives un-prefixed,\n * `prefixDefaultLocale: false`). Single-locale configs yield `[]` (the integration also\n * skips injecting the route then \u2014 defense in depth).\n */\nexport function enumerateLocaleStaticPaths(\n mappings: SlugMap[],\n config: I18nConfig,\n): LocaleStaticPath[] {\n if (!config.locales || config.locales.length <= 1) return [];\n const paths: LocaleStaticPath[] = [];\n for (const { code } of config.locales) {\n if (code === config.defaultLocale) continue;\n for (const { pageId, slugs } of mappings) {\n const slug = (\n slugs[code] ??\n slugs[config.defaultLocale] ??\n slugs._default ??\n (pageId === 'index' ? '' : pageId)\n ).replace(/^\\/+/, '');\n paths.push({\n params: { locale: code, path: slug === '' ? undefined : slug },\n props: { pageId },\n });\n }\n }\n return paths;\n}\n\n/**\n * The `import.meta.glob('/src/pages/**\\/*.astro')` key for a pageId \u2014 the glob-key\n * normalization seam between `loadSlugMappings`' pageIds and `LocaleRoute.astro`'s\n * page-module map.\n */\nexport function pageModuleKey(pageId: string): string {\n return `/src/pages/${pageId}.astro`;\n}\n\n/** One hreflang alternate for BaseLayout's `<head>`. */\nexport interface HreflangLink {\n hreflang: string;\n href: string;\n}\n\n/**\n * Strip a trailing slash (keeping the root `/`). Astro's rendered pathnames carry one\n * under its default `build.format: 'directory'` (`/pl/o-nas/`), but meno-core's\n * `translatePath` extracts slugs by bare string surgery \u2014 `o-nas/` would miss the slug\n * index and silently degrade every link to the prefix-swap fallback.\n */\nfunction normalizePathname(pathname: string): string {\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') || '/' : pathname;\n}\n\n/**\n * Resolve the locale-stripped `pathname` to a known pageId, or undefined. The slug-index\n * lookup is tried for the current locale then the default locale (the same fallback\n * `translatePath` uses); `_default` entries never match a locale lookup, so slugless\n * pages resolve via the bare path-as-pageId check.\n */\nfunction resolveCurrentPage(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n index: Map<string, { pageId: string; slugs: Record<string, string> }>,\n): string | undefined {\n const { pathWithoutLocale } = extractLocaleFromPath(pathname, config);\n const slug = pathWithoutLocale.replace(/^\\/+|\\/+$/g, '');\n const byLocale =\n resolveSlugToPageId(slug, currentLocale, index) ??\n resolveSlugToPageId(slug, config.defaultLocale, index);\n if (byLocale) return byLocale;\n const asPageId = slug === '' ? 'index' : slug;\n return mappings.some((m) => m.pageId === asPageId) ? asPageId : undefined;\n}\n\n/**\n * Hreflang alternates for the page at `pathname` \u2014 the dialect twin of meno-core's\n * `metaTagGenerator` hreflang block (one `<link rel=\"alternate\">` per locale, `langTag`\n * values, relative hrefs, plus `x-default` \u2192 the default-locale path).\n *\n * Returns `[]` when the project is single-locale, `mappings` is empty, or the current\n * path doesn't resolve to a known page. The last guard is a deliberate (small) deviation\n * from SSR: pages this module can't route (e.g. CMS items, until the CMS i18n follow-up)\n * must not advertise `/pl/\u2026` URLs that 404 in static output.\n */\nexport function buildHreflangLinks(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n): HreflangLink[] {\n if (!config.locales || config.locales.length <= 1 || mappings.length === 0) return [];\n const path = normalizePathname(pathname);\n const index = buildSlugIndex(mappings);\n if (!resolveCurrentPage(path, currentLocale, config, mappings, index)) return [];\n\n const links = getLocaleLinks(path, currentLocale, config, index);\n const out: HreflangLink[] = links.map((l) => ({ hreflang: l.langTag, href: l.path }));\n const defaultLink = links.find((l) => l.locale === config.defaultLocale);\n if (defaultLink) out.push({ hreflang: 'x-default', href: defaultLink.path });\n return out;\n}\n\n/** One LocaleList switcher item (pre-translated href; label = nativeName ?? code). */\nexport interface LocaleListItem {\n locale: string;\n href: string;\n label: string;\n langTag: string;\n isCurrent: boolean;\n icon?: string;\n}\n\n/**\n * Slug-translated locale-switcher items for `LocaleList.astro` \u2014 `getLocaleLinks` over\n * the slug index (so `/pl/o-nas`, not a naive `/pl/about` prefix swap), with the\n * component's `showCurrent` filter applied. For unknown paths `translatePath` degrades to\n * the locale-prefix swap, which matches the previous LocaleList behavior.\n */\nexport function localeListItems(\n pathname: string,\n currentLocale: string,\n config: I18nConfig,\n mappings: SlugMap[],\n showCurrent: boolean,\n): LocaleListItem[] {\n const index = buildSlugIndex(mappings);\n const byCode = new Map(config.locales.map((l) => [l.code, l]));\n return getLocaleLinks(normalizePathname(pathname), currentLocale, config, index)\n .filter((l: LocaleLink) => showCurrent || !l.isCurrent)\n .map((l: LocaleLink) => ({\n locale: l.locale,\n href: l.path,\n label: l.nativeName || l.locale,\n langTag: l.langTag,\n isCurrent: l.isCurrent,\n icon: byCode.get(l.locale)?.icon,\n }));\n}\n", "/**\n * meno-astro/server \u2014 `loadFontCss`.\n *\n * Reads a project's `project.config.json`, pulls its `fonts` array, and renders the\n * `@font-face` rules + `<link rel=\"preload\">` tags for `BaseLayout.astro` to drop into\n * `<head>`. This is the dialect twin's counterpart to what meno-core's SSR\n * (`htmlGenerator`) and JSON\u2192Astro export (`build-astro`) already emit \u2014 without it the\n * font files ship but are never *defined*, so any `font-family: 'Inter'` declared via\n * `style()` silently falls back to a system font.\n *\n * Formatting is delegated wholesale to meno-core's pure `fontCss` helpers so all three\n * renderers stay byte-identical. Any failure (missing file, bad JSON, no `fonts`)\n * degrades to empty strings \u2014 a project always renders, just without custom fonts.\n *\n * Server/build-only (touches the filesystem); BaseLayout's frontmatter runs at\n * build/SSR time, never in the browser.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n// Import from the `meno-core/shared` barrel (not the deep `./fontCss` path): the\n// published meno-core bundles shared modules into the barrel and does not emit\n// `dist/lib/shared/fontCss.js`, so the deep path would 404 in a consumer project.\nimport { fontFaceCss, fontPreloadLinks, type FontConfig } from 'meno-core/shared';\n\nexport interface FontHeadAssets {\n /** `@font-face` rules for a `<style>` tag (empty string when no fonts). */\n css: string;\n /** `<link rel=\"preload\">` tags (empty string when no fonts). */\n preloads: string;\n}\n\nconst EMPTY: FontHeadAssets = { css: '', preloads: '' };\n\n/**\n * Load font `<head>` assets for the project rooted at `projectRoot`.\n *\n * Resolution: read `<projectRoot>/project.config.json`, take its `.fonts` array, and\n * format it. Never throws \u2014 every failure path returns empty strings.\n */\nexport function loadFontCss(projectRoot: string): FontHeadAssets {\n try {\n const cfgPath = join(projectRoot, 'project.config.json');\n if (!existsSync(cfgPath)) return EMPTY;\n const parsed = JSON.parse(readFileSync(cfgPath, 'utf8')) as { fonts?: FontConfig[] };\n const fonts = Array.isArray(parsed.fonts) ? parsed.fonts : [];\n if (fonts.length === 0) return EMPTY;\n return { css: fontFaceCss(fonts), preloads: fontPreloadLinks(fonts) };\n } catch {\n return EMPTY;\n }\n}\n", "/**\n * meno-astro/server \u2014 `loadLibraries`.\n *\n * Reads a project's `project.config.json`, merges its three library tiers (global\n * `libraries` \u2192 component tier \u2192 the page's `meta.libraries`), and renders the `<link>` /\n * `<script>` / inline `<style>` tags for `BaseLayout.astro` to drop into `<head>` (and\n * before `</body>` for body-end scripts). Without it the libraries config ships in the\n * project but is never *loaded* \u2014 the converter's gap this closes.\n *\n * The component tier is collected LIVE from `src/components/**\u2215*.astro` (each component's\n * `const __meno = {\u2026}` literal) on every render \u2014 the dialect twin of meno-core's SSR,\n * which runs `collectComponentLibraries` over the live registry per render. A project being\n * edited bidirectionally rewrites its component files continuously; the convert-time\n * `__componentLibraries` snapshot in `project.config.json` goes stale the moment that\n * happens (nothing refreshes it), which made component-tier libraries vanish from real\n * Astro renders (`astro dev` play mode, `astro build`). The snapshot remains the fallback\n * for output without a components dir.\n *\n * This is the dialect twin of meno-core's SSR (`htmlGenerator.ts`): same merge order, same\n * URL-dedupe, same local-CSS inlining, so the two runtimes stay byte-identical. The Astro\n * output is always the **build** context (`disableBuild` libs dropped, `disableEditor` kept;\n * no dev cache-busting). The pure tag/merge logic is reused wholesale from meno-core's\n * `libraryLoader` (exposed on the `meno-core/shared` barrel).\n *\n * Any failure (missing file, bad JSON, unreadable local CSS) degrades to empty strings \u2014 a\n * project always renders, just without its libraries.\n *\n * Server/build-only (touches the filesystem); BaseLayout's frontmatter runs at build/SSR\n * time, never in the browser.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\n// Import from the `meno-core/shared` barrel (not a deep `./libraryLoader` path): the\n// published meno-core bundles shared modules into the barrel and does not emit\n// `dist/lib/shared/libraryLoader.js`, so the deep path would 404 in a consumer project.\n// Same constraint that drives loadFontCss's import.\nimport {\n mergeLibraries,\n filterLibrariesByContext,\n generateLibraryTags,\n type LibrariesConfig,\n type PageLibrariesConfig,\n type JSLibraryConfig,\n type CSSLibraryConfig,\n type LibraryTags,\n} from 'meno-core/shared';\nimport { stripImportedLibraries } from '../dialect/libraryImports';\nimport { readComponentMeta } from '../dialect/parse/parseFrontmatter';\n\nconst EMPTY: LibraryTags = { headCSS: '', headJS: '', bodyEndJS: '' };\n\n/**\n * A page's meta as far as libraries are concerned. Structural (not `MenoPageMeta`) to avoid\n * a type cycle with the root barrel, which re-exports `loadLibraries`. BaseLayout passes the\n * full `meta`, which is assignable.\n */\nexport interface PageLibrariesMeta {\n libraries?: PageLibrariesConfig;\n}\n\n/**\n * Normalize a raw `libraries` block (string URLs OR object configs) to object form \u2014 mirrors\n * `ConfigService.getLibraries` so author-written `\"js\": [\"https://\u2026\"]` shorthand still works.\n */\nfunction normalizeLibraries(raw: unknown): LibrariesConfig {\n if (!raw || typeof raw !== 'object') return { js: [], css: [] };\n const libs = raw as { js?: unknown; css?: unknown };\n const js = Array.isArray(libs.js)\n ? (libs.js.map((l) => (typeof l === 'string' ? { url: l } : l)) as JSLibraryConfig[])\n : [];\n const css = Array.isArray(libs.css)\n ? (libs.css.map((l) => (typeof l === 'string' ? { url: l } : l)) as CSSLibraryConfig[])\n : [];\n return { js, css };\n}\n\n/** Deduplicate JS + CSS libraries by URL (first occurrence wins). */\nfunction dedupeByUrl(libs: LibrariesConfig): LibrariesConfig {\n const seenJS = new Set<string>();\n const seenCSS = new Set<string>();\n return {\n js: (libs.js || []).filter((l) => (seenJS.has(l.url) ? false : (seenJS.add(l.url), true))),\n css: (libs.css || []).filter((l) => (seenCSS.has(l.url) ? false : (seenCSS.add(l.url), true))),\n };\n}\n\n/**\n * Per-file cache for component-library extraction, keyed by absolute path and reused while\n * the file's mtime is unchanged. `astro dev` calls loadLibraries on every page render \u2014\n * re-parsing every component each time would be wasted work \u2014 while an editor save (the\n * workdir mirror rewrites the file, bumping its mtime) invalidates naturally.\n */\nconst componentLibsCache = new Map<string, { mtimeMs: number; libs: LibrariesConfig | null }>();\n\n/** A single component file's tag-tier libraries (mtime-cached), or null when it has none. */\nfunction componentLibsOf(absPath: string): LibrariesConfig | null {\n let mtimeMs: number;\n try {\n mtimeMs = statSync(absPath).mtimeMs;\n } catch {\n return null;\n }\n const cached = componentLibsCache.get(absPath);\n if (cached && cached.mtimeMs === mtimeMs) return cached.libs;\n\n let libs: LibrariesConfig | null = null;\n try {\n const raw = readComponentMeta(readFileSync(absPath, 'utf8'))?.libraries;\n if (raw) {\n // Vite-imported locals (local CSS / module JS) load via the component's own emitted\n // `import` \u2014 strip them so they aren't ALSO rendered as tags (same rule as\n // convertProject's snapshot). What remains (external + local classic JS) is the tag tier.\n const normalized = stripImportedLibraries(normalizeLibraries(raw));\n if (normalized.js?.length || normalized.css?.length) libs = normalized;\n }\n } catch {\n // Unreadable/unparseable component \u2014 contributes nothing.\n }\n\n componentLibsCache.set(absPath, { mtimeMs, libs });\n return libs;\n}\n\n/**\n * Collect the component tier the way meno-core SSR does (`collectComponentLibraries` over\n * the LIVE registry on every render), but from the dialect's canonical store: each\n * `src/components/**\u2215*.astro` carries its `libraries` inside the `const __meno = {\u2026}`\n * literal. Deduped by URL (first wins, files walked in sorted order for determinism).\n * Returns null when the project has no `src/components` dir, so the caller can fall back\n * to the convert-time `__componentLibraries` snapshot.\n */\nexport function collectAstroComponentLibraries(projectRoot: string): LibrariesConfig | null {\n const dir = join(projectRoot, 'src', 'components');\n if (!existsSync(dir)) return null;\n\n const collected: LibrariesConfig = { js: [], css: [] };\n const seenJS = new Set<string>();\n const seenCSS = new Set<string>();\n\n const visit = (d: string) => {\n let entries;\n try {\n entries = readdirSync(d, { withFileTypes: true });\n } catch {\n return;\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n for (const e of entries) {\n const abs = join(d, e.name);\n if (e.isDirectory()) {\n visit(abs);\n continue;\n }\n if (!e.isFile() || !e.name.endsWith('.astro')) continue;\n const libs = componentLibsOf(abs);\n if (!libs) continue;\n for (const js of libs.js || [])\n if (!seenJS.has(js.url)) {\n seenJS.add(js.url);\n collected.js!.push(js);\n }\n for (const css of libs.css || [])\n if (!seenCSS.has(css.url)) {\n seenCSS.add(css.url);\n collected.css!.push(css);\n }\n }\n };\n visit(dir);\n return collected;\n}\n\n/**\n * Load library `<head>`/body tags for the project rooted at `projectRoot`, for the given page.\n *\n * Merge order (mirrors htmlGenerator): global \u2192 component \u2192 page. The page tier may `extend`\n * (default) or `replace` global+component. Never throws \u2014 every failure returns empty strings.\n */\nexport function loadLibraries(projectRoot: string, pageMeta?: PageLibrariesMeta): LibraryTags {\n try {\n const cfgPath = join(projectRoot, 'project.config.json');\n if (!existsSync(cfgPath)) return EMPTY;\n const cfg = JSON.parse(readFileSync(cfgPath, 'utf8')) as {\n libraries?: unknown;\n __componentLibraries?: unknown;\n };\n\n const global = normalizeLibraries(cfg.libraries);\n // Component tier \u2014 collected LIVE from src/components/*.astro (mtime-cached) so editor\n // saves are reflected without re-converting; the convert-time `__componentLibraries`\n // snapshot is only the fallback for output without a components dir. Either way the tier\n // always extends, matching meno-core which injects every component's libs on every page.\n const component =\n collectAstroComponentLibraries(projectRoot) ?? normalizeLibraries(cfg.__componentLibraries);\n // Page tier: drop locals the page emits as Vite imports (local CSS / module JS) so they\n // aren't ALSO rendered as tags here. Global-tier locals stay (no per-page import exists for\n // them) \u2014 they're inlined (CSS) / tagged (JS) below, as in Phase 1.\n const page = pageMeta?.libraries ? stripImportedLibraries(pageMeta.libraries) : undefined;\n\n const globalPlusComponent = mergeLibraries(global, component);\n const merged = mergeLibraries(globalPlusComponent, page);\n const deduped = dedupeByUrl(merged);\n const filtered = filterLibrariesByContext(deduped, 'build');\n\n // Inline local CSS (default for `/\u2026` paths unless `inline:false`) \u2014 byte-parity with SSR.\n // External URLs and `inline:false` stay as <link>. Local files live at <projectRoot>/<path>\n // (the meno() integration serves /libraries/* etc. from the project root).\n const inlineContents = new Map<string, string>();\n for (const css of filtered.css || []) {\n if (css.inline === false || !css.url.startsWith('/')) continue;\n try {\n const filePath = join(projectRoot, css.url.split('?')[0].slice(1));\n if (filePath.startsWith(projectRoot)) {\n inlineContents.set(css.url, readFileSync(filePath, 'utf8'));\n }\n } catch {\n // Unreadable local file \u2014 fall back to a <link> tag (skip inlining).\n }\n }\n\n return generateLibraryTags(filtered, inlineContents);\n } catch {\n return EMPTY;\n }\n}\n", "/**\n * meno-astro \u2014 `href()` / `embedHtml()` runtime resolvers.\n *\n * Emitted markup binds a Link's `href` and an Embed's `html` through these helpers when\n * the model value is a prop-`_mapping` (`{ _mapping: true, prop, values? }`) rather than a\n * literal string/template:\n *\n * <Link href={href(mapping, __props)} />\n * <Embed html={embedHtml(mapping, __props)} />\n *\n * `__props` is the host component's resolved props (threaded by the emitter, exactly like\n * `style()`); pages have no props scope, so the emitter calls the 1-arg form and the\n * mapping degrades to a safe default.\n *\n * \u2500\u2500 Parity with meno-core \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * These mirror meno-core's canonical `resolveLinkMapping` / `resolveHtmlMapping`\n * (`client/templateEngine.ts`), re-implemented locally so this Node/SSR package keeps its\n * narrow `meno-core/shared`-only import graph \u2014 the `meno-core/client` barrel re-exports\n * the whole React renderer (and browser globals), which must not enter an Astro build.\n * `style.ts` mirrors core's style-mapping resolver for the same reason.\n *\n * Both mapping modes are supported, matching core:\n * - value mapping: `mapping.values[String(props[prop])]`\n * - passthrough : `props[prop]` used directly (an object link `{ href, target? }` or a\n * bare string URL / HTML string) when `values` is omitted/empty.\n * The resolved link is flattened to its URL the same way the JSON\u2192Astro exporter does\n * (`(typeof v === 'string' ? v : v?.href) ?? '#'`) \u2014 the dialect `Link.astro` takes a\n * string `href`. Anything that can't resolve (no props, prop unset, missing key) degrades\n * to a safe default \u2014 `\"#\"` for href, `\"\"` for html \u2014 and never throws.\n */\n\n/** A prop-binding `_mapping` of either link or html flavour. */\ninterface RefMapping {\n _mapping: true;\n prop: string;\n values?: Record<string, unknown>;\n}\n\nfunction isMapping(value: unknown): value is RefMapping {\n return (\n !!value &&\n typeof value === 'object' &&\n (value as { _mapping?: unknown })._mapping === true &&\n typeof (value as { prop?: unknown }).prop === 'string'\n );\n}\n\n/** Flatten a resolved link value (object `{ href }` or bare string) to its URL, else `\"#\"`. */\nfunction toHrefString(value: unknown): string {\n if (typeof value === 'string') return value;\n if (value && typeof value === 'object' && 'href' in value) {\n const h = (value as { href?: unknown }).href;\n return typeof h === 'string' ? h : '#';\n }\n return '#';\n}\n\n/**\n * Resolve a Link node's `href` to a URL string.\n *\n * @param value A `LinkMapping` (`{ _mapping, prop, values? }`) for a prop-bound link, or\n * an already-literal value (passed straight through).\n * @param props The host component's resolved props (`__props`); absent for pages.\n * @returns The URL the dialect `Link.astro` renders; `\"#\"` when unresolvable.\n */\nexport function href(value: unknown, props?: Record<string, unknown>): string {\n if (!isMapping(value)) return toHrefString(value);\n const propValue = props?.[value.prop];\n if (propValue === undefined || propValue === null) return '#';\n // Passthrough: the prop value is already a link object \u2014 use it directly (mirrors\n // resolveLinkMapping, which prefers passthrough even when a `values` table exists).\n if (typeof propValue === 'object' && propValue !== null && 'href' in propValue) {\n return toHrefString(propValue);\n }\n // Value-mapping mode: look the prop value up in the mapping table.\n if (value.values) return toHrefString(value.values[String(propValue)]);\n // Passthrough of a coerced string URL.\n return toHrefString(propValue);\n}\n\n/**\n * Resolve an Embed node's `html` to an HTML string.\n *\n * @param value An `HtmlMapping` (`{ _mapping, prop, values? }`) for a prop-bound embed, or\n * an already-literal string (passed straight through).\n * @param props The host component's resolved props (`__props`); absent for pages.\n * @returns The HTML string to inject; `\"\"` when unresolvable.\n */\nexport function embedHtml(value: unknown, props?: Record<string, unknown>): string {\n if (!isMapping(value)) return typeof value === 'string' ? value : '';\n const propValue = props?.[value.prop];\n if (propValue === undefined || propValue === null) return '';\n // Value-mapping mode (non-empty table); else passthrough of a string prop.\n if (value.values && Object.keys(value.values).length > 0) {\n const mapped = value.values[String(propValue)];\n return typeof mapped === 'string' ? mapped : '';\n }\n return typeof propValue === 'string' ? propValue : '';\n}\n\n/**\n * Resolve a node's `if` condition when it is a `BooleanMapping`\n * (`{ _mapping, prop, values }`) \u2014 the emitter wraps that form in `when(...)`. Mirrors\n * meno-core's `resolveConditionalValue` (nodeUtils.ts): look the host prop's value up in\n * the mapping's table and coerce to a boolean.\n *\n * Defaults to `true` (render) when the mapping can't be resolved \u2014 no props, or the prop\n * value isn't in the table \u2014 matching meno-core, so a misconfigured condition shows the\n * node rather than silently hiding it. A non-mapping argument is coerced directly.\n *\n * @param value A `BooleanMapping`, or an already-evaluated condition value.\n * @param props The host component's resolved props (`__props`); absent for pages.\n */\nexport function when(value: unknown, props?: Record<string, unknown>): boolean {\n if (!isMapping(value)) return Boolean(value);\n if (!props) return true;\n const propValue = props[value.prop];\n const mapped = value.values?.[String(propValue)];\n return mapped !== undefined ? Boolean(mapped) : true;\n}\n", "/**\n * Runtime helper for CMS list nodes (`sourceType: 'collection'`) in the meno-astro\n * dialect. The emitter generates:\n *\n * const blogList = await getCollectionList(\"blog\", { sort, filter, limit, \u2026 }, Astro);\n * blogList.map((blog, blogIndex) => ( \u2026 ))\n *\n * It pulls the collection's items from Astro's content layer (`astro:content`\n * `getCollection`) and applies the same query semantics as the JSON runtime's\n * `CMSService.queryItems` / `getItemsByIds` (mirrored here so both render the same\n * lists). Each returned item is the raw stored JSON (`entry.data`) \u2014 the converter\n * writes items with a permissive `z.record(z.string(), z.any())` schema, so `_id`,\n * `_createdAt`, and all fields sit at the top level, exactly as the emitted child\n * template expects (`blog.title`, `blog._createdAt`, \u2026).\n *\n * `astro:content`'s `getCollection` is passed IN by the calling page (which lives in\n * the host Astro project where the virtual module resolves natively). meno-astro never\n * imports `astro:content` itself \u2014 doing so from inside node_modules both breaks\n * non-Astro loaders (tooling/tests) and corrupts Astro's generated content module.\n */\n\ntype GetCollection = (name: string) => Promise<Array<{ data: Record<string, any> }>>;\n\ntype Item = Record<string, any>;\ntype SortConfig = { field: string; order?: 'asc' | 'desc' };\ntype FilterCondition = { field: string; operator?: string; value?: unknown };\n\nexport interface CollectionListQuery {\n filter?: FilterCondition | FilterCondition[] | Record<string, unknown>;\n sort?: SortConfig | SortConfig[];\n limit?: number;\n offset?: number;\n /** Explicit id/filename list (reference fields) \u2014 preserves the given order. */\n items?: string[] | string;\n /** Drop the current page's CMS item (for related-item lists on template routes). */\n excludeCurrentItem?: boolean;\n /** Emit-only hint; unused at runtime. */\n emitTemplate?: unknown;\n}\n\ninterface AstroLike {\n props?: { cms?: { _id?: string } | undefined } & Record<string, unknown>;\n}\n\n/**\n * Resolve a CMS collection list to its items, applying the node's query. `getCollection`\n * is the host project's `astro:content` export, passed by the emitted page.\n */\nexport async function getCollectionList(\n source: string,\n query: CollectionListQuery = {},\n astro?: AstroLike,\n getCollection?: GetCollection,\n): Promise<Item[]> {\n if (typeof getCollection !== 'function') return [];\n let entries: Array<{ data: Item }>;\n try {\n entries = await getCollection(source);\n } catch {\n // No such collection \u2192 empty list (matches the SSR fallback).\n return [];\n }\n\n let items: Item[] = entries.map((e) => e.data);\n\n if (query.items !== undefined) {\n // Reference-field path: fetch specific ids in order (match _id or _filename),\n // skipping missing \u2014 mirrors CMSService.getItemsByIds.\n const ids = (Array.isArray(query.items) ? query.items : [query.items]).filter(Boolean).map(String);\n const byId = new Map(items.map((i) => [i._id, i]));\n const byFilename = new Map(items.filter((i) => i._filename).map((i) => [i._filename, i]));\n items = ids.map((id) => byId.get(id) ?? byFilename.get(id)).filter((i): i is Item => i !== undefined);\n } else {\n if (query.filter) items = applyFilters(items, query.filter);\n if (query.sort) items = applySorting(items, query.sort);\n if (query.offset !== undefined && query.offset > 0) items = items.slice(query.offset);\n if (query.limit !== undefined && query.limit > 0) items = items.slice(0, query.limit);\n }\n\n // Exclude the current CMS item (template [slug] pages pass it on Astro.props.cms).\n if (query.excludeCurrentItem) {\n const currentId = astro?.props?.cms?._id;\n if (currentId) items = items.filter((i) => i._id !== currentId);\n }\n\n return items;\n}\n\n// --- filter/sort, ported verbatim from CMSService for identical semantics ---\n\nfunction isFilterCondition(obj: unknown): obj is FilterCondition {\n return typeof obj === 'object' && obj !== null && 'field' in obj;\n}\n\nfunction applyFilters(\n items: Item[],\n filter: FilterCondition | FilterCondition[] | Record<string, unknown>,\n): Item[] {\n // Simple object filter: { featured: true } \u2192 equality on every key.\n if (!Array.isArray(filter) && !isFilterCondition(filter)) {\n const entries = Object.entries(filter);\n return items.filter((item) => entries.every(([key, value]) => item[key] === value));\n }\n const conditions = Array.isArray(filter) ? filter : [filter as FilterCondition];\n return items.filter((item) => conditions.every((cond) => matchCondition(item, cond)));\n}\n\nfunction matchCondition(item: Item, condition: FilterCondition): boolean {\n const value = item[condition.field];\n switch (condition.operator || 'eq') {\n case 'eq': return value === condition.value;\n case 'neq': return value !== condition.value;\n case 'gt': return (value as number) > (condition.value as number);\n case 'gte': return (value as number) >= (condition.value as number);\n case 'lt': return (value as number) < (condition.value as number);\n case 'lte': return (value as number) <= (condition.value as number);\n case 'contains': return String(value).includes(String(condition.value));\n case 'in': return Array.isArray(condition.value) && condition.value.includes(value);\n default: return false;\n }\n}\n\nfunction applySorting(items: Item[], sort: SortConfig | SortConfig[]): Item[] {\n const sorts = Array.isArray(sort) ? sort : [sort];\n return [...items].sort((a, b) => {\n for (const s of sorts) {\n const aVal = a[s.field];\n const bVal = b[s.field];\n const isDesc = s.order === 'desc';\n if (typeof aVal === 'boolean' && typeof bVal === 'boolean') {\n if (aVal === bVal) continue;\n return isDesc ? (aVal ? -1 : 1) : (aVal ? 1 : -1);\n }\n let result = 0;\n if ((aVal as string | number) < (bVal as string | number)) result = -1;\n else if ((aVal as string | number) > (bVal as string | number)) result = 1;\n if (result !== 0) return isDesc ? -result : result;\n }\n return 0;\n });\n}\n", "/**\n * meno-astro \u2014 runtime entry point.\n *\n * This is the package that Meno-generated Astro projects import at runtime so the\n * `.astro` source stays thin and consistent (one shared resolver for styles, i18n,\n * and CMS queries \u2014 the same code paths the Studio React preview uses).\n *\n * Design note: `meno-astro` depends on `meno-core` (single direction). The heavy,\n * battle-tested logic (style/utility-class generation, i18n resolution, CMS query\n * parsing, the template engine) lives in `meno-core` and is composed here behind a\n * curated, stable surface. No `meno-core` files move, so the existing JSON runtime is\n * untouched by construction. Slimming the published bundle (so generated projects do\n * not transitively pull all of `meno-core`) is a later optimization.\n */\n\nimport type {\n JSONPage,\n PageData,\n ComponentNode,\n StructuredComponentDefinition,\n PropDefinition,\n StyleObject,\n ResponsiveStyleObject,\n} from 'meno-core/shared/types';\n\n// ---------------------------------------------------------------------------\n// Dialect version \u2014 written into generated projects so a project can be\n// migrated forward if the on-disk `.astro` dialect format evolves. Part of the\n// package's semver contract.\n// ---------------------------------------------------------------------------\nexport const dialectVersion = '0.1.0' as const;\n\n// ---------------------------------------------------------------------------\n// Model types re-exported for the dialect + generated projects. These are the\n// Meno in-memory model the emitter serializes and the parser reconstructs.\n// ---------------------------------------------------------------------------\nexport type {\n JSONPage,\n PageData,\n ComponentNode,\n StructuredComponentDefinition,\n PropDefinition,\n StyleObject,\n ResponsiveStyleObject,\n};\n\n/** A responsive style payload as carried verbatim in `style({...})` calls. */\nexport type MenoStyle = ResponsiveStyleObject | StyleObject;\n\n/** A component's prop definitions, as carried by the `resolveProps(Astro, {\u2026})` call. */\nexport type MenoProps = Record<string, PropDefinition>;\n\n/** A page's `export const meta` payload. */\nexport type MenoPageMeta = NonNullable<JSONPage['meta']>;\n\n/**\n * A component's non-interface metadata, carried by the `__meno` frontmatter const.\n * `defineVars` is intentionally not here: it is emitted as the script's native\n * `<script define:vars={{\u2026}}>` directive and reconstructed on parse, not via `__meno`.\n */\nexport type MenoComponentMeta = Pick<\n StructuredComponentDefinition,\n 'category' | 'acceptsStyles' | 'libraries'\n>;\n\n// ---------------------------------------------------------------------------\n// Curated runtime helpers, composed from meno-core. The dialect-specific wrappers\n// the emitter targets: `i18n()`, `list()`, `style()`, `getCollectionList()`,\n// `href()`, and `embedHtml()` are implemented (below / `runtime/*.ts`). The primitives\n// re-exported here are the unambiguous, dependency-free foundations they build on.\n// ---------------------------------------------------------------------------\n\n// i18n resolution (pure primitives; only type-imports inside meno-core).\nexport {\n isI18nValue,\n resolveI18nValue,\n extractLocaleFromPath,\n buildLocalizedPath,\n} from 'meno-core/shared';\n\n// Slug translation primitives (pure; meno-core's slug translator). Re-exported here so\n// the published components (LocaleList/BaseLayout/LocaleRoute) can reach them alongside\n// `loadSlugMappings`; the locale-route helpers in `runtime/localeRoutes.ts` build on them.\nexport {\n buildSlugIndex,\n getLocaleLinks,\n resolveSlugToPageId,\n} from 'meno-core/shared';\nexport type { SlugMap, LocaleLink } from 'meno-core/shared';\n\n// Locale-aware href rewriting \u2014 Link.astro/Embed.astro rewrite authored\n// (default-locale) internal hrefs to the active render locale, slug-translated\n// through the project slug map (SSR `localizeHref` parity). See `runtime/localizeHref.ts`.\nexport { localizeHref, localizeHrefFor, localizeRichTextLinks } from './runtime/localizeHref';\n\n// Locale route helpers (pure) \u2014 enumerate the injected `/[locale]/[...path]` route's\n// getStaticPaths entries from the slug map, plus the slug-translated link builders the\n// published components consume (LocaleRoute / LocaleList / BaseLayout hreflang). See\n// `runtime/localeRoutes.ts`.\nexport {\n enumerateLocaleStaticPaths,\n pageModuleKey,\n buildHreflangLinks,\n localeListItems,\n} from './runtime/localeRoutes';\nexport type {\n LocaleStaticPath,\n HreflangLink,\n LocaleListItem,\n} from './runtime/localeRoutes';\n\n// i18n runtime \u2014 the emitter-facing `i18n()` resolver + its locale-context seam.\n// Emitted markup calls `i18n({\u2026})`; `runWithLocale` is how BaseLayout/middleware will\n// set the active locale per route. See `runtime/i18n.ts`.\nexport {\n i18n,\n runWithLocale,\n getLocaleContext,\n localeFromAstro,\n} from './runtime/i18n';\nexport type {\n LocaleContextValue,\n AstroLike,\n I18nOverride,\n} from './runtime/i18n';\n\n// Locale middleware factory \u2014 wraps each page render in `runWithLocale(...)` so `i18n()`\n// resolves per locale. `createLocaleMiddleware(config)` is the pure, testable factory;\n// `deriveLocale` is its locale-selection policy. The injected middleware module (the one\n// the integration points Astro at) lives at the `meno-astro/runtime/localeMiddleware`\n// subpath. See `runtime/middleware.ts`.\nexport {\n createLocaleMiddleware,\n deriveLocale,\n} from './runtime/middleware';\nexport type {\n LocaleMiddleware,\n LocaleMiddlewareContext,\n} from './runtime/middleware';\n\n// loadI18nConfig \u2014 read + migrate a converted project's i18n config (server/build helper).\n// Also available from `meno-astro/server`; re-exported here as the natural root surface\n// for the middleware/integration story. Touches the filesystem (server/build only).\nexport { loadI18nConfig } from './server/loadI18nConfig';\n\n// loadSlugMappings \u2014 scan a project's `src/pages` for `meta.slugs` \u2192 the SlugMap[] that\n// drives locale routing (LocaleRoute's getStaticPaths), LocaleList links, and BaseLayout\n// hreflang. Re-exported here (alongside loadI18nConfig) so the published components can\n// reach it; `meno-astro/server` is workspace-only and not shipped. Touches the filesystem\n// (build/SSR only \u2014 runs in route/component frontmatter; mtime-cached per file).\nexport { loadSlugMappings } from './server/loadSlugMappings';\n\n// loadFontCss \u2014 read a project's `fonts` config \u2192 `@font-face` CSS + preload tags for\n// BaseLayout's <head>. Re-exported here (alongside loadI18nConfig) so the published\n// BaseLayout component can reach it; `meno-astro/server` is workspace-only and not\n// shipped. Touches the filesystem (build/SSR only \u2014 runs in BaseLayout frontmatter).\nexport { loadFontCss } from './server/loadFontCss';\nexport type { FontHeadAssets } from './server/loadFontCss';\n\n// loadLibraries \u2014 merge a converted project's library tiers (global + component +\n// page `meta.libraries`) \u2192 external <link>/<script>/inline <style> tags for BaseLayout's\n// <head>/body. Re-exported here (alongside loadFontCss) so the published BaseLayout can\n// reach it; `meno-astro/server` is workspace-only and not shipped. Touches the filesystem\n// (build/SSR only \u2014 runs in BaseLayout frontmatter).\nexport { loadLibraries } from './server/loadLibraries';\nexport type { PageLibrariesMeta } from './server/loadLibraries';\n\n// style() runtime \u2014 the emitter-facing style resolver (class-name only). Emitted markup\n// calls `style(styleObject[, props][, meta])` and `style()` returns just the `class={...}`\n// value; the matching utility/interactive CSS is generated at BUILD time by the meno()\n// integration (`virtual:meno-utilities.css`), not collected at render time. See\n// `runtime/style.ts`.\nexport { style } from './runtime/style';\nexport type { StyleMeta } from './runtime/style';\n\n// href() / embedHtml() / when() runtime \u2014 resolve a Link's href / an Embed's html / a\n// node's `if` condition when the model value is a prop-`_mapping`. Emitted markup calls\n// `href(mapping, __props)` / `embedHtml(mapping, __props)` / `when(mapping, __props)`;\n// each mirrors meno-core's resolver (resolveLinkMapping / resolveHtmlMapping /\n// resolveConditionalValue). See `runtime/refs.ts`.\nexport { href, embedHtml, when } from './runtime/refs';\n\n/**\n * Prop-list helper used by generated `.astro`: `list(items, { limit, offset }).map(\u2026)`.\n * Tolerates null/undefined sources and applies offset/limit. (The scope-aware\n * `when()` / `href()` runtime + the CSS-injection integration are the remaining,\n * Astro-toolchain-verified part of Phase 0b.)\n */\nexport function list<T>(\n source: T[] | null | undefined,\n opts?: { offset?: number; limit?: number },\n): T[] {\n let out = Array.isArray(source) ? source : [];\n if (opts?.offset) out = out.slice(opts.offset);\n if (opts?.limit != null) out = out.slice(0, opts.limit);\n return out;\n}\n\n// CMS collection list helper used by generated `.astro`:\n// `const blogList = await getCollectionList(\"blog\", { sort, limit, \u2026 }, Astro)`.\n// Pulls items from astro:content and applies the JSON runtime's query semantics.\nexport { getCollectionList } from './runtime/collectionList';\nexport type { CollectionListQuery } from './runtime/collectionList';\n\n// CMS filter/sort expression parsing + serialization (pure).\nexport {\n parseFilterExpression,\n serializeFilterExpression,\n parseSortExpression,\n serializeSortExpression,\n} from 'meno-core/shared';\n\n// ---------------------------------------------------------------------------\n// resolveProps \u2014 the single authoritative prop block for `.astro` components.\n//\n// A component's frontmatter declares its props exactly once:\n//\n// const { text, isMarginTop, class: className } = resolveProps(Astro, {\n// text: { type: \"string\", default: \"Link\" },\n// isMarginTop: { type: \"boolean\", default: false },\n// });\n//\n// The `{\u2026}` literal is the authoritative prop definition the dialect parser reads\n// back into `def.interface`. At runtime this merges `Astro.props` over each def's\n// `default` (and always provides `class`, defaulting to \"\"). The destructured locals\n// are typed by inferring each prop's value type from its definition (mirroring the\n// old `interface Props` mapping), so editing the file stays fully type-checked.\n// ---------------------------------------------------------------------------\n\n/** Map a single prop definition to the runtime value type of that prop. */\ntype InferMenoProp<D> =\n D extends { type: 'number' } ? number :\n D extends { type: 'boolean' } ? boolean :\n D extends { type: 'link' } ? { href: string; target?: string } :\n D extends { type: 'list' } ? unknown[] :\n D extends { type: 'select'; options: infer O }\n ? (O extends readonly (infer U)[] ? U : string)\n : string; // string, rich-text, image, embed, file \u2192 string\n\n/** Map a whole prop-definition record to the destructured locals' value types. */\ntype InferMenoProps<T extends MenoProps> = { [K in keyof T]: InferMenoProp<T[K]> };\n\n/**\n * Resolve a component's props from `Astro.props`, merging each provided value over\n * its declared `default` and always supplying `class` (defaulting to \"\").\n *\n * `defs` is the authoritative prop definition (the same literal the dialect parser\n * reads). The `const` type parameter preserves literal `type`/`options` so the\n * returned locals are precisely typed (e.g. `select` options become a string union).\n */\nexport function resolveProps<const T extends MenoProps>(\n astro: { props: Record<string, unknown> },\n defs: T,\n): InferMenoProps<T> & { class: string } {\n const props = astro.props ?? {};\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(defs)) {\n if (key === 'children') continue;\n const provided = props[key];\n out[key] = provided !== undefined ? provided : (defs[key] as PropDefinition).default;\n }\n out.class = typeof props.class === 'string' ? props.class : '';\n return out as InferMenoProps<T> & { class: string };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["href", "existsSync", "readFileSync", "join", "join", "existsSync", "readFileSync", "existsSync", "readFileSync", "readdirSync", "statSync", "join", "EMPTY", "statSync", "readFileSync", "join", "existsSync", "readdirSync"]
|
|
7
7
|
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computeClassName,
|
|
3
3
|
resolveMappingsInStyle
|
|
4
|
-
} from "../../chunks/chunk-
|
|
4
|
+
} from "../../chunks/chunk-Q43GCLMA.js";
|
|
5
5
|
import {
|
|
6
6
|
parse,
|
|
7
7
|
parseFile
|
|
8
|
-
} from "../../chunks/chunk-
|
|
9
|
-
import "../../chunks/chunk-
|
|
8
|
+
} from "../../chunks/chunk-MCIBOYQT.js";
|
|
9
|
+
import "../../chunks/chunk-SYOR6LP4.js";
|
|
10
10
|
import {
|
|
11
11
|
loadI18nConfig
|
|
12
|
-
} from "../../chunks/chunk-
|
|
12
|
+
} from "../../chunks/chunk-P2SFVSXG.js";
|
|
13
13
|
import {
|
|
14
14
|
DEFAULT_BREAKPOINTS,
|
|
15
15
|
generateInteractiveCSS,
|
|
16
16
|
generateUtilityCSS,
|
|
17
17
|
isStyleMapping,
|
|
18
18
|
responsiveStylesToClasses
|
|
19
|
-
} from "../../chunks/chunk-
|
|
19
|
+
} from "../../chunks/chunk-GITHFAZG.js";
|
|
20
20
|
import "../../chunks/chunk-5BVKIPKS.js";
|
|
21
21
|
|
|
22
22
|
// lib/integration/index.ts
|
|
@@ -118,7 +118,11 @@ function buildUtilityStylesheet(sources) {
|
|
|
118
118
|
// lib/integration/xray.ts
|
|
119
119
|
import { readFileSync } from "fs";
|
|
120
120
|
var PLAY_XRAY_MESSAGE_TYPE = "meno:astro:xray";
|
|
121
|
+
var PLAY_SELECT_MESSAGE_TYPE = "meno:astro:select";
|
|
122
|
+
var PLAY_HOVER_MESSAGE_TYPE = "meno:astro:hover";
|
|
123
|
+
var PLAY_KEY_MESSAGE_TYPE = "meno:astro:key";
|
|
121
124
|
var INSTANCE_ATTR = "data-meno-instance";
|
|
125
|
+
var SLOT_ATTR = "data-meno-slot";
|
|
122
126
|
var STAMPABLE_TYPES = /* @__PURE__ */ new Set(["node", "link"]);
|
|
123
127
|
function rootNode(model) {
|
|
124
128
|
const component = model.component;
|
|
@@ -141,27 +145,29 @@ function stampElementPaths(source) {
|
|
|
141
145
|
while (j < span.end && /[A-Za-z0-9.\-_]/.test(source[j])) j++;
|
|
142
146
|
splices.push({ pos: j, text });
|
|
143
147
|
};
|
|
144
|
-
const walk = (node, key) => {
|
|
148
|
+
const walk = (node, key, slotOwner) => {
|
|
145
149
|
const span = spans.get(node);
|
|
150
|
+
const slotStamp = slotOwner ? ` ${SLOT_ATTR}="${slotOwner}"` : "";
|
|
146
151
|
if (span) {
|
|
147
152
|
const type = node.type;
|
|
148
153
|
if (type === "component") {
|
|
149
|
-
stampAt(span, ` ${INSTANCE_ATTR}="${key}"`);
|
|
154
|
+
stampAt(span, ` ${INSTANCE_ATTR}="${key}"${slotStamp}`);
|
|
150
155
|
} else if (typeof type === "string" && STAMPABLE_TYPES.has(type)) {
|
|
151
|
-
stampAt(span, ` data-element-path="${key}"`);
|
|
156
|
+
stampAt(span, ` data-element-path="${key}"${slotStamp}`);
|
|
152
157
|
if (isComponentFile && node === root) {
|
|
153
|
-
stampAt(span, ` ${INSTANCE_ATTR}={Astro.props['${INSTANCE_ATTR}']}`);
|
|
158
|
+
stampAt(span, ` ${INSTANCE_ATTR}={Astro.props['${INSTANCE_ATTR}']} ${SLOT_ATTR}={Astro.props['${SLOT_ATTR}']}`);
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
162
|
const children = node.children;
|
|
158
163
|
if (Array.isArray(children)) {
|
|
164
|
+
const childOwner = node.type === "component" ? key : slotOwner;
|
|
159
165
|
children.forEach((child, i) => {
|
|
160
|
-
if (child && typeof child === "object") walk(child, `${key},${i}
|
|
166
|
+
if (child && typeof child === "object") walk(child, `${key},${i}`, childOwner);
|
|
161
167
|
});
|
|
162
168
|
}
|
|
163
169
|
};
|
|
164
|
-
walk(root, "0");
|
|
170
|
+
walk(root, "0", null);
|
|
165
171
|
if (splices.length === 0) return source;
|
|
166
172
|
splices.sort((a, b) => b.pos - a.pos);
|
|
167
173
|
let out = source;
|
|
@@ -199,6 +205,12 @@ if (window.self !== window.top) {
|
|
|
199
205
|
var targets = [];
|
|
200
206
|
var container = null;
|
|
201
207
|
var raf = 0;
|
|
208
|
+
var selectMode = false;
|
|
209
|
+
var lastHoverKey = null;
|
|
210
|
+
|
|
211
|
+
function post(msg) {
|
|
212
|
+
try { window.parent.postMessage(msg, '*'); } catch (e) {}
|
|
213
|
+
}
|
|
202
214
|
|
|
203
215
|
function ensureContainer() {
|
|
204
216
|
if (container && container.isConnected) return container;
|
|
@@ -296,6 +308,8 @@ if (window.self !== window.top) {
|
|
|
296
308
|
window.addEventListener('message', function (ev) {
|
|
297
309
|
var d = ev && ev.data;
|
|
298
310
|
if (!d || d.type !== '${PLAY_XRAY_MESSAGE_TYPE}' || !Array.isArray(d.targets)) return;
|
|
311
|
+
selectMode = !!d.selectMode;
|
|
312
|
+
if (!selectMode) lastHoverKey = null;
|
|
299
313
|
targets = d.targets.map(function (t) {
|
|
300
314
|
return {
|
|
301
315
|
chain: Array.isArray(t.chain) ? t.chain.map(String) : [],
|
|
@@ -309,6 +323,120 @@ if (window.self !== window.top) {
|
|
|
309
323
|
if (!raf) raf = requestAnimationFrame(frame);
|
|
310
324
|
});
|
|
311
325
|
|
|
326
|
+
// ---- click-to-select + hover reporting (select mode only) ----
|
|
327
|
+
//
|
|
328
|
+
// Resolve a DOM element to its editor identity: the instance-path chain
|
|
329
|
+
// from the page file down to the element's owning file, plus the element's
|
|
330
|
+
// file-local path. Walk instance roots upward from the element; at each
|
|
331
|
+
// root decide HOP vs SKIP exactly via the slot attribute: the carrier's
|
|
332
|
+
// markup is slot content of this root (same file as the root's instance
|
|
333
|
+
// tag \u2014 SKIP) iff slot(carrier) === inst(root). Everything else is a real
|
|
334
|
+
// file transition (HOP, contributing the root's instance path).
|
|
335
|
+
function identify(start) {
|
|
336
|
+
var t = start && start.closest ? start.closest('[data-element-path]') : null;
|
|
337
|
+
if (!t) return null;
|
|
338
|
+
var path = t.getAttribute('data-element-path');
|
|
339
|
+
if (!KEY_RE.test(path)) return null;
|
|
340
|
+
var chain = [];
|
|
341
|
+
var carrier = t;
|
|
342
|
+
var r = t.closest('[${INSTANCE_ATTR}]'); // incl. self: t may be a component root
|
|
343
|
+
while (r) {
|
|
344
|
+
var inst = r.getAttribute('${INSTANCE_ATTR}');
|
|
345
|
+
if (!KEY_RE.test(inst)) break;
|
|
346
|
+
if (carrier.getAttribute('${SLOT_ATTR}') !== inst) chain.unshift(inst);
|
|
347
|
+
carrier = r;
|
|
348
|
+
r = r.parentElement ? r.parentElement.closest('[${INSTANCE_ATTR}]') : null;
|
|
349
|
+
}
|
|
350
|
+
return { chain: chain, path: path };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
document.addEventListener('click', function (e) {
|
|
354
|
+
if (!selectMode) return;
|
|
355
|
+
// The click belongs to the editor: never navigate links or fire the
|
|
356
|
+
// page's own handlers while selecting (same as the SSR canvas).
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
e.stopPropagation();
|
|
359
|
+
var id = identify(e.target);
|
|
360
|
+
if (id) post({ type: '${PLAY_SELECT_MESSAGE_TYPE}', chain: id.chain, path: id.path });
|
|
361
|
+
}, true);
|
|
362
|
+
|
|
363
|
+
document.addEventListener('mouseover', function (e) {
|
|
364
|
+
if (!selectMode) return;
|
|
365
|
+
var id = identify(e.target);
|
|
366
|
+
var key = id ? id.chain.join('|') + '#' + id.path : null;
|
|
367
|
+
if (key === lastHoverKey) return;
|
|
368
|
+
lastHoverKey = key;
|
|
369
|
+
post({
|
|
370
|
+
type: '${PLAY_HOVER_MESSAGE_TYPE}',
|
|
371
|
+
chain: id ? id.chain : [],
|
|
372
|
+
path: id ? id.path : null
|
|
373
|
+
});
|
|
374
|
+
}, true);
|
|
375
|
+
document.documentElement.addEventListener('mouseleave', function () {
|
|
376
|
+
if (!selectMode || lastHoverKey === null) return;
|
|
377
|
+
lastHoverKey = null;
|
|
378
|
+
post({ type: '${PLAY_HOVER_MESSAGE_TYPE}', chain: [], path: null });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// ---- keyboard forwarding ----
|
|
382
|
+
//
|
|
383
|
+
// Focus lives in this (cross-origin) document whenever the user clicks the
|
|
384
|
+
// preview, which would silently swallow every editor shortcut. Forward key
|
|
385
|
+
// events to the editor, which re-dispatches them into its own shortcut
|
|
386
|
+
// pipeline. Ownership rules:
|
|
387
|
+
// - editable targets (typing into the page's own inputs) are never touched
|
|
388
|
+
// - select mode: the editor owns the whole keyboard (arrows navigate the
|
|
389
|
+
// tree, Delete removes the node, plain-letter shortcuts work)
|
|
390
|
+
// - browse mode: only modifier chords (Cmd/Ctrl+\u2026) and Escape are the
|
|
391
|
+
// editor's \u2014 plain keys keep driving the page (forms, sliders, scroll)
|
|
392
|
+
// - Cmd/Ctrl+C/X with a real text selection in the page stays native, so
|
|
393
|
+
// copying text out of the preview keeps working in either mode
|
|
394
|
+
function isEditable(t) {
|
|
395
|
+
if (!t || !t.tagName) return false;
|
|
396
|
+
var tag = t.tagName;
|
|
397
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable === true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function shouldForwardKey(e) {
|
|
401
|
+
if (isEditable(e.target)) return false;
|
|
402
|
+
var mod = e.metaKey || e.ctrlKey;
|
|
403
|
+
if (!selectMode && !mod && e.key !== 'Escape') return false;
|
|
404
|
+
if (mod && (e.key === 'c' || e.key === 'x')) {
|
|
405
|
+
var sel = window.getSelection();
|
|
406
|
+
if (sel && !sel.isCollapsed) return false; // native text copy wins
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function keyPayload(kind, e) {
|
|
412
|
+
return {
|
|
413
|
+
type: '${PLAY_KEY_MESSAGE_TYPE}',
|
|
414
|
+
kind: kind,
|
|
415
|
+
key: e.key,
|
|
416
|
+
code: e.code,
|
|
417
|
+
metaKey: e.metaKey,
|
|
418
|
+
ctrlKey: e.ctrlKey,
|
|
419
|
+
shiftKey: e.shiftKey,
|
|
420
|
+
altKey: e.altKey,
|
|
421
|
+
repeat: e.repeat
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
window.addEventListener('keydown', function (e) {
|
|
426
|
+
if (!shouldForwardKey(e)) return;
|
|
427
|
+
// The keystroke belongs to the editor: stop page handlers and browser
|
|
428
|
+
// defaults (Cmd+S save dialog, Cmd+P print, arrow/space scroll in
|
|
429
|
+
// select mode, \u2026).
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
e.stopPropagation();
|
|
432
|
+
post(keyPayload('keydown', e));
|
|
433
|
+
}, true);
|
|
434
|
+
|
|
435
|
+
window.addEventListener('keyup', function (e) {
|
|
436
|
+
if (!shouldForwardKey(e)) return;
|
|
437
|
+
post(keyPayload('keyup', e));
|
|
438
|
+
}, true);
|
|
439
|
+
|
|
312
440
|
// Soft navigations replace the DOM: drop cached elements so the next
|
|
313
441
|
// frame re-resolves against the new document.
|
|
314
442
|
document.addEventListener('astro:page-load', function () {
|