@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/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Define a collection:
|
|
|
45
45
|
// content.config.ts
|
|
46
46
|
import { defineCollection, defineContentConfig } from '@lupinum/ginko-content/config'
|
|
47
47
|
|
|
48
|
-
export const pages = defineCollection(
|
|
48
|
+
export const pages = defineCollection({
|
|
49
49
|
type: 'page',
|
|
50
50
|
source: '**/*.md'
|
|
51
51
|
})
|
|
@@ -74,9 +74,7 @@ Render the current route through the collection:
|
|
|
74
74
|
```vue
|
|
75
75
|
<!-- pages/[...slug].vue -->
|
|
76
76
|
<script setup lang="ts">
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const { page } = await useContentPage(pages)
|
|
77
|
+
const { page } = await useContentPage('pages')
|
|
80
78
|
</script>
|
|
81
79
|
|
|
82
80
|
<template>
|
|
@@ -89,9 +87,10 @@ const { page } = await useContentPage(pages)
|
|
|
89
87
|
- file-authored pages and navigation from `content/`
|
|
90
88
|
- collection definitions as the source of truth for content shape and source
|
|
91
89
|
files
|
|
92
|
-
- Markdown,
|
|
90
|
+
- Markdown, Comark component tags, YAML, JSON, and CSV ingestion
|
|
93
91
|
- locale-aware content routing
|
|
94
|
-
- route-aware page loading with `useContentPage(
|
|
92
|
+
- route-aware page loading with `useContentPage('pages')`
|
|
93
|
+
- semantic previous/next route-page data through `useContentPage('pages', { surround })`
|
|
95
94
|
- server reads through `one`, `many`, `paginate`, `resolveOne`, `tree`, and
|
|
96
95
|
`neighbors`
|
|
97
96
|
- Vue composables for the same read model
|
|
@@ -99,6 +98,23 @@ const { page } = await useContentPage(pages)
|
|
|
99
98
|
- sitemap integration for public content routes
|
|
100
99
|
- a server-side provider contract for advanced custom sources
|
|
101
100
|
|
|
101
|
+
## I18n, Sitemap, and Prerender Ownership
|
|
102
|
+
|
|
103
|
+
For localized Nuxt apps, keep one source of truth per route type:
|
|
104
|
+
|
|
105
|
+
- Static Nuxt page paths belong in Nuxt I18n `i18n.pages`.
|
|
106
|
+
- Content page paths belong in Ginko collection routes and content files.
|
|
107
|
+
- Sitemap XML is generated by `@nuxtjs/sitemap`.
|
|
108
|
+
- Ginko registers the content sitemap source and contributes content prerender
|
|
109
|
+
routes.
|
|
110
|
+
|
|
111
|
+
Do not duplicate docs, blog, pricing, privacy, or translated locale paths in
|
|
112
|
+
`sitemap.urls` or app-owned `nitro.prerender.routes` arrays. Use
|
|
113
|
+
`@nuxtjs/sitemap >= 8.0.15` when translated static app slugs such as `/preise`
|
|
114
|
+
and `/en/pricing` need cross-locale sitemap alternates.
|
|
115
|
+
|
|
116
|
+
See the public guide: [Sitemap and prerender](../../docs/content/docs/6.i18n/4.sitemap-prerender.md).
|
|
117
|
+
|
|
102
118
|
## Scope
|
|
103
119
|
|
|
104
120
|
The default provider reads files from your Nuxt project. The package does not
|
package/compatibility.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Ginko Content release compatibility",
|
|
3
3
|
"releaseStack": {
|
|
4
|
-
"@lupinum/ginko-content": "0.1.
|
|
4
|
+
"@lupinum/ginko-content": "0.1.2",
|
|
5
5
|
"@lupinum/ginko-cms": "0.1.2",
|
|
6
6
|
"@lupinum/ginko-cms-convex": "0.1.1",
|
|
7
7
|
"@lupinum/ginko-cms-contract": "0.1.1"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"convex": ["1.38.0", "^1.38.0"],
|
|
14
14
|
"convex-helpers": ["^0.1.117", "0.1.117"],
|
|
15
15
|
"h3": ["1.15.11"],
|
|
16
|
-
"nuxt": ["^4.4.
|
|
16
|
+
"nuxt": ["^4.4.7", "4.4.7", ">=4.4.7"],
|
|
17
17
|
"typescript": ["~6.0.3", "^6.0.3", "6.0.3"],
|
|
18
18
|
"vite": ["^7.3.3", "7.3.3"],
|
|
19
19
|
"vitest": ["^4.1.6", ">=4.1.6"],
|
package/dist/cli.mjs
CHANGED
|
@@ -42,32 +42,27 @@ const sourceChecks = [
|
|
|
42
42
|
{
|
|
43
43
|
pattern: /\bqueryCollectionItemSurroundings\s*\(/,
|
|
44
44
|
message: "Nuxt Content v3 surround helper found.",
|
|
45
|
-
suggestion: "Use
|
|
45
|
+
suggestion: "Use useContentPage('docs', { surround: true }) in route page components."
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
pattern: /\bqueryCollectionSearchSections\s*\(/,
|
|
49
49
|
message: "Nuxt Content v3 search sections helper found.",
|
|
50
|
-
suggestion: "Use useContentSearchData(
|
|
50
|
+
suggestion: "Use useContentSearchData('docs') for UI search data."
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
pattern: /\bqueryCollectionNavigation\s*\(/,
|
|
54
54
|
message: "Nuxt Content v3 navigation helper found.",
|
|
55
|
-
suggestion: "Use
|
|
55
|
+
suggestion: "Use useContentNavigation('docs') for layout navigation."
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
pattern: /\bqueryCollection\s*\(/,
|
|
59
59
|
message: "Removed collection query helper found.",
|
|
60
|
-
suggestion: "Use one(
|
|
60
|
+
suggestion: "Use one('docs', options), many('docs', options), paginate('docs', options), or the matching useContent* composable."
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
pattern: /\buseContentList\s*\(/,
|
|
64
64
|
message: "Removed content list composable found.",
|
|
65
|
-
suggestion: "Use useContentMany(
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
pattern: /\buseContentNavigation\s*\(/,
|
|
69
|
-
message: "Removed content navigation composable found.",
|
|
70
|
-
suggestion: "Use useContentTree(handle) for layout navigation."
|
|
65
|
+
suggestion: "Use useContentMany('docs', options) or many('docs', options)."
|
|
71
66
|
},
|
|
72
67
|
{
|
|
73
68
|
pattern: /\bcontent\.(database|preview|build)\b/,
|
|
@@ -84,15 +79,10 @@ const sourceChecks = [
|
|
|
84
79
|
message: "ContentRenderer is receiving a document body.",
|
|
85
80
|
suggestion: "Pass the full content document to ContentRenderer, not document.body."
|
|
86
81
|
},
|
|
87
|
-
{
|
|
88
|
-
pattern: /<NuxtLink\b[^>]*:to\s*=\s*["'][^"']+\.path["']/,
|
|
89
|
-
message: "NuxtLink may be using raw query .path.",
|
|
90
|
-
suggestion: "Use useContentMany(handle, options) for route-safe list items, or link route-page payloads with their explicit path field."
|
|
91
|
-
},
|
|
92
82
|
{
|
|
93
83
|
pattern: /<NuxtLink\b[^>]*:to\s*=\s*["'][^"']+\._path["']/,
|
|
94
84
|
message: "NuxtLink is using raw content _path.",
|
|
95
|
-
suggestion: "Use useContentMany(
|
|
85
|
+
suggestion: "Use useContentMany('docs', options) for list pages and bind the route-safe item.path field."
|
|
96
86
|
}
|
|
97
87
|
];
|
|
98
88
|
const lockfileNames = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
|
|
@@ -156,36 +146,91 @@ function findMatchingBrace(text, start) {
|
|
|
156
146
|
}
|
|
157
147
|
return text.length - 1;
|
|
158
148
|
}
|
|
159
|
-
function
|
|
160
|
-
const definitions =
|
|
149
|
+
function findAuthoredCollectionDefinitions(text) {
|
|
150
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
161
151
|
const callPattern = /\bdefineCollection\s*\(/g;
|
|
162
152
|
for (const match of text.matchAll(callPattern)) {
|
|
163
153
|
const callStart = match.index || 0;
|
|
164
154
|
const argsStart = callStart + match[0].length;
|
|
165
155
|
const args = text.slice(argsStart);
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
const bodyStart = argsStart + namedMatch[0].lastIndexOf("{");
|
|
169
|
-
const bodyEnd = findMatchingBrace(text, bodyStart);
|
|
170
|
-
definitions.push({
|
|
171
|
-
name: namedMatch[2],
|
|
172
|
-
block: text.slice(bodyStart, bodyEnd + 1)
|
|
173
|
-
});
|
|
156
|
+
const objectMatch = args.match(/^\s*\{/);
|
|
157
|
+
if (!objectMatch) {
|
|
174
158
|
continue;
|
|
175
159
|
}
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
160
|
+
const bodyStart = argsStart + objectMatch[0].lastIndexOf("{");
|
|
161
|
+
const bodyEnd = findMatchingBrace(text, bodyStart);
|
|
162
|
+
const block = text.slice(bodyStart, bodyEnd + 1);
|
|
163
|
+
const prefix = text.slice(0, callStart);
|
|
164
|
+
const propertyMatch = prefix.match(/([a-z_$][\w$]*)\s*:\s*$/i);
|
|
165
|
+
if (propertyMatch) {
|
|
166
|
+
definitions.set(propertyMatch[1], {
|
|
167
|
+
key: propertyMatch[1],
|
|
168
|
+
block
|
|
184
169
|
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const variableMatch = prefix.match(/(?:^|[\s;])(?:export\s+)?(?:const|let|var)\s+([a-z_$][\w$]*)\s*=\s*$/i);
|
|
173
|
+
if (variableMatch) {
|
|
174
|
+
definitions.set(variableMatch[1], { block });
|
|
185
175
|
}
|
|
186
176
|
}
|
|
187
177
|
return definitions;
|
|
188
178
|
}
|
|
179
|
+
function findCollectionDefinitions(text) {
|
|
180
|
+
const authoredCollections = findAuthoredCollectionDefinitions(text);
|
|
181
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const block of findObjectPropertyBlocks(text, "collections")) {
|
|
183
|
+
for (const match of block.matchAll(/([a-z_$][\w$]*)\s*:\s*([a-z_$][\w$]*)\b/g)) {
|
|
184
|
+
const [, key, identifier] = match;
|
|
185
|
+
const authored = authoredCollections.get(identifier);
|
|
186
|
+
if (authored) {
|
|
187
|
+
definitions.set(key, {
|
|
188
|
+
name: key,
|
|
189
|
+
block: authored.block
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const match of block.matchAll(/(?:^|[,{]\s*)([a-z_$][\w$]*)\s*(?=,|\})/g)) {
|
|
194
|
+
const key = match[1];
|
|
195
|
+
const authored = authoredCollections.get(key);
|
|
196
|
+
if (authored) {
|
|
197
|
+
definitions.set(key, {
|
|
198
|
+
name: key,
|
|
199
|
+
block: authored.block
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const authored of authoredCollections.values()) {
|
|
205
|
+
if (authored.key) {
|
|
206
|
+
definitions.set(authored.key, {
|
|
207
|
+
name: authored.key,
|
|
208
|
+
block: authored.block
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return [...definitions.values()];
|
|
213
|
+
}
|
|
214
|
+
function findObjectPropertyBlocks(text, property) {
|
|
215
|
+
const blocks = [];
|
|
216
|
+
const pattern = new RegExp(`\\b${property}\\s*:\\s*\\{`, "g");
|
|
217
|
+
for (const match of text.matchAll(pattern)) {
|
|
218
|
+
const bodyStart = text.indexOf("{", match.index);
|
|
219
|
+
if (bodyStart === -1) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const bodyEnd = findMatchingBrace(text, bodyStart);
|
|
223
|
+
blocks.push(text.slice(bodyStart, bodyEnd + 1));
|
|
224
|
+
}
|
|
225
|
+
return blocks;
|
|
226
|
+
}
|
|
227
|
+
function extractStringArrayProperty(block, property) {
|
|
228
|
+
const match = block.match(new RegExp(`\\b${property}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
|
|
229
|
+
if (!match) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
return [...match[1].matchAll(/['"]([^'"]+)['"]/g)].map((item) => item[1]);
|
|
233
|
+
}
|
|
189
234
|
async function collectFiles(dir, rootDir, files = []) {
|
|
190
235
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
191
236
|
for (const entry of entries) {
|
|
@@ -261,6 +306,32 @@ async function inspectSourceFiles(rootDir) {
|
|
|
261
306
|
}
|
|
262
307
|
return findings;
|
|
263
308
|
}
|
|
309
|
+
async function inspectSearchCollections(rootDir) {
|
|
310
|
+
const contentConfig = await readTextIfPresent(join(rootDir, "content.config.ts"));
|
|
311
|
+
const nuxtConfig = await readTextIfPresent(join(rootDir, "nuxt.config.ts"));
|
|
312
|
+
if (!contentConfig || !nuxtConfig) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
const dataCollections = new Set(
|
|
316
|
+
findCollectionDefinitions(contentConfig).filter((collection) => /\btype\s*:\s*['"]data['"]/.test(collection.block)).map((collection) => collection.name)
|
|
317
|
+
);
|
|
318
|
+
if (!dataCollections.size) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const configuredCollections = new Set(
|
|
322
|
+
findObjectPropertyBlocks(nuxtConfig, "search").flatMap((block) => extractStringArrayProperty(block, "collections"))
|
|
323
|
+
);
|
|
324
|
+
const dataSearchCollections = [...configuredCollections].filter((collection) => dataCollections.has(collection));
|
|
325
|
+
if (!dataSearchCollections.length) {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
return [{
|
|
329
|
+
severity: "info",
|
|
330
|
+
file: "nuxt.config.ts",
|
|
331
|
+
message: `Data-only collections listed in content.search.collections: ${dataSearchCollections.join(", ")}.`,
|
|
332
|
+
suggestion: "Remove data-only collections from the static public search index, make them route-backed pages, or use provider-backed search with route-safe results."
|
|
333
|
+
}];
|
|
334
|
+
}
|
|
264
335
|
async function inspectLockfiles(rootDir) {
|
|
265
336
|
const findings = [];
|
|
266
337
|
for (const lockfileName of lockfileNames) {
|
|
@@ -510,7 +581,7 @@ async function inspectI18nSourceSmells(rootDir, locales) {
|
|
|
510
581
|
severity: "error",
|
|
511
582
|
file: relativeFile,
|
|
512
583
|
message: "Hardcoded locale route branch found.",
|
|
513
|
-
suggestion: "Use @nuxtjs/i18n route helpers,
|
|
584
|
+
suggestion: "Use @nuxtjs/i18n route helpers, useContentNavigation('docs'), or useContentMany('docs', options) item paths instead of branching on locale codes."
|
|
514
585
|
});
|
|
515
586
|
}
|
|
516
587
|
if (/<(?:NuxtLink|U[A-Za-z]+)\b[^>]*:to\s*=\s*["'][^"']+\._path["']/.test(text)) {
|
|
@@ -518,7 +589,7 @@ async function inspectI18nSourceSmells(rootDir, locales) {
|
|
|
518
589
|
severity: "error",
|
|
519
590
|
file: relativeFile,
|
|
520
591
|
message: "UI link is bound to raw content _path.",
|
|
521
|
-
suggestion: "Use useContentMany(
|
|
592
|
+
suggestion: "Use useContentMany('docs', options) for localized list pages and bind item.path."
|
|
522
593
|
});
|
|
523
594
|
}
|
|
524
595
|
}
|
|
@@ -703,6 +774,7 @@ async function runDoctor(options = {}) {
|
|
|
703
774
|
const findings = [
|
|
704
775
|
...await inspectPackageJson(rootDir),
|
|
705
776
|
...await inspectSourceFiles(rootDir),
|
|
777
|
+
...await inspectSearchCollections(rootDir),
|
|
706
778
|
...await inspectLockfiles(rootDir),
|
|
707
779
|
...await inspectSitemap(rootDir),
|
|
708
780
|
...options.i18n ? await inspectI18n(rootDir) : []
|
|
@@ -5,4 +5,5 @@ export declare const getObjectShape: (schema: any) => Record<string, any>;
|
|
|
5
5
|
export declare const getReferenceDescriptor: (schema: any) => {
|
|
6
6
|
collection?: string;
|
|
7
7
|
} | null;
|
|
8
|
+
export declare const collectTopLevelReferenceFieldsByTarget: (schema: unknown) => Record<string, string[]>;
|
|
8
9
|
export declare const collectTopLevelReferenceFields: (schema: unknown, targetCollection?: string) => string[];
|
|
@@ -53,18 +53,41 @@ const getArrayElement = (schema) => {
|
|
|
53
53
|
const def = getSchemaDef(current);
|
|
54
54
|
return def.element || def.type || null;
|
|
55
55
|
};
|
|
56
|
-
|
|
56
|
+
const wildcardReferenceTarget = "*";
|
|
57
|
+
const getTopLevelReferenceDescriptor = (schema) => {
|
|
58
|
+
const direct = getReferenceDescriptor(schema);
|
|
59
|
+
if (direct) {
|
|
60
|
+
return direct;
|
|
61
|
+
}
|
|
62
|
+
const arrayElement = getArrayElement(schema);
|
|
63
|
+
return arrayElement ? getReferenceDescriptor(arrayElement) : null;
|
|
64
|
+
};
|
|
65
|
+
export const collectTopLevelReferenceFieldsByTarget = (schema) => {
|
|
57
66
|
const current = unwrapSchema(schema);
|
|
58
67
|
if (getSchemaTypeName(current) !== "ZodObject") {
|
|
59
|
-
return
|
|
68
|
+
return {};
|
|
60
69
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
const references = {};
|
|
71
|
+
for (const [field, childSchema] of Object.entries(getObjectShape(current))) {
|
|
72
|
+
const descriptor = getTopLevelReferenceDescriptor(childSchema);
|
|
73
|
+
if (!descriptor) {
|
|
74
|
+
continue;
|
|
65
75
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
76
|
+
const target = descriptor.collection || wildcardReferenceTarget;
|
|
77
|
+
references[target] ||= [];
|
|
78
|
+
references[target].push(field);
|
|
79
|
+
}
|
|
80
|
+
return references;
|
|
81
|
+
};
|
|
82
|
+
export const collectTopLevelReferenceFields = (schema, targetCollection) => {
|
|
83
|
+
const references = collectTopLevelReferenceFieldsByTarget(schema);
|
|
84
|
+
if (!targetCollection) {
|
|
85
|
+
return [...new Set(Object.values(references).flat())];
|
|
86
|
+
}
|
|
87
|
+
return [
|
|
88
|
+
.../* @__PURE__ */ new Set([
|
|
89
|
+
...references[targetCollection] || [],
|
|
90
|
+
...references[wildcardReferenceTarget] || []
|
|
91
|
+
])
|
|
92
|
+
];
|
|
70
93
|
};
|
package/dist/module.d.mts
CHANGED
|
@@ -4,8 +4,8 @@ import { ContentCollectionConfig as ContentCollectionConfig$1, ContentCollection
|
|
|
4
4
|
export { defineCollection, defineContentConfig, reference } from '../dist/types/config';
|
|
5
5
|
import { StorageValue } from 'unstorage';
|
|
6
6
|
import { ModuleOptions as ModuleOptions$1, ContentContext as ContentContext$1, ContentRevalidateOptions as ContentRevalidateOptions$1 } from '../dist/types/module';
|
|
7
|
-
import { ContentSearchPublicRuntimeConfig as ContentSearchPublicRuntimeConfig$1 } from '../dist/types/search';
|
|
8
7
|
import { ZodType, z } from 'zod';
|
|
8
|
+
import { ContentSearchPublicRuntimeConfig as ContentSearchPublicRuntimeConfig$1 } from '../dist/types/search';
|
|
9
9
|
import { ListenOptions } from 'listhen';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -29,6 +29,7 @@ type ContentCollectionSource = string | string[];
|
|
|
29
29
|
* express roots such as `{ en: '/docs', de: '/dokumentation' }`.
|
|
30
30
|
*/
|
|
31
31
|
type ContentCollectionRouteConfig = string | Record<string, string>;
|
|
32
|
+
type ContentCollectionKind = 'page' | 'data';
|
|
32
33
|
type ContentCmsFieldType = 'text' | 'textarea' | 'richtext' | 'slug' | 'email' | 'url' | 'number' | 'range' | 'select' | 'multiselect' | 'radio' | 'checkbox' | 'toggle' | 'date' | 'datetime' | 'time' | 'json' | 'object' | 'array' | 'blocks' | 'relation' | 'relations' | 'image' | 'images' | 'file' | 'icon' | 'code' | 'color' | 'divider' | 'section';
|
|
33
34
|
interface ContentCmsRelationConfig {
|
|
34
35
|
collectionId: string;
|
|
@@ -88,6 +89,11 @@ interface ContentCollectionSourceObject {
|
|
|
88
89
|
* Declarative collection definition used by `content.config.ts`.
|
|
89
90
|
*/
|
|
90
91
|
interface ContentCollectionConfig<TSchema extends ZodType | undefined = ZodType | undefined> {
|
|
92
|
+
/**
|
|
93
|
+
* Collection kind declared in `defineCollection`. Page collections are public
|
|
94
|
+
* content routes by default; data collections are app-owned records.
|
|
95
|
+
*/
|
|
96
|
+
type?: ContentCollectionKind;
|
|
91
97
|
/**
|
|
92
98
|
* Source glob or source descriptor understood by the filesystem ingestion
|
|
93
99
|
* layer. CMS-backed projects do not need a runtime source; filesystem
|
|
@@ -140,13 +146,13 @@ interface ContentCollectionConfig<TSchema extends ZodType | undefined = ZodType
|
|
|
140
146
|
*/
|
|
141
147
|
cms?: ContentCmsCollectionConfig;
|
|
142
148
|
}
|
|
143
|
-
type ContentCollectionKind = 'page' | 'data';
|
|
144
149
|
type ContentProviderName = 'filesystem' | (string & {});
|
|
145
150
|
type DefineCollectionOptions<TSchema extends ZodType | undefined = ZodType | undefined> = Omit<ContentCollectionConfig<TSchema>, 'source' | 'exclude'>;
|
|
146
151
|
interface DefineCollectionObject<TSchema extends ZodType | undefined = ZodType | undefined> extends DefineCollectionOptions<TSchema> {
|
|
147
152
|
/**
|
|
148
153
|
* Nuxt Content v3-compatible collection kind. Ginko does not keep separate
|
|
149
|
-
* page/data
|
|
154
|
+
* page/data query builders, but the kind remains canonical metadata at runtime.
|
|
155
|
+
* `data` collections default to `sitemap: false`.
|
|
150
156
|
*/
|
|
151
157
|
type: ContentCollectionKind;
|
|
152
158
|
/**
|
|
@@ -219,6 +225,17 @@ interface ContentCollectionHandle<Name extends string = string, TSchema extends
|
|
|
219
225
|
*/
|
|
220
226
|
readonly __schema: TSchema;
|
|
221
227
|
}
|
|
228
|
+
type IsI18nConfig<TConfig> = TConfig extends {
|
|
229
|
+
i18n: true | ContentCollectionI18nConfig;
|
|
230
|
+
} ? TConfig['i18n'] extends false ? false : true : false;
|
|
231
|
+
type CollectionNameFromConfigKey<Key extends string, TCollection> = TCollection extends ContentCollectionHandle<infer Name, ZodType | undefined, boolean> ? [Name] extends [never] ? Key : Name : Key;
|
|
232
|
+
type NamedContentCollection<Key extends string, TCollection> = TCollection extends ContentCollectionHandle<infer Name, infer TSchema, infer TI18n> ? Omit<TCollection, 'name' | '__schema' | '__i18n'> & ContentCollectionHandle<[
|
|
233
|
+
Name
|
|
234
|
+
] extends [never] ? Key : Name, TSchema, TI18n> : TCollection extends ContentCollectionConfig<infer TSchema> ? Omit<TCollection, 'name' | '__schema' | '__i18n'> & ContentCollectionHandle<CollectionNameFromConfigKey<Key, TCollection>, TSchema, IsI18nConfig<TCollection>> : TCollection;
|
|
235
|
+
type NamedContentCollections<TCollections extends Record<string, ContentCollectionConfig>> = {
|
|
236
|
+
[Key in keyof TCollections]: Key extends string ? NamedContentCollection<Key, TCollections[Key]> : TCollections[Key];
|
|
237
|
+
};
|
|
238
|
+
declare function normalizeContentConfigCollectionNames<TCollections extends Record<string, ContentCollectionConfig>>(collections: TCollections): NamedContentCollections<TCollections>;
|
|
222
239
|
|
|
223
240
|
type LayoutKey = string;
|
|
224
241
|
interface ContentSeoImage {
|
|
@@ -1107,12 +1124,20 @@ interface ContentQueryRequest {
|
|
|
1107
1124
|
*/
|
|
1108
1125
|
type ContentQueryFetcher<T> = (query: ContentQueryRequest) => Promise<ContentQueryResponse<T>>;
|
|
1109
1126
|
type QueryMatchOperator = (item: unknown, condition: unknown) => boolean;
|
|
1110
|
-
|
|
1127
|
+
declare global {
|
|
1128
|
+
interface GinkoContentCollectionMap {
|
|
1129
|
+
}
|
|
1130
|
+
interface GinkoContentCollectionI18nMap {
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
interface ContentCollectionMap extends GinkoContentCollectionMap {
|
|
1111
1134
|
}
|
|
1112
|
-
interface ContentCollectionI18nMap {
|
|
1135
|
+
interface ContentCollectionI18nMap extends GinkoContentCollectionI18nMap {
|
|
1113
1136
|
}
|
|
1114
|
-
type ContentCollectionName = keyof
|
|
1137
|
+
type ContentCollectionName = keyof GinkoContentCollectionMap & string;
|
|
1115
1138
|
type ContentCollectionItem<K extends ContentCollectionName> = ContentCollectionMap[K];
|
|
1139
|
+
type ContentCollectionStringName = [ContentCollectionName] extends [never] ? string : ContentCollectionName;
|
|
1140
|
+
type ContentCollectionTarget = ContentCollectionHandle | ContentCollectionStringName;
|
|
1116
1141
|
/**
|
|
1117
1142
|
* ============================================================================
|
|
1118
1143
|
* Unified Query API — public surface (ADR-0016)
|
|
@@ -1203,8 +1228,8 @@ type HandleSchema<H> = H extends {
|
|
|
1203
1228
|
__schema: infer S;
|
|
1204
1229
|
} ? S extends {
|
|
1205
1230
|
_output: infer O;
|
|
1206
|
-
} ? O & StrictParsedContentMeta : StrictParsedContentMeta : StrictParsedContentMeta;
|
|
1207
|
-
type SelectFields<H> =
|
|
1231
|
+
} ? O & StrictParsedContentMeta : StrictParsedContentMeta : H extends ContentCollectionName ? ContentCollectionMap[H] & StrictParsedContentMeta : StrictParsedContentMeta;
|
|
1232
|
+
type SelectFields<H> = string extends H ? ReadonlyArray<string> : ReadonlyArray<Extract<keyof HandleSchema<H>, string>>;
|
|
1208
1233
|
/**
|
|
1209
1234
|
* Runtime document shape inferred from a collection handle.
|
|
1210
1235
|
*/
|
|
@@ -1212,12 +1237,12 @@ type DocumentFromHandle<H> = H extends {
|
|
|
1212
1237
|
__schema: {
|
|
1213
1238
|
_output: infer O;
|
|
1214
1239
|
};
|
|
1215
|
-
} ? O &
|
|
1240
|
+
} ? O & StrictParsedContent : H extends ContentCollectionName ? ContentCollectionMap[H] : ParsedContent;
|
|
1216
1241
|
/**
|
|
1217
1242
|
* Explicit reference population map. Keys are fields on the source document;
|
|
1218
1243
|
* values are target collection handles.
|
|
1219
1244
|
*/
|
|
1220
|
-
type PopulateSpec = Record<string,
|
|
1245
|
+
type PopulateSpec = Record<string, ContentCollectionTarget>;
|
|
1221
1246
|
type PopulatedDocument<T, P> = P extends undefined ? T : P extends PopulateSpec ? string extends keyof P ? T : Omit<T, keyof P> & {
|
|
1222
1247
|
[K in keyof P & keyof T]: T[K] extends ReadonlyArray<unknown> ? Array<LocalizedDoc<DocumentFromHandle<P[K]>>> : LocalizedDoc<DocumentFromHandle<P[K]>> | null;
|
|
1223
1248
|
} : T;
|
|
@@ -1232,7 +1257,7 @@ type PopulateOption<P extends PopulateSpec | undefined = undefined> = {
|
|
|
1232
1257
|
*/
|
|
1233
1258
|
type HandleIsI18n<H> = H extends {
|
|
1234
1259
|
__i18n: infer I;
|
|
1235
|
-
} ? I extends true ? true : false : false;
|
|
1260
|
+
} ? I extends true ? true : false : H extends keyof GinkoContentCollectionI18nMap ? true : false;
|
|
1236
1261
|
/**
|
|
1237
1262
|
* Locale option made required when the handle is i18n.
|
|
1238
1263
|
*/
|
|
@@ -1349,7 +1374,7 @@ interface PaginationResult<T = ParsedContentMeta> {
|
|
|
1349
1374
|
nextPage: number | null;
|
|
1350
1375
|
prevPage: number | null;
|
|
1351
1376
|
}
|
|
1352
|
-
type BacklinkSource =
|
|
1377
|
+
type BacklinkSource = ContentCollectionTarget;
|
|
1353
1378
|
type SourceName<S> = S extends string ? S : S extends {
|
|
1354
1379
|
name: infer N;
|
|
1355
1380
|
} ? Extract<N, string> : string;
|
|
@@ -1376,7 +1401,7 @@ type BacklinksOptions<Target = unknown, Source extends BacklinkSource | Readonly
|
|
|
1376
1401
|
limit?: number;
|
|
1377
1402
|
skip?: number;
|
|
1378
1403
|
fallback?: LocaleFallback;
|
|
1379
|
-
select?:
|
|
1404
|
+
select?: string extends Source ? ReadonlyArray<string> : ReadonlyArray<Extract<keyof DocumentFromSource<Source>, string>>;
|
|
1380
1405
|
} & BacklinksLocaleOption<Target, Source> & PopulateOption<P>;
|
|
1381
1406
|
type BacklinksResult<Source extends BacklinkSource | ReadonlyArray<BacklinkSource> = BacklinkSource | ReadonlyArray<BacklinkSource>, P extends PopulateSpec | undefined = undefined> = Array<LocalizedDoc<PopulatedDocument<DocumentFromSource<Source>, P>>>;
|
|
1382
1407
|
/**
|
|
@@ -1448,7 +1473,7 @@ interface ContentMiniSearchOptions {
|
|
|
1448
1473
|
/**
|
|
1449
1474
|
* Fields copied into search result payloads.
|
|
1450
1475
|
*
|
|
1451
|
-
* @default ['path', 'title', 'excerpt', 'anchor', 'locale']
|
|
1476
|
+
* @default ['path', 'title', 'excerpt', 'anchor', 'locale', 'collection']
|
|
1452
1477
|
*/
|
|
1453
1478
|
storeFields: string[];
|
|
1454
1479
|
/**
|
|
@@ -1478,6 +1503,7 @@ interface ContentSearchPublicRuntimeConfig {
|
|
|
1478
1503
|
}
|
|
1479
1504
|
interface ContentSearchIndexRecord {
|
|
1480
1505
|
id: string;
|
|
1506
|
+
collection: string;
|
|
1481
1507
|
path: string;
|
|
1482
1508
|
title: string;
|
|
1483
1509
|
excerpt: string;
|
|
@@ -1488,6 +1514,7 @@ interface ContentSearchIndexRecord {
|
|
|
1488
1514
|
[field: string]: unknown;
|
|
1489
1515
|
}
|
|
1490
1516
|
interface ContentSearchResult {
|
|
1517
|
+
collection: string;
|
|
1491
1518
|
path: string;
|
|
1492
1519
|
title: string;
|
|
1493
1520
|
excerpt: string;
|
|
@@ -1659,6 +1686,36 @@ interface ContentSitemapAssertOptions {
|
|
|
1659
1686
|
* @default []
|
|
1660
1687
|
*/
|
|
1661
1688
|
requiredCollections?: string[];
|
|
1689
|
+
/**
|
|
1690
|
+
* Public URL paths that must appear in generated sitemap loc values.
|
|
1691
|
+
*
|
|
1692
|
+
* Values are compared by URL pathname, so absolute sitemap URLs are
|
|
1693
|
+
* normalized before assertion. Use this for release QA of important route
|
|
1694
|
+
* identities; keep `requiredCollections` for the broader content invariant.
|
|
1695
|
+
*
|
|
1696
|
+
* @default []
|
|
1697
|
+
*/
|
|
1698
|
+
requiredPaths?: string[];
|
|
1699
|
+
/**
|
|
1700
|
+
* Public URL path prefixes that must not appear in generated sitemap loc
|
|
1701
|
+
* values.
|
|
1702
|
+
*
|
|
1703
|
+
* Use this to guard against internal/generated routes such as `/_payload`,
|
|
1704
|
+
* `/_nuxt`, API routes, or auth-only sections.
|
|
1705
|
+
*
|
|
1706
|
+
* @default []
|
|
1707
|
+
*/
|
|
1708
|
+
forbiddenPathPrefixes?: string[];
|
|
1709
|
+
/**
|
|
1710
|
+
* Fail when generated sitemap URLs use placeholder or local hosts such as
|
|
1711
|
+
* `example.com`, `localhost`, `127.0.0.1`, or `.localhost`.
|
|
1712
|
+
*
|
|
1713
|
+
* Enable this for production-like release checks. It is disabled by default
|
|
1714
|
+
* so local examples and template builds can still generate static output.
|
|
1715
|
+
*
|
|
1716
|
+
* @default false
|
|
1717
|
+
*/
|
|
1718
|
+
requireProductionSiteUrl?: boolean;
|
|
1662
1719
|
/**
|
|
1663
1720
|
* Optional per-sitemap overrides keyed by sitemap name, for example `en-US`.
|
|
1664
1721
|
*
|
|
@@ -1694,7 +1751,8 @@ interface ContentSearchOptions {
|
|
|
1694
1751
|
/**
|
|
1695
1752
|
* Collections included in the built-in index.
|
|
1696
1753
|
*
|
|
1697
|
-
* When omitted,
|
|
1754
|
+
* When omitted, Ginko indexes route-backed public collections only. Data-only
|
|
1755
|
+
* collections are excluded unless listed explicitly.
|
|
1698
1756
|
*/
|
|
1699
1757
|
collections?: string[];
|
|
1700
1758
|
/**
|
|
@@ -1757,8 +1815,9 @@ interface ModuleOptions {
|
|
|
1757
1815
|
* Built-in full-text search configuration.
|
|
1758
1816
|
*
|
|
1759
1817
|
* When enabled, Ginko exposes JSON/Pagefind search endpoints under
|
|
1760
|
-
* the content api base route.
|
|
1761
|
-
*
|
|
1818
|
+
* the content api base route. `useContentSearchData()` and
|
|
1819
|
+
* `useContentSearchResults()` are auto-imported; import the headless
|
|
1820
|
+
* `useContentSearch()` helper from `@lupinum/ginko-content/client`.
|
|
1762
1821
|
*/
|
|
1763
1822
|
search: false | ContentSearchOptions;
|
|
1764
1823
|
/**
|
|
@@ -2041,5 +2100,5 @@ declare module 'nitropack' {
|
|
|
2041
2100
|
}
|
|
2042
2101
|
}
|
|
2043
2102
|
|
|
2044
|
-
export { _default as default };
|
|
2045
|
-
export type { BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, CollectionQueryBuilder, CollectionQueryOperator, CollectionQueryValue, CollectionSchema, ContentCacheArtifact, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionHandle, ContentCollectionI18nConfig, ContentCollectionI18nMap, ContentCollectionItem, ContentCollectionItemSurroundingsOptions, ContentCollectionKind, ContentCollectionMap, ContentCollectionName, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteConfig, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentCollectionSource, ContentCollectionSourceObject, ContentConfig, ContentContext, ContentI18nOptions, ContentLocaleEntry, ContentLocaleRoute, ContentManifest, ContentMiniSearchOptions, ContentNavigationItem, ContentPageResult, ContentPreviewOptions, ContentProviderName, ContentProviderSearchRequest, ContentQueryBuilder, ContentQueryBuilderParams, ContentQueryBuilderWhere, ContentQueryFetcher, ContentQueryRequest, ContentQuerySortFields, ContentQuerySortOptions, ContentQuerySortParams, ContentReferenceSchema, ContentResolvedMeta, ContentRevalidateOptions, ContentRouteMeta, ContentSearchEngine, ContentSearchIndexRecord, ContentSearchOptions, ContentSearchPublicRuntimeConfig, ContentSearchResult, ContentSearchSection, ContentSelector, ContentSeoImage, ContentSeoMeta, ContentSitemapAlternative, ContentSitemapAssertOptions, ContentSitemapAssertSitemapOptions, ContentSitemapEntry, ContentSitemapImage, ContentSitemapOptions, ContentTransformer, ContentTreeItem, ContentVariant, DefineCollectionObject, DefineCollectionOptions, DocumentFromHandle, LocaleFallback, LocalePathEntry, LocalizedDoc, ManifestVariant, ManyOptions, MarkdownNode, MarkdownOptions, MarkdownParsedContent, MarkdownPluginDescriptor, MarkdownPluginOptions, MarkdownRoot, ModuleHooks, ModuleOptions, MountOptions, NavItem, NeighborsOptions, NeighborsResult, OneOptions, PaginationOptions, PaginationResult, ParseContentOptions, ParsedContent, ParsedContentInternalMeta, ParsedContentMeta, PopulateFromOptions, PopulateSpec, PopulatedDocument, QueryGroupBuilder, QueryGroupFunction, QueryMatchOperator, QueryOperators, QueryOrderDirection, QueryOrderOptions, QueryWhere, ResolutionEnvelope, ResolveContentReferenceOptions, ResolveOneOptions, ResolveOneResult, ResolvedContentI18nOptions, ResolvedMarkdownPlugin, ResolvedVariant, SortDirection, SortSpec, StrictParsedContent, StrictParsedContentMeta, Toc, TocLink, TransformContentOptions, TransformContentSource, TreeOptions, VariantsOptions };
|
|
2103
|
+
export { _default as default, normalizeContentConfigCollectionNames };
|
|
2104
|
+
export type { BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, CollectionQueryBuilder, CollectionQueryOperator, CollectionQueryValue, CollectionSchema, ContentCacheArtifact, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionHandle, ContentCollectionI18nConfig, ContentCollectionI18nMap, ContentCollectionItem, ContentCollectionItemSurroundingsOptions, ContentCollectionKind, ContentCollectionMap, ContentCollectionName, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteConfig, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentCollectionSource, ContentCollectionSourceObject, ContentCollectionStringName, ContentCollectionTarget, ContentConfig, ContentContext, ContentI18nOptions, ContentLocaleEntry, ContentLocaleRoute, ContentManifest, ContentMiniSearchOptions, ContentNavigationItem, ContentPageResult, ContentPreviewOptions, ContentProviderName, ContentProviderSearchRequest, ContentQueryBuilder, ContentQueryBuilderParams, ContentQueryBuilderWhere, ContentQueryFetcher, ContentQueryRequest, ContentQuerySortFields, ContentQuerySortOptions, ContentQuerySortParams, ContentReferenceSchema, ContentResolvedMeta, ContentRevalidateOptions, ContentRouteMeta, ContentSearchEngine, ContentSearchIndexRecord, ContentSearchOptions, ContentSearchPublicRuntimeConfig, ContentSearchResult, ContentSearchSection, ContentSelector, ContentSeoImage, ContentSeoMeta, ContentSitemapAlternative, ContentSitemapAssertOptions, ContentSitemapAssertSitemapOptions, ContentSitemapEntry, ContentSitemapImage, ContentSitemapOptions, ContentTransformer, ContentTreeItem, ContentVariant, DefineCollectionObject, DefineCollectionOptions, DocumentFromHandle, LocaleFallback, LocalePathEntry, LocalizedDoc, ManifestVariant, ManyOptions, MarkdownNode, MarkdownOptions, MarkdownParsedContent, MarkdownPluginDescriptor, MarkdownPluginOptions, MarkdownRoot, ModuleHooks, ModuleOptions, MountOptions, NavItem, NeighborsOptions, NeighborsResult, OneOptions, PaginationOptions, PaginationResult, ParseContentOptions, ParsedContent, ParsedContentInternalMeta, ParsedContentMeta, PopulateFromOptions, PopulateSpec, PopulatedDocument, QueryGroupBuilder, QueryGroupFunction, QueryMatchOperator, QueryOperators, QueryOrderDirection, QueryOrderOptions, QueryWhere, ResolutionEnvelope, ResolveContentReferenceOptions, ResolveOneOptions, ResolveOneResult, ResolvedContentI18nOptions, ResolvedMarkdownPlugin, ResolvedVariant, SortDirection, SortSpec, StrictParsedContent, StrictParsedContentMeta, Toc, TocLink, TransformContentOptions, TransformContentSource, TreeOptions, VariantsOptions };
|