@contentrain/emitter-astro 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +61 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/css.ts","../src/util.ts","../src/index.ts"],"mappings":";;;;;AAWA;;;;;UAAiB,QAAA;EACf,IAAA;EACA,KAAA;EAKA;EAHA,IAAA;EAKA;EAHA,KAAA;EACA,MAAA;EACA,YAAA;EACA,WAAA;EACA,KAAA;EAOe;EALf,QAAA;EACA,OAAA;AAAA;;UAIe,SAAA;EACf,MAAA,EAAQ,MAAA;EACR,KAAA,EAAO,QAAA;EAQP;;;AAGF;;;;EAHE,aAAA;AAAA;AAAA,UAGe,WAAA;EAIC;EAFhB,KAAA,GAAQ,QAAA;EAAA;EAER,OAAA,GAAU,MAAA,SAAe,SAAA;AAAA;;UAIV,WAAA;EACf,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,WAAA;EAJf;EAMA,WAAA;EAFe;EAIf,QAAA;AAAA;AAAA,UAGe,SAAA;EACf,EAAA,EAAI,SAAA;EACJ,OAAA,GAAU,WAAA;EACV,GAAA,GAAM,WAAA;EACN,OAAA,GAAU,WAAA;AAAA;;;;;;UAUK,UAAA;EACf,KAAA,EAAO,MAAA;EACP,QAAA;AAAA;;;UC7De,aAAA;EACf,OAAA;EACA,OAAA;AAAA;AAAA,iBAKc,aAAA,CAAc,IAAA,UAAc,GAAA,WAAc,aAAA;;;;iBCtB1C,UAAA,CAAW,EAAA;;AFU3B;;;;iBEGgB,iBAAA,CAAkB,OAAA;;iBAuBlB,UAAA,CAAW,KAAA;;;iBCrBX,gBAAA,CAAiB,KAAA,EAAO,SAAA,GAAY,UAAA;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/css.ts","../src/util.ts","../src/index.ts"],"mappings":";;;;;AAWA;;;;;UAAiB,QAAA;EACf,IAAA;EACA,KAAA;EAKA;EAHA,IAAA;EAKA;EAHA,KAAA;EACA,MAAA;EACA,YAAA;EACA,WAAA;EACA,KAAA;EAOe;EALf,QAAA;EACA,OAAA;AAAA;;UAIe,SAAA;EACf,MAAA,EAAQ,MAAA;EACR,KAAA,EAAO,QAAA;EAQP;;;AAGF;;;;EAHE,aAAA;AAAA;AAAA,UAGe,WAAA;EAIC;EAFhB,KAAA,GAAQ,QAAA;EAAA;EAER,OAAA,GAAU,MAAA,SAAe,SAAA;AAAA;;UAIV,WAAA;EACf,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,WAAA;EAJf;EAMA,WAAA;EAFe;EAIf,QAAA;AAAA;AAAA,UAGe,SAAA;EACf,EAAA,EAAI,SAAA;EACJ,OAAA,GAAU,WAAA;EACV,GAAA,GAAM,WAAA;EACN,OAAA,GAAU,WAAA;AAAA;;;;;;UAUK,UAAA;EACf,KAAA,EAAO,MAAA;EACP,QAAA;AAAA;;;UC7De,aAAA;EACf,OAAA;EACA,OAAA;AAAA;AAAA,iBAKc,aAAA,CAAc,IAAA,UAAc,GAAA,WAAc,aAAA;;;;iBCtB1C,UAAA,CAAW,EAAA;;AFU3B;;;;iBEGgB,iBAAA,CAAkB,OAAA;;iBAuBlB,UAAA,CAAW,KAAA;;;iBCrBX,gBAAA,CAAiB,KAAA,EAAO,SAAA,GAAY,UAAA;;iBAmD9B,SAAA,CAAU,MAAA,EAAQ,UAAA,EAAY,GAAA,WAAc,OAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LEGACY_CSS_LAYER } from "@contentrain/types";
|
|
1
|
+
import { CHROME_BODY_SLOT, LEGACY_CSS_LAYER } from "@contentrain/types";
|
|
2
2
|
//#region src/util.ts
|
|
3
3
|
/** "f-archive" → "FArchive", "posts" → "Posts" — Astro component file names. */
|
|
4
4
|
function pascalCase(id) {
|
|
@@ -58,11 +58,17 @@ function scaffoldFiles(ir, options) {
|
|
|
58
58
|
...tailwind ? [`import tailwindcss from '@tailwindcss/vite'`] : [],
|
|
59
59
|
``,
|
|
60
60
|
`export default defineConfig({`,
|
|
61
|
+
` site: ${JSON.stringify(ir.site.url)},`,
|
|
61
62
|
` build: { format: 'directory' },`,
|
|
62
63
|
...tailwind ? [` vite: { plugins: [tailwindcss()] },`] : [],
|
|
63
64
|
`})`,
|
|
64
65
|
``
|
|
65
66
|
].join("\n");
|
|
67
|
+
files["tsconfig.json"] = `${JSON.stringify({
|
|
68
|
+
extends: "astro/tsconfigs/base",
|
|
69
|
+
include: [".astro/types.d.ts", "**/*"],
|
|
70
|
+
exclude: ["dist"]
|
|
71
|
+
}, null, 2)}\n`;
|
|
66
72
|
if (tailwind) files["src/styles/modern.css"] = modernCss(ir.tokens);
|
|
67
73
|
files["src/lib/fill.ts"] = FILL_TS;
|
|
68
74
|
return files;
|
|
@@ -85,6 +91,9 @@ function modernCss(tokens) {
|
|
|
85
91
|
const FILL_TS = `// Emitted by @contentrain/emitter-astro — shared mark-filling helpers.
|
|
86
92
|
// Chrome and item templates carry @@mark@@ placeholders; pages fill them from content.
|
|
87
93
|
|
|
94
|
+
/** Where page content splices into the body chrome — must match @contentrain/types CHROME_BODY_SLOT. */
|
|
95
|
+
export const BODY_SLOT = '<!--@@body@@-->'
|
|
96
|
+
|
|
88
97
|
export const esc = (value: unknown): string =>
|
|
89
98
|
String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
90
99
|
|
|
@@ -123,28 +132,45 @@ export function postMarks(post: MarkablePost): Record<string, string> {
|
|
|
123
132
|
`;
|
|
124
133
|
//#endregion
|
|
125
134
|
//#region src/layouts.ts
|
|
126
|
-
function familyFiles(family) {
|
|
135
|
+
function familyFiles(family, lang) {
|
|
127
136
|
const name = pascalCase(family.id);
|
|
128
137
|
const chunks = family.chrome ?? [];
|
|
138
|
+
const warnings = [];
|
|
129
139
|
const joined = (position) => chunks.filter((c) => c.position === position).map((c) => c.html).join("\n");
|
|
140
|
+
const bodyChunks = chunks.filter((c) => c.position === "body");
|
|
141
|
+
let body;
|
|
142
|
+
if (bodyChunks.length) {
|
|
143
|
+
body = bodyChunks.map((c) => c.html).join("\n");
|
|
144
|
+
if (!body.includes(CHROME_BODY_SLOT)) {
|
|
145
|
+
warnings.push(`family ${family.id}: body chrome has no ${CHROME_BODY_SLOT} marker — content appended at the end`);
|
|
146
|
+
body += CHROME_BODY_SLOT;
|
|
147
|
+
}
|
|
148
|
+
} else body = `${joined("before_body")}${CHROME_BODY_SLOT}${joined("after_body")}`;
|
|
130
149
|
const files = {};
|
|
131
150
|
files[`src/data/chrome/${family.id}.json`] = stableJson({
|
|
132
151
|
head: joined("head"),
|
|
133
|
-
|
|
134
|
-
after: joined("after_body")
|
|
152
|
+
body
|
|
135
153
|
});
|
|
136
154
|
const cssLinks = (family.css.files ?? []).map((f) => `<link rel="stylesheet" href="/styles/legacy/${f.split("/").pop()}" />`).join("\n");
|
|
137
155
|
files[`src/layouts/${name}.astro`] = `---
|
|
138
156
|
// Family: ${family.id}${family.name ? ` (${family.name})` : ""} — emitted by @contentrain/emitter-astro
|
|
139
157
|
import chrome from '../data/chrome/${family.id}.json'
|
|
140
|
-
import { fillMarks } from '../lib/fill'
|
|
141
|
-
|
|
158
|
+
import { fillMarks, BODY_SLOT } from '../lib/fill'
|
|
159
|
+
|
|
160
|
+
interface Props {
|
|
161
|
+
title?: string
|
|
162
|
+
marks?: Record<string, string>
|
|
163
|
+
/** Page content as an HTML string; slot children are used when absent. */
|
|
164
|
+
body?: string
|
|
165
|
+
}
|
|
166
|
+
const { title = '', marks = {}, body } = Astro.props
|
|
142
167
|
const head = fillMarks(chrome.head, marks)
|
|
143
|
-
const
|
|
144
|
-
|
|
168
|
+
const content = body ?? (Astro.slots.has('default') ? await Astro.slots.render('default') : '')
|
|
169
|
+
// Marks fill the CHROME only; content splices in untouched, then ONE injection.
|
|
170
|
+
const html = fillMarks(chrome.body, marks).split(BODY_SLOT).join(content)
|
|
145
171
|
---
|
|
146
172
|
<!doctype html>
|
|
147
|
-
<html lang
|
|
173
|
+
<html lang=${JSON.stringify(lang)}>
|
|
148
174
|
<head>
|
|
149
175
|
<meta charset="utf-8" />
|
|
150
176
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
@@ -153,13 +179,14 @@ ${cssLinks.split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n")}
|
|
|
153
179
|
<title>{title}</title>
|
|
154
180
|
</head>
|
|
155
181
|
<body>
|
|
156
|
-
<Fragment set:html={
|
|
157
|
-
<slot />
|
|
158
|
-
<Fragment set:html={after} />
|
|
182
|
+
<Fragment set:html={html} />
|
|
159
183
|
</body>
|
|
160
184
|
</html>
|
|
161
185
|
`;
|
|
162
|
-
return
|
|
186
|
+
return {
|
|
187
|
+
files,
|
|
188
|
+
warnings
|
|
189
|
+
};
|
|
163
190
|
}
|
|
164
191
|
//#endregion
|
|
165
192
|
//#region src/pages.ts
|
|
@@ -216,41 +243,36 @@ import { postMarks } from '${up}lib/fill'
|
|
|
216
243
|
export function getStaticPaths() {
|
|
217
244
|
return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }))
|
|
218
245
|
}
|
|
219
|
-
|
|
246
|
+
type Props = { post: (typeof posts)[number] }
|
|
247
|
+
const { post } = Astro.props as Props
|
|
220
248
|
---
|
|
221
|
-
<Layout title={post.title} marks={postMarks(post)}
|
|
222
|
-
<Fragment set:html={post.body} />
|
|
223
|
-
</Layout>
|
|
249
|
+
<Layout title={post.title} marks={postMarks(post)} body={post.body} />
|
|
224
250
|
`;
|
|
225
251
|
}
|
|
226
252
|
function listPage(route, layout, up, hasParams) {
|
|
227
253
|
const paths = hasParams ? `export function getStaticPaths() {
|
|
228
254
|
return pages.map((page) => ({ params: page.params, props: { page } }))
|
|
229
255
|
}
|
|
230
|
-
|
|
256
|
+
type Props = { page: (typeof pages)[number] }
|
|
257
|
+
const { page } = Astro.props as Props` : `const page = pages[0] ?? { params: {}, items: [] }`;
|
|
231
258
|
return `---
|
|
232
259
|
// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro
|
|
233
260
|
import Layout from '${up}layouts/${layout}.astro'
|
|
234
261
|
import pages from '${up}data/queries/${route.query}.json'
|
|
235
|
-
import { fillMarks, postMarks } from '${up}lib/fill'
|
|
262
|
+
import { esc, fillMarks, postMarks } from '${up}lib/fill'
|
|
236
263
|
|
|
237
264
|
${paths}
|
|
238
265
|
const items = page.items.map((item) =>
|
|
239
266
|
page.item_template ? fillMarks(page.item_template, postMarks(item)) : undefined,
|
|
240
267
|
)
|
|
241
268
|
const fallback = items.some((h) => h === undefined)
|
|
269
|
+
const content = fallback
|
|
270
|
+
? '<ul class="cr-post-list">' +
|
|
271
|
+
page.items.map((item) => '<li><a href="/' + item.slug + '/">' + esc(item.title) + '</a></li>').join('') +
|
|
272
|
+
'</ul>'
|
|
273
|
+
: items.join('')
|
|
242
274
|
---
|
|
243
|
-
<Layout title={page.params.term ?? ''} marks={page.params}
|
|
244
|
-
{fallback ? (
|
|
245
|
-
<ul class="cr-post-list">
|
|
246
|
-
{page.items.map((item) => (
|
|
247
|
-
<li><a href={'/' + item.slug + '/'}>{item.title}</a></li>
|
|
248
|
-
))}
|
|
249
|
-
</ul>
|
|
250
|
-
) : (
|
|
251
|
-
<Fragment set:html={items.join('')} />
|
|
252
|
-
)}
|
|
253
|
-
</Layout>
|
|
275
|
+
<Layout title={page.params.term ?? ''} marks={page.params} body={content} />
|
|
254
276
|
`;
|
|
255
277
|
}
|
|
256
278
|
function staticPage(route, layout, up, hasParams, warnings) {
|
|
@@ -277,6 +299,9 @@ function componentFiles(components) {
|
|
|
277
299
|
* cr-component: type=${c.type} source=${c.source}${c.name ? ` — ${c.name}` : ""}
|
|
278
300
|
* Emitted by @contentrain/emitter-astro as a placeholder.${c.source === "runtime" ? "\n * Needs a live provider — see the migration handoff offers." : ""}${variants.length ? `\n * Variants: ${variants.join(", ")}` : ""}
|
|
279
301
|
*/
|
|
302
|
+
interface Props {
|
|
303
|
+
variant?: string
|
|
304
|
+
}
|
|
280
305
|
const { variant = ${JSON.stringify(variants[0] ?? "default")} } = Astro.props
|
|
281
306
|
---
|
|
282
307
|
<cr-component data-type=${JSON.stringify(c.type)} data-variant={variant}></cr-component>
|
|
@@ -312,7 +337,12 @@ function emitAstroProject(input) {
|
|
|
312
337
|
};
|
|
313
338
|
add(scaffoldFiles(ir, input.options ?? {}));
|
|
314
339
|
const familiesById = new Map(ir.families.map((f) => [f.id, f]));
|
|
315
|
-
|
|
340
|
+
const lang = ir.site.locales?.[0] ?? "en";
|
|
341
|
+
for (const family of ir.families) {
|
|
342
|
+
const fam = familyFiles(family, lang);
|
|
343
|
+
add(fam.files);
|
|
344
|
+
warnings.push(...fam.warnings);
|
|
345
|
+
}
|
|
316
346
|
for (const css of input.css ?? []) {
|
|
317
347
|
const wrapped = wrapLegacyCss(css.path, css.content);
|
|
318
348
|
if (wrapped.warning) warnings.push(wrapped.warning);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/util.ts","../src/scaffold.ts","../src/layouts.ts","../src/pages.ts","../src/components.ts","../src/css.ts","../src/index.ts"],"sourcesContent":["/** \"f-archive\" → \"FArchive\", \"posts\" → \"Posts\" — Astro component file names. */\nexport function pascalCase(id: string): string {\n return id\n .split(/[^a-zA-Z0-9]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\n/**\n * Route pattern → Astro pages path.\n * `/` → `index.astro`; `/category/:term/page/:page` → `category/[term]/page/[page].astro`.\n * Returns null for patterns Astro's file routing cannot express (recorded as a warning upstream).\n */\nexport function patternToPagePath(pattern: string): string | null {\n if (pattern === '/' || pattern === '') return 'index.astro'\n if (pattern.includes('*')) return null\n const segments = pattern.replace(/^\\/+|\\/+$/g, '').split('/')\n const mapped = segments.map((seg) => (seg.startsWith(':') ? `[${seg.slice(1)}]` : seg))\n if (mapped.some((seg) => !/^[[\\]a-zA-Z0-9._-]+$/.test(seg))) return null\n const last = mapped.pop()\n return [...mapped, `${last}.astro`].join('/')\n}\n\nconst sortDeep = (v: unknown): unknown => {\n if (Array.isArray(v)) return v.map(sortDeep)\n if (v && typeof v === 'object') {\n return Object.fromEntries(\n Object.entries(v as Record<string, unknown>)\n .toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, val]) => [k, sortDeep(val)]),\n )\n }\n return v\n}\n\n/** JSON with stable key order and trailing newline — deterministic output, stable diffs. */\nexport function stableJson(value: unknown): string {\n return `${JSON.stringify(sortDeep(value), null, 2)}\\n`\n}\n","// Project scaffolding: manifest, config, styles, and the shared fill helper\n// the generated pages use to pour content into `@@mark@@` placeholders.\n\nimport type { DesignTokens, ProjectIR } from '@contentrain/types'\nimport type { EmitOptions } from './types.js'\nimport { stableJson } from './util.js'\n\nexport function scaffoldFiles(ir: ProjectIR, options: EmitOptions): Record<string, string> {\n const tailwind = options.tailwind !== false\n const split = ir.viewport_strategy === 'split'\n const files: Record<string, string> = {}\n\n const pkg: Record<string, unknown> = {\n name: options.projectName ?? 'migrated-site',\n private: true,\n type: 'module',\n scripts: {\n dev: 'astro dev',\n build: 'astro build',\n preview: 'astro preview',\n // Split viewport production (skeleton): one build per device class, served by device.\n ...(split ? { 'build:desktop': 'VIEWPORT=desktop astro build --outDir dist/desktop', 'build:mobile': 'VIEWPORT=mobile astro build --outDir dist/mobile' } : {}),\n },\n dependencies: {\n astro: '^5.0.0',\n ...(tailwind ? { tailwindcss: '^4.0.0', '@tailwindcss/vite': '^4.0.0' } : {}),\n },\n }\n files['package.json'] = stableJson(pkg)\n\n files['astro.config.mjs'] = [\n `import { defineConfig } from 'astro/config'`,\n ...(tailwind ? [`import tailwindcss from '@tailwindcss/vite'`] : []),\n ``,\n `export default defineConfig({`,\n ` build: { format: 'directory' },`,\n ...(tailwind ? [` vite: { plugins: [tailwindcss()] },`] : []),\n `})`,\n ``,\n ].join('\\n')\n\n if (tailwind) files['src/styles/modern.css'] = modernCss(ir.tokens)\n\n files['src/lib/fill.ts'] = FILL_TS\n return files\n}\n\n/** Tailwind 4 evolution layer: CSS-first config with the site's extracted tokens. */\nfunction modernCss(tokens: DesignTokens | undefined): string {\n const lines: string[] = [`@import 'tailwindcss';`, ``]\n const theme: string[] = []\n const push = (prefix: string, map: Record<string, string> | undefined) => {\n for (const [key, value] of Object.entries(map ?? {})) theme.push(` --${prefix}-${key}: ${value};`)\n }\n push('color', tokens?.colors)\n push('font', tokens?.font_families)\n push('text', tokens?.font_sizes)\n push('spacing', tokens?.spacing)\n push('breakpoint', tokens?.breakpoints)\n if (theme.length) lines.push(`@theme {`, ...theme, `}`)\n return `${lines.join('\\n')}\\n`\n}\n\nconst FILL_TS = `// Emitted by @contentrain/emitter-astro — shared mark-filling helpers.\n// Chrome and item templates carry @@mark@@ placeholders; pages fill them from content.\n\nexport const esc = (value: unknown): string =>\n String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n\nexport function fillMarks(html: string, marks: Record<string, unknown>): string {\n return html.replace(/@@([a-z0-9_]+)@@/gi, (_all, key: string) => esc(marks[key] ?? ''))\n}\n\nexport interface MarkablePost {\n slug: string\n title: string\n dates?: string[]\n author?: string\n author_first?: string\n author_last?: string\n terms?: string[]\n featured?: string[]\n excerpt?: string\n}\n\n/** The mark vocabulary the measurement chain proved out: title, author (+ first/last), date{n}, term{n}, feat{n}, excerpt, slug. */\nexport function postMarks(post: MarkablePost): Record<string, string> {\n const marks: Record<string, string> = {\n title: post.title,\n author: post.author ?? '',\n author_first: post.author_first ?? '',\n author_last: post.author_last ?? '',\n excerpt: post.excerpt ?? '',\n slug: post.slug,\n feat: post.featured?.[0] ?? '',\n }\n for (const [i, d] of (post.dates ?? []).entries()) marks[\\`date\\${i}\\`] = d\n for (const [i, t] of (post.terms ?? []).entries()) marks[\\`term\\${i}\\`] = t\n for (const [i, f] of (post.featured ?? []).entries()) marks[\\`feat\\${i}\\`] = f\n return marks\n}\n`\n","// Layout families → Astro layouts.\n//\n// Chrome travels as DATA, not as Astro template source: the compiler must\n// never parse browser-tolerated theme markup (that distinction alone took a\n// measured corpus from build failures to zero). Each family's chrome chunks\n// land in a JSON data file; the layout injects them with `set:html` and fills\n// `@@mark@@` placeholders from the page's props.\n\nimport type { LayoutFamily } from '@contentrain/types'\nimport { pascalCase, stableJson } from './util.js'\n\nexport function familyFiles(family: LayoutFamily): Record<string, string> {\n const name = pascalCase(family.id)\n const chunks = family.chrome ?? []\n const joined = (position: 'head' | 'before_body' | 'after_body') =>\n chunks.filter((c) => c.position === position).map((c) => c.html).join('\\n')\n\n const files: Record<string, string> = {}\n files[`src/data/chrome/${family.id}.json`] = stableJson({\n head: joined('head'),\n before: joined('before_body'),\n after: joined('after_body'),\n })\n\n const cssLinks = (family.css.files ?? [])\n .map((f) => `<link rel=\"stylesheet\" href=\"/styles/legacy/${f.split('/').pop()}\" />`)\n .join('\\n')\n\n files[`src/layouts/${name}.astro`] = `---\n// Family: ${family.id}${family.name ? ` (${family.name})` : ''} — emitted by @contentrain/emitter-astro\nimport chrome from '../data/chrome/${family.id}.json'\nimport { fillMarks } from '../lib/fill'\nconst { title = '', marks = {} } = Astro.props\nconst head = fillMarks(chrome.head, marks)\nconst before = fillMarks(chrome.before, marks)\nconst after = fillMarks(chrome.after, marks)\n---\n<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n${cssLinks\n .split('\\n')\n .filter(Boolean)\n .map((l) => ` ${l}`)\n .join('\\n')}\n <Fragment set:html={head} />\n <title>{title}</title>\n </head>\n <body>\n <Fragment set:html={before} />\n <slot />\n <Fragment set:html={after} />\n </body>\n</html>\n`\n return files\n}\n","// Routes → Astro pages.\n//\n// Three shapes, matching what a migrated site actually needs:\n// - `single`: one page per post, body injected with `set:html` inside the family layout.\n// - list kinds (`archive`/`term`/`author`/`date`/`front` with a query): static\n// paths come from the producer's query data; items render through the\n// extracted item template when one exists.\n// - static kinds (no query): the family chrome IS the page.\n//\n// Pagination is a route parameter (`/news/page/:page`), never a separate\n// family — the generated paths simply include the page number in params.\n\nimport type { RouteModel, LayoutFamily } from '@contentrain/types'\nimport type { EmitContent, QueryPage } from './types.js'\nimport { pascalCase, patternToPagePath, stableJson } from './util.js'\n\nexport interface PageGenResult {\n files: Record<string, string>\n warnings: string[]\n}\n\nexport function routeFiles(\n route: RouteModel,\n family: LayoutFamily | undefined,\n content: EmitContent,\n): PageGenResult {\n const warnings: string[] = []\n const files: Record<string, string> = {}\n const pagePath = patternToPagePath(route.pattern)\n if (!pagePath) {\n return { files, warnings: [`route ${route.id}: pattern \"${route.pattern}\" cannot be expressed as an Astro page — skipped`] }\n }\n if (!family) {\n return { files, warnings: [`route ${route.id}: family \"${route.family}\" not found — skipped`] }\n }\n const layout = pascalCase(family.id)\n const depth = pagePath.split('/').length - 1\n const up = '../'.repeat(depth + 1) || '../'\n const hasParams = pagePath.includes('[')\n\n if (route.kind === 'single') {\n const posts = content.posts ?? []\n if (!posts.length) warnings.push(`route ${route.id}: single route with no posts — page emitted, data file empty`)\n files['src/data/posts.json'] = stableJson(posts)\n files[`src/pages/${pagePath}`] = singlePage(route, layout, up)\n return { files, warnings }\n }\n\n const queryPages: QueryPage[] = route.query ? (content.queries?.[route.query] ?? []) : []\n if (route.query) {\n if (!queryPages.length) warnings.push(`route ${route.id}: query \"${route.query}\" has no data — page emitted, data file empty`)\n if (queryPages.some((p) => !p.item_template)) {\n warnings.push(`route ${route.id}: item template missing on some pages — plain fallback list rendered (not fidelity)`)\n }\n files[`src/data/queries/${route.query}.json`] = stableJson(queryPages)\n files[`src/pages/${pagePath}`] = listPage(route, layout, up, hasParams)\n return { files, warnings }\n }\n\n files[`src/pages/${pagePath}`] = staticPage(route, layout, up, hasParams, warnings)\n return { files, warnings }\n}\n\nfunction singlePage(route: RouteModel, layout: string, up: string): string {\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\nimport posts from '${up}data/posts.json'\nimport { postMarks } from '${up}lib/fill'\n\nexport function getStaticPaths() {\n return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }))\n}\nconst { post } = Astro.props\n---\n<Layout title={post.title} marks={postMarks(post)}>\n <Fragment set:html={post.body} />\n</Layout>\n`\n}\n\nfunction listPage(route: RouteModel, layout: string, up: string, hasParams: boolean): string {\n const paths = hasParams\n ? `export function getStaticPaths() {\n return pages.map((page) => ({ params: page.params, props: { page } }))\n}\nconst { page } = Astro.props`\n : `const page = pages[0] ?? { params: {}, items: [] }`\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\nimport pages from '${up}data/queries/${route.query}.json'\nimport { fillMarks, postMarks } from '${up}lib/fill'\n\n${paths}\nconst items = page.items.map((item) =>\n page.item_template ? fillMarks(page.item_template, postMarks(item)) : undefined,\n)\nconst fallback = items.some((h) => h === undefined)\n---\n<Layout title={page.params.term ?? ''} marks={page.params}>\n {fallback ? (\n <ul class=\"cr-post-list\">\n {page.items.map((item) => (\n <li><a href={'/' + item.slug + '/'}>{item.title}</a></li>\n ))}\n </ul>\n ) : (\n <Fragment set:html={items.join('')} />\n )}\n</Layout>\n`\n}\n\nfunction staticPage(route: RouteModel, layout: string, up: string, hasParams: boolean, warnings: string[]): string {\n if (hasParams) warnings.push(`route ${route.id}: parameterized route without a query — emitted with empty paths`)\n const paths = hasParams\n ? `export function getStaticPaths() {\n return []\n}\n`\n : ''\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\n${paths}---\n<Layout title=\"\" marks={{}} />\n`\n}\n","// Component placeholders.\n//\n// The emitter cannot invent a comments form or an ad slot — it marks the\n// spot. `rest`-sourced components are candidates for real implementations\n// against content data; `runtime` components need a live provider (that\n// conversation belongs to MigrationHandoff offers); `chrome` components\n// already travel inside the family chrome and get a stub for refactoring\n// toward.\n\nimport type { ComponentDef } from '@contentrain/types'\nimport { pascalCase } from './util.js'\n\nexport function componentFiles(components: ComponentDef[]): Record<string, string> {\n const files: Record<string, string> = {}\n for (const c of components) {\n const variants = (c.variants ?? []).map((v) => v.key)\n files[`src/components/${pascalCase(c.id)}.astro`] = `---\n/**\n * cr-component: type=${c.type} source=${c.source}${c.name ? ` — ${c.name}` : ''}\n * Emitted by @contentrain/emitter-astro as a placeholder.${\n c.source === 'runtime' ? '\\n * Needs a live provider — see the migration handoff offers.' : ''\n }${variants.length ? `\\n * Variants: ${variants.join(', ')}` : ''}\n */\nconst { variant = ${JSON.stringify(variants[0] ?? 'default')} } = Astro.props\n---\n<cr-component data-type=${JSON.stringify(c.type)} data-variant={variant}></cr-component>\n`\n }\n return files\n}\n","// Legacy CSS quarantine (the fidelity layer).\n//\n// Migrated pages must render pixel-identically, and a utility framework's\n// preflight/reset would fight the theme's CSS. The truce is a cascade layer:\n// every legacy stylesheet is wrapped in `@layer legacy { … }`, and modern CSS\n// lives in its own layer — the two never meet in specificity.\n//\n// One CSS rule constrains the wrapping: `@import` (and `@charset`) must\n// precede all other statements, so a file cannot simply be wrapped whole.\n// Leading imports are hoisted above the wrapper and given `layer(legacy)`;\n// an import *after* other rules is already invalid CSS that browsers ignore,\n// so the file is left unwrapped and a warning recorded instead of silently\n// producing different invalidity.\n\nimport { LEGACY_CSS_LAYER } from '@contentrain/types'\n\nexport interface WrapCssResult {\n content: string\n warning?: string\n}\n\nconst LEADING = /^(\\s*(?:@charset[^;]+;|@import[^;]+;|\\/\\*[\\s\\S]*?\\*\\/)\\s*)+/\n\nexport function wrapLegacyCss(path: string, css: string): WrapCssResult {\n const rest = css.replace(LEADING, '')\n if (/@import\\s/.test(rest)) {\n return {\n content: css,\n warning: `${path}: @import after other rules — left unwrapped (fix the source stylesheet; mid-file imports are ignored by browsers)`,\n }\n }\n const leading = css.slice(0, css.length - rest.length)\n const hoisted = leading.replace(/@import\\s+([^;]+);/g, (full, spec: string) =>\n /\\blayer\\s*\\(/.test(spec) ? full : `@import ${spec.trim()} layer(${LEGACY_CSS_LAYER});`,\n )\n return { content: `${hoisted}@layer ${LEGACY_CSS_LAYER} {\\n${rest}\\n}\\n` }\n}\n","// @contentrain/emitter-astro — the open half of the migration pipeline.\n//\n// A ProjectIR (route model, layout families, component variants, query\n// bindings, design tokens) plus prepared content goes in; a complete Astro\n// project comes out as a pure file map. The analysis that *produces* a good\n// ProjectIR is hard and lives elsewhere; rendering one is deliberately\n// boring — which is exactly why it can be open, portable, and replaceable\n// by community emitters for other frameworks.\n\nimport type { EmitInput, EmitResult } from './types.js'\nimport { scaffoldFiles } from './scaffold.js'\nimport { familyFiles } from './layouts.js'\nimport { routeFiles } from './pages.js'\nimport { componentFiles } from './components.js'\nimport { wrapLegacyCss } from './css.js'\n\nexport function emitAstroProject(input: EmitInput): EmitResult {\n const { ir } = input\n const warnings: string[] = []\n const files: Record<string, string> = {}\n const add = (batch: Record<string, string>) => {\n for (const [path, content] of Object.entries(batch)) {\n if (files[path] !== undefined && files[path] !== content) {\n warnings.push(`duplicate file with different content: ${path} — keeping the first`)\n continue\n }\n files[path] = content\n }\n }\n\n add(scaffoldFiles(ir, input.options ?? {}))\n\n const familiesById = new Map(ir.families.map((f) => [f.id, f]))\n for (const family of ir.families) add(familyFiles(family))\n\n for (const css of input.css ?? []) {\n const wrapped = wrapLegacyCss(css.path, css.content)\n if (wrapped.warning) warnings.push(wrapped.warning)\n const base = css.path.split('/').pop() ?? css.path\n add({ [`public/styles/legacy/${base}`]: wrapped.content })\n }\n for (const family of ir.families) {\n for (const ref of family.css.files ?? []) {\n const base = ref.split('/').pop()\n if (!(input.css ?? []).some((c) => (c.path.split('/').pop() ?? c.path) === base)) {\n warnings.push(`family ${family.id}: css file \"${ref}\" not provided in input.css`)\n }\n }\n }\n\n for (const route of ir.routes) {\n const result = routeFiles(route, familiesById.get(route.family), input.content ?? {})\n add(result.files)\n warnings.push(...result.warnings)\n }\n\n add(componentFiles(ir.components ?? []))\n\n return { files, warnings }\n}\n\n/** Write an EmitResult to disk. Separate from emit so the core stays pure. */\nexport async function writeEmit(result: EmitResult, dir: string): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises')\n const { dirname, join } = await import('node:path')\n const dirs = new Set(Object.keys(result.files).map((p) => dirname(join(dir, p))))\n await Promise.all([...dirs].map((d) => mkdir(d, { recursive: true })))\n await Promise.all(\n Object.entries(result.files).map(([path, content]) => writeFile(join(dir, path), content, 'utf8')),\n )\n}\n\nexport type {\n EmitInput,\n EmitResult,\n EmitContent,\n EmitPost,\n QueryPage,\n EmitCssFile,\n EmitOptions,\n} from './types.js'\nexport { wrapLegacyCss } from './css.js'\nexport { pascalCase, patternToPagePath, stableJson } from './util.js'\n"],"mappings":";;;AACA,SAAgB,WAAW,IAAoB;AAC7C,QAAO,GACJ,MAAM,gBAAgB,CACtB,OAAO,QAAQ,CACf,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;;;;;;;AAQb,SAAgB,kBAAkB,SAAgC;AAChE,KAAI,YAAY,OAAO,YAAY,GAAI,QAAO;AAC9C,KAAI,QAAQ,SAAS,IAAI,CAAE,QAAO;CAElC,MAAM,SADW,QAAQ,QAAQ,cAAc,GAAG,CAAC,MAAM,IAAI,CACrC,KAAK,QAAS,IAAI,WAAW,IAAI,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,KAAK,IAAK;AACvF,KAAI,OAAO,MAAM,QAAQ,CAAC,uBAAuB,KAAK,IAAI,CAAC,CAAE,QAAO;CACpE,MAAM,OAAO,OAAO,KAAK;AACzB,QAAO,CAAC,GAAG,QAAQ,GAAG,KAAK,QAAQ,CAAC,KAAK,IAAI;;AAG/C,MAAM,YAAY,MAAwB;AACxC,KAAI,MAAM,QAAQ,EAAE,CAAE,QAAO,EAAE,IAAI,SAAS;AAC5C,KAAI,KAAK,OAAO,MAAM,SACpB,QAAO,OAAO,YACZ,OAAO,QAAQ,EAA6B,CACzC,UAAU,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,EAAG,CACpD,KAAK,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,IAAI,CAAC,CAAC,CACzC;AAEH,QAAO;;;AAIT,SAAgB,WAAW,OAAwB;AACjD,QAAO,GAAG,KAAK,UAAU,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC;;;;AC/BrD,SAAgB,cAAc,IAAe,SAA8C;CACzF,MAAM,WAAW,QAAQ,aAAa;CACtC,MAAM,QAAQ,GAAG,sBAAsB;CACvC,MAAM,QAAgC,EAAE;AAkBxC,OAAM,kBAAkB,WAhBa;EACnC,MAAM,QAAQ,eAAe;EAC7B,SAAS;EACT,MAAM;EACN,SAAS;GACP,KAAK;GACL,OAAO;GACP,SAAS;GAET,GAAI,QAAQ;IAAE,iBAAiB;IAAsD,gBAAgB;IAAoD,GAAG,EAAE;GAC/J;EACD,cAAc;GACZ,OAAO;GACP,GAAI,WAAW;IAAE,aAAa;IAAU,qBAAqB;IAAU,GAAG,EAAE;GAC7E;EACF,CACsC;AAEvC,OAAM,sBAAsB;EAC1B;EACA,GAAI,WAAW,CAAC,8CAA8C,GAAG,EAAE;EACnE;EACA;EACA;EACA,GAAI,WAAW,CAAC,wCAAwC,GAAG,EAAE;EAC7D;EACA;EACD,CAAC,KAAK,KAAK;AAEZ,KAAI,SAAU,OAAM,2BAA2B,UAAU,GAAG,OAAO;AAEnE,OAAM,qBAAqB;AAC3B,QAAO;;;AAIT,SAAS,UAAU,QAA0C;CAC3D,MAAM,QAAkB,CAAC,0BAA0B,GAAG;CACtD,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAQ,QAAgB,QAA4C;AACxE,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE,CAAC,CAAE,OAAM,KAAK,OAAO,OAAO,GAAG,IAAI,IAAI,MAAM,GAAG;;AAErG,MAAK,SAAS,QAAQ,OAAO;AAC7B,MAAK,QAAQ,QAAQ,cAAc;AACnC,MAAK,QAAQ,QAAQ,WAAW;AAChC,MAAK,WAAW,QAAQ,QAAQ;AAChC,MAAK,cAAc,QAAQ,YAAY;AACvC,KAAI,MAAM,OAAQ,OAAM,KAAK,YAAY,GAAG,OAAO,IAAI;AACvD,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;AAG7B,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpDhB,SAAgB,YAAY,QAA8C;CACxE,MAAM,OAAO,WAAW,OAAO,GAAG;CAClC,MAAM,SAAS,OAAO,UAAU,EAAE;CAClC,MAAM,UAAU,aACd,OAAO,QAAQ,MAAM,EAAE,aAAa,SAAS,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;CAE7E,MAAM,QAAgC,EAAE;AACxC,OAAM,mBAAmB,OAAO,GAAG,UAAU,WAAW;EACtD,MAAM,OAAO,OAAO;EACpB,QAAQ,OAAO,cAAc;EAC7B,OAAO,OAAO,aAAa;EAC5B,CAAC;CAEF,MAAM,YAAY,OAAO,IAAI,SAAS,EAAE,EACrC,KAAK,MAAM,+CAA+C,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CACnF,KAAK,KAAK;AAEb,OAAM,eAAe,KAAK,WAAW;aAC1B,OAAO,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG;qCAC3B,OAAO,GAAG;;;;;;;;;;;;EAY7C,SACC,MAAM,KAAK,CACX,OAAO,QAAQ,CACf,KAAK,MAAM,OAAO,IAAI,CACtB,KAAK,KAAK,CAAC;;;;;;;;;;;AAWZ,QAAO;;;;ACpCT,SAAgB,WACd,OACA,QACA,SACe;CACf,MAAM,WAAqB,EAAE;CAC7B,MAAM,QAAgC,EAAE;CACxC,MAAM,WAAW,kBAAkB,MAAM,QAAQ;AACjD,KAAI,CAAC,SACH,QAAO;EAAE;EAAO,UAAU,CAAC,SAAS,MAAM,GAAG,aAAa,MAAM,QAAQ,kDAAkD;EAAE;AAE9H,KAAI,CAAC,OACH,QAAO;EAAE;EAAO,UAAU,CAAC,SAAS,MAAM,GAAG,YAAY,MAAM,OAAO,uBAAuB;EAAE;CAEjG,MAAM,SAAS,WAAW,OAAO,GAAG;CACpC,MAAM,QAAQ,SAAS,MAAM,IAAI,CAAC,SAAS;CAC3C,MAAM,KAAK,MAAM,OAAO,QAAQ,EAAE,IAAI;CACtC,MAAM,YAAY,SAAS,SAAS,IAAI;AAExC,KAAI,MAAM,SAAS,UAAU;EAC3B,MAAM,QAAQ,QAAQ,SAAS,EAAE;AACjC,MAAI,CAAC,MAAM,OAAQ,UAAS,KAAK,SAAS,MAAM,GAAG,8DAA8D;AACjH,QAAM,yBAAyB,WAAW,MAAM;AAChD,QAAM,aAAa,cAAc,WAAW,OAAO,QAAQ,GAAG;AAC9D,SAAO;GAAE;GAAO;GAAU;;CAG5B,MAAM,aAA0B,MAAM,QAAS,QAAQ,UAAU,MAAM,UAAU,EAAE,GAAI,EAAE;AACzF,KAAI,MAAM,OAAO;AACf,MAAI,CAAC,WAAW,OAAQ,UAAS,KAAK,SAAS,MAAM,GAAG,WAAW,MAAM,MAAM,+CAA+C;AAC9H,MAAI,WAAW,MAAM,MAAM,CAAC,EAAE,cAAc,CAC1C,UAAS,KAAK,SAAS,MAAM,GAAG,qFAAqF;AAEvH,QAAM,oBAAoB,MAAM,MAAM,UAAU,WAAW,WAAW;AACtE,QAAM,aAAa,cAAc,SAAS,OAAO,QAAQ,IAAI,UAAU;AACvE,SAAO;GAAE;GAAO;GAAU;;AAG5B,OAAM,aAAa,cAAc,WAAW,OAAO,QAAQ,IAAI,WAAW,SAAS;AACnF,QAAO;EAAE;EAAO;EAAU;;AAG5B,SAAS,WAAW,OAAmB,QAAgB,IAAoB;AACzE,QAAO;YACG,MAAM,GAAG,IAAI,MAAM,QAAQ;sBACjB,GAAG,UAAU,OAAO;qBACrB,GAAG;6BACK,GAAG;;;;;;;;;;;;AAahC,SAAS,SAAS,OAAmB,QAAgB,IAAY,WAA4B;CAC3F,MAAM,QAAQ,YACV;;;gCAIA;AACJ,QAAO;YACG,MAAM,GAAG,IAAI,MAAM,QAAQ;sBACjB,GAAG,UAAU,OAAO;qBACrB,GAAG,eAAe,MAAM,MAAM;wCACX,GAAG;;EAEzC,MAAM;;;;;;;;;;;;;;;;;;;AAoBR,SAAS,WAAW,OAAmB,QAAgB,IAAY,WAAoB,UAA4B;AACjH,KAAI,UAAW,UAAS,KAAK,SAAS,MAAM,GAAG,kEAAkE;CACjH,MAAM,QAAQ,YACV;;;IAIA;AACJ,QAAO;YACG,MAAM,GAAG,IAAI,MAAM,QAAQ;sBACjB,GAAG,UAAU,OAAO;EACxC,MAAM;;;;;;ACjHR,SAAgB,eAAe,YAAoD;CACjF,MAAM,QAAgC,EAAE;AACxC,MAAK,MAAM,KAAK,YAAY;EAC1B,MAAM,YAAY,EAAE,YAAY,EAAE,EAAE,KAAK,MAAM,EAAE,IAAI;AACrD,QAAM,kBAAkB,WAAW,EAAE,GAAG,CAAC,WAAW;;wBAEhC,EAAE,KAAK,UAAU,EAAE,SAAS,EAAE,OAAO,MAAM,EAAE,SAAS,GAAG;4DAE9E,EAAE,WAAW,YAAY,mEAAmE,KAC3F,SAAS,SAAS,kBAAkB,SAAS,KAAK,KAAK,KAAK,GAAG;;oBAE/C,KAAK,UAAU,SAAS,MAAM,UAAU,CAAC;;0BAEnC,KAAK,UAAU,EAAE,KAAK,CAAC;;;AAG/C,QAAO;;;;ACPT,MAAM,UAAU;AAEhB,SAAgB,cAAc,MAAc,KAA4B;CACtE,MAAM,OAAO,IAAI,QAAQ,SAAS,GAAG;AACrC,KAAI,YAAY,KAAK,KAAK,CACxB,QAAO;EACL,SAAS;EACT,SAAS,GAAG,KAAK;EAClB;AAMH,QAAO,EAAE,SAAS,GAJF,IAAI,MAAM,GAAG,IAAI,SAAS,KAAK,OAAO,CAC9B,QAAQ,wBAAwB,MAAM,SAC5D,eAAe,KAAK,KAAK,GAAG,OAAO,WAAW,KAAK,MAAM,CAAC,SAAS,iBAAiB,IACrF,CAC4B,SAAS,iBAAiB,MAAM,KAAK,QAAQ;;;;ACnB5E,SAAgB,iBAAiB,OAA8B;CAC7D,MAAM,EAAE,OAAO;CACf,MAAM,WAAqB,EAAE;CAC7B,MAAM,QAAgC,EAAE;CACxC,MAAM,OAAO,UAAkC;AAC7C,OAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,MAAM,EAAE;AACnD,OAAI,MAAM,UAAU,KAAA,KAAa,MAAM,UAAU,SAAS;AACxD,aAAS,KAAK,0CAA0C,KAAK,sBAAsB;AACnF;;AAEF,SAAM,QAAQ;;;AAIlB,KAAI,cAAc,IAAI,MAAM,WAAW,EAAE,CAAC,CAAC;CAE3C,MAAM,eAAe,IAAI,IAAI,GAAG,SAAS,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/D,MAAK,MAAM,UAAU,GAAG,SAAU,KAAI,YAAY,OAAO,CAAC;AAE1D,MAAK,MAAM,OAAO,MAAM,OAAO,EAAE,EAAE;EACjC,MAAM,UAAU,cAAc,IAAI,MAAM,IAAI,QAAQ;AACpD,MAAI,QAAQ,QAAS,UAAS,KAAK,QAAQ,QAAQ;AAEnD,MAAI,GAAG,wBADM,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,IAAI,SACN,QAAQ,SAAS,CAAC;;AAE5D,MAAK,MAAM,UAAU,GAAG,SACtB,MAAK,MAAM,OAAO,OAAO,IAAI,SAAS,EAAE,EAAE;EACxC,MAAM,OAAO,IAAI,MAAM,IAAI,CAAC,KAAK;AACjC,MAAI,EAAE,MAAM,OAAO,EAAE,EAAE,MAAM,OAAO,EAAE,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,UAAU,KAAK,CAC9E,UAAS,KAAK,UAAU,OAAO,GAAG,cAAc,IAAI,6BAA6B;;AAKvF,MAAK,MAAM,SAAS,GAAG,QAAQ;EAC7B,MAAM,SAAS,WAAW,OAAO,aAAa,IAAI,MAAM,OAAO,EAAE,MAAM,WAAW,EAAE,CAAC;AACrF,MAAI,OAAO,MAAM;AACjB,WAAS,KAAK,GAAG,OAAO,SAAS;;AAGnC,KAAI,eAAe,GAAG,cAAc,EAAE,CAAC,CAAC;AAExC,QAAO;EAAE;EAAO;EAAU;;;AAI5B,eAAsB,UAAU,QAAoB,KAA4B;CAC9E,MAAM,EAAE,OAAO,cAAc,MAAM,OAAO;CAC1C,MAAM,EAAE,SAAS,SAAS,MAAM,OAAO;CACvC,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,OAAO,MAAM,CAAC,KAAK,MAAM,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;AACjF,OAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,MAAM,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC,CAAC,CAAC;AACtE,OAAM,QAAQ,IACZ,OAAO,QAAQ,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,aAAa,UAAU,KAAK,KAAK,KAAK,EAAE,SAAS,OAAO,CAAC,CACnG"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/util.ts","../src/scaffold.ts","../src/layouts.ts","../src/pages.ts","../src/components.ts","../src/css.ts","../src/index.ts"],"sourcesContent":["/** \"f-archive\" → \"FArchive\", \"posts\" → \"Posts\" — Astro component file names. */\nexport function pascalCase(id: string): string {\n return id\n .split(/[^a-zA-Z0-9]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\n/**\n * Route pattern → Astro pages path.\n * `/` → `index.astro`; `/category/:term/page/:page` → `category/[term]/page/[page].astro`.\n * Returns null for patterns Astro's file routing cannot express (recorded as a warning upstream).\n */\nexport function patternToPagePath(pattern: string): string | null {\n if (pattern === '/' || pattern === '') return 'index.astro'\n if (pattern.includes('*')) return null\n const segments = pattern.replace(/^\\/+|\\/+$/g, '').split('/')\n const mapped = segments.map((seg) => (seg.startsWith(':') ? `[${seg.slice(1)}]` : seg))\n if (mapped.some((seg) => !/^[[\\]a-zA-Z0-9._-]+$/.test(seg))) return null\n const last = mapped.pop()\n return [...mapped, `${last}.astro`].join('/')\n}\n\nconst sortDeep = (v: unknown): unknown => {\n if (Array.isArray(v)) return v.map(sortDeep)\n if (v && typeof v === 'object') {\n return Object.fromEntries(\n Object.entries(v as Record<string, unknown>)\n .toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, val]) => [k, sortDeep(val)]),\n )\n }\n return v\n}\n\n/** JSON with stable key order and trailing newline — deterministic output, stable diffs. */\nexport function stableJson(value: unknown): string {\n return `${JSON.stringify(sortDeep(value), null, 2)}\\n`\n}\n","// Project scaffolding: manifest, config, styles, and the shared fill helper\n// the generated pages use to pour content into `@@mark@@` placeholders.\n\nimport type { DesignTokens, ProjectIR } from '@contentrain/types'\nimport type { EmitOptions } from './types.js'\nimport { stableJson } from './util.js'\n\nexport function scaffoldFiles(ir: ProjectIR, options: EmitOptions): Record<string, string> {\n const tailwind = options.tailwind !== false\n const split = ir.viewport_strategy === 'split'\n const files: Record<string, string> = {}\n\n const pkg: Record<string, unknown> = {\n name: options.projectName ?? 'migrated-site',\n private: true,\n type: 'module',\n scripts: {\n dev: 'astro dev',\n build: 'astro build',\n preview: 'astro preview',\n // Split viewport production (skeleton): one build per device class, served by device.\n ...(split ? { 'build:desktop': 'VIEWPORT=desktop astro build --outDir dist/desktop', 'build:mobile': 'VIEWPORT=mobile astro build --outDir dist/mobile' } : {}),\n },\n dependencies: {\n astro: '^5.0.0',\n ...(tailwind ? { tailwindcss: '^4.0.0', '@tailwindcss/vite': '^4.0.0' } : {}),\n },\n }\n files['package.json'] = stableJson(pkg)\n\n files['astro.config.mjs'] = [\n `import { defineConfig } from 'astro/config'`,\n ...(tailwind ? [`import tailwindcss from '@tailwindcss/vite'`] : []),\n ``,\n `export default defineConfig({`,\n // Canonical URLs and sitemaps hang off \\`site\\` — for a migration, SEO\n // continuity is the point, so the source site's URL always lands here.\n ` site: ${JSON.stringify(ir.site.url)},`,\n ` build: { format: 'directory' },`,\n ...(tailwind ? [` vite: { plugins: [tailwindcss()] },`] : []),\n `})`,\n ``,\n ].join('\\n')\n\n // Astro wants a tsconfig in every project — editors and the compiler read\n // it even in JS-only projects, and the emitted src/lib/fill.ts is TS.\n files['tsconfig.json'] = `${JSON.stringify({ extends: 'astro/tsconfigs/base', include: ['.astro/types.d.ts', '**/*'], exclude: ['dist'] }, null, 2)}\\n`\n\n if (tailwind) files['src/styles/modern.css'] = modernCss(ir.tokens)\n\n files['src/lib/fill.ts'] = FILL_TS\n return files\n}\n\n/** Tailwind 4 evolution layer: CSS-first config with the site's extracted tokens. */\nfunction modernCss(tokens: DesignTokens | undefined): string {\n const lines: string[] = [`@import 'tailwindcss';`, ``]\n const theme: string[] = []\n const push = (prefix: string, map: Record<string, string> | undefined) => {\n for (const [key, value] of Object.entries(map ?? {})) theme.push(` --${prefix}-${key}: ${value};`)\n }\n push('color', tokens?.colors)\n push('font', tokens?.font_families)\n push('text', tokens?.font_sizes)\n push('spacing', tokens?.spacing)\n push('breakpoint', tokens?.breakpoints)\n if (theme.length) lines.push(`@theme {`, ...theme, `}`)\n return `${lines.join('\\n')}\\n`\n}\n\nconst FILL_TS = `// Emitted by @contentrain/emitter-astro — shared mark-filling helpers.\n// Chrome and item templates carry @@mark@@ placeholders; pages fill them from content.\n\n/** Where page content splices into the body chrome — must match @contentrain/types CHROME_BODY_SLOT. */\nexport const BODY_SLOT = '<!--@@body@@-->'\n\nexport const esc = (value: unknown): string =>\n String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n\nexport function fillMarks(html: string, marks: Record<string, unknown>): string {\n return html.replace(/@@([a-z0-9_]+)@@/gi, (_all, key: string) => esc(marks[key] ?? ''))\n}\n\nexport interface MarkablePost {\n slug: string\n title: string\n dates?: string[]\n author?: string\n author_first?: string\n author_last?: string\n terms?: string[]\n featured?: string[]\n excerpt?: string\n}\n\n/** The mark vocabulary the measurement chain proved out: title, author (+ first/last), date{n}, term{n}, feat{n}, excerpt, slug. */\nexport function postMarks(post: MarkablePost): Record<string, string> {\n const marks: Record<string, string> = {\n title: post.title,\n author: post.author ?? '',\n author_first: post.author_first ?? '',\n author_last: post.author_last ?? '',\n excerpt: post.excerpt ?? '',\n slug: post.slug,\n feat: post.featured?.[0] ?? '',\n }\n for (const [i, d] of (post.dates ?? []).entries()) marks[\\`date\\${i}\\`] = d\n for (const [i, t] of (post.terms ?? []).entries()) marks[\\`term\\${i}\\`] = t\n for (const [i, f] of (post.featured ?? []).entries()) marks[\\`feat\\${i}\\`] = f\n return marks\n}\n`\n","// Layout families → Astro layouts.\n//\n// Chrome travels as DATA, not as Astro template source: the compiler must\n// never parse browser-tolerated theme markup. And the body chrome is injected\n// as ONE fragment: real themes nest the content container deep inside the\n// chrome, so splitting it into before/after halves produces unbalanced\n// fragments that the parser silently \"repairs\" (measured cost: 36 vs 100).\n// Content splices in at CHROME_BODY_SLOT before the single injection.\n\nimport type { LayoutFamily } from '@contentrain/types'\nimport { CHROME_BODY_SLOT } from '@contentrain/types'\nimport { pascalCase, stableJson } from './util.js'\n\nexport interface FamilyGenResult {\n files: Record<string, string>\n warnings: string[]\n}\n\nexport function familyFiles(family: LayoutFamily, lang: string): FamilyGenResult {\n const name = pascalCase(family.id)\n const chunks = family.chrome ?? []\n const warnings: string[] = []\n const joined = (position: string) =>\n chunks.filter((c) => c.position === position).map((c) => c.html).join('\\n')\n\n const bodyChunks = chunks.filter((c) => c.position === 'body')\n let body: string\n if (bodyChunks.length) {\n body = bodyChunks.map((c) => c.html).join('\\n')\n if (!body.includes(CHROME_BODY_SLOT)) {\n warnings.push(`family ${family.id}: body chrome has no ${CHROME_BODY_SLOT} marker — content appended at the end`)\n body += CHROME_BODY_SLOT\n }\n } else {\n // Legacy pair: compose into one string with the slot between the halves.\n body = `${joined('before_body')}${CHROME_BODY_SLOT}${joined('after_body')}`\n }\n\n const files: Record<string, string> = {}\n files[`src/data/chrome/${family.id}.json`] = stableJson({\n head: joined('head'),\n body,\n })\n\n const cssLinks = (family.css.files ?? [])\n .map((f) => `<link rel=\"stylesheet\" href=\"/styles/legacy/${f.split('/').pop()}\" />`)\n .join('\\n')\n\n files[`src/layouts/${name}.astro`] = `---\n// Family: ${family.id}${family.name ? ` (${family.name})` : ''} — emitted by @contentrain/emitter-astro\nimport chrome from '../data/chrome/${family.id}.json'\nimport { fillMarks, BODY_SLOT } from '../lib/fill'\n\ninterface Props {\n title?: string\n marks?: Record<string, string>\n /** Page content as an HTML string; slot children are used when absent. */\n body?: string\n}\nconst { title = '', marks = {}, body } = Astro.props\nconst head = fillMarks(chrome.head, marks)\nconst content = body ?? (Astro.slots.has('default') ? await Astro.slots.render('default') : '')\n// Marks fill the CHROME only; content splices in untouched, then ONE injection.\nconst html = fillMarks(chrome.body, marks).split(BODY_SLOT).join(content)\n---\n<!doctype html>\n<html lang=${JSON.stringify(lang)}>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n${cssLinks\n .split('\\n')\n .filter(Boolean)\n .map((l) => ` ${l}`)\n .join('\\n')}\n <Fragment set:html={head} />\n <title>{title}</title>\n </head>\n <body>\n <Fragment set:html={html} />\n </body>\n</html>\n`\n return { files, warnings }\n}\n","// Routes → Astro pages.\n//\n// Three shapes, matching what a migrated site actually needs:\n// - `single`: one page per post, body injected with `set:html` inside the family layout.\n// - list kinds (`archive`/`term`/`author`/`date`/`front` with a query): static\n// paths come from the producer's query data; items render through the\n// extracted item template when one exists.\n// - static kinds (no query): the family chrome IS the page.\n//\n// Pagination is a route parameter (`/news/page/:page`), never a separate\n// family — the generated paths simply include the page number in params.\n\nimport type { RouteModel, LayoutFamily } from '@contentrain/types'\nimport type { EmitContent, QueryPage } from './types.js'\nimport { pascalCase, patternToPagePath, stableJson } from './util.js'\n\nexport interface PageGenResult {\n files: Record<string, string>\n warnings: string[]\n}\n\nexport function routeFiles(\n route: RouteModel,\n family: LayoutFamily | undefined,\n content: EmitContent,\n): PageGenResult {\n const warnings: string[] = []\n const files: Record<string, string> = {}\n const pagePath = patternToPagePath(route.pattern)\n if (!pagePath) {\n return { files, warnings: [`route ${route.id}: pattern \"${route.pattern}\" cannot be expressed as an Astro page — skipped`] }\n }\n if (!family) {\n return { files, warnings: [`route ${route.id}: family \"${route.family}\" not found — skipped`] }\n }\n const layout = pascalCase(family.id)\n const depth = pagePath.split('/').length - 1\n const up = '../'.repeat(depth + 1) || '../'\n const hasParams = pagePath.includes('[')\n\n if (route.kind === 'single') {\n const posts = content.posts ?? []\n if (!posts.length) warnings.push(`route ${route.id}: single route with no posts — page emitted, data file empty`)\n files['src/data/posts.json'] = stableJson(posts)\n files[`src/pages/${pagePath}`] = singlePage(route, layout, up)\n return { files, warnings }\n }\n\n const queryPages: QueryPage[] = route.query ? (content.queries?.[route.query] ?? []) : []\n if (route.query) {\n if (!queryPages.length) warnings.push(`route ${route.id}: query \"${route.query}\" has no data — page emitted, data file empty`)\n if (queryPages.some((p) => !p.item_template)) {\n warnings.push(`route ${route.id}: item template missing on some pages — plain fallback list rendered (not fidelity)`)\n }\n files[`src/data/queries/${route.query}.json`] = stableJson(queryPages)\n files[`src/pages/${pagePath}`] = listPage(route, layout, up, hasParams)\n return { files, warnings }\n }\n\n files[`src/pages/${pagePath}`] = staticPage(route, layout, up, hasParams, warnings)\n return { files, warnings }\n}\n\nfunction singlePage(route: RouteModel, layout: string, up: string): string {\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\nimport posts from '${up}data/posts.json'\nimport { postMarks } from '${up}lib/fill'\n\nexport function getStaticPaths() {\n return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }))\n}\ntype Props = { post: (typeof posts)[number] }\nconst { post } = Astro.props as Props\n---\n<Layout title={post.title} marks={postMarks(post)} body={post.body} />\n`\n}\n\nfunction listPage(route: RouteModel, layout: string, up: string, hasParams: boolean): string {\n const paths = hasParams\n ? `export function getStaticPaths() {\n return pages.map((page) => ({ params: page.params, props: { page } }))\n}\ntype Props = { page: (typeof pages)[number] }\nconst { page } = Astro.props as Props`\n : `const page = pages[0] ?? { params: {}, items: [] }`\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\nimport pages from '${up}data/queries/${route.query}.json'\nimport { esc, fillMarks, postMarks } from '${up}lib/fill'\n\n${paths}\nconst items = page.items.map((item) =>\n page.item_template ? fillMarks(page.item_template, postMarks(item)) : undefined,\n)\nconst fallback = items.some((h) => h === undefined)\nconst content = fallback\n ? '<ul class=\"cr-post-list\">' +\n page.items.map((item) => '<li><a href=\"/' + item.slug + '/\">' + esc(item.title) + '</a></li>').join('') +\n '</ul>'\n : items.join('')\n---\n<Layout title={page.params.term ?? ''} marks={page.params} body={content} />\n`\n}\n\nfunction staticPage(route: RouteModel, layout: string, up: string, hasParams: boolean, warnings: string[]): string {\n if (hasParams) warnings.push(`route ${route.id}: parameterized route without a query — emitted with empty paths`)\n const paths = hasParams\n ? `export function getStaticPaths() {\n return []\n}\n`\n : ''\n return `---\n// Route: ${route.id} (${route.pattern}) — emitted by @contentrain/emitter-astro\nimport Layout from '${up}layouts/${layout}.astro'\n${paths}---\n<Layout title=\"\" marks={{}} />\n`\n}\n","// Component placeholders.\n//\n// The emitter cannot invent a comments form or an ad slot — it marks the\n// spot. `rest`-sourced components are candidates for real implementations\n// against content data; `runtime` components need a live provider (that\n// conversation belongs to MigrationHandoff offers); `chrome` components\n// already travel inside the family chrome and get a stub for refactoring\n// toward.\n\nimport type { ComponentDef } from '@contentrain/types'\nimport { pascalCase } from './util.js'\n\nexport function componentFiles(components: ComponentDef[]): Record<string, string> {\n const files: Record<string, string> = {}\n for (const c of components) {\n const variants = (c.variants ?? []).map((v) => v.key)\n files[`src/components/${pascalCase(c.id)}.astro`] = `---\n/**\n * cr-component: type=${c.type} source=${c.source}${c.name ? ` — ${c.name}` : ''}\n * Emitted by @contentrain/emitter-astro as a placeholder.${\n c.source === 'runtime' ? '\\n * Needs a live provider — see the migration handoff offers.' : ''\n }${variants.length ? `\\n * Variants: ${variants.join(', ')}` : ''}\n */\ninterface Props {\n variant?: string\n}\nconst { variant = ${JSON.stringify(variants[0] ?? 'default')} } = Astro.props\n---\n<cr-component data-type=${JSON.stringify(c.type)} data-variant={variant}></cr-component>\n`\n }\n return files\n}\n","// Legacy CSS quarantine (the fidelity layer).\n//\n// Migrated pages must render pixel-identically, and a utility framework's\n// preflight/reset would fight the theme's CSS. The truce is a cascade layer:\n// every legacy stylesheet is wrapped in `@layer legacy { … }`, and modern CSS\n// lives in its own layer — the two never meet in specificity.\n//\n// One CSS rule constrains the wrapping: `@import` (and `@charset`) must\n// precede all other statements, so a file cannot simply be wrapped whole.\n// Leading imports are hoisted above the wrapper and given `layer(legacy)`;\n// an import *after* other rules is already invalid CSS that browsers ignore,\n// so the file is left unwrapped and a warning recorded instead of silently\n// producing different invalidity.\n\nimport { LEGACY_CSS_LAYER } from '@contentrain/types'\n\nexport interface WrapCssResult {\n content: string\n warning?: string\n}\n\nconst LEADING = /^(\\s*(?:@charset[^;]+;|@import[^;]+;|\\/\\*[\\s\\S]*?\\*\\/)\\s*)+/\n\nexport function wrapLegacyCss(path: string, css: string): WrapCssResult {\n const rest = css.replace(LEADING, '')\n if (/@import\\s/.test(rest)) {\n return {\n content: css,\n warning: `${path}: @import after other rules — left unwrapped (fix the source stylesheet; mid-file imports are ignored by browsers)`,\n }\n }\n const leading = css.slice(0, css.length - rest.length)\n const hoisted = leading.replace(/@import\\s+([^;]+);/g, (full, spec: string) =>\n /\\blayer\\s*\\(/.test(spec) ? full : `@import ${spec.trim()} layer(${LEGACY_CSS_LAYER});`,\n )\n return { content: `${hoisted}@layer ${LEGACY_CSS_LAYER} {\\n${rest}\\n}\\n` }\n}\n","// @contentrain/emitter-astro — the open half of the migration pipeline.\n//\n// A ProjectIR (route model, layout families, component variants, query\n// bindings, design tokens) plus prepared content goes in; a complete Astro\n// project comes out as a pure file map. The analysis that *produces* a good\n// ProjectIR is hard and lives elsewhere; rendering one is deliberately\n// boring — which is exactly why it can be open, portable, and replaceable\n// by community emitters for other frameworks.\n\nimport type { EmitInput, EmitResult } from './types.js'\nimport { scaffoldFiles } from './scaffold.js'\nimport { familyFiles } from './layouts.js'\nimport { routeFiles } from './pages.js'\nimport { componentFiles } from './components.js'\nimport { wrapLegacyCss } from './css.js'\n\nexport function emitAstroProject(input: EmitInput): EmitResult {\n const { ir } = input\n const warnings: string[] = []\n const files: Record<string, string> = {}\n const add = (batch: Record<string, string>) => {\n for (const [path, content] of Object.entries(batch)) {\n if (files[path] !== undefined && files[path] !== content) {\n warnings.push(`duplicate file with different content: ${path} — keeping the first`)\n continue\n }\n files[path] = content\n }\n }\n\n add(scaffoldFiles(ir, input.options ?? {}))\n\n const familiesById = new Map(ir.families.map((f) => [f.id, f]))\n const lang = ir.site.locales?.[0] ?? 'en'\n for (const family of ir.families) {\n const fam = familyFiles(family, lang)\n add(fam.files)\n warnings.push(...fam.warnings)\n }\n\n for (const css of input.css ?? []) {\n const wrapped = wrapLegacyCss(css.path, css.content)\n if (wrapped.warning) warnings.push(wrapped.warning)\n const base = css.path.split('/').pop() ?? css.path\n add({ [`public/styles/legacy/${base}`]: wrapped.content })\n }\n for (const family of ir.families) {\n for (const ref of family.css.files ?? []) {\n const base = ref.split('/').pop()\n if (!(input.css ?? []).some((c) => (c.path.split('/').pop() ?? c.path) === base)) {\n warnings.push(`family ${family.id}: css file \"${ref}\" not provided in input.css`)\n }\n }\n }\n\n for (const route of ir.routes) {\n const result = routeFiles(route, familiesById.get(route.family), input.content ?? {})\n add(result.files)\n warnings.push(...result.warnings)\n }\n\n add(componentFiles(ir.components ?? []))\n\n return { files, warnings }\n}\n\n/** Write an EmitResult to disk. Separate from emit so the core stays pure. */\nexport async function writeEmit(result: EmitResult, dir: string): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises')\n const { dirname, join } = await import('node:path')\n const dirs = new Set(Object.keys(result.files).map((p) => dirname(join(dir, p))))\n await Promise.all([...dirs].map((d) => mkdir(d, { recursive: true })))\n await Promise.all(\n Object.entries(result.files).map(([path, content]) => writeFile(join(dir, path), content, 'utf8')),\n )\n}\n\nexport type {\n EmitInput,\n EmitResult,\n EmitContent,\n EmitPost,\n QueryPage,\n EmitCssFile,\n EmitOptions,\n} from './types.js'\nexport { wrapLegacyCss } from './css.js'\nexport { pascalCase, patternToPagePath, stableJson } from './util.js'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/emitter-astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Open Astro emitter for Contentrain migrations — renders a ProjectIR into a working Astro project",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@contentrain/types": "1.
|
|
40
|
+
"@contentrain/types": "1.5.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.10.0",
|