@lupinum/ginko-content 0.1.1 → 0.1.2
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/README.md +22 -6
- package/compatibility.json +2 -2
- package/dist/cli.mjs +108 -36
- package/dist/core/references/schema.d.ts +1 -0
- package/dist/core/references/schema.js +33 -10
- package/dist/module.d.mts +78 -19
- package/dist/module.json +1 -1
- package/dist/module.mjs +196 -33
- package/dist/public/client.d.ts +4 -2
- package/dist/public/client.js +2 -0
- package/dist/runtime/app/components/ContentRenderer.vue +9 -5
- package/dist/runtime/app/composables/head.d.ts +27 -3
- package/dist/runtime/app/composables/head.js +82 -78
- package/dist/runtime/app/composables/query-api.d.ts +9 -10
- package/dist/runtime/app/composables/search.d.ts +4 -4
- package/dist/runtime/app/composables/search.js +11 -5
- package/dist/runtime/app/composables/use-content.d.ts +35 -15
- package/dist/runtime/app/composables/use-content.js +83 -13
- package/dist/runtime/query/unified.d.ts +1 -0
- package/dist/runtime/query/unified.js +93 -6
- package/dist/runtime/server/api/search.d.ts +2 -1
- package/dist/runtime/server/api/search.js +6 -2
- package/dist/runtime/server/pagefind.js +1 -0
- package/dist/runtime/server/search.d.ts +11 -0
- package/dist/runtime/server/search.js +24 -7
- package/dist/runtime/shared/search.js +3 -2
- package/dist/testing/provider-fixture.js +1 -0
- package/dist/types/config.d.ts +27 -7
- package/dist/types/config.js +26 -2
- package/dist/types/module.d.ts +35 -3
- package/dist/types/query.d.ts +19 -11
- package/dist/types/search.d.ts +3 -1
- package/dist/types.d.mts +2 -2
- package/dist/web-types.json +1 -1
- package/package.json +4 -4
package/dist/module.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { defu } from 'defu';
|
|
|
6
6
|
import { hash } from 'ohash';
|
|
7
7
|
import { resolve, join, relative, isAbsolute } from 'pathe';
|
|
8
8
|
import { withTrailingSlash } from 'ufo';
|
|
9
|
+
import { normalizeContentConfigCollectionNames } from '../dist/types/config';
|
|
10
|
+
export { defineCollection, defineContentConfig, reference } from '../dist/types/config';
|
|
9
11
|
import 'unstorage/drivers/fs';
|
|
10
12
|
import 'unstorage/drivers/http';
|
|
11
13
|
import 'unstorage/drivers/github';
|
|
@@ -27,10 +29,11 @@ import { buildContentGraph } from '../dist/core/content/graph';
|
|
|
27
29
|
import { normalizeCollectionSources, normalizeCollectionExcludes } from '../dist/core/content/sources';
|
|
28
30
|
import { resolveCollectionI18n, prefixPathWithLocale } from '../dist/features/localization/path';
|
|
29
31
|
import { resolveCollection } from '../dist/core/content/collection';
|
|
30
|
-
|
|
32
|
+
import { normalizeRouteMounts, normalizeContentPath } from '../dist/core/content/path';
|
|
33
|
+
import { collectTopLevelReferenceFieldsByTarget } from '../dist/core/references/schema';
|
|
31
34
|
|
|
32
35
|
const name = "@lupinum/ginko-content";
|
|
33
|
-
const version = "0.1.
|
|
36
|
+
const version = "0.1.2";
|
|
34
37
|
|
|
35
38
|
const logger = consola.withTag("@lupinum/ginko-content");
|
|
36
39
|
const CACHE_VERSION = 3;
|
|
@@ -241,16 +244,8 @@ const registerContentDevRuntime = (nuxt, options, contentContext) => {
|
|
|
241
244
|
|
|
242
245
|
const registerRuntimeImports = (resolveRuntimeModule) => {
|
|
243
246
|
addImports([
|
|
244
|
-
// Unified query API (ADR-0016) — app wrappers bind the Nuxt content context explicitly.
|
|
245
|
-
{ name: "one", as: "one", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
246
|
-
{ name: "many", as: "many", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
247
|
-
{ name: "paginate", as: "paginate", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
248
|
-
{ name: "backlinks", as: "backlinks", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
249
|
-
{ name: "resolveOne", as: "resolveOne", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
250
|
-
{ name: "variants", as: "variants", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
251
|
-
{ name: "tree", as: "tree", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
252
|
-
{ name: "neighbors", as: "neighbors", from: resolveRuntimeModule("./app/composables/query-api.js") },
|
|
253
247
|
{ name: "getCollectionPath", as: "getCollectionPath", from: resolveRuntimeModule("./query/routes.js") },
|
|
248
|
+
{ name: "useContentHead", as: "useContentHead", from: resolveRuntimeModule("./app/composables/head.js") },
|
|
254
249
|
// Vue composables — reactive wrappers over the same options shape.
|
|
255
250
|
{ name: "useContentPage", as: "useContentPage", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
256
251
|
{ name: "useContentOne", as: "useContentOne", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
@@ -260,9 +255,12 @@ const registerRuntimeImports = (resolveRuntimeModule) => {
|
|
|
260
255
|
{ name: "useContentResolveOne", as: "useContentResolveOne", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
261
256
|
{ name: "useContentVariants", as: "useContentVariants", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
262
257
|
{ name: "useContentTree", as: "useContentTree", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
258
|
+
{ name: "useContentNavigation", as: "useContentNavigation", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
263
259
|
{ name: "useContentNeighbors", as: "useContentNeighbors", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
264
260
|
{ name: "useContentLocaleSwitch", as: "useContentLocaleSwitch", from: resolveRuntimeModule("./app/composables/use-content.js") },
|
|
265
|
-
|
|
261
|
+
{ name: "useContentSwitchLocalePath", as: "useContentSwitchLocalePath", from: resolveRuntimeModule("./app/composables/route.js") },
|
|
262
|
+
// Search & site data — keep the headless useContentSearch export explicit
|
|
263
|
+
// because Nuxt UI owns the same auto-import name.
|
|
266
264
|
{ name: "useContentSearchData", as: "useContentSearchData", from: resolveRuntimeModule("./app/composables/search.js") },
|
|
267
265
|
{ name: "useContentSearchResults", as: "useContentSearchResults", from: resolveRuntimeModule("./app/composables/search.js") },
|
|
268
266
|
{ name: "querySiteData", as: "querySiteData", from: resolveRuntimeModule("./app/composables/site-data.js") }
|
|
@@ -357,25 +355,68 @@ const registerUserContentComponents = async (nuxt, resolve) => {
|
|
|
357
355
|
}
|
|
358
356
|
}
|
|
359
357
|
};
|
|
360
|
-
const registerGeneratedTypes = (contentConfigPath, resolveRuntimeModuleRoot) => {
|
|
358
|
+
const registerGeneratedTypes = (contentConfigPath, resolveRuntimeModuleRoot, collectionNames = [], i18nCollectionNames = []) => {
|
|
359
|
+
const collectionNameUnion = collectionNames.length ? collectionNames.map((name) => JSON.stringify(name)).join(" | ") : "never";
|
|
360
|
+
const i18nCollectionNameUnion = i18nCollectionNames.length ? i18nCollectionNames.map((name) => JSON.stringify(name)).join(" | ") : "never";
|
|
361
|
+
const collectionMapProperties = collectionNames.map((name) => {
|
|
362
|
+
const key = JSON.stringify(name);
|
|
363
|
+
return ` ${key}: StrictParsedContent & __GeneratedCollectionSchema<__ContentCollectionExport<${key}>>`;
|
|
364
|
+
});
|
|
361
365
|
return addTypeTemplate({
|
|
362
366
|
filename: "types/content.d.ts",
|
|
363
367
|
getContents: () => [
|
|
364
368
|
contentConfigPath ? `import type contentConfig from ${JSON.stringify(contentConfigPath)}` : "type __ContentConfig = { collections?: {} }",
|
|
369
|
+
contentConfigPath ? `import type * as contentConfigModule from ${JSON.stringify(contentConfigPath)}` : "declare const contentConfigModule: {}",
|
|
365
370
|
"import type { StrictParsedContent } from '@lupinum/ginko-content'",
|
|
366
371
|
"import type { CollectionSchema } from '@lupinum/ginko-content/config'",
|
|
367
372
|
contentConfigPath ? "type __ContentConfig = typeof contentConfig" : "",
|
|
368
373
|
"type __ContentCollections = NonNullable<__ContentConfig['collections']>",
|
|
374
|
+
`type __RuntimeContentCollectionNames = ${collectionNameUnion}`,
|
|
375
|
+
"type __NamedContentCollectionNames = {",
|
|
376
|
+
" [K in keyof typeof contentConfigModule & string]: typeof contentConfigModule[K] extends { name: K } ? K : never",
|
|
377
|
+
"}[keyof typeof contentConfigModule & string]",
|
|
378
|
+
"type __InferredContentCollectionNames = string extends keyof __ContentCollections",
|
|
379
|
+
" ? __NamedContentCollectionNames",
|
|
380
|
+
" : keyof __ContentCollections & string",
|
|
381
|
+
"type __ContentCollectionNames = [__RuntimeContentCollectionNames] extends [never]",
|
|
382
|
+
" ? __InferredContentCollectionNames",
|
|
383
|
+
" : __RuntimeContentCollectionNames",
|
|
384
|
+
"type __ContentCollectionExport<K extends string> = K extends keyof typeof contentConfigModule",
|
|
385
|
+
" ? typeof contentConfigModule[K]",
|
|
386
|
+
" : __ContentCollections[K]",
|
|
387
|
+
"type __GeneratedCollectionSchema<TCollection> = TCollection extends { __schema: infer TSchema }",
|
|
388
|
+
" ? TSchema extends { _output: infer TOutput } ? TOutput : {}",
|
|
389
|
+
" : CollectionSchema<TCollection>",
|
|
369
390
|
"type __GeneratedContentCollectionMap = {",
|
|
370
|
-
" [K in
|
|
391
|
+
" [K in __ContentCollectionNames]: StrictParsedContent & __GeneratedCollectionSchema<__ContentCollectionExport<K>>",
|
|
371
392
|
"}",
|
|
372
|
-
|
|
373
|
-
"
|
|
374
|
-
"
|
|
393
|
+
`type __RuntimeI18nCollectionNames = ${i18nCollectionNameUnion}`,
|
|
394
|
+
"type __InferredI18nCollectionNames = {",
|
|
395
|
+
" [K in __ContentCollectionNames]: __ContentCollectionExport<K> extends { __i18n: true }",
|
|
396
|
+
" ? K",
|
|
397
|
+
" : __ContentCollectionExport<K> extends { i18n: true } ? K : never",
|
|
398
|
+
"}[__ContentCollectionNames]",
|
|
399
|
+
"type __GeneratedI18nCollectionNames = [__RuntimeI18nCollectionNames] extends [never]",
|
|
400
|
+
" ? __InferredI18nCollectionNames",
|
|
401
|
+
" : __RuntimeI18nCollectionNames",
|
|
375
402
|
"declare module '@lupinum/ginko-content' {",
|
|
376
403
|
" interface ContentCollectionMap extends __GeneratedContentCollectionMap {}",
|
|
377
404
|
" interface ContentCollectionI18nMap extends Pick<__GeneratedContentCollectionMap, __GeneratedI18nCollectionNames> {}",
|
|
378
405
|
"}",
|
|
406
|
+
"declare global {",
|
|
407
|
+
" interface GinkoContentCollectionMap {",
|
|
408
|
+
...collectionMapProperties,
|
|
409
|
+
" }",
|
|
410
|
+
" interface GinkoContentCollectionI18nMap extends Pick<__GeneratedContentCollectionMap, __GeneratedI18nCollectionNames> {}",
|
|
411
|
+
"}",
|
|
412
|
+
"declare module '@lupinum/ginko-content/dist/types/query' {",
|
|
413
|
+
" interface ContentCollectionMap extends __GeneratedContentCollectionMap {}",
|
|
414
|
+
" interface ContentCollectionI18nMap extends Pick<__GeneratedContentCollectionMap, __GeneratedI18nCollectionNames> {}",
|
|
415
|
+
"}",
|
|
416
|
+
"declare module '@lupinum/ginko-content/dist/types/query.js' {",
|
|
417
|
+
" interface ContentCollectionMap extends __GeneratedContentCollectionMap {}",
|
|
418
|
+
" interface ContentCollectionI18nMap extends Pick<__GeneratedContentCollectionMap, __GeneratedI18nCollectionNames> {}",
|
|
419
|
+
"}",
|
|
379
420
|
"declare module '#content/server' {",
|
|
380
421
|
` const one: typeof import('${resolveRuntimeModuleRoot("./server")}').one`,
|
|
381
422
|
` const many: typeof import('${resolveRuntimeModuleRoot("./server")}').many`,
|
|
@@ -499,6 +540,23 @@ const toLocalPath = (value) => {
|
|
|
499
540
|
return value;
|
|
500
541
|
}
|
|
501
542
|
};
|
|
543
|
+
const placeholderHosts = /* @__PURE__ */ new Set([
|
|
544
|
+
"example.com",
|
|
545
|
+
"example.net",
|
|
546
|
+
"example.org",
|
|
547
|
+
"localhost",
|
|
548
|
+
"127.0.0.1",
|
|
549
|
+
"0.0.0.0",
|
|
550
|
+
"::1"
|
|
551
|
+
]);
|
|
552
|
+
const isPlaceholderHost = (host) => placeholderHosts.has(host) || host.endsWith(".example.com") || host.endsWith(".example.net") || host.endsWith(".example.org") || host.endsWith(".localhost");
|
|
553
|
+
const extractPlaceholderSiteUrls = (locValues) => Array.from(new Set(locValues.filter((value) => {
|
|
554
|
+
try {
|
|
555
|
+
return isPlaceholderHost(new URL(value).hostname);
|
|
556
|
+
} catch {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
})));
|
|
502
560
|
const discoverSitemapsFromDisk = async (outputPublicDir) => {
|
|
503
561
|
const indexPath = join$1(outputPublicDir, SITEMAP_INDEX);
|
|
504
562
|
try {
|
|
@@ -554,6 +612,9 @@ const normalizeContentSitemapAssertOptions = (options) => ({
|
|
|
554
612
|
minUrlsPerSitemap: options?.minUrlsPerSitemap ?? 1,
|
|
555
613
|
requireImages: options?.requireImages ?? false,
|
|
556
614
|
requiredCollections: options?.requiredCollections ?? [],
|
|
615
|
+
requiredPaths: options?.requiredPaths ?? [],
|
|
616
|
+
forbiddenPathPrefixes: options?.forbiddenPathPrefixes ?? [],
|
|
617
|
+
requireProductionSiteUrl: options?.requireProductionSiteUrl ?? false,
|
|
557
618
|
sitemaps: options?.sitemaps ?? {}
|
|
558
619
|
});
|
|
559
620
|
const shouldRunSitemapAssertionOnCompiled = (options, nitro) => {
|
|
@@ -595,6 +656,29 @@ async function assertGeneratedSitemaps({
|
|
|
595
656
|
failures.push(`- ${target.name}: expected image entries but found none`);
|
|
596
657
|
}
|
|
597
658
|
}
|
|
659
|
+
const sitemapLocValues = discoveredSitemaps.flatMap((target) => extractLocValues(target.xml));
|
|
660
|
+
const sitemapPaths = Array.from(new Set(sitemapLocValues.map(toLocalPath)));
|
|
661
|
+
const pathSet = new Set(sitemapPaths);
|
|
662
|
+
const missingPaths = options.requiredPaths.filter((path) => !pathSet.has(path));
|
|
663
|
+
if (missingPaths.length) {
|
|
664
|
+
failures.push(`- Missing required sitemap paths: ${missingPaths.join(", ")}`);
|
|
665
|
+
}
|
|
666
|
+
const forbiddenPaths = sitemapPaths.filter(
|
|
667
|
+
(path) => options.forbiddenPathPrefixes.some((prefix) => path === prefix || path.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))
|
|
668
|
+
);
|
|
669
|
+
if (forbiddenPaths.length) {
|
|
670
|
+
failures.push(`- Forbidden sitemap paths found: ${forbiddenPaths.join(", ")}`);
|
|
671
|
+
}
|
|
672
|
+
if (options.requireProductionSiteUrl) {
|
|
673
|
+
const placeholderSiteUrls = extractPlaceholderSiteUrls(sitemapLocValues);
|
|
674
|
+
if (placeholderSiteUrls.length) {
|
|
675
|
+
failures.push([
|
|
676
|
+
`- Placeholder sitemap URLs found: ${placeholderSiteUrls.join(", ")}`,
|
|
677
|
+
"Expected production URLs in generated sitemap loc values.",
|
|
678
|
+
"Set site.url or runtimeConfig.public.siteUrl to the deployed origin for production release checks."
|
|
679
|
+
].join(" "));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
598
682
|
const missingCollections = options.requiredCollections.filter((collection) => (collectionRouteCounts[collection] || 0) === 0);
|
|
599
683
|
if (missingCollections.length) {
|
|
600
684
|
failures.push(`- Missing content sitemap routes for collections: ${missingCollections.join(", ")}`);
|
|
@@ -663,12 +747,15 @@ function configureNuxtSitemapSource(nuxt, apiBaseURL, sitemapPath = "/sitemap")
|
|
|
663
747
|
}),
|
|
664
748
|
source
|
|
665
749
|
];
|
|
666
|
-
sitemap.excludeAppSources = true;
|
|
667
750
|
}
|
|
668
751
|
function resolveNuxtSitemapPrerenderRoutes(nuxt) {
|
|
669
752
|
if (!hasNuxtSitemapModule(nuxt.options.modules)) {
|
|
670
753
|
return [];
|
|
671
754
|
}
|
|
755
|
+
const nuxtSitemap = nuxt.options.sitemap;
|
|
756
|
+
if (nuxtSitemap?.sitemaps === false) {
|
|
757
|
+
return ["/sitemap.xml"];
|
|
758
|
+
}
|
|
672
759
|
const nuxtI18n = nuxt.options.i18n || {};
|
|
673
760
|
if (!hasNuxtI18nModule(nuxt.options.modules) || !Array.isArray(nuxtI18n.locales) || nuxtI18n.locales.length === 0) {
|
|
674
761
|
return ["/sitemap.xml"];
|
|
@@ -733,7 +820,7 @@ function normalizeSearchOptions(options) {
|
|
|
733
820
|
}
|
|
734
821
|
const defaultMiniSearchOptions = {
|
|
735
822
|
fields: ["title", "content", "headings"],
|
|
736
|
-
storeFields: ["path", "title", "excerpt", "anchor", "locale"],
|
|
823
|
+
storeFields: ["path", "title", "excerpt", "anchor", "locale", "collection"],
|
|
737
824
|
boost: {
|
|
738
825
|
title: 4,
|
|
739
826
|
headings: 2,
|
|
@@ -1069,12 +1156,68 @@ const registerContentNitroIntegrationHooks = (nitroConfig, options, contentConte
|
|
|
1069
1156
|
routes.add(route);
|
|
1070
1157
|
}
|
|
1071
1158
|
}
|
|
1072
|
-
|
|
1159
|
+
const sitemapPrerenderRoutes = typeof options.sitemapPrerenderRoutes === "function" ? options.sitemapPrerenderRoutes() : options.sitemapPrerenderRoutes || [];
|
|
1160
|
+
for (const route of sitemapPrerenderRoutes) {
|
|
1073
1161
|
routes.add(route);
|
|
1074
1162
|
}
|
|
1075
1163
|
});
|
|
1076
1164
|
};
|
|
1077
1165
|
|
|
1166
|
+
const getContentPageMeta = (page) => {
|
|
1167
|
+
const content = page.meta?.content;
|
|
1168
|
+
return content && typeof content === "object" ? content : void 0;
|
|
1169
|
+
};
|
|
1170
|
+
const isRouteConfig = (value) => typeof value === "string" || value !== null && typeof value === "object" && Object.values(value).every((entry) => typeof entry === "string");
|
|
1171
|
+
const describePage = (page) => page.file || page.path || "<unknown page>";
|
|
1172
|
+
const expectedLocalesFor = (collection, locales, defaultLocale) => {
|
|
1173
|
+
const collectionI18n = collection.i18n && typeof collection.i18n === "object" ? collection.i18n : void 0;
|
|
1174
|
+
return collectionI18n?.locales?.length ? collectionI18n.locales : locales.length ? locales : defaultLocale ? [defaultLocale] : [];
|
|
1175
|
+
};
|
|
1176
|
+
function validateContentPageRouteMetadata(pages, collections, options = {}) {
|
|
1177
|
+
const visit = (page) => {
|
|
1178
|
+
const contentMeta = getContentPageMeta(page);
|
|
1179
|
+
if (contentMeta) {
|
|
1180
|
+
validatePage(page, contentMeta, collections, options);
|
|
1181
|
+
}
|
|
1182
|
+
for (const child of page.children || []) {
|
|
1183
|
+
visit(child);
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
for (const page of pages) {
|
|
1187
|
+
visit(page);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function validatePage(page, contentMeta, collections, options) {
|
|
1191
|
+
const collectionName = contentMeta.collection;
|
|
1192
|
+
if (typeof collectionName !== "string" || !collectionName) {
|
|
1193
|
+
throw new Error(`@lupinum/ginko-content page "${describePage(page)}" declares content route metadata without a string collection name.`);
|
|
1194
|
+
}
|
|
1195
|
+
const collection = collections[collectionName];
|
|
1196
|
+
if (!collection) {
|
|
1197
|
+
throw new Error(`@lupinum/ginko-content page "${describePage(page)}" declares content collection "${collectionName}", but no matching collection exists in content.config.ts.`);
|
|
1198
|
+
}
|
|
1199
|
+
if (!isRouteConfig(contentMeta.route)) {
|
|
1200
|
+
throw new Error(`@lupinum/ginko-content page "${describePage(page)}" declares content collection "${collectionName}" but no route metadata. Add definePageMeta({ content: { collection: '${collectionName}', route: ... } }).`);
|
|
1201
|
+
}
|
|
1202
|
+
const locales = expectedLocalesFor(collection, options.locales || [], options.defaultLocale);
|
|
1203
|
+
const defaultLocale = collection.i18n && typeof collection.i18n === "object" ? collection.i18n.defaultLocale : options.defaultLocale;
|
|
1204
|
+
const expected = normalizeRouteMounts(collection.route, locales, defaultLocale);
|
|
1205
|
+
const actual = normalizeRouteMounts(contentMeta.route, locales, defaultLocale);
|
|
1206
|
+
if (!expected) {
|
|
1207
|
+
throw new Error(`@lupinum/ginko-content page "${describePage(page)}" declares route metadata for collection "${collectionName}", but that collection has no route config in content.config.ts.`);
|
|
1208
|
+
}
|
|
1209
|
+
for (const locale of Object.keys(expected)) {
|
|
1210
|
+
const expectedRoute = normalizeContentPath(expected[locale]);
|
|
1211
|
+
const actualRoute = actual?.[locale] ? normalizeContentPath(actual[locale]) : void 0;
|
|
1212
|
+
if (!actualRoute) {
|
|
1213
|
+
throw new Error(`@lupinum/ginko-content route metadata mismatch in "${describePage(page)}": collection "${collectionName}" is missing locale "${locale}" route "${expectedRoute}".`);
|
|
1214
|
+
}
|
|
1215
|
+
if (actualRoute !== expectedRoute) {
|
|
1216
|
+
throw new Error(`@lupinum/ginko-content route metadata mismatch in "${describePage(page)}": collection "${collectionName}" locale "${locale}" expected route "${expectedRoute}" but page metadata declares "${actualRoute}".`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1078
1221
|
const module$1 = defineNuxtModule({
|
|
1079
1222
|
meta: {
|
|
1080
1223
|
name,
|
|
@@ -1180,6 +1323,7 @@ const module$1 = defineNuxtModule({
|
|
|
1180
1323
|
const resolvedI18n = resolveModuleI18nOptions(options, nuxt);
|
|
1181
1324
|
const resolvedSitemap = normalizeSitemapOptions(options);
|
|
1182
1325
|
const resolvedSearch = normalizeSearchOptions(options);
|
|
1326
|
+
validateCollectionNames(appContentConfig.collections);
|
|
1183
1327
|
options.collections = Object.fromEntries(Object.entries(appContentConfig.collections).map(([name2, collection]) => [
|
|
1184
1328
|
name2,
|
|
1185
1329
|
{
|
|
@@ -1212,6 +1356,12 @@ const module$1 = defineNuxtModule({
|
|
|
1212
1356
|
if (resolvedSitemap !== false) {
|
|
1213
1357
|
configureNuxtSitemapSource(nuxt, options.api.baseURL, resolvedSitemap.path);
|
|
1214
1358
|
}
|
|
1359
|
+
nuxt.hook("pages:extend", (pages) => {
|
|
1360
|
+
validateContentPageRouteMetadata(pages, options.collections, {
|
|
1361
|
+
locales: resolvedI18n.locales,
|
|
1362
|
+
defaultLocale: resolvedI18n.defaultLocale
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1215
1365
|
if (resolvedSitemap && resolvedSitemap.assert.enabled) {
|
|
1216
1366
|
nuxt.hook("sitemap:prerender:done", async ({ sitemaps }) => {
|
|
1217
1367
|
const assertOptions = contentContext.sitemap?.assert;
|
|
@@ -1233,7 +1383,12 @@ const module$1 = defineNuxtModule({
|
|
|
1233
1383
|
registerRuntimeImports(resolveRuntimeModule);
|
|
1234
1384
|
registerRuntimeComponents(resolve);
|
|
1235
1385
|
registerContentComponentsTemplate(addTemplate);
|
|
1236
|
-
registerGeneratedTypes(
|
|
1386
|
+
registerGeneratedTypes(
|
|
1387
|
+
contentConfigPath,
|
|
1388
|
+
resolveRuntimeModule,
|
|
1389
|
+
Object.keys(appContentConfig.collections),
|
|
1390
|
+
Object.entries(appContentConfig.collections).filter(([, collection]) => Boolean(collection.i18n)).map(([name2]) => name2)
|
|
1391
|
+
);
|
|
1237
1392
|
await registerUserContentComponents(nuxt, resolve);
|
|
1238
1393
|
const getSearchRuntime = () => contentContext.search === false ? false : createSearchRuntimeConfig(contentContext.search, options.api.baseURL);
|
|
1239
1394
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
@@ -1291,7 +1446,7 @@ const module$1 = defineNuxtModule({
|
|
|
1291
1446
|
}
|
|
1292
1447
|
registerContentNitroIntegrationHooks(nitroConfig, {
|
|
1293
1448
|
rootDir: nuxt.options.rootDir,
|
|
1294
|
-
sitemapPrerenderRoutes: contentContext.sitemap === false ? [] : resolveNuxtSitemapPrerenderRoutes(nuxt)
|
|
1449
|
+
sitemapPrerenderRoutes: () => contentContext.sitemap === false ? [] : resolveNuxtSitemapPrerenderRoutes(nuxt)
|
|
1295
1450
|
}, {
|
|
1296
1451
|
collections: contentContext.collections,
|
|
1297
1452
|
locales: contentContext.locales,
|
|
@@ -1345,17 +1500,22 @@ const module$1 = defineNuxtModule({
|
|
|
1345
1500
|
contentContext.defaultLocale = contentContext.defaultLocale || contentContext.locales[0];
|
|
1346
1501
|
contentContext.markdown = processMarkdownOptions(contentContext.markdown);
|
|
1347
1502
|
await validateBuiltinMarkdownPlugins(contentContext.markdown.plugins, resolvePath);
|
|
1348
|
-
const runtimeCollections = Object.fromEntries(Object.entries(options.collections || {}).map(([name2, collection]) =>
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1503
|
+
const runtimeCollections = Object.fromEntries(Object.entries(options.collections || {}).map(([name2, collection]) => {
|
|
1504
|
+
const references = collectTopLevelReferenceFieldsByTarget(collection.schema);
|
|
1505
|
+
return [
|
|
1506
|
+
name2,
|
|
1507
|
+
{
|
|
1508
|
+
...collection.source ? { source: collection.source } : {},
|
|
1509
|
+
...collection.exclude ? { exclude: collection.exclude } : {},
|
|
1510
|
+
...collection.type ? { type: collection.type } : {},
|
|
1511
|
+
strict: collection.strict ?? true,
|
|
1512
|
+
...collection.route ? { route: collection.route } : {},
|
|
1513
|
+
...typeof collection.sitemap === "boolean" ? { sitemap: collection.sitemap } : {},
|
|
1514
|
+
...collection.i18n && collection.i18n !== true ? { i18n: collection.i18n } : {},
|
|
1515
|
+
...Object.keys(references).length ? { references } : {}
|
|
1516
|
+
}
|
|
1517
|
+
];
|
|
1518
|
+
}));
|
|
1359
1519
|
const cacheIntegrity = hash({
|
|
1360
1520
|
locales: contentContext.locales,
|
|
1361
1521
|
defaultLocale: contentContext.defaultLocale,
|
|
@@ -1385,6 +1545,9 @@ function assertConfiguredProviderAvailable(contentContext) {
|
|
|
1385
1545
|
}
|
|
1386
1546
|
throw new Error(`content.config.ts sets provider "${provider}", but no provider module registered it. Register a module for "${provider}" or add it to content providers.`);
|
|
1387
1547
|
}
|
|
1548
|
+
function validateCollectionNames(collections) {
|
|
1549
|
+
normalizeContentConfigCollectionNames(collections);
|
|
1550
|
+
}
|
|
1388
1551
|
function validateRemovedMarkdownOptions(options) {
|
|
1389
1552
|
if (options.highlight !== void 0) {
|
|
1390
1553
|
throw new Error("`content.highlight` was removed. Enable syntax highlighting with `content.markdown.plugins`, for example `[['highlight', { ...options }]]`.");
|
package/dist/public/client.d.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
export { one, many, paginate, backlinks, resolveOne, variants, tree, neighbors } from '../runtime/app/composables/query-api.js';
|
|
8
8
|
export { getCollectionPath } from '../runtime/query/routes.js';
|
|
9
9
|
export type { CollectionPathOptions } from '../runtime/query/routes.js';
|
|
10
|
-
export {
|
|
10
|
+
export { useContentHead } from '../runtime/app/composables/head.js';
|
|
11
|
+
export { useContentPage, useContentOne, useContentMany, useContentPagination, useContentBacklinks, useContentResolveOne, useContentVariants, useContentTree, useContentNavigation, useContentNeighbors, useContentLocaleSwitch } from '../runtime/app/composables/use-content.js';
|
|
11
12
|
export type { UseContentPageOptions } from '../runtime/app/composables/use-content.js';
|
|
12
|
-
export type { QueryWhere, QueryOperators, ContentSelector, ContentResolvedMeta, BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, LocaleFallback, DocumentFromHandle, OneOptions, ManyOptions, PaginationOptions, PaginationResult, PopulateSpec, PopulatedDocument, ResolveOneOptions, ResolveOneResult, LocalizedDoc, LocalePathEntry, NeighborsOptions, NeighborsResult, ResolutionEnvelope, SortSpec, TreeOptions, VariantsOptions, ContentVariant, ContentNavigationItem, ContentTreeItem, ContentRouteMeta, ContentSearchSection } from '../types/query.js';
|
|
13
|
+
export type { QueryWhere, QueryOperators, ContentSelector, ContentResolvedMeta, BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, LocaleFallback, DocumentFromHandle, OneOptions, ManyOptions, PaginationOptions, PaginationResult, PopulateSpec, PopulatedDocument, ResolveOneOptions, ResolveOneResult, LocalizedDoc, LocalePathEntry, NeighborsOptions, NeighborsResult, ResolutionEnvelope, SortSpec, TreeOptions, VariantsOptions, ContentVariant, ContentNavigationItem, ContentCollectionName, ContentCollectionStringName, ContentCollectionTarget, ContentTreeItem, ContentRouteMeta, ContentSearchSection } from '../types/query.js';
|
|
14
|
+
export type { ContentNavigationNode } from '../runtime/app/composables/use-content.js';
|
|
13
15
|
export { useContentSearch, useContentSearchData, useContentSearchResults } from '../runtime/app/composables/search.js';
|
|
14
16
|
export type { UseContentSearchDataOptions, UseContentSearchDataResult, UseContentSearchOptions, UseContentSearchResult, UseContentSearchResultsOptions, UseContentSearchResultsResult } from '../runtime/app/composables/search.js';
|
|
15
17
|
export { querySiteData } from '../runtime/app/composables/site-data.js';
|
package/dist/public/client.js
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
neighbors
|
|
10
10
|
} from "../runtime/app/composables/query-api.js";
|
|
11
11
|
export { getCollectionPath } from "../runtime/query/routes.js";
|
|
12
|
+
export { useContentHead } from "../runtime/app/composables/head.js";
|
|
12
13
|
export {
|
|
13
14
|
useContentPage,
|
|
14
15
|
useContentOne,
|
|
@@ -18,6 +19,7 @@ export {
|
|
|
18
19
|
useContentResolveOne,
|
|
19
20
|
useContentVariants,
|
|
20
21
|
useContentTree,
|
|
22
|
+
useContentNavigation,
|
|
21
23
|
useContentNeighbors,
|
|
22
24
|
useContentLocaleSwitch
|
|
23
25
|
} from "../runtime/app/composables/use-content.js";
|
|
@@ -11,6 +11,13 @@ function normalizeBody(value, excerpt) {
|
|
|
11
11
|
}
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
+
function warnUnsupportedValue(value, excerpt) {
|
|
15
|
+
const path = value?._path ? ` for "${value._path}"` : "";
|
|
16
|
+
const target = excerpt ? "excerpt" : "body";
|
|
17
|
+
console.warn(
|
|
18
|
+
`[ginko-content] <ContentRenderer> could not render ${target}${path}. Pass the full content document with a markdown body, or provide an \`empty\` slot for empty/unsupported content.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
14
21
|
export default defineComponent({
|
|
15
22
|
name: "ContentRenderer",
|
|
16
23
|
props: {
|
|
@@ -74,11 +81,8 @@ export default defineComponent({
|
|
|
74
81
|
...this.$attrs
|
|
75
82
|
});
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
null,
|
|
80
|
-
JSON.stringify({ message: "You should use slots with <ContentRenderer>", value, excerpt, tag, prose, unwrap }, null, 2)
|
|
81
|
-
);
|
|
84
|
+
warnUnsupportedValue(value, excerpt);
|
|
85
|
+
return null;
|
|
82
86
|
}
|
|
83
87
|
});
|
|
84
88
|
</script>
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router';
|
|
2
2
|
import type { Ref } from 'vue';
|
|
3
|
-
|
|
3
|
+
type ContentHeadObject = {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
image?: string | Record<string, any>;
|
|
7
|
+
meta?: Array<Record<string, any>>;
|
|
8
|
+
link?: Array<Record<string, any>>;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
type ContentHeadDocument = {
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
image?: string | Record<string, any>;
|
|
15
|
+
head?: ContentHeadObject;
|
|
16
|
+
seo?: ContentHeadObject;
|
|
17
|
+
};
|
|
18
|
+
type ContentHeadRuntimeConfig = {
|
|
19
|
+
app: {
|
|
20
|
+
baseURL: string;
|
|
21
|
+
};
|
|
22
|
+
public: {
|
|
23
|
+
siteUrl?: unknown;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
4
26
|
/**
|
|
5
27
|
* Apply sensible page head defaults from a parsed content document.
|
|
6
28
|
*
|
|
7
|
-
* The composable merges explicit `content.head` values with common fallbacks
|
|
29
|
+
* The composable merges explicit `content.head` or `content.seo` values with common fallbacks
|
|
8
30
|
* from the document (`title`, `description`, `image`) and adds canonical /
|
|
9
31
|
* Open Graph URL metadata when a public site URL is configured.
|
|
10
32
|
*/
|
|
11
|
-
export declare const useContentHead: (_content:
|
|
33
|
+
export declare const useContentHead: (_content: ContentHeadDocument | null | undefined | Ref<ContentHeadDocument | null | undefined>, to?: RouteLocationNormalized | RouteLocationNormalizedLoaded) => void;
|
|
34
|
+
export declare const resolveContentHead: (data: ContentHeadDocument | null | undefined, to: Pick<RouteLocationNormalized | RouteLocationNormalizedLoaded, "path" | "fullPath">, config: ContentHeadRuntimeConfig) => ContentHeadObject | null;
|
|
35
|
+
export {};
|