@ubean/content 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,30 @@
1
1
  import ubeanContentPlugin, { UbeanContentOptions } from "./vite.js";
2
- import { A as ParsedContentMeta, C as ContentNavigationItem, D as ContentTocItem, E as ContentSourceConfig, O as ContentType, S as ContentModuleOptions, T as ContentSchema, _ as parseMarkdown, a as getContentItem, b as ContentDocument, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as parseFrontmatter, h as parseContent, i as getCollection, k as MarkdownNode, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, v as ContentBody, w as ContentQueryBuilder, x as ContentFieldSchema, y as ContentCollection } from "./runtime-DN9pr02v.js";
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, buildNavigation, clearLiveCollections, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, defineLiveCollection, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getCollection, getContentItem, getLiveCollection, listCollections, listLiveCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, queryCollection as queryContent, registerContent, ubeanContentPlugin };
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 parseMarkdown, a as getContentItem, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as parseFrontmatter, h as parseContent, i as getCollection, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation } from "./runtime-n9EHNy40.js";
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/live.ts
4
- const liveCollections = /* @__PURE__ */ new Map();
5
- /**
6
- * Validate an entry against a schema. Only checks required fields and basic
7
- * types does not perform full JSON Schema validation. Invalid entries
8
- * are logged to console.warn and still returned (lenient validation).
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
- * Define a live collection that fetches content at request time.
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
- * @param options Collection definition
42
- * @returns LiveCollection instance
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 getLiveCollection(name) {
216
- return liveCollections.get(name);
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
- * List all registered live collection names.
220
- */
221
- function listLiveCollections() {
222
- return Array.from(liveCollections.keys());
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 clearLiveCollections() {
228
- liveCollections.clear();
229
- }
230
- function getNested(obj, path) {
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 { buildNavigation, clearLiveCollections, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, defineLiveCollection, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getCollection, getContentItem, getLiveCollection, listCollections, listLiveCollections, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, queryCollection, queryCollection as queryContent, registerContent, ubeanContentPlugin };
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 { parseMarkdown as _, getContentItem as a, queryCollection as c, createContentCollection as d, createQueryBuilder as f, parseFrontmatter as g, parseContent as h, getCollection as i, registerContent as l, generateId as m, defineCollection as n, listCollections as o, defineContentCollection as p, fetchContentNavigation as r, parseContentFile as s, configureContentRuntime as t, buildNavigation as u };
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 { ParsedContentMeta as A, ContentNavigationItem as C, ContentTocItem as D, ContentSourceConfig as E, ContentType as O, ContentModuleOptions as S, ContentSchema as T, parseMarkdown as _, getContentItem as a, ContentDocument as b, queryCollection as c, createContentCollection as d, createQueryBuilder as f, parseFrontmatter as g, parseContent as h, getCollection as i, MarkdownNode as k, registerContent as l, generateId as m, defineCollection as n, listCollections as o, defineContentCollection as p, fetchContentNavigation as r, parseContentFile as s, configureContentRuntime as t, buildNavigation as u, ContentBody as v, ContentQueryBuilder as w, ContentFieldSchema as x, ContentCollection as y };
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 ContentNavigationItem, D as ContentTocItem, E as ContentSourceConfig, O as ContentType, S as ContentModuleOptions, T as ContentSchema, _ as parseMarkdown, a as getContentItem, b as ContentDocument, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as parseFrontmatter, h as parseContent, i as getCollection, k as MarkdownNode, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, v as ContentBody, w as ContentQueryBuilder, y as ContentCollection } from "./runtime-DN9pr02v.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, 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 parseMarkdown, a as getContentItem, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as parseFrontmatter, h as parseContent, i as getCollection, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation } from "./runtime-n9EHNy40.js";
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 { l as registerContent, s as parseContentFile, t as configureContentRuntime } from "./runtime-n9EHNy40.js";
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, relative, resolve } from "pathe";
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
- for (const [name, sourceConfig] of Object.entries(options.sources || {})) {
39
- const contentDir = resolve(rootDir, sourceConfig.dir || options.defaultDir);
40
- if (!existsSync(contentDir)) continue;
41
- const files = walkDir(contentDir, contentDir);
42
- const documents = [];
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.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "File-based content module for ubean (markdown/MDX/YAML/JSON)",
5
5
  "files": [
6
6
  "dist"
@@ -27,7 +27,7 @@
27
27
  "defu": "6.1.7",
28
28
  "pathe": "^2.0.3",
29
29
  "scule": "^1.3.0",
30
- "@ubean/shared": "0.2.2"
30
+ "@ubean/shared": "0.3.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^26.2.0",