meno-core 1.0.51 → 1.0.53
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/build-astro.ts +183 -13
- package/build-next.ts +1361 -0
- package/build-static.ts +7 -5
- package/dist/bin/cli.js +2 -2
- package/dist/build-static.js +6 -6
- package/dist/chunks/{chunk-HNLUO36W.js → chunk-GZHGVVW3.js} +2 -2
- package/dist/chunks/chunk-GZHGVVW3.js.map +7 -0
- package/dist/chunks/{chunk-LPVETICS.js → chunk-H3GJ4H2U.js} +185 -1
- package/dist/chunks/chunk-H3GJ4H2U.js.map +7 -0
- package/dist/chunks/{chunk-DM54NPEC.js → chunk-IGYR22T6.js} +89 -271
- package/dist/chunks/chunk-IGYR22T6.js.map +7 -0
- package/dist/chunks/{chunk-3KJ6SJZC.js → chunk-JGP5A3Y5.js} +12 -11
- package/dist/chunks/chunk-JGP5A3Y5.js.map +7 -0
- package/dist/chunks/{chunk-7NIC4I3V.js → chunk-JGWFTO6P.js} +167 -21
- package/dist/chunks/chunk-JGWFTO6P.js.map +7 -0
- package/dist/chunks/{chunk-EDQSMAMP.js → chunk-O3NAGJP4.js} +85 -4
- package/dist/chunks/chunk-O3NAGJP4.js.map +7 -0
- package/dist/chunks/{chunk-H4JSCDNW.js → chunk-QB2LNO4W.js} +24 -1
- package/dist/chunks/chunk-QB2LNO4W.js.map +7 -0
- package/dist/chunks/{chunk-V7CD7V7W.js → chunk-R6XHAFBF.js} +561 -112
- package/dist/chunks/chunk-R6XHAFBF.js.map +7 -0
- package/dist/chunks/{chunk-J23ZX5AP.js → chunk-X754AHS5.js} +277 -1
- package/dist/chunks/chunk-X754AHS5.js.map +7 -0
- package/dist/chunks/{chunk-2QK6U5UK.js → chunk-YBLHKYFF.js} +12 -2
- package/dist/chunks/chunk-YBLHKYFF.js.map +7 -0
- package/dist/chunks/{constants-GWBAD66U.js → constants-STK2YBIW.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +354 -59
- package/dist/lib/client/index.js.map +4 -4
- package/dist/lib/server/index.js +1458 -190
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +202 -34
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/test-utils/index.js +1 -1
- package/entries/client-router.tsx +5 -165
- package/lib/client/ErrorBoundary.test.tsx +27 -25
- package/lib/client/ErrorBoundary.tsx +34 -19
- package/lib/client/core/ComponentBuilder.ts +19 -2
- package/lib/client/core/builders/embedBuilder.ts +8 -4
- package/lib/client/core/builders/listBuilder.ts +23 -4
- package/lib/client/fontFamiliesService.test.ts +76 -0
- package/lib/client/fontFamiliesService.ts +69 -0
- package/lib/client/hmrCssReload.ts +160 -0
- package/lib/client/hooks/useColorVariables.ts +2 -0
- package/lib/client/index.ts +4 -0
- package/lib/client/meno-filter/ui.ts +2 -0
- package/lib/client/routing/RouteLoader.test.ts +2 -2
- package/lib/client/routing/RouteLoader.ts +8 -2
- package/lib/client/routing/Router.tsx +81 -15
- package/lib/client/scripts/ScriptExecutor.test.ts +143 -0
- package/lib/client/scripts/ScriptExecutor.ts +56 -2
- package/lib/client/styles/StyleInjector.ts +20 -5
- package/lib/client/styles/UtilityClassCollector.ts +7 -1
- package/lib/client/styles/cspNonce.test.ts +67 -0
- package/lib/client/styles/cspNonce.ts +63 -0
- package/lib/client/templateEngine.test.ts +80 -0
- package/lib/client/templateEngine.ts +5 -0
- package/lib/server/astro/cmsPageEmitter.ts +35 -5
- package/lib/server/astro/componentEmitter.ts +61 -5
- package/lib/server/astro/nodeToAstro.ts +149 -11
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +264 -0
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +184 -0
- package/lib/server/createServer.ts +11 -0
- package/lib/server/draftPageStore.ts +49 -0
- package/lib/server/fileWatcher.ts +62 -2
- package/lib/server/index.ts +13 -1
- package/lib/server/providers/fileSystemPageProvider.ts +8 -0
- package/lib/server/routes/api/components.ts +9 -4
- package/lib/server/routes/api/core-routes.ts +2 -2
- package/lib/server/routes/api/pages.ts +14 -22
- package/lib/server/routes/api/shared.ts +56 -0
- package/lib/server/routes/index.ts +90 -0
- package/lib/server/routes/pages.ts +13 -6
- package/lib/server/services/componentService.test.ts +199 -2
- package/lib/server/services/componentService.ts +354 -49
- package/lib/server/services/fileWatcherService.ts +4 -24
- package/lib/server/services/pageService.test.ts +23 -0
- package/lib/server/services/pageService.ts +124 -6
- package/lib/server/ssr/attributeBuilder.ts +8 -2
- package/lib/server/ssr/buildErrorOverlay.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +21 -2
- package/lib/server/ssr/errorOverlay.ts +38 -11
- package/lib/server/ssr/htmlGenerator.test.ts +53 -13
- package/lib/server/ssr/htmlGenerator.ts +71 -27
- package/lib/server/ssr/liveReloadIntegration.test.ts +123 -2
- package/lib/server/ssr/metaTagGenerator.ts +19 -1
- package/lib/server/ssr/ssrRenderer.test.ts +67 -0
- package/lib/server/ssr/ssrRenderer.ts +94 -9
- package/lib/server/ssrRenderer.test.ts +70 -0
- package/lib/server/websocketManager.ts +0 -1
- package/lib/shared/componentRefs.ts +45 -0
- package/lib/shared/constants.ts +8 -0
- package/lib/shared/cssGeneration.ts +2 -0
- package/lib/shared/cssProperties.ts +184 -0
- package/lib/shared/expressionEvaluator.ts +54 -0
- package/lib/shared/fontCss.ts +101 -0
- package/lib/shared/fontLoader.ts +8 -86
- package/lib/shared/friendlyError.test.ts +87 -0
- package/lib/shared/friendlyError.ts +121 -0
- package/lib/shared/hrefRefs.test.ts +130 -0
- package/lib/shared/hrefRefs.ts +100 -0
- package/lib/shared/index.ts +52 -0
- package/lib/shared/inlineSvgStyleRules.test.ts +108 -0
- package/lib/shared/inlineSvgStyleRules.ts +134 -0
- package/lib/shared/interfaces/contentProvider.ts +13 -0
- package/lib/shared/itemTemplateUtils.test.ts +14 -0
- package/lib/shared/itemTemplateUtils.ts +4 -1
- package/lib/shared/registry/NodeTypeDefinition.ts +1 -1
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +1 -1
- package/lib/shared/slugTranslator.test.ts +24 -0
- package/lib/shared/slugTranslator.ts +24 -0
- package/lib/shared/styleNodeUtils.ts +4 -1
- package/lib/shared/tree/PathBuilder.test.ts +128 -1
- package/lib/shared/tree/PathBuilder.ts +83 -31
- package/lib/shared/types/comment.ts +99 -0
- package/lib/shared/types/index.ts +12 -0
- package/lib/shared/types/rendering.ts +8 -0
- package/lib/shared/utilityClassConfig.ts +4 -2
- package/lib/shared/utilityClassMapper.test.ts +24 -0
- package/lib/shared/validation/commentValidators.ts +69 -0
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/viewportUnits.integration.test.ts +42 -0
- package/lib/shared/viewportUnits.test.ts +103 -0
- package/lib/shared/viewportUnits.ts +63 -0
- package/lib/test-utils/dom-setup.ts +6 -0
- package/package.json +1 -1
- package/dist/chunks/chunk-2QK6U5UK.js.map +0 -7
- package/dist/chunks/chunk-3KJ6SJZC.js.map +0 -7
- package/dist/chunks/chunk-7NIC4I3V.js.map +0 -7
- package/dist/chunks/chunk-DM54NPEC.js.map +0 -7
- package/dist/chunks/chunk-EDQSMAMP.js.map +0 -7
- package/dist/chunks/chunk-H4JSCDNW.js.map +0 -7
- package/dist/chunks/chunk-HNLUO36W.js.map +0 -7
- package/dist/chunks/chunk-J23ZX5AP.js.map +0 -7
- package/dist/chunks/chunk-LPVETICS.js.map +0 -7
- package/dist/chunks/chunk-V7CD7V7W.js.map +0 -7
- /package/dist/chunks/{constants-GWBAD66U.js.map → constants-STK2YBIW.js.map} +0 -0
package/build-astro.ts
CHANGED
|
@@ -23,9 +23,12 @@ import { loadProjectConfig, generateFontCSS, generateFontPreloadTags } from "./l
|
|
|
23
23
|
import { FileSystemCMSProvider } from "./lib/server/providers/fileSystemCMSProvider";
|
|
24
24
|
import { CMSService } from "./lib/server/services/cmsService";
|
|
25
25
|
import { isI18nValue, resolveI18nValue } from "./lib/shared/i18n";
|
|
26
|
+
import { tiptapToHtml } from "./lib/shared/richtext/tiptapToHtml";
|
|
27
|
+
import { isTiptapDocument } from "./lib/shared/richtext/types";
|
|
26
28
|
import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig } from "./lib/shared/types";
|
|
27
29
|
import type { CMSFieldDefinition } from "./lib/shared/types/cms";
|
|
28
30
|
import { isItemDraftForLocale } from "./lib/shared/types";
|
|
31
|
+
import { CMS_DRAFT_SUFFIX } from "./lib/shared/pathSecurity";
|
|
29
32
|
import type { SlugMap } from "./lib/shared/slugTranslator";
|
|
30
33
|
import { renderPageSSR } from "./lib/server/ssr/ssrRenderer";
|
|
31
34
|
import { generateThemeColorVariablesCSS, generateVariablesCSS } from "./lib/server/cssGenerator";
|
|
@@ -38,7 +41,8 @@ import { collectComponentLibraries, filterLibrariesByContext, mergeLibraries, ge
|
|
|
38
41
|
import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
|
|
39
42
|
import { emitAstroComponent } from "./lib/server/astro/componentEmitter";
|
|
40
43
|
import { emitAstroPage } from "./lib/server/astro/pageEmitter";
|
|
41
|
-
import {
|
|
44
|
+
import { normalizeOrphanTemplateProps } from "./lib/server/astro/normalizeOrphanTemplateProps";
|
|
45
|
+
import { emitCMSPage, CMS_SLUG_PLACEHOLDER } from './lib/server/astro/cmsPageEmitter';
|
|
42
46
|
import { collectAllMappingClasses } from "./lib/server/astro/cssCollector";
|
|
43
47
|
import { buildImageMetadataMap, RESPONSIVE_WIDTHS } from "./lib/server/ssr/imageMetadata";
|
|
44
48
|
import { needsFormHandler, formHandlerScript } from "./lib/client/scripts/formHandler";
|
|
@@ -182,7 +186,9 @@ function cmsFieldToZod(field: CMSFieldDefinition): string {
|
|
|
182
186
|
case 'string':
|
|
183
187
|
case 'text':
|
|
184
188
|
case 'rich-text':
|
|
185
|
-
//
|
|
189
|
+
// Rich-text is serialized to an HTML string at item-copy time (see the
|
|
190
|
+
// CMS collection emission below), so by the time Astro validates it the
|
|
191
|
+
// value is a string (or an i18n object of strings). Support both.
|
|
186
192
|
return 'z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()])';
|
|
187
193
|
case 'number':
|
|
188
194
|
return 'z.number()';
|
|
@@ -206,6 +212,99 @@ function cmsFieldToZod(field: CMSFieldDefinition): string {
|
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Serialize a single rich-text field value to an HTML string. Handles plain
|
|
217
|
+
* Tiptap doc objects, the `{ html: "..." }` marker shape, i18n-wrapped values
|
|
218
|
+
* ({ _i18n: true, en: <doc|string>, ... }), and already-serialized strings.
|
|
219
|
+
*/
|
|
220
|
+
function serializeRichTextValue(value: unknown): unknown {
|
|
221
|
+
const one = (v: unknown): unknown => {
|
|
222
|
+
if (v == null || typeof v === 'string') return v ?? '';
|
|
223
|
+
if (isTiptapDocument(v)) return tiptapToHtml(v);
|
|
224
|
+
if (typeof v === 'object' && v !== null && typeof (v as { html?: unknown }).html === 'string') {
|
|
225
|
+
return (v as { html: string }).html;
|
|
226
|
+
}
|
|
227
|
+
return v;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (isI18nValue(value)) {
|
|
231
|
+
const out: Record<string, unknown> = { _i18n: true };
|
|
232
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
233
|
+
if (k === '_i18n') continue;
|
|
234
|
+
out[k] = one(v);
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
return one(value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Walk a component structure and collect the names of every component instance
|
|
243
|
+
* it references (`{ type: 'component', component: 'X' }`).
|
|
244
|
+
*/
|
|
245
|
+
function collectComponentRefs(node: unknown, acc: Set<string>): void {
|
|
246
|
+
if (Array.isArray(node)) {
|
|
247
|
+
for (const child of node) collectComponentRefs(child, acc);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (!node || typeof node !== 'object') return;
|
|
251
|
+
const n = node as Record<string, unknown>;
|
|
252
|
+
if (n.type === 'component' && typeof n.component === 'string') {
|
|
253
|
+
acc.add(n.component);
|
|
254
|
+
}
|
|
255
|
+
for (const value of Object.values(n)) {
|
|
256
|
+
if (value && typeof value === 'object') collectComponentRefs(value, acc);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Compute the set of components that consume the CMS entry (`{{cms.*}}`),
|
|
262
|
+
* transitively including any component that renders a consumer in its own
|
|
263
|
+
* structure (it must forward the `cms` prop down). Astro components have
|
|
264
|
+
* isolated scopes, so each consumer needs the entry threaded in explicitly.
|
|
265
|
+
*/
|
|
266
|
+
function computeCmsConsumerComponents(
|
|
267
|
+
components: Record<string, ComponentDefinition>
|
|
268
|
+
): Set<string> {
|
|
269
|
+
const consumers = new Set<string>();
|
|
270
|
+
const refsByComponent = new Map<string, Set<string>>();
|
|
271
|
+
|
|
272
|
+
for (const [name, def] of Object.entries(components)) {
|
|
273
|
+
const structure = def.component?.structure;
|
|
274
|
+
if (structure && JSON.stringify(structure).includes('{{cms.')) {
|
|
275
|
+
consumers.add(name);
|
|
276
|
+
}
|
|
277
|
+
const refs = new Set<string>();
|
|
278
|
+
collectComponentRefs(structure, refs);
|
|
279
|
+
refsByComponent.set(name, refs);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Fixpoint: a component that renders a consumer is itself a consumer/forwarder.
|
|
283
|
+
let changed = true;
|
|
284
|
+
while (changed) {
|
|
285
|
+
changed = false;
|
|
286
|
+
for (const [name, refs] of refsByComponent) {
|
|
287
|
+
if (consumers.has(name)) continue;
|
|
288
|
+
for (const ref of refs) {
|
|
289
|
+
if (consumers.has(ref)) { consumers.add(name); changed = true; break; }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return consumers;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Build the `_url` expression (a JS template literal over the `e` collection
|
|
298
|
+
* entry) for a CMS collection, so flattened list items expose a usable link.
|
|
299
|
+
* E.g. urlPattern "/blog/{{slug}}" + slugField "slug" → `/blog/${e.data.slug ?? e.id}`.
|
|
300
|
+
*/
|
|
301
|
+
function buildCollectionUrlExpr(schema: CMSSchema): string {
|
|
302
|
+
const slugField = schema.slugField || 'slug';
|
|
303
|
+
const pattern = schema.urlPattern || `/${schema.id}/{{slug}}`;
|
|
304
|
+
const body = pattern.replace(/\{\{[^}]+\}\}/, '${e.data.' + slugField + ' ?? e.id}');
|
|
305
|
+
return '`' + body + '`';
|
|
306
|
+
}
|
|
307
|
+
|
|
209
308
|
// ---------------------------------------------------------------------------
|
|
210
309
|
// Types
|
|
211
310
|
// ---------------------------------------------------------------------------
|
|
@@ -484,7 +583,12 @@ export async function buildAstroProject(
|
|
|
484
583
|
// Compute URL path
|
|
485
584
|
let slug: string;
|
|
486
585
|
if (slugs && slugs[locale]) {
|
|
487
|
-
|
|
586
|
+
// Slugs may be authored with a leading slash (e.g. "/blog" from the
|
|
587
|
+
// page-rename flow). Normalize to the bare form the rest of the
|
|
588
|
+
// pipeline expects — matches buildPageUrlForLocale(). A leading slash
|
|
589
|
+
// here would otherwise inflate fileDepth (".../blog.astro".split('/'))
|
|
590
|
+
// and produce a wrong "../../layouts" import.
|
|
591
|
+
slug = slugs[locale].replace(/^\/+/, '');
|
|
488
592
|
} else if (basePath === '/') {
|
|
489
593
|
slug = '';
|
|
490
594
|
} else {
|
|
@@ -584,6 +688,30 @@ export async function buildAstroProject(
|
|
|
584
688
|
const templateSchemas: CMSSchema[] = [];
|
|
585
689
|
let cmsPageCount = 0;
|
|
586
690
|
|
|
691
|
+
// Pre-pass: components that consume the CMS entry (so the page/component
|
|
692
|
+
// emitters know which instances to thread `cms={...}` into), plus per-
|
|
693
|
+
// collection `_url` expressions (for flattened collection lists). Built
|
|
694
|
+
// before the template loop so the very first emitted CMS page sees the
|
|
695
|
+
// full picture even when it lists items from another collection.
|
|
696
|
+
const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
|
|
697
|
+
const collectionUrlExpr = new Map<string, string>();
|
|
698
|
+
const mergedRichTextFields = new Set<string>();
|
|
699
|
+
if (existsSync(templatesDir)) {
|
|
700
|
+
for (const file of readdirSync(templatesDir).filter((f) => f.endsWith('.json'))) {
|
|
701
|
+
const tc = await loadJSONFile(join(templatesDir, file));
|
|
702
|
+
if (!tc) continue;
|
|
703
|
+
try {
|
|
704
|
+
const pd = parseJSON<JSONPage>(tc);
|
|
705
|
+
const schema = pd.meta?.cms as CMSSchema | undefined;
|
|
706
|
+
if (!schema?.id) continue;
|
|
707
|
+
collectionUrlExpr.set(schema.id, buildCollectionUrlExpr(schema));
|
|
708
|
+
for (const [fn, fd] of Object.entries(schema.fields || {})) {
|
|
709
|
+
if (fd.type === 'rich-text') mergedRichTextFields.add(fn);
|
|
710
|
+
}
|
|
711
|
+
} catch { /* ignore parse errors; handled in main loop */ }
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
587
715
|
if (existsSync(templatesDir)) {
|
|
588
716
|
const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
|
|
589
717
|
|
|
@@ -613,7 +741,7 @@ export async function buildAstroProject(
|
|
|
613
741
|
|
|
614
742
|
// Render SSR once for metadata collection (interactive styles, component CSS, JS)
|
|
615
743
|
const defaultLocale = i18nConfig.defaultLocale;
|
|
616
|
-
const dummyPath = cmsSchema.urlPattern.replace('{{slug}}',
|
|
744
|
+
const dummyPath = cmsSchema.urlPattern.replace('{{slug}}', CMS_SLUG_PLACEHOLDER);
|
|
617
745
|
|
|
618
746
|
const metaResult = await renderPageSSR(
|
|
619
747
|
pageData,
|
|
@@ -699,6 +827,8 @@ export async function buildAstroProject(
|
|
|
699
827
|
imageFormat: configService.getImageFormat(),
|
|
700
828
|
processedRawHtml: metaResult.processedRawHtmlCollector,
|
|
701
829
|
remConfig: remConversionConfig,
|
|
830
|
+
cmsConsumers: cmsConsumerComponents,
|
|
831
|
+
collectionUrlExpr,
|
|
702
832
|
});
|
|
703
833
|
|
|
704
834
|
const astroFileFull = join(pagesOutDir, astroFilePath);
|
|
@@ -815,10 +945,20 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
815
945
|
// ----------------------------------------------------------
|
|
816
946
|
// 7.5. Generate component .astro files
|
|
817
947
|
// ----------------------------------------------------------
|
|
948
|
+
// SSR/Next.js resolve orphan `{{x}}` refs in a component's structure via
|
|
949
|
+
// the parentProps cascade in templateEngine. Astro can't fall back at
|
|
950
|
+
// runtime (each .astro file is its own scope), so we lift orphan refs
|
|
951
|
+
// onto each component's interface and forward them from hosts that
|
|
952
|
+
// declare the same prop. See `normalizeOrphanTemplateProps` for details.
|
|
953
|
+
const emittableComponents = normalizeOrphanTemplateProps(globalComponents);
|
|
818
954
|
let componentFileCount = 0;
|
|
819
|
-
for (const [compName, compDef] of Object.entries(
|
|
955
|
+
for (const [compName, compDef] of Object.entries(emittableComponents)) {
|
|
820
956
|
try {
|
|
821
|
-
const astroContent = emitAstroComponent(compName, compDef,
|
|
957
|
+
const astroContent = emitAstroComponent(compName, compDef, emittableComponents, breakpoints, i18nConfig.defaultLocale, responsiveScales, remConversionConfig, {
|
|
958
|
+
cmsConsumers: cmsConsumerComponents,
|
|
959
|
+
cmsRichTextFields: mergedRichTextFields,
|
|
960
|
+
collectionUrlExpr,
|
|
961
|
+
});
|
|
822
962
|
await writeFile(join(componentsOutDir, `${compName}.astro`), astroContent, 'utf-8');
|
|
823
963
|
componentFileCount++;
|
|
824
964
|
} catch (error: any) {
|
|
@@ -852,7 +992,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
852
992
|
// because each .astro component already has its own inline <script>
|
|
853
993
|
astroContent = emitAstroPage({
|
|
854
994
|
pageData: result.pageData,
|
|
855
|
-
globalComponents,
|
|
995
|
+
globalComponents: emittableComponents,
|
|
856
996
|
title: result.title,
|
|
857
997
|
meta: result.meta,
|
|
858
998
|
locale: result.locale,
|
|
@@ -931,10 +1071,22 @@ export const GET: APIRoute = () => {
|
|
|
931
1071
|
const collectionDir = join(contentDir, schema.id);
|
|
932
1072
|
mkdirSync(collectionDir, { recursive: true });
|
|
933
1073
|
|
|
1074
|
+
// Rich-text fields are stored as Tiptap doc objects; Astro's content
|
|
1075
|
+
// schema (and `<Fragment set:html>`) expects HTML strings. Mirror the
|
|
1076
|
+
// live SSR (cmsSSRProcessor) by serializing them to HTML at copy time.
|
|
1077
|
+
const richTextFieldNames = Object.entries(schema.fields || {})
|
|
1078
|
+
.filter(([, fd]) => fd.type === 'rich-text')
|
|
1079
|
+
.map(([fn]) => fn);
|
|
1080
|
+
|
|
934
1081
|
// Copy CMS item JSON files, resolving i18n values to default locale
|
|
935
1082
|
const cmsItemsDir = join(projectPaths.cms(), schema.id);
|
|
936
1083
|
if (existsSync(cmsItemsDir)) {
|
|
937
|
-
|
|
1084
|
+
// Skip `*.draft.json` siblings in production builds — they mirror the
|
|
1085
|
+
// published item-list filter and may hold partial/invalid WIP data.
|
|
1086
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
1087
|
+
const itemFiles = readdirSync(cmsItemsDir).filter(f =>
|
|
1088
|
+
f.endsWith('.json') && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`))
|
|
1089
|
+
);
|
|
938
1090
|
|
|
939
1091
|
for (const itemFile of itemFiles) {
|
|
940
1092
|
try {
|
|
@@ -944,6 +1096,12 @@ export const GET: APIRoute = () => {
|
|
|
944
1096
|
// Keep i18n values as-is so getStaticPaths() can resolve per-locale
|
|
945
1097
|
const resolved: Record<string, unknown> = { ...item };
|
|
946
1098
|
|
|
1099
|
+
// Serialize rich-text fields (Tiptap doc → HTML string), handling
|
|
1100
|
+
// i18n-wrapped values ({ _i18n: true, en: <doc>, ... }) per-locale.
|
|
1101
|
+
for (const fieldName of richTextFieldNames) {
|
|
1102
|
+
resolved[fieldName] = serializeRichTextValue(resolved[fieldName]);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
947
1105
|
await writeFile(
|
|
948
1106
|
join(collectionDir, itemFile),
|
|
949
1107
|
JSON.stringify(resolved, null, 2),
|
|
@@ -1052,15 +1210,13 @@ export { collections };
|
|
|
1052
1210
|
preview: 'astro preview',
|
|
1053
1211
|
},
|
|
1054
1212
|
dependencies: {
|
|
1055
|
-
|
|
1213
|
+
// Astro 5 (stable Vite), NOT 6 — Astro 6's rolldown-vite breaks
|
|
1214
|
+
// @tailwindcss/vite at build time ("Missing field `tsconfigPaths`").
|
|
1215
|
+
'astro': '^5.0.0',
|
|
1056
1216
|
'@astrojs/sitemap': '^3.0.0',
|
|
1057
1217
|
'@tailwindcss/vite': '^4.0.0',
|
|
1058
1218
|
'tailwindcss': '^4.0.0',
|
|
1059
1219
|
},
|
|
1060
|
-
// Astro 6 expects Vite 7; pin it so npm doesn't pull Vite 8+ and warn.
|
|
1061
|
-
overrides: {
|
|
1062
|
-
'vite': '^7.0.0',
|
|
1063
|
-
},
|
|
1064
1220
|
};
|
|
1065
1221
|
|
|
1066
1222
|
await writeFile(join(outDir, 'package.json'), JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
@@ -1092,6 +1248,20 @@ export default defineConfig({${siteUrl ? `\n site: '${siteUrl}',` : ''}${i18nBl
|
|
|
1092
1248
|
|
|
1093
1249
|
await writeFile(join(outDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2), 'utf-8');
|
|
1094
1250
|
|
|
1251
|
+
// netlify.toml — makes the exported project deployable on Netlify out of the
|
|
1252
|
+
// box. `astro` is a real dependency here (see package.json above), so once
|
|
1253
|
+
// Netlify runs `npm install`, `astro build` is on PATH and writes to dist/.
|
|
1254
|
+
// Harmless on other hosts (Cloudflare Pages / Vercel ignore this file).
|
|
1255
|
+
const netlifyToml = `# Generated by Meno's Astro build.
|
|
1256
|
+
[build]
|
|
1257
|
+
command = "npm run build"
|
|
1258
|
+
publish = "dist"
|
|
1259
|
+
|
|
1260
|
+
[build.environment]
|
|
1261
|
+
NODE_VERSION = "22"
|
|
1262
|
+
`;
|
|
1263
|
+
await writeFile(join(outDir, 'netlify.toml'), netlifyToml, 'utf-8');
|
|
1264
|
+
|
|
1095
1265
|
// src/env.d.ts — resolves astro:assets and other virtual module types in IDE
|
|
1096
1266
|
await writeFile(join(outDir, 'src', 'env.d.ts'), '/// <reference path="../.astro/types.d.ts" />\n', 'utf-8');
|
|
1097
1267
|
|