@ubean/content 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +27 -25
- package/dist/index.js +24 -224
- package/dist/{runtime-n9EHNy40.js → runtime-Dhw-W7yi.js} +62 -1
- package/dist/{runtime-DN9pr02v.d.ts → runtime-bK3ae0UQ.d.ts} +26 -1
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +2 -2
- package/dist/vite.js +7 -35
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import ubeanContentPlugin, { UbeanContentOptions } from "./vite.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as ContentSourceConfig, C as ContentCollection, D as ContentNavigationItem, E as ContentModuleOptions, M as ContentType, N as MarkdownNode, O as ContentQueryBuilder, P as ParsedContentMeta, S as ContentBody, T as ContentFieldSchema, _ as defineContentCollection, a as getCollection, b as parseFrontmatter, c as parseContentFile, d as ContentSourceScanConfig, f as ScanContentSourcesOptions, g as createQueryBuilder, h as createContentCollection, i as fetchContentNavigation, j as ContentTocItem, k as ContentSchema, l as queryCollection, m as buildNavigation, n as configureContentRuntime, o as getContentItem, p as scanContentSources, r as defineCollection, s as listCollections, t as bootstrapContentFromDisk, u as registerContent, v as generateId, w as ContentDocument, x as parseMarkdown, y as parseContent } from "./runtime-bK3ae0UQ.js";
|
|
3
|
+
//#region src/routing.d.ts
|
|
4
|
+
interface ContentPageRouteOptions {
|
|
5
|
+
/** URL prefix, e.g. `/blog`. `_path` `/hello` becomes `/blog/hello`. */
|
|
6
|
+
prefix?: string;
|
|
7
|
+
includeDrafts?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface DiscoverContentPageRoutesOptions extends ScanContentSourcesOptions {
|
|
10
|
+
includeDrafts?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Map content collection documents to concrete prerender / file-route URLs.
|
|
14
|
+
*
|
|
15
|
+
* Pair with a catch-all page (`pages/blog/[...slug].vue`) that reads
|
|
16
|
+
* `queryCollection('blog')`. Pass the result into `prerender.include` or
|
|
17
|
+
* `collectPrerenderRoutes({ contentRoutes })`.
|
|
18
|
+
*/
|
|
19
|
+
declare function extractContentPageRoutes(docs: Array<Pick<ContentDocument, '_path' | '_draft' | '_partial'>>, options?: ContentPageRouteOptions): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Scan content source directories on disk and return prerender URLs.
|
|
22
|
+
*
|
|
23
|
+
* Used by `ubean build` when `content` is enabled, and by apps that want
|
|
24
|
+
* the same discovery without running Vite.
|
|
25
|
+
*/
|
|
26
|
+
declare function discoverContentPageRoutes(rootDir: string, options?: DiscoverContentPageRoutesOptions): string[];
|
|
27
|
+
//#endregion
|
|
3
28
|
//#region src/live.d.ts
|
|
4
29
|
/**
|
|
5
30
|
* A single entry in a live collection. Must include `_id` and `_path`
|
|
@@ -57,28 +82,5 @@ interface LiveCollection extends Omit<ContentCollection, 'documents' | 'list' |
|
|
|
57
82
|
/** Invalidate the cache, forcing the next fetch to call the loader. */
|
|
58
83
|
invalidate(): void;
|
|
59
84
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Define a live collection that fetches content at request time.
|
|
62
|
-
*
|
|
63
|
-
* The loader is called on every request (unless caching is enabled).
|
|
64
|
-
* Entries are converted to `ContentDocument` shape and can be queried
|
|
65
|
-
* using the standard query builder.
|
|
66
|
-
*
|
|
67
|
-
* @param options Collection definition
|
|
68
|
-
* @returns LiveCollection instance
|
|
69
|
-
*/
|
|
70
|
-
declare function defineLiveCollection(options: LiveCollectionOptions): LiveCollection;
|
|
71
|
-
/**
|
|
72
|
-
* Get a registered live collection by name.
|
|
73
|
-
*/
|
|
74
|
-
declare function getLiveCollection(name: string): LiveCollection | undefined;
|
|
75
|
-
/**
|
|
76
|
-
* List all registered live collection names.
|
|
77
|
-
*/
|
|
78
|
-
declare function listLiveCollections(): string[];
|
|
79
|
-
/**
|
|
80
|
-
* Clear all registered live collections (useful for testing).
|
|
81
|
-
*/
|
|
82
|
-
declare function clearLiveCollections(): void;
|
|
83
85
|
//#endregion
|
|
84
|
-
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentFieldSchema, type ContentModuleOptions, type ContentNavigationItem, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentTocItem, type ContentType, type LiveCollection, type LiveCollectionCacheOptions, type LiveCollectionEntry, type LiveCollectionLoader, type LiveCollectionLoaderParams, type LiveCollectionOptions, type MarkdownNode, type ParsedContentMeta, type UbeanContentOptions,
|
|
86
|
+
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentFieldSchema, type ContentModuleOptions, type ContentNavigationItem, type ContentPageRouteOptions, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentSourceScanConfig, type ContentTocItem, type ContentType, type DiscoverContentPageRoutesOptions, type LiveCollection, type LiveCollectionCacheOptions, type LiveCollectionEntry, type LiveCollectionLoader, type LiveCollectionLoaderParams, type LiveCollectionOptions, type MarkdownNode, type ParsedContentMeta, type ScanContentSourcesOptions, type UbeanContentOptions, bootstrapContentFromDisk, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, discoverContentPageRoutes, extractContentPageRoutes, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, queryCollection as queryContent, registerContent, scanContentSources, ubeanContentPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,234 +1,34 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as parseContent, a as getCollection, c as parseContentFile, d as scanContentSources, f as buildNavigation, g as generateId, h as defineContentCollection, i as fetchContentNavigation, l as queryCollection, m as createQueryBuilder, n as configureContentRuntime, o as getContentItem, p as createContentCollection, r as defineCollection, s as listCollections, t as bootstrapContentFromDisk, u as registerContent, v as parseFrontmatter, y as parseMarkdown } from "./runtime-Dhw-W7yi.js";
|
|
2
2
|
import ubeanContentPlugin from "./vite.js";
|
|
3
|
-
//#region src/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function validateEntry(entry, schema, collectionName) {
|
|
11
|
-
if (!schema) return entry;
|
|
12
|
-
const doc = {
|
|
13
|
-
_type: "json",
|
|
14
|
-
_extension: "json",
|
|
15
|
-
_dir: "",
|
|
16
|
-
_file: "",
|
|
17
|
-
_draft: false,
|
|
18
|
-
_partial: false,
|
|
19
|
-
_empty: false,
|
|
20
|
-
...entry
|
|
21
|
-
};
|
|
22
|
-
if (schema.required) {
|
|
23
|
-
for (const field of schema.required) if (entry[field] === void 0) console.warn(`[ubean/content] Live collection "${collectionName}": entry "${entry._id}" is missing required field "${field}"`);
|
|
24
|
-
}
|
|
25
|
-
if (schema.properties) for (const [field, fieldSchema] of Object.entries(schema.properties)) {
|
|
26
|
-
const value = entry[field];
|
|
27
|
-
if (value === void 0) continue;
|
|
28
|
-
const expectedType = fieldSchema.type;
|
|
29
|
-
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
30
|
-
if (expectedType && actualType !== expectedType && !(expectedType === "date" && value instanceof Date)) console.warn(`[ubean/content] Live collection "${collectionName}": field "${field}" expected "${expectedType}" but got "${actualType}" in entry "${entry._id}"`);
|
|
31
|
-
}
|
|
32
|
-
return doc;
|
|
3
|
+
//#region src/routing.ts
|
|
4
|
+
function joinPrefix(prefix, path) {
|
|
5
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
6
|
+
if (!prefix || prefix === "/") return normalized === "" ? "/" : normalized;
|
|
7
|
+
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
8
|
+
if (normalized === "/") return base || "/";
|
|
9
|
+
return `${base}${normalized}`;
|
|
33
10
|
}
|
|
34
11
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* The loader is called on every request (unless caching is enabled).
|
|
38
|
-
* Entries are converted to `ContentDocument` shape and can be queried
|
|
39
|
-
* using the standard query builder.
|
|
12
|
+
* Map content collection documents to concrete prerender / file-route URLs.
|
|
40
13
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
function defineLiveCollection(options) {
|
|
45
|
-
const cacheStore = /* @__PURE__ */ new Map();
|
|
46
|
-
const fetchEntries = async (params = {}) => {
|
|
47
|
-
const cacheKey = options.cache?.key ? options.cache.key(params) : JSON.stringify(params);
|
|
48
|
-
const now = Date.now();
|
|
49
|
-
if (options.cache && options.cache.ttl > 0) {
|
|
50
|
-
const cached = cacheStore.get(cacheKey);
|
|
51
|
-
if (cached && cached.expiresAt > now) return cached.data;
|
|
52
|
-
}
|
|
53
|
-
const docs = (await options.loader(params)).map((e) => validateEntry(e, options.schema, options.name));
|
|
54
|
-
if (options.cache && options.cache.ttl > 0) cacheStore.set(cacheKey, {
|
|
55
|
-
data: docs,
|
|
56
|
-
expiresAt: now + options.cache.ttl * 1e3
|
|
57
|
-
});
|
|
58
|
-
return docs;
|
|
59
|
-
};
|
|
60
|
-
const collection = {
|
|
61
|
-
name: options.name,
|
|
62
|
-
source: "live",
|
|
63
|
-
type: "json",
|
|
64
|
-
isLive: true,
|
|
65
|
-
loader: options.loader,
|
|
66
|
-
schema: options.schema,
|
|
67
|
-
cache: options.cache,
|
|
68
|
-
async list(params = {}) {
|
|
69
|
-
return fetchEntries(params);
|
|
70
|
-
},
|
|
71
|
-
async getItem(path) {
|
|
72
|
-
const found = (await fetchEntries({ path })).find((d) => d._path === path);
|
|
73
|
-
if (found) return found;
|
|
74
|
-
return (await fetchEntries({})).find((d) => d._path === path) || null;
|
|
75
|
-
},
|
|
76
|
-
query(params = {}) {
|
|
77
|
-
let docsPromise = null;
|
|
78
|
-
const getDocs = () => {
|
|
79
|
-
if (!docsPromise) docsPromise = fetchEntries(params);
|
|
80
|
-
return docsPromise;
|
|
81
|
-
};
|
|
82
|
-
const deferredBuilder = {
|
|
83
|
-
where(fieldOrQuery, operator, value) {
|
|
84
|
-
pendingWhere.push({
|
|
85
|
-
fieldOrQuery,
|
|
86
|
-
operator,
|
|
87
|
-
value
|
|
88
|
-
});
|
|
89
|
-
return deferredBuilder;
|
|
90
|
-
},
|
|
91
|
-
sort(field, direction = "asc") {
|
|
92
|
-
pendingSort.push({
|
|
93
|
-
field,
|
|
94
|
-
direction
|
|
95
|
-
});
|
|
96
|
-
return deferredBuilder;
|
|
97
|
-
},
|
|
98
|
-
limit(count) {
|
|
99
|
-
limitCount = count;
|
|
100
|
-
return deferredBuilder;
|
|
101
|
-
},
|
|
102
|
-
skip(count) {
|
|
103
|
-
skipCount = count;
|
|
104
|
-
return deferredBuilder;
|
|
105
|
-
},
|
|
106
|
-
only(fields) {
|
|
107
|
-
selectedFields = fields;
|
|
108
|
-
return deferredBuilder;
|
|
109
|
-
},
|
|
110
|
-
without(fields) {
|
|
111
|
-
excludedFields = fields;
|
|
112
|
-
return deferredBuilder;
|
|
113
|
-
},
|
|
114
|
-
async find() {
|
|
115
|
-
return applySelection(applyFilters(await getDocs()));
|
|
116
|
-
},
|
|
117
|
-
async findOne() {
|
|
118
|
-
limitCount = 1;
|
|
119
|
-
return applyFilters(await getDocs())[0] || null;
|
|
120
|
-
},
|
|
121
|
-
async findSurround(path, surroundOptions = {}) {
|
|
122
|
-
const before = surroundOptions.before ?? 1;
|
|
123
|
-
const after = surroundOptions.after ?? 1;
|
|
124
|
-
const filtered = applyFilters(await getDocs());
|
|
125
|
-
const index = filtered.findIndex((d) => d._path === path);
|
|
126
|
-
if (index === -1) return [];
|
|
127
|
-
const start = Math.max(0, index - before);
|
|
128
|
-
const end = Math.min(filtered.length, index + after + 1);
|
|
129
|
-
return applySelection(filtered.slice(start, end).filter((_, i) => i !== before));
|
|
130
|
-
},
|
|
131
|
-
async count() {
|
|
132
|
-
return applyFilters(await getDocs()).length;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
let pendingWhere = [];
|
|
136
|
-
let pendingSort = [];
|
|
137
|
-
let limitCount = null;
|
|
138
|
-
let skipCount = 0;
|
|
139
|
-
let selectedFields = null;
|
|
140
|
-
let excludedFields = null;
|
|
141
|
-
function applyFilters(docs) {
|
|
142
|
-
let result = [...docs];
|
|
143
|
-
for (const w of pendingWhere) if (typeof w.fieldOrQuery === "object") for (const [key, val] of Object.entries(w.fieldOrQuery)) result = result.filter((d) => getNested(d, key) === val);
|
|
144
|
-
else {
|
|
145
|
-
const field = w.fieldOrQuery;
|
|
146
|
-
let op = w.operator;
|
|
147
|
-
let val = w.value;
|
|
148
|
-
if (val === void 0 && w.operator !== void 0) {
|
|
149
|
-
op = "==";
|
|
150
|
-
val = w.operator;
|
|
151
|
-
}
|
|
152
|
-
result = result.filter((d) => {
|
|
153
|
-
const dv = getNested(d, field);
|
|
154
|
-
switch (op) {
|
|
155
|
-
case "=":
|
|
156
|
-
case "==":
|
|
157
|
-
case void 0: return dv === val;
|
|
158
|
-
case "!=": return dv !== val;
|
|
159
|
-
case ">": return dv > val;
|
|
160
|
-
case ">=": return dv >= val;
|
|
161
|
-
case "<": return dv < val;
|
|
162
|
-
case "<=": return dv <= val;
|
|
163
|
-
case "contains": return String(dv).includes(val);
|
|
164
|
-
case "in": return Array.isArray(val) && val.includes(dv);
|
|
165
|
-
case "exists": return val ? dv !== void 0 : dv === void 0;
|
|
166
|
-
default: return dv === val;
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (pendingSort.length === 0) pendingSort = [{
|
|
171
|
-
field: "_path",
|
|
172
|
-
direction: "asc"
|
|
173
|
-
}];
|
|
174
|
-
result.sort((a, b) => {
|
|
175
|
-
for (const { field, direction } of pendingSort) {
|
|
176
|
-
const av = getNested(a, field);
|
|
177
|
-
const bv = getNested(b, field);
|
|
178
|
-
if (av === bv) continue;
|
|
179
|
-
const cmp = av < bv ? -1 : 1;
|
|
180
|
-
return direction === "desc" ? -cmp : cmp;
|
|
181
|
-
}
|
|
182
|
-
return 0;
|
|
183
|
-
});
|
|
184
|
-
if (skipCount > 0) result = result.slice(skipCount);
|
|
185
|
-
if (limitCount !== null) result = result.slice(0, limitCount);
|
|
186
|
-
return result;
|
|
187
|
-
}
|
|
188
|
-
function applySelection(docs) {
|
|
189
|
-
if (!selectedFields && !excludedFields) return docs;
|
|
190
|
-
return docs.map((doc) => {
|
|
191
|
-
const newDoc = { ...doc };
|
|
192
|
-
if (excludedFields) for (const f of excludedFields) delete newDoc[f];
|
|
193
|
-
if (selectedFields) {
|
|
194
|
-
const kept = {};
|
|
195
|
-
for (const f of selectedFields) kept[f] = doc[f];
|
|
196
|
-
kept._id = doc._id;
|
|
197
|
-
kept._path = doc._path;
|
|
198
|
-
return kept;
|
|
199
|
-
}
|
|
200
|
-
return newDoc;
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return deferredBuilder;
|
|
204
|
-
},
|
|
205
|
-
invalidate() {
|
|
206
|
-
cacheStore.clear();
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
liveCollections.set(options.name, collection);
|
|
210
|
-
return collection;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Get a registered live collection by name.
|
|
14
|
+
* Pair with a catch-all page (`pages/blog/[...slug].vue`) that reads
|
|
15
|
+
* `queryCollection('blog')`. Pass the result into `prerender.include` or
|
|
16
|
+
* `collectPrerenderRoutes({ contentRoutes })`.
|
|
214
17
|
*/
|
|
215
|
-
function
|
|
216
|
-
|
|
18
|
+
function extractContentPageRoutes(docs, options = {}) {
|
|
19
|
+
const includeDrafts = options.includeDrafts === true;
|
|
20
|
+
return docs.filter((doc) => (includeDrafts || !doc._draft) && !doc._partial).map((doc) => joinPrefix(options.prefix, doc._path || "/"));
|
|
217
21
|
}
|
|
218
22
|
/**
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Clear all registered live collections (useful for testing).
|
|
23
|
+
* Scan content source directories on disk and return prerender URLs.
|
|
24
|
+
*
|
|
25
|
+
* Used by `ubean build` when `content` is enabled, and by apps that want
|
|
26
|
+
* the same discovery without running Vite.
|
|
226
27
|
*/
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return path.split(".").reduce((o, key) => o?.[key], obj);
|
|
28
|
+
function discoverContentPageRoutes(rootDir, options = {}) {
|
|
29
|
+
const { includeDrafts, ...scanOptions } = options;
|
|
30
|
+
const collections = scanContentSources(rootDir, scanOptions);
|
|
31
|
+
return extractContentPageRoutes(Object.values(collections).flat(), { includeDrafts });
|
|
232
32
|
}
|
|
233
33
|
//#endregion
|
|
234
|
-
export {
|
|
34
|
+
export { bootstrapContentFromDisk, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, discoverContentPageRoutes, extractContentPageRoutes, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, queryCollection as queryContent, registerContent, scanContentSources, ubeanContentPlugin };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { join, relative } from "pathe";
|
|
1
2
|
import { getBasename, getDirname, getExtension, getStem, normalizePath, pathToTitle } from "@ubean/shared";
|
|
2
3
|
import { kebabCase } from "scule";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
5
|
//#region src/core.ts
|
|
4
6
|
function generateId(path, extension) {
|
|
5
7
|
const cleanPath = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -469,6 +471,53 @@ function createContentCollection(name, source, documents = []) {
|
|
|
469
471
|
});
|
|
470
472
|
}
|
|
471
473
|
//#endregion
|
|
474
|
+
//#region src/scan.ts
|
|
475
|
+
function joinPrefix(prefix, path) {
|
|
476
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
477
|
+
if (!prefix || prefix === "/") return normalized === "" ? "/" : normalized;
|
|
478
|
+
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
479
|
+
if (normalized === "/") return base || "/";
|
|
480
|
+
return `${base}${normalized}`;
|
|
481
|
+
}
|
|
482
|
+
function walkContentFiles(dir, files = []) {
|
|
483
|
+
if (!existsSync(dir)) return files;
|
|
484
|
+
for (const entry of readdirSync(dir)) {
|
|
485
|
+
const fullPath = join(dir, entry);
|
|
486
|
+
if (statSync(fullPath).isDirectory()) {
|
|
487
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
488
|
+
walkContentFiles(fullPath, files);
|
|
489
|
+
} else if (/\.(md|mdx|json|ya?ml)$/.test(entry)) files.push(fullPath);
|
|
490
|
+
}
|
|
491
|
+
return files;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Read content source directories on disk into named document arrays.
|
|
495
|
+
*
|
|
496
|
+
* Used by the Vite plugin (dev), production server bootstrap, and
|
|
497
|
+
* prerender URL discovery so `queryCollection` is not Vite-only.
|
|
498
|
+
*/
|
|
499
|
+
function scanContentSources(rootDir, options = {}) {
|
|
500
|
+
const defaultDir = options.defaultDir ?? "content";
|
|
501
|
+
const sources = options.sources ?? { content: { dir: defaultDir } };
|
|
502
|
+
const loaded = {};
|
|
503
|
+
for (const [name, source] of Object.entries(sources)) {
|
|
504
|
+
const contentDir = join(rootDir, source.dir || defaultDir);
|
|
505
|
+
const documents = [];
|
|
506
|
+
for (const fullPath of walkContentFiles(contentDir)) {
|
|
507
|
+
const rel = relative(contentDir, fullPath).replace(/\\/g, "/");
|
|
508
|
+
try {
|
|
509
|
+
const parsed = parseContent(readFileSync(fullPath, "utf-8"), rel, { type: source.type });
|
|
510
|
+
if (source.prefix) parsed._path = joinPrefix(source.prefix, parsed._path);
|
|
511
|
+
documents.push(parsed);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.warn(`[ubean-content] Failed to parse ${rel}:`, err);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
loaded[name] = documents;
|
|
517
|
+
}
|
|
518
|
+
return loaded;
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
472
521
|
//#region src/runtime.ts
|
|
473
522
|
let collections = /* @__PURE__ */ new Map();
|
|
474
523
|
let navigationCache = null;
|
|
@@ -528,5 +577,17 @@ function registerContent(collectionName, documents) {
|
|
|
528
577
|
function parseContentFile(raw, filePath, options) {
|
|
529
578
|
return parseContent(raw, filePath, options);
|
|
530
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* Fill the in-memory collection map from disk (production SSR / tests).
|
|
582
|
+
*
|
|
583
|
+
* Dev already does this via the Vite plugin; call this from a custom
|
|
584
|
+
* server entry if you skip the generated production snapshot.
|
|
585
|
+
*/
|
|
586
|
+
function bootstrapContentFromDisk(rootDir, options = {}) {
|
|
587
|
+
configureContentRuntime();
|
|
588
|
+
const loaded = scanContentSources(rootDir, options);
|
|
589
|
+
for (const [name, docs] of Object.entries(loaded)) registerContent(name, docs);
|
|
590
|
+
return loaded;
|
|
591
|
+
}
|
|
531
592
|
//#endregion
|
|
532
|
-
export {
|
|
593
|
+
export { parseContent as _, getCollection as a, parseContentFile as c, scanContentSources as d, buildNavigation as f, generateId as g, defineContentCollection as h, fetchContentNavigation as i, queryCollection as l, createQueryBuilder as m, configureContentRuntime as n, getContentItem as o, createContentCollection as p, defineCollection as r, listCollections as s, bootstrapContentFromDisk as t, registerContent as u, parseFrontmatter as v, parseMarkdown as y };
|
|
@@ -168,6 +168,24 @@ declare function defineContentCollection(collection: {
|
|
|
168
168
|
}): ContentCollection;
|
|
169
169
|
declare function createContentCollection(name: string, source: string, documents?: ContentDocument[]): ContentCollection;
|
|
170
170
|
//#endregion
|
|
171
|
+
//#region src/scan.d.ts
|
|
172
|
+
interface ContentSourceScanConfig {
|
|
173
|
+
dir: string;
|
|
174
|
+
prefix?: string;
|
|
175
|
+
type?: string;
|
|
176
|
+
}
|
|
177
|
+
interface ScanContentSourcesOptions {
|
|
178
|
+
sources?: Record<string, ContentSourceScanConfig>;
|
|
179
|
+
defaultDir?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Read content source directories on disk into named document arrays.
|
|
183
|
+
*
|
|
184
|
+
* Used by the Vite plugin (dev), production server bootstrap, and
|
|
185
|
+
* prerender URL discovery so `queryCollection` is not Vite-only.
|
|
186
|
+
*/
|
|
187
|
+
declare function scanContentSources(rootDir: string, options?: ScanContentSourcesOptions): Record<string, ContentDocument[]>;
|
|
188
|
+
//#endregion
|
|
171
189
|
//#region src/runtime.d.ts
|
|
172
190
|
declare function configureContentRuntime(_options?: Partial<ContentModuleOptions>): void;
|
|
173
191
|
declare function defineCollection(config: {
|
|
@@ -185,5 +203,12 @@ declare function registerContent(collectionName: string, documents: ContentDocum
|
|
|
185
203
|
declare function parseContentFile(raw: string, filePath: string, options?: {
|
|
186
204
|
type?: string;
|
|
187
205
|
}): ContentDocument;
|
|
206
|
+
/**
|
|
207
|
+
* Fill the in-memory collection map from disk (production SSR / tests).
|
|
208
|
+
*
|
|
209
|
+
* Dev already does this via the Vite plugin; call this from a custom
|
|
210
|
+
* server entry if you skip the generated production snapshot.
|
|
211
|
+
*/
|
|
212
|
+
declare function bootstrapContentFromDisk(rootDir: string, options?: ScanContentSourcesOptions): Record<string, ContentDocument[]>;
|
|
188
213
|
//#endregion
|
|
189
|
-
export {
|
|
214
|
+
export { ContentSourceConfig as A, ContentCollection as C, ContentNavigationItem as D, ContentModuleOptions as E, ContentType as M, MarkdownNode as N, ContentQueryBuilder as O, ParsedContentMeta as P, ContentBody as S, ContentFieldSchema as T, defineContentCollection as _, getCollection as a, parseFrontmatter as b, parseContentFile as c, ContentSourceScanConfig as d, ScanContentSourcesOptions as f, createQueryBuilder as g, createContentCollection as h, fetchContentNavigation as i, ContentTocItem as j, ContentSchema as k, queryCollection as l, buildNavigation as m, configureContentRuntime as n, getContentItem as o, scanContentSources as p, defineCollection as r, listCollections as s, bootstrapContentFromDisk as t, registerContent as u, generateId as v, ContentDocument as w, parseMarkdown as x, parseContent as y };
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentModuleOptions, type ContentNavigationItem, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentTocItem, type ContentType, type MarkdownNode, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, registerContent };
|
|
1
|
+
import { A as ContentSourceConfig, C as ContentCollection, D as ContentNavigationItem, E as ContentModuleOptions, M as ContentType, N as MarkdownNode, O as ContentQueryBuilder, S as ContentBody, _ as defineContentCollection, a as getCollection, b as parseFrontmatter, c as parseContentFile, g as createQueryBuilder, h as createContentCollection, i as fetchContentNavigation, j as ContentTocItem, k as ContentSchema, l as queryCollection, m as buildNavigation, n as configureContentRuntime, o as getContentItem, r as defineCollection, s as listCollections, t as bootstrapContentFromDisk, u as registerContent, v as generateId, w as ContentDocument, x as parseMarkdown, y as parseContent } from "./runtime-bK3ae0UQ.js";
|
|
2
|
+
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentModuleOptions, type ContentNavigationItem, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentTocItem, type ContentType, type MarkdownNode, bootstrapContentFromDisk, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, registerContent };
|
package/dist/runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, registerContent };
|
|
1
|
+
import { _ as parseContent, a as getCollection, c as parseContentFile, f as buildNavigation, g as generateId, h as defineContentCollection, i as fetchContentNavigation, l as queryCollection, m as createQueryBuilder, n as configureContentRuntime, o as getContentItem, p as createContentCollection, r as defineCollection, s as listCollections, t as bootstrapContentFromDisk, u as registerContent, v as parseFrontmatter, y as parseMarkdown } from "./runtime-Dhw-W7yi.js";
|
|
2
|
+
export { bootstrapContentFromDisk, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, registerContent };
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
1
|
+
import { d as scanContentSources, n as configureContentRuntime, u as registerContent } from "./runtime-Dhw-W7yi.js";
|
|
3
2
|
import { defu } from "defu";
|
|
4
|
-
import { join
|
|
3
|
+
import { join } from "pathe";
|
|
5
4
|
//#region src/vite.ts
|
|
6
5
|
const defaultOptions = {
|
|
7
6
|
sources: { content: { dir: "content" } },
|
|
@@ -20,40 +19,13 @@ function ubeanContentPlugin(userOptions = {}) {
|
|
|
20
19
|
const options = defu(userOptions, defaultOptions);
|
|
21
20
|
let rootDir;
|
|
22
21
|
let loadedDocuments = {};
|
|
23
|
-
function walkDir(dir, baseDir, files = []) {
|
|
24
|
-
if (!existsSync(dir)) return files;
|
|
25
|
-
const entries = readdirSync(dir);
|
|
26
|
-
for (const entry of entries) {
|
|
27
|
-
const fullPath = join(dir, entry);
|
|
28
|
-
if (statSync(fullPath).isDirectory()) {
|
|
29
|
-
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
30
|
-
walkDir(fullPath, baseDir, files);
|
|
31
|
-
} else if (/\.(md|mdx|json|ya?ml)$/.test(entry)) files.push(relative(baseDir, fullPath));
|
|
32
|
-
}
|
|
33
|
-
return files;
|
|
34
|
-
}
|
|
35
22
|
function scanContent() {
|
|
36
|
-
loadedDocuments = {};
|
|
37
23
|
configureContentRuntime();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for (const file of files) {
|
|
44
|
-
const fullPath = join(contentDir, file);
|
|
45
|
-
try {
|
|
46
|
-
const raw = readFileSync(fullPath, "utf-8");
|
|
47
|
-
const parsed = parseContentFile(raw, file, { type: sourceConfig.type });
|
|
48
|
-
if (sourceConfig.prefix) parsed._path = sourceConfig.prefix + parsed._path;
|
|
49
|
-
documents.push(parsed);
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.warn(`[ubean-content] Failed to parse ${file}:`, err);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
loadedDocuments[name] = documents;
|
|
55
|
-
registerContent(name, documents);
|
|
56
|
-
}
|
|
24
|
+
loadedDocuments = scanContentSources(rootDir, {
|
|
25
|
+
sources: options.sources,
|
|
26
|
+
defaultDir: options.defaultDir
|
|
27
|
+
});
|
|
28
|
+
for (const [name, documents] of Object.entries(loadedDocuments)) registerContent(name, documents);
|
|
57
29
|
}
|
|
58
30
|
return {
|
|
59
31
|
name: "ubean:content",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/content",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "File-based content module for ubean (markdown/MDX/YAML/JSON)",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -24,16 +24,16 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@ubean/shared": "0.3.1",
|
|
27
28
|
"defu": "6.1.7",
|
|
28
29
|
"pathe": "^2.0.3",
|
|
29
|
-
"scule": "^1.3.0"
|
|
30
|
-
"@ubean/shared": "0.2.2"
|
|
30
|
+
"scule": "^1.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^26.
|
|
33
|
+
"@types/node": "^26.4.0",
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@0.
|
|
36
|
-
"vite-plus": "0.
|
|
35
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
|
|
36
|
+
"vite-plus": "0.3.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"vite": "^5.0.0 || ^6.0.0"
|