scribe-cms 0.0.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/README.md +150 -0
- package/dist/cli/index.cjs +2303 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2289 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +2268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +2238 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.cjs +1216 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.js +1207 -0
- package/dist/runtime.js.map +1 -0
- package/dist/src/config/load-config.d.ts +10 -0
- package/dist/src/config/load-config.d.ts.map +1 -0
- package/dist/src/config/load-env.d.ts +3 -0
- package/dist/src/config/load-env.d.ts.map +1 -0
- package/dist/src/config/resolve-config.d.ts +13 -0
- package/dist/src/config/resolve-config.d.ts.map +1 -0
- package/dist/src/core/alias-helpers.d.ts +7 -0
- package/dist/src/core/alias-helpers.d.ts.map +1 -0
- package/dist/src/core/builtin-fields.d.ts +38 -0
- package/dist/src/core/builtin-fields.d.ts.map +1 -0
- package/dist/src/core/field.d.ts +60 -0
- package/dist/src/core/field.d.ts.map +1 -0
- package/dist/src/core/introspect-schema.d.ts +20 -0
- package/dist/src/core/introspect-schema.d.ts.map +1 -0
- package/dist/src/core/slug-aliases.d.ts +27 -0
- package/dist/src/core/slug-aliases.d.ts.map +1 -0
- package/dist/src/core/types.d.ts +261 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/create-project.d.ts +10 -0
- package/dist/src/create-project.d.ts.map +1 -0
- package/dist/src/create-scribe.d.ts +11 -0
- package/dist/src/create-scribe.d.ts.map +1 -0
- package/dist/src/hash/page-hash.d.ts +7 -0
- package/dist/src/hash/page-hash.d.ts.map +1 -0
- package/dist/src/history/record-revision.d.ts +12 -0
- package/dist/src/history/record-revision.d.ts.map +1 -0
- package/dist/src/i18n/build-url.d.ts +14 -0
- package/dist/src/i18n/build-url.d.ts.map +1 -0
- package/dist/src/i18n/resolve-document.d.ts +12 -0
- package/dist/src/i18n/resolve-document.d.ts.map +1 -0
- package/dist/src/i18n/translation-index.d.ts +7 -0
- package/dist/src/i18n/translation-index.d.ts.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/loader/create-loader.d.ts +17 -0
- package/dist/src/loader/create-loader.d.ts.map +1 -0
- package/dist/src/loader/normalize-en.d.ts +3 -0
- package/dist/src/loader/normalize-en.d.ts.map +1 -0
- package/dist/src/loader/parse-mdx.d.ts +7 -0
- package/dist/src/loader/parse-mdx.d.ts.map +1 -0
- package/dist/src/redirects/build-redirects.d.ts +17 -0
- package/dist/src/redirects/build-redirects.d.ts.map +1 -0
- package/dist/src/redirects/translation-index.d.ts +4 -0
- package/dist/src/redirects/translation-index.d.ts.map +1 -0
- package/dist/src/redirects/types.d.ts +6 -0
- package/dist/src/redirects/types.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +5 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/sitemap/generate-sitemap.d.ts +5 -0
- package/dist/src/sitemap/generate-sitemap.d.ts.map +1 -0
- package/dist/src/sitemap/join-base-url.d.ts +3 -0
- package/dist/src/sitemap/join-base-url.d.ts.map +1 -0
- package/dist/src/sitemap/types.d.ts +39 -0
- package/dist/src/sitemap/types.d.ts.map +1 -0
- package/dist/src/storage/sqlite.d.ts +9 -0
- package/dist/src/storage/sqlite.d.ts.map +1 -0
- package/dist/src/storage/translations.d.ts +58 -0
- package/dist/src/storage/translations.d.ts.map +1 -0
- package/dist/src/translate/gemini-client.d.ts +15 -0
- package/dist/src/translate/gemini-client.d.ts.map +1 -0
- package/dist/src/translate/page-translator.d.ts +24 -0
- package/dist/src/translate/page-translator.d.ts.map +1 -0
- package/dist/src/translate/prompts/translation-prompt.d.ts +14 -0
- package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -0
- package/dist/src/translate/resolve-translate-config.d.ts +10 -0
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -0
- package/dist/src/translate/worklist.d.ts +19 -0
- package/dist/src/translate/worklist.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts +16 -0
- package/dist/src/validate/validate-project.d.ts.map +1 -0
- package/dist/src/validate/validate-relations.d.ts +19 -0
- package/dist/src/validate/validate-relations.d.ts.map +1 -0
- package/dist/src/validate/validate-slug-suffix.d.ts +12 -0
- package/dist/src/validate/validate-slug-suffix.d.ts.map +1 -0
- package/dist/studio/server.cjs +673 -0
- package/dist/studio/server.cjs.map +1 -0
- package/dist/studio/server.d.ts +7 -0
- package/dist/studio/server.d.ts.map +1 -0
- package/dist/studio/server.js +664 -0
- package/dist/studio/server.js.map +1 -0
- package/package.json +70 -0
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
import path3 from 'path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import fs2 from 'fs';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
// src/config/resolve-config.ts
|
|
8
|
+
|
|
9
|
+
// src/i18n/build-url.ts
|
|
10
|
+
var SLUG_PLACEHOLDER = "{slug}";
|
|
11
|
+
function isRoutableType(type) {
|
|
12
|
+
return typeof type.path === "string" && type.path.length > 0;
|
|
13
|
+
}
|
|
14
|
+
function assertValidPathTemplate(path5, typeId) {
|
|
15
|
+
if (!path5.startsWith("/")) {
|
|
16
|
+
throw new Error(`Content type "${typeId}": path must start with / (got "${path5}")`);
|
|
17
|
+
}
|
|
18
|
+
const count = (path5.match(/\{slug\}/g) ?? []).length;
|
|
19
|
+
if (count !== 1) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Content type "${typeId}": path must contain exactly one {slug} (got "${path5}")`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function pathPrefix(template) {
|
|
26
|
+
const idx = template.indexOf(SLUG_PLACEHOLDER);
|
|
27
|
+
if (idx === -1) throw new Error(`Invalid path template (missing {slug}): ${template}`);
|
|
28
|
+
return template.slice(0, idx);
|
|
29
|
+
}
|
|
30
|
+
function pathSuffix(template) {
|
|
31
|
+
const idx = template.indexOf(SLUG_PLACEHOLDER);
|
|
32
|
+
if (idx === -1) throw new Error(`Invalid path template (missing {slug}): ${template}`);
|
|
33
|
+
return template.slice(idx + SLUG_PLACEHOLDER.length);
|
|
34
|
+
}
|
|
35
|
+
function resolvePath(template, slug, locale, defaultLocale) {
|
|
36
|
+
const relative = template.replace(SLUG_PLACEHOLDER, slug);
|
|
37
|
+
if (locale === defaultLocale) return relative;
|
|
38
|
+
return `/${locale}${relative}`;
|
|
39
|
+
}
|
|
40
|
+
function extractSlugFromResolvedPath(template, resolvedPath) {
|
|
41
|
+
const prefix = pathPrefix(template);
|
|
42
|
+
const suffix = pathSuffix(template);
|
|
43
|
+
if (!resolvedPath.startsWith(prefix)) return null;
|
|
44
|
+
if (suffix && !resolvedPath.endsWith(suffix)) return null;
|
|
45
|
+
const slugEnd = suffix ? resolvedPath.length - suffix.length : resolvedPath.length;
|
|
46
|
+
const slug = resolvedPath.slice(prefix.length, slugEnd);
|
|
47
|
+
return slug.length > 0 ? slug : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/config/resolve-config.ts
|
|
51
|
+
var RESOLVED = /* @__PURE__ */ Symbol.for("@genlook/scribe/resolvedConfig");
|
|
52
|
+
function isResolvedConfig(config) {
|
|
53
|
+
return typeof config === "object" && config !== null && config[RESOLVED] === true;
|
|
54
|
+
}
|
|
55
|
+
function resolveConfig(input, baseDir) {
|
|
56
|
+
if (isResolvedConfig(input)) return input;
|
|
57
|
+
const raw = input;
|
|
58
|
+
if (!raw.rootDir) {
|
|
59
|
+
throw new Error("scribe config: rootDir is required");
|
|
60
|
+
}
|
|
61
|
+
if (!raw.locales || raw.locales.length === 0) {
|
|
62
|
+
throw new Error("scribe config: locales must be a non-empty array");
|
|
63
|
+
}
|
|
64
|
+
const defaultLocale = raw.defaultLocale ?? "en";
|
|
65
|
+
if (!raw.locales.includes(defaultLocale)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`scribe config: defaultLocale "${defaultLocale}" is not in locales [${raw.locales.join(", ")}]`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const projectRoot = path3.resolve(process.cwd(), raw.rootDir);
|
|
71
|
+
const contentRoot = path3.resolve(projectRoot, raw.contentDir ?? "content");
|
|
72
|
+
const storePath = path3.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
73
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
74
|
+
const types = raw.types.map((type) => {
|
|
75
|
+
if (seenIds.has(type.id)) {
|
|
76
|
+
throw new Error(`scribe config: duplicate content type id "${type.id}"`);
|
|
77
|
+
}
|
|
78
|
+
seenIds.add(type.id);
|
|
79
|
+
if (type.path) {
|
|
80
|
+
assertValidPathTemplate(type.path, type.id);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...type,
|
|
84
|
+
contentDir: type.contentDir ?? type.id,
|
|
85
|
+
label: type.label ?? type.id.charAt(0).toUpperCase() + type.id.slice(1),
|
|
86
|
+
slugStrategy: type.slugStrategy ?? "fixed",
|
|
87
|
+
indexFallback: type.indexFallback ?? (type.path ? "en" : "none")
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const config = {
|
|
91
|
+
rootDir: contentRoot,
|
|
92
|
+
storePath,
|
|
93
|
+
locales: [...raw.locales],
|
|
94
|
+
defaultLocale,
|
|
95
|
+
localePresets: raw.localePresets,
|
|
96
|
+
translate: raw.translate,
|
|
97
|
+
types
|
|
98
|
+
};
|
|
99
|
+
Object.defineProperty(config, RESOLVED, {
|
|
100
|
+
value: true,
|
|
101
|
+
enumerable: false
|
|
102
|
+
});
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
105
|
+
var FIELD_KIND = /* @__PURE__ */ Symbol.for("@genlook/scribe/fieldKind");
|
|
106
|
+
var RELATION_META = /* @__PURE__ */ Symbol.for("@genlook/scribe/relationMeta");
|
|
107
|
+
function getFieldKind(schema) {
|
|
108
|
+
const tagged = schema;
|
|
109
|
+
return tagged[FIELD_KIND] ?? "structural";
|
|
110
|
+
}
|
|
111
|
+
function getRelationTarget(schema) {
|
|
112
|
+
let current = schema;
|
|
113
|
+
for (let i = 0; i < 8; i++) {
|
|
114
|
+
const tagged = current;
|
|
115
|
+
if (tagged[RELATION_META]) {
|
|
116
|
+
return tagged[RELATION_META];
|
|
117
|
+
}
|
|
118
|
+
if (typeof tagged.unwrap === "function") {
|
|
119
|
+
current = tagged.unwrap();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (typeof tagged.removeDefault === "function") {
|
|
123
|
+
current = tagged.removeDefault();
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (tagged._def?.innerType) {
|
|
127
|
+
current = tagged._def.innerType;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function unwrapSchema(schema) {
|
|
135
|
+
let current = schema;
|
|
136
|
+
for (let i = 0; i < 8; i++) {
|
|
137
|
+
const anySchema = current;
|
|
138
|
+
if (typeof anySchema.unwrap === "function") {
|
|
139
|
+
current = anySchema.unwrap();
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (typeof anySchema.removeDefault === "function") {
|
|
143
|
+
current = anySchema.removeDefault();
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (anySchema._def?.innerType) {
|
|
147
|
+
current = anySchema._def.innerType;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
return current;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/core/introspect-schema.ts
|
|
156
|
+
function introspectSchema(schema, prefix = []) {
|
|
157
|
+
const relation = getRelationTarget(schema);
|
|
158
|
+
if (relation && prefix.length > 0) {
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
path: prefix,
|
|
162
|
+
kind: "relation",
|
|
163
|
+
relationTarget: relation.typeId,
|
|
164
|
+
relationMultiple: relation.multiple,
|
|
165
|
+
relationOptional: relation.optional
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
const base = unwrapSchema(schema);
|
|
170
|
+
if (base instanceof Object && "shape" in base) {
|
|
171
|
+
const shape = base.shape;
|
|
172
|
+
const fields = [];
|
|
173
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
174
|
+
fields.push(...introspectSchema(child, [...prefix, key]));
|
|
175
|
+
}
|
|
176
|
+
return fields;
|
|
177
|
+
}
|
|
178
|
+
if (base instanceof Object && "element" in base) {
|
|
179
|
+
const element = base.element;
|
|
180
|
+
const elementBase = unwrapSchema(element);
|
|
181
|
+
if (elementBase instanceof Object && "shape" in elementBase) {
|
|
182
|
+
const shape = elementBase.shape;
|
|
183
|
+
const fields = [];
|
|
184
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
185
|
+
fields.push(...introspectSchema(child, [...prefix, "*", key]));
|
|
186
|
+
}
|
|
187
|
+
return fields;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return [{ path: prefix, kind: getFieldKind(schema) }];
|
|
191
|
+
}
|
|
192
|
+
function extractByPaths(data, paths) {
|
|
193
|
+
const out = {};
|
|
194
|
+
for (const path5 of paths) {
|
|
195
|
+
const value = getAtPath(data, path5);
|
|
196
|
+
if (value !== void 0) {
|
|
197
|
+
setAtPath(out, path5.filter((p) => p !== "*"), value);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function pickTranslatable(data, schema) {
|
|
203
|
+
const translatablePaths = introspectSchema(schema).filter((f) => f.kind === "translatable").map((f) => f.path);
|
|
204
|
+
return extractByPaths(data, translatablePaths);
|
|
205
|
+
}
|
|
206
|
+
function pickStructural(data, schema) {
|
|
207
|
+
const structuralPaths = introspectSchema(schema).filter((f) => f.kind === "structural" || f.kind === "relation").map((f) => f.path);
|
|
208
|
+
return extractByPaths(data, structuralPaths);
|
|
209
|
+
}
|
|
210
|
+
function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
211
|
+
const structural = pickStructural(enData, schema);
|
|
212
|
+
const translatable = pickTranslatable(localeData, schema);
|
|
213
|
+
return deepMerge(structural, translatable);
|
|
214
|
+
}
|
|
215
|
+
function getAtPath(obj, path5) {
|
|
216
|
+
let current = obj;
|
|
217
|
+
for (const segment of path5) {
|
|
218
|
+
if (segment === "*") {
|
|
219
|
+
if (!Array.isArray(current)) return void 0;
|
|
220
|
+
return current.map(
|
|
221
|
+
(item) => typeof item === "object" && item !== null ? getAtPath(item, path5.slice(path5.indexOf("*") + 1)) : void 0
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
225
|
+
current = current[segment];
|
|
226
|
+
}
|
|
227
|
+
return current;
|
|
228
|
+
}
|
|
229
|
+
function setAtPath(obj, path5, value) {
|
|
230
|
+
if (path5.length === 0) return;
|
|
231
|
+
if (path5.length === 1) {
|
|
232
|
+
obj[path5[0]] = value;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const [head, ...rest] = path5;
|
|
236
|
+
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
237
|
+
obj[head] = {};
|
|
238
|
+
}
|
|
239
|
+
setAtPath(obj[head], rest, value);
|
|
240
|
+
}
|
|
241
|
+
function deepMerge(base, overlay) {
|
|
242
|
+
const out = { ...base };
|
|
243
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
244
|
+
if (value && typeof value === "object" && !Array.isArray(value) && out[key] && typeof out[key] === "object" && !Array.isArray(out[key])) {
|
|
245
|
+
out[key] = deepMerge(out[key], value);
|
|
246
|
+
} else if (Array.isArray(value) && Array.isArray(out[key])) {
|
|
247
|
+
out[key] = mergeArrayOverlay(out[key], value);
|
|
248
|
+
} else {
|
|
249
|
+
out[key] = value;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
function mergeArrayOverlay(base, overlay) {
|
|
255
|
+
return base.map((item, index) => {
|
|
256
|
+
const overlayItem = overlay[index];
|
|
257
|
+
if (item && typeof item === "object" && !Array.isArray(item) && overlayItem && typeof overlayItem === "object" && !Array.isArray(overlayItem)) {
|
|
258
|
+
return deepMerge(item, overlayItem);
|
|
259
|
+
}
|
|
260
|
+
return overlayItem ?? item;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function listRelationFields(schema) {
|
|
264
|
+
return introspectSchema(schema).filter((f) => f.kind === "relation");
|
|
265
|
+
}
|
|
266
|
+
var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
267
|
+
var slugPatternSchema = z.string().regex(SLUG_PATTERN, "slug must be lowercase-kebab-case");
|
|
268
|
+
var isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]*)?$/, "Use ISO date YYYY-MM-DD or full ISO 8601");
|
|
269
|
+
var canonicalPathSchema = z.string().regex(/^\//, "canonicalPath must start with /");
|
|
270
|
+
function extractBuiltinEnFields(data, pathTemplate, enSlug, defaultLocale) {
|
|
271
|
+
const issues = [];
|
|
272
|
+
const rest = { ...data };
|
|
273
|
+
const aliasesResult = z.array(slugPatternSchema).max(20).optional().default([]).safeParse(rest.aliases ?? []);
|
|
274
|
+
delete rest.aliases;
|
|
275
|
+
const redirectRaw = rest.redirect_to;
|
|
276
|
+
delete rest.redirect_to;
|
|
277
|
+
let redirectTo;
|
|
278
|
+
if (redirectRaw !== void 0 && redirectRaw !== null && redirectRaw !== "") {
|
|
279
|
+
if (typeof redirectRaw !== "string") {
|
|
280
|
+
issues.push({
|
|
281
|
+
field: "redirect_to",
|
|
282
|
+
message: "redirect_to must be a string path",
|
|
283
|
+
level: "error"
|
|
284
|
+
});
|
|
285
|
+
} else if (!pathTemplate) {
|
|
286
|
+
issues.push({
|
|
287
|
+
field: "redirect_to",
|
|
288
|
+
message: "redirect_to is not allowed on reference-only content types",
|
|
289
|
+
level: "error"
|
|
290
|
+
});
|
|
291
|
+
} else if (!extractSlugFromResolvedPath(pathTemplate, redirectRaw)) {
|
|
292
|
+
issues.push({
|
|
293
|
+
field: "redirect_to",
|
|
294
|
+
message: `redirect_to must match path template "${pathTemplate}" (prefix "${pathPrefix(pathTemplate)}"${pathSuffix(pathTemplate) ? `, suffix "${pathSuffix(pathTemplate)}"` : ""})`,
|
|
295
|
+
level: "error"
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
redirectTo = redirectRaw;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const aliases = aliasesResult.success ? aliasesResult.data : [];
|
|
302
|
+
if (!aliasesResult.success) {
|
|
303
|
+
for (const issue of aliasesResult.error.issues) {
|
|
304
|
+
issues.push({
|
|
305
|
+
field: `aliases.${issue.path.join(".")}`,
|
|
306
|
+
message: issue.message,
|
|
307
|
+
level: "error"
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let publishedAt;
|
|
312
|
+
if (rest.publishedAt !== void 0 && rest.publishedAt !== null && rest.publishedAt !== "") {
|
|
313
|
+
const parsed = isoDateSchema.safeParse(rest.publishedAt);
|
|
314
|
+
if (parsed.success) {
|
|
315
|
+
publishedAt = parsed.data;
|
|
316
|
+
} else {
|
|
317
|
+
for (const issue of parsed.error.issues) {
|
|
318
|
+
issues.push({ field: "publishedAt", message: issue.message, level: "error" });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
delete rest.publishedAt;
|
|
322
|
+
}
|
|
323
|
+
let updatedAt;
|
|
324
|
+
if (rest.updatedAt !== void 0 && rest.updatedAt !== null && rest.updatedAt !== "") {
|
|
325
|
+
const parsed = isoDateSchema.safeParse(rest.updatedAt);
|
|
326
|
+
if (parsed.success) {
|
|
327
|
+
updatedAt = parsed.data;
|
|
328
|
+
} else {
|
|
329
|
+
for (const issue of parsed.error.issues) {
|
|
330
|
+
issues.push({ field: "updatedAt", message: issue.message, level: "error" });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
delete rest.updatedAt;
|
|
334
|
+
} else if (publishedAt) {
|
|
335
|
+
updatedAt = publishedAt;
|
|
336
|
+
}
|
|
337
|
+
let noindex = false;
|
|
338
|
+
if (rest.noindex !== void 0 && rest.noindex !== null && rest.noindex !== "") {
|
|
339
|
+
if (typeof rest.noindex === "boolean") {
|
|
340
|
+
noindex = rest.noindex;
|
|
341
|
+
} else {
|
|
342
|
+
issues.push({ field: "noindex", message: "noindex must be a boolean", level: "error" });
|
|
343
|
+
}
|
|
344
|
+
delete rest.noindex;
|
|
345
|
+
}
|
|
346
|
+
let canonicalPathOverride;
|
|
347
|
+
if (rest.canonicalPath !== void 0 && rest.canonicalPath !== null && rest.canonicalPath !== "") {
|
|
348
|
+
const parsed = canonicalPathSchema.safeParse(rest.canonicalPath);
|
|
349
|
+
if (parsed.success) {
|
|
350
|
+
canonicalPathOverride = parsed.data;
|
|
351
|
+
} else {
|
|
352
|
+
for (const issue of parsed.error.issues) {
|
|
353
|
+
issues.push({ field: "canonicalPath", message: issue.message, level: "error" });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
delete rest.canonicalPath;
|
|
357
|
+
}
|
|
358
|
+
for (const internalKey of ["translationOf", "enSlug"]) {
|
|
359
|
+
if (rest[internalKey] !== void 0) {
|
|
360
|
+
issues.push({
|
|
361
|
+
field: internalKey,
|
|
362
|
+
message: `${internalKey} is managed internally by Scribe; remove from frontmatter`,
|
|
363
|
+
level: "warning"
|
|
364
|
+
});
|
|
365
|
+
delete rest[internalKey];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
builtin: {
|
|
370
|
+
aliases,
|
|
371
|
+
redirectTo,
|
|
372
|
+
publishedAt,
|
|
373
|
+
updatedAt,
|
|
374
|
+
noindex,
|
|
375
|
+
canonicalPathOverride
|
|
376
|
+
},
|
|
377
|
+
rest,
|
|
378
|
+
issues
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function documentLastModified(doc) {
|
|
382
|
+
const raw = doc.updatedAt ?? doc.publishedAt;
|
|
383
|
+
if (!raw) return void 0;
|
|
384
|
+
const date = new Date(raw);
|
|
385
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
386
|
+
}
|
|
387
|
+
function resolveCanonicalPathname(type, doc, defaultLocale) {
|
|
388
|
+
if (doc.canonicalPathOverride) {
|
|
389
|
+
if (doc.locale === defaultLocale) return doc.canonicalPathOverride;
|
|
390
|
+
return `/${doc.locale}${doc.canonicalPathOverride}`;
|
|
391
|
+
}
|
|
392
|
+
if (!type.path) return `/${doc.slug}`;
|
|
393
|
+
return resolvePath(type.path, doc.slug, doc.locale, defaultLocale);
|
|
394
|
+
}
|
|
395
|
+
function mergeBuiltinsIntoFrontmatter(frontmatter, doc, type, defaultLocale) {
|
|
396
|
+
const out = { ...frontmatter };
|
|
397
|
+
if (doc.publishedAt !== void 0) out.publishedAt = doc.publishedAt;
|
|
398
|
+
if (doc.updatedAt !== void 0) out.updatedAt = doc.updatedAt;
|
|
399
|
+
out.noindex = doc.noindex;
|
|
400
|
+
out.canonicalPath = resolveCanonicalPathname(type, doc, defaultLocale);
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
function seoFieldsFromEn(enDoc) {
|
|
404
|
+
return {
|
|
405
|
+
publishedAt: enDoc.publishedAt,
|
|
406
|
+
updatedAt: enDoc.updatedAt,
|
|
407
|
+
noindex: enDoc.noindex,
|
|
408
|
+
canonicalPathOverride: enDoc.canonicalPathOverride,
|
|
409
|
+
redirectTo: enDoc.redirectTo
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
var SCHEMA_VERSION = 2;
|
|
413
|
+
var MIGRATIONS = [
|
|
414
|
+
`CREATE TABLE IF NOT EXISTS meta (
|
|
415
|
+
key TEXT PRIMARY KEY,
|
|
416
|
+
value TEXT NOT NULL
|
|
417
|
+
)`,
|
|
418
|
+
`CREATE TABLE IF NOT EXISTS translations (
|
|
419
|
+
content_type TEXT NOT NULL,
|
|
420
|
+
en_slug TEXT NOT NULL,
|
|
421
|
+
locale TEXT NOT NULL,
|
|
422
|
+
slug TEXT NOT NULL,
|
|
423
|
+
frontmatter_json TEXT NOT NULL,
|
|
424
|
+
body TEXT NOT NULL,
|
|
425
|
+
en_hash TEXT NOT NULL,
|
|
426
|
+
translated_at TEXT NOT NULL,
|
|
427
|
+
model TEXT NOT NULL,
|
|
428
|
+
PRIMARY KEY (content_type, en_slug, locale)
|
|
429
|
+
)`,
|
|
430
|
+
`CREATE INDEX IF NOT EXISTS idx_translations_type_locale
|
|
431
|
+
ON translations(content_type, locale)`,
|
|
432
|
+
`CREATE TABLE IF NOT EXISTS revisions (
|
|
433
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
434
|
+
content_type TEXT NOT NULL,
|
|
435
|
+
en_slug TEXT NOT NULL,
|
|
436
|
+
locale TEXT,
|
|
437
|
+
revision_kind TEXT NOT NULL,
|
|
438
|
+
en_hash TEXT NOT NULL,
|
|
439
|
+
body_hash TEXT NOT NULL,
|
|
440
|
+
created_at TEXT NOT NULL,
|
|
441
|
+
model TEXT,
|
|
442
|
+
body_preview TEXT
|
|
443
|
+
)`,
|
|
444
|
+
`CREATE INDEX IF NOT EXISTS idx_revisions_lookup
|
|
445
|
+
ON revisions(content_type, en_slug, locale, created_at DESC)`,
|
|
446
|
+
`CREATE TABLE IF NOT EXISTS slug_aliases (
|
|
447
|
+
content_type TEXT NOT NULL,
|
|
448
|
+
canonical_en_slug TEXT NOT NULL,
|
|
449
|
+
alias_en_slug TEXT NOT NULL,
|
|
450
|
+
created_at TEXT NOT NULL,
|
|
451
|
+
PRIMARY KEY (content_type, alias_en_slug)
|
|
452
|
+
)`,
|
|
453
|
+
`CREATE INDEX IF NOT EXISTS idx_slug_aliases_canonical
|
|
454
|
+
ON slug_aliases(content_type, canonical_en_slug)`,
|
|
455
|
+
`CREATE TABLE IF NOT EXISTS alias_locale_slugs (
|
|
456
|
+
content_type TEXT NOT NULL,
|
|
457
|
+
alias_en_slug TEXT NOT NULL,
|
|
458
|
+
locale TEXT NOT NULL,
|
|
459
|
+
locale_slug TEXT NOT NULL,
|
|
460
|
+
captured_at TEXT NOT NULL,
|
|
461
|
+
PRIMARY KEY (content_type, alias_en_slug, locale)
|
|
462
|
+
)`
|
|
463
|
+
];
|
|
464
|
+
function resolveStorePath(config) {
|
|
465
|
+
return config.storePath;
|
|
466
|
+
}
|
|
467
|
+
function openStore(config, mode = "readwrite") {
|
|
468
|
+
const storePath = resolveStorePath(config);
|
|
469
|
+
fs2.mkdirSync(path3.dirname(storePath), { recursive: true });
|
|
470
|
+
const db = new Database(storePath, { readonly: mode === "readonly" });
|
|
471
|
+
if (mode === "readwrite") {
|
|
472
|
+
migrate(db);
|
|
473
|
+
}
|
|
474
|
+
return db;
|
|
475
|
+
}
|
|
476
|
+
function migrate(db) {
|
|
477
|
+
for (const sql of MIGRATIONS) {
|
|
478
|
+
db.exec(sql);
|
|
479
|
+
}
|
|
480
|
+
db.prepare(
|
|
481
|
+
`INSERT INTO meta(key, value) VALUES('schema_version', ?)
|
|
482
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
483
|
+
).run(String(SCHEMA_VERSION));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/storage/translations.ts
|
|
487
|
+
function listTranslationsForType(db, contentType) {
|
|
488
|
+
return db.prepare(`SELECT * FROM translations WHERE content_type = ? ORDER BY en_slug, locale`).all(contentType);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/loader/normalize-en.ts
|
|
492
|
+
function normalizeEnFrontmatter(data) {
|
|
493
|
+
const out = { ...data };
|
|
494
|
+
if (!out.publishedAt && typeof out.date === "string") {
|
|
495
|
+
out.publishedAt = out.date;
|
|
496
|
+
}
|
|
497
|
+
if (!out.heroImage && typeof out.image === "string" && out.image.startsWith("/")) {
|
|
498
|
+
out.heroImage = out.image;
|
|
499
|
+
}
|
|
500
|
+
if (!out.updatedAt && typeof out.publishedAt === "string") {
|
|
501
|
+
out.updatedAt = out.publishedAt;
|
|
502
|
+
}
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
function isPublishableContentFile(name) {
|
|
506
|
+
if (!name.endsWith(".md") && !name.endsWith(".mdx")) return false;
|
|
507
|
+
const first = name.charAt(0);
|
|
508
|
+
return first === first.toLowerCase() && first !== "_";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/loader/create-loader.ts
|
|
512
|
+
function isPostFile(name) {
|
|
513
|
+
return isPublishableContentFile(name);
|
|
514
|
+
}
|
|
515
|
+
function listEnFiles(contentDir) {
|
|
516
|
+
if (!fs2.existsSync(contentDir)) return [];
|
|
517
|
+
return fs2.readdirSync(contentDir).filter(isPostFile).map((f) => path3.join(contentDir, f));
|
|
518
|
+
}
|
|
519
|
+
function slugFromPath(filePath) {
|
|
520
|
+
return path3.basename(filePath).replace(/\.(md|mdx)$/, "");
|
|
521
|
+
}
|
|
522
|
+
function parseEnMdx(filePath, config, type) {
|
|
523
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
524
|
+
const parsed = matter(raw);
|
|
525
|
+
const slug = slugFromPath(filePath);
|
|
526
|
+
const normalized = normalizeEnFrontmatter(parsed.data);
|
|
527
|
+
const { builtin, rest, issues: builtinIssues } = extractBuiltinEnFields(
|
|
528
|
+
normalized,
|
|
529
|
+
type.path,
|
|
530
|
+
slug,
|
|
531
|
+
config.defaultLocale
|
|
532
|
+
);
|
|
533
|
+
const issues = builtinIssues.map((issue) => ({
|
|
534
|
+
field: issue.field,
|
|
535
|
+
message: issue.message,
|
|
536
|
+
level: issue.level
|
|
537
|
+
}));
|
|
538
|
+
const result = type.schema.safeParse(rest);
|
|
539
|
+
if (!result.success) {
|
|
540
|
+
return {
|
|
541
|
+
document: null,
|
|
542
|
+
issues: [
|
|
543
|
+
...issues,
|
|
544
|
+
...result.error.issues.map((issue) => ({
|
|
545
|
+
field: issue.path.join("."),
|
|
546
|
+
message: issue.message,
|
|
547
|
+
level: "error"
|
|
548
|
+
}))
|
|
549
|
+
]
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
if (issues.some((i) => i.level === "error")) {
|
|
553
|
+
return { document: null, issues };
|
|
554
|
+
}
|
|
555
|
+
const frontmatter = mergeBuiltinsIntoFrontmatter(
|
|
556
|
+
result.data,
|
|
557
|
+
{
|
|
558
|
+
publishedAt: builtin.publishedAt,
|
|
559
|
+
updatedAt: builtin.updatedAt,
|
|
560
|
+
noindex: builtin.noindex,
|
|
561
|
+
canonicalPathOverride: builtin.canonicalPathOverride,
|
|
562
|
+
slug,
|
|
563
|
+
locale: config.defaultLocale
|
|
564
|
+
},
|
|
565
|
+
type,
|
|
566
|
+
config.defaultLocale
|
|
567
|
+
);
|
|
568
|
+
const crossIssues = type.crossValidate?.(result.data, {
|
|
569
|
+
locale: config.defaultLocale,
|
|
570
|
+
defaultLocale: config.defaultLocale,
|
|
571
|
+
slug,
|
|
572
|
+
enSlug: slug,
|
|
573
|
+
knownLocales: config.locales
|
|
574
|
+
}) ?? [];
|
|
575
|
+
issues.push(...crossIssues);
|
|
576
|
+
const document = {
|
|
577
|
+
slug,
|
|
578
|
+
enSlug: slug,
|
|
579
|
+
locale: config.defaultLocale,
|
|
580
|
+
aliases: builtin.aliases,
|
|
581
|
+
redirectTo: builtin.redirectTo,
|
|
582
|
+
publishedAt: builtin.publishedAt,
|
|
583
|
+
updatedAt: builtin.updatedAt,
|
|
584
|
+
noindex: builtin.noindex,
|
|
585
|
+
canonicalPathOverride: builtin.canonicalPathOverride,
|
|
586
|
+
frontmatter,
|
|
587
|
+
content: parsed.content,
|
|
588
|
+
filePath
|
|
589
|
+
};
|
|
590
|
+
return { document, issues };
|
|
591
|
+
}
|
|
592
|
+
function buildDocumentFromTranslation(row, enDoc, type) {
|
|
593
|
+
const localeFm = JSON.parse(row.frontmatter_json);
|
|
594
|
+
const merged = mergeStructuralOntoLocale(localeFm, enDoc.frontmatter, type.schema);
|
|
595
|
+
const seo = seoFieldsFromEn(enDoc);
|
|
596
|
+
const frontmatter = mergeBuiltinsIntoFrontmatter(
|
|
597
|
+
merged,
|
|
598
|
+
{ ...seo, slug: row.slug, locale: row.locale },
|
|
599
|
+
type,
|
|
600
|
+
enDoc.locale
|
|
601
|
+
);
|
|
602
|
+
return {
|
|
603
|
+
slug: row.slug,
|
|
604
|
+
enSlug: row.en_slug,
|
|
605
|
+
locale: row.locale,
|
|
606
|
+
aliases: [],
|
|
607
|
+
redirectTo: seo.redirectTo,
|
|
608
|
+
publishedAt: seo.publishedAt,
|
|
609
|
+
updatedAt: seo.updatedAt,
|
|
610
|
+
noindex: seo.noindex,
|
|
611
|
+
canonicalPathOverride: seo.canonicalPathOverride,
|
|
612
|
+
frontmatter,
|
|
613
|
+
content: row.body
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
var DEV_REVALIDATE_MS = 1500;
|
|
617
|
+
function createContentLoader(config, type) {
|
|
618
|
+
let cached = null;
|
|
619
|
+
let signature = "";
|
|
620
|
+
let lastCheck = 0;
|
|
621
|
+
const contentDir = path3.join(
|
|
622
|
+
/* turbopackIgnore: true */
|
|
623
|
+
config.rootDir,
|
|
624
|
+
type.contentDir
|
|
625
|
+
);
|
|
626
|
+
const storePath = resolveStorePath(config);
|
|
627
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
628
|
+
function computeSignature() {
|
|
629
|
+
const files = listEnFiles(contentDir);
|
|
630
|
+
let newest = 0;
|
|
631
|
+
for (const f of files) {
|
|
632
|
+
try {
|
|
633
|
+
newest = Math.max(newest, fs2.statSync(f).mtimeMs);
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
let store = 0;
|
|
638
|
+
try {
|
|
639
|
+
store = fs2.statSync(storePath).mtimeMs;
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
return `${files.length}:${newest}:${store}`;
|
|
643
|
+
}
|
|
644
|
+
function build() {
|
|
645
|
+
const out = /* @__PURE__ */ new Map();
|
|
646
|
+
const englishBySlug = /* @__PURE__ */ new Map();
|
|
647
|
+
for (const file of listEnFiles(contentDir)) {
|
|
648
|
+
const { document, issues } = parseEnMdx(file, config, type);
|
|
649
|
+
if (document) {
|
|
650
|
+
englishBySlug.set(document.slug, document);
|
|
651
|
+
if (issues.length > 0) {
|
|
652
|
+
for (const issue of issues) {
|
|
653
|
+
console.warn(
|
|
654
|
+
`[scribe:${type.id}] ${file} ${issue.level}: ${issue.field}: ${issue.message}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
console.warn(
|
|
660
|
+
`[scribe:${type.id}] Skipping ${file} \u2014 validation failed:
|
|
661
|
+
` + issues.map((i) => ` - ${i.field}: ${i.message}`).join("\n")
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
out.set(config.defaultLocale, {
|
|
666
|
+
bySlug: englishBySlug,
|
|
667
|
+
byEnSlug: englishBySlug
|
|
668
|
+
});
|
|
669
|
+
const db = openStore(config, "readonly");
|
|
670
|
+
const rows = listTranslationsForType(db, type.id);
|
|
671
|
+
db.close();
|
|
672
|
+
const rowsByLocale = /* @__PURE__ */ new Map();
|
|
673
|
+
for (const row of rows) {
|
|
674
|
+
const list = rowsByLocale.get(row.locale) ?? [];
|
|
675
|
+
list.push(row);
|
|
676
|
+
rowsByLocale.set(row.locale, list);
|
|
677
|
+
}
|
|
678
|
+
for (const locale of config.locales) {
|
|
679
|
+
if (locale === config.defaultLocale) continue;
|
|
680
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
681
|
+
const byEnSlug = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const row of rowsByLocale.get(locale) ?? []) {
|
|
683
|
+
const enDoc = englishBySlug.get(row.en_slug);
|
|
684
|
+
if (!enDoc) continue;
|
|
685
|
+
const doc = buildDocumentFromTranslation(row, enDoc, type);
|
|
686
|
+
bySlug.set(doc.slug, doc);
|
|
687
|
+
byEnSlug.set(row.en_slug, doc);
|
|
688
|
+
}
|
|
689
|
+
out.set(locale, { bySlug, byEnSlug });
|
|
690
|
+
}
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
return () => {
|
|
694
|
+
if (cached) {
|
|
695
|
+
if (isProd) return cached;
|
|
696
|
+
const now = Date.now();
|
|
697
|
+
if (now - lastCheck < DEV_REVALIDATE_MS) return cached;
|
|
698
|
+
lastCheck = now;
|
|
699
|
+
const sig = computeSignature();
|
|
700
|
+
if (sig === signature) return cached;
|
|
701
|
+
cached = build();
|
|
702
|
+
signature = sig;
|
|
703
|
+
return cached;
|
|
704
|
+
}
|
|
705
|
+
lastCheck = Date.now();
|
|
706
|
+
cached = build();
|
|
707
|
+
signature = isProd ? "" : computeSignature();
|
|
708
|
+
return cached;
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function readEnDocument(config, type, enSlug) {
|
|
712
|
+
const contentDir = path3.join(
|
|
713
|
+
/* turbopackIgnore: true */
|
|
714
|
+
config.rootDir,
|
|
715
|
+
type.contentDir
|
|
716
|
+
);
|
|
717
|
+
for (const ext of [".mdx", ".md"]) {
|
|
718
|
+
const filePath = path3.join(contentDir, `${enSlug}${ext}`);
|
|
719
|
+
if (!fs2.existsSync(filePath)) continue;
|
|
720
|
+
const { document } = parseEnMdx(filePath, config, type);
|
|
721
|
+
return document;
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/i18n/resolve-document.ts
|
|
727
|
+
function findEnDocByAlias(slug, allDocs, defaultLocale) {
|
|
728
|
+
const enIdx = allDocs.get(defaultLocale);
|
|
729
|
+
if (!enIdx) return null;
|
|
730
|
+
for (const doc of enIdx.bySlug.values()) {
|
|
731
|
+
if (doc.aliases.includes(slug)) return doc;
|
|
732
|
+
}
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
function enCanonicalDoc(doc, allDocs, defaultLocale) {
|
|
736
|
+
if (doc.locale === defaultLocale) return doc;
|
|
737
|
+
return allDocs.get(defaultLocale)?.bySlug.get(doc.enSlug) ?? doc;
|
|
738
|
+
}
|
|
739
|
+
function redirectPathForDocument(doc, locale, defaultLocale, allDocs, type) {
|
|
740
|
+
if (!type.path) return void 0;
|
|
741
|
+
const enDoc = enCanonicalDoc(doc, allDocs, defaultLocale);
|
|
742
|
+
if (!enDoc.redirectTo) return void 0;
|
|
743
|
+
const targetEnSlug = extractSlugFromResolvedPath(type.path, enDoc.redirectTo);
|
|
744
|
+
if (!targetEnSlug) return void 0;
|
|
745
|
+
const targetEnDoc = allDocs.get(defaultLocale)?.bySlug.get(targetEnSlug);
|
|
746
|
+
const targetSlug = targetEnDoc ? getSlugForLocale(targetEnDoc, defaultLocale, locale, allDocs, defaultLocale) ?? targetEnSlug : targetEnSlug;
|
|
747
|
+
return resolvePath(type.path, targetSlug, locale, defaultLocale);
|
|
748
|
+
}
|
|
749
|
+
function withRedirectTo(result, locale, defaultLocale, allDocs, type) {
|
|
750
|
+
if (result.shouldRedirectTo || !result.document) return result;
|
|
751
|
+
const redirect = redirectPathForDocument(result.document, locale, defaultLocale, allDocs, type);
|
|
752
|
+
if (!redirect) return result;
|
|
753
|
+
return {
|
|
754
|
+
document: null,
|
|
755
|
+
actualLocale: result.actualLocale,
|
|
756
|
+
shouldRedirectTo: redirect
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type) {
|
|
760
|
+
const idx = allDocs.get(locale);
|
|
761
|
+
const direct = idx?.bySlug.get(slug);
|
|
762
|
+
if (direct) {
|
|
763
|
+
return withRedirectTo({ document: direct, actualLocale: locale }, locale, defaultLocale, allDocs, type);
|
|
764
|
+
}
|
|
765
|
+
const aliasDoc = findEnDocByAlias(slug, allDocs, defaultLocale);
|
|
766
|
+
if (aliasDoc && type.path) {
|
|
767
|
+
const canonicalSlug = aliasDoc.slug;
|
|
768
|
+
const localizedSlug = getSlugForLocale(aliasDoc, defaultLocale, locale, allDocs, defaultLocale);
|
|
769
|
+
const targetSlug = localizedSlug ?? canonicalSlug;
|
|
770
|
+
if (targetSlug !== slug) {
|
|
771
|
+
return {
|
|
772
|
+
document: null,
|
|
773
|
+
actualLocale: locale,
|
|
774
|
+
shouldRedirectTo: resolvePath(type.path, targetSlug, locale, defaultLocale)
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
for (const [docLocale, docIdx] of allDocs) {
|
|
779
|
+
const found = docIdx.bySlug.get(slug);
|
|
780
|
+
if (!found) continue;
|
|
781
|
+
const correctSlug = getSlugForLocale(found, docLocale, locale, allDocs, defaultLocale);
|
|
782
|
+
if (correctSlug && correctSlug !== slug) {
|
|
783
|
+
if (!type.path) {
|
|
784
|
+
return { document: null, actualLocale: locale };
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
document: null,
|
|
788
|
+
actualLocale: locale,
|
|
789
|
+
shouldRedirectTo: resolvePath(type.path, correctSlug, locale, defaultLocale)
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
if (docLocale === defaultLocale) {
|
|
793
|
+
return withRedirectTo(
|
|
794
|
+
{ document: found, actualLocale: defaultLocale },
|
|
795
|
+
locale,
|
|
796
|
+
defaultLocale,
|
|
797
|
+
allDocs,
|
|
798
|
+
type
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (!type.path) {
|
|
802
|
+
return { document: null, actualLocale: locale };
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
document: null,
|
|
806
|
+
actualLocale: locale,
|
|
807
|
+
shouldRedirectTo: resolvePath(type.path, found.enSlug, locale, defaultLocale)
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
if (locale !== defaultLocale) {
|
|
811
|
+
const enDoc = allDocs.get(defaultLocale)?.bySlug.get(slug);
|
|
812
|
+
if (enDoc && type.indexFallback === "en") {
|
|
813
|
+
return withRedirectTo(
|
|
814
|
+
{ document: enDoc, actualLocale: defaultLocale },
|
|
815
|
+
locale,
|
|
816
|
+
defaultLocale,
|
|
817
|
+
allDocs,
|
|
818
|
+
type
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
const byEn = idx?.byEnSlug.get(slug);
|
|
822
|
+
if (byEn) {
|
|
823
|
+
return withRedirectTo(
|
|
824
|
+
{ document: byEn, actualLocale: locale },
|
|
825
|
+
locale,
|
|
826
|
+
defaultLocale,
|
|
827
|
+
allDocs,
|
|
828
|
+
type
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
const fallback = allDocs.get(defaultLocale)?.bySlug.get(slug);
|
|
832
|
+
if (fallback && type.indexFallback === "en") {
|
|
833
|
+
return withRedirectTo(
|
|
834
|
+
{ document: fallback, actualLocale: defaultLocale },
|
|
835
|
+
locale,
|
|
836
|
+
defaultLocale,
|
|
837
|
+
allDocs,
|
|
838
|
+
type
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return { document: null, actualLocale: locale };
|
|
843
|
+
}
|
|
844
|
+
function getSlugForLocale(document, sourceLocale, targetLocale, allDocs, defaultLocale) {
|
|
845
|
+
if (sourceLocale === targetLocale) return document.slug;
|
|
846
|
+
const englishSlug = sourceLocale === defaultLocale ? document.slug : document.enSlug;
|
|
847
|
+
if (!englishSlug) return null;
|
|
848
|
+
if (targetLocale === defaultLocale) return englishSlug;
|
|
849
|
+
return allDocs.get(targetLocale)?.byEnSlug.get(englishSlug)?.slug ?? null;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/create-project.ts
|
|
853
|
+
function comparatorFor(orderBy) {
|
|
854
|
+
if (typeof orderBy === "function") return orderBy;
|
|
855
|
+
switch (orderBy) {
|
|
856
|
+
case "publishedAt":
|
|
857
|
+
return (a, b) => (a.publishedAt ?? "").localeCompare(b.publishedAt ?? "");
|
|
858
|
+
case "-publishedAt":
|
|
859
|
+
return (a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? "");
|
|
860
|
+
case "updatedAt":
|
|
861
|
+
return (a, b) => (a.updatedAt ?? "").localeCompare(b.updatedAt ?? "");
|
|
862
|
+
case "-updatedAt":
|
|
863
|
+
return (a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "");
|
|
864
|
+
default:
|
|
865
|
+
return (a, b) => a.slug.localeCompare(b.slug);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function buildRuntime(config, type, getRuntime) {
|
|
869
|
+
const load = createContentLoader(config, type);
|
|
870
|
+
const relationFields = new Map(
|
|
871
|
+
listRelationFields(type.schema).filter((f) => f.path.length === 1).map((f) => [f.path[0], f])
|
|
872
|
+
);
|
|
873
|
+
function assertRoutable(method) {
|
|
874
|
+
if (!type.path) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
`Content type "${type.id}" has no path template \u2014 ${method}() requires one`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
return type.path;
|
|
880
|
+
}
|
|
881
|
+
const runtime = {
|
|
882
|
+
id: type.id,
|
|
883
|
+
config: type,
|
|
884
|
+
load,
|
|
885
|
+
list(locale = config.defaultLocale, options = {}) {
|
|
886
|
+
const idx = load().get(locale);
|
|
887
|
+
if (!idx) return [];
|
|
888
|
+
const docs = Array.from(idx.bySlug.values());
|
|
889
|
+
docs.sort(comparatorFor(options.orderBy ?? type.orderBy ?? "slug"));
|
|
890
|
+
return options.limit !== void 0 ? docs.slice(0, options.limit) : docs;
|
|
891
|
+
},
|
|
892
|
+
get(slug, locale = config.defaultLocale) {
|
|
893
|
+
return load().get(locale)?.bySlug.get(slug) ?? null;
|
|
894
|
+
},
|
|
895
|
+
resolve(slug, locale) {
|
|
896
|
+
const result = resolveLocalizedDocument(slug, locale, config.defaultLocale, load(), type);
|
|
897
|
+
if (result.document && type.path) {
|
|
898
|
+
return {
|
|
899
|
+
...result,
|
|
900
|
+
canonicalPath: resolvePath(
|
|
901
|
+
type.path,
|
|
902
|
+
result.document.slug,
|
|
903
|
+
result.actualLocale,
|
|
904
|
+
config.defaultLocale
|
|
905
|
+
)
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return result;
|
|
909
|
+
},
|
|
910
|
+
staticParams(options = {}) {
|
|
911
|
+
assertRoutable("staticParams");
|
|
912
|
+
const all = load();
|
|
913
|
+
const enIdx = all.get(config.defaultLocale);
|
|
914
|
+
if (!enIdx) return [];
|
|
915
|
+
const params = [];
|
|
916
|
+
for (const locale of options.locales ?? config.locales) {
|
|
917
|
+
const localeIdx = all.get(locale);
|
|
918
|
+
for (const doc of enIdx.bySlug.values()) {
|
|
919
|
+
const slug = locale === config.defaultLocale ? doc.slug : localeIdx?.byEnSlug.get(doc.slug)?.slug ?? doc.slug;
|
|
920
|
+
params.push({ locale, slug });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return params;
|
|
924
|
+
},
|
|
925
|
+
alternates(doc) {
|
|
926
|
+
const pathTemplate = assertRoutable("alternates");
|
|
927
|
+
const out = {
|
|
928
|
+
[config.defaultLocale]: resolvePath(
|
|
929
|
+
pathTemplate,
|
|
930
|
+
doc.enSlug,
|
|
931
|
+
config.defaultLocale,
|
|
932
|
+
config.defaultLocale
|
|
933
|
+
)
|
|
934
|
+
};
|
|
935
|
+
const all = load();
|
|
936
|
+
for (const locale of config.locales) {
|
|
937
|
+
if (locale === config.defaultLocale) continue;
|
|
938
|
+
const translated = all.get(locale)?.byEnSlug.get(doc.enSlug);
|
|
939
|
+
if (translated) {
|
|
940
|
+
out[locale] = resolvePath(pathTemplate, translated.slug, locale, config.defaultLocale);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
},
|
|
945
|
+
translation(doc, targetLocale) {
|
|
946
|
+
if (targetLocale === doc.locale) return doc;
|
|
947
|
+
const all = load();
|
|
948
|
+
if (targetLocale === config.defaultLocale) {
|
|
949
|
+
return all.get(config.defaultLocale)?.bySlug.get(doc.enSlug) ?? null;
|
|
950
|
+
}
|
|
951
|
+
return all.get(targetLocale)?.byEnSlug.get(doc.enSlug) ?? null;
|
|
952
|
+
},
|
|
953
|
+
url(slug, locale) {
|
|
954
|
+
const pathTemplate = assertRoutable("url");
|
|
955
|
+
return resolvePath(pathTemplate, slug, locale, config.defaultLocale);
|
|
956
|
+
},
|
|
957
|
+
related(doc, fieldName, locale) {
|
|
958
|
+
const meta = relationFields.get(fieldName);
|
|
959
|
+
if (!meta) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
`Content type "${type.id}" has no top-level relation field "${fieldName}"`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
const target = getRuntime(meta.relationTarget);
|
|
965
|
+
const lookupLocale = locale ?? doc.locale;
|
|
966
|
+
const value = doc.frontmatter[fieldName];
|
|
967
|
+
const deref = (enSlug) => {
|
|
968
|
+
const all = target.load();
|
|
969
|
+
return all.get(lookupLocale)?.byEnSlug.get(enSlug) ?? all.get(config.defaultLocale)?.bySlug.get(enSlug) ?? null;
|
|
970
|
+
};
|
|
971
|
+
if (meta.relationMultiple) {
|
|
972
|
+
if (!Array.isArray(value)) return [];
|
|
973
|
+
return value.filter((slug) => typeof slug === "string" && slug.length > 0).map(deref).filter((d) => d !== null);
|
|
974
|
+
}
|
|
975
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
976
|
+
if (meta.relationOptional) return null;
|
|
977
|
+
throw new Error(
|
|
978
|
+
`${type.id} "${doc.enSlug}": required relation "${fieldName}" is missing`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const resolved = deref(value);
|
|
982
|
+
if (!resolved && !meta.relationOptional) {
|
|
983
|
+
throw new Error(
|
|
984
|
+
`${type.id} "${doc.enSlug}": relation "${fieldName}" references missing ${meta.relationTarget} "${value}" \u2014 run \`scribe validate\``
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
return resolved;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
return runtime;
|
|
991
|
+
}
|
|
992
|
+
function createProject(config) {
|
|
993
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
994
|
+
const getRuntime = (id) => {
|
|
995
|
+
const runtime = runtimes.get(id);
|
|
996
|
+
if (!runtime) {
|
|
997
|
+
throw new Error(`Unknown content type "${id}"`);
|
|
998
|
+
}
|
|
999
|
+
return runtime;
|
|
1000
|
+
};
|
|
1001
|
+
for (const type of config.types) {
|
|
1002
|
+
runtimes.set(type.id, buildRuntime(config, type, getRuntime));
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
config,
|
|
1006
|
+
rootDir: config.rootDir,
|
|
1007
|
+
storePath: config.storePath,
|
|
1008
|
+
getType: getRuntime,
|
|
1009
|
+
listTypes() {
|
|
1010
|
+
return Array.from(runtimes.values());
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function listEnSlugs(rootDir, contentDir) {
|
|
1015
|
+
const dir = path3.join(rootDir, contentDir);
|
|
1016
|
+
if (!fs2.existsSync(dir)) return [];
|
|
1017
|
+
return fs2.readdirSync(dir).filter(isPublishableContentFile).map((f) => f.replace(/\.(md|mdx)$/, ""));
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/core/slug-aliases.ts
|
|
1021
|
+
function buildGlobalAliasIndex(project) {
|
|
1022
|
+
const { config } = project;
|
|
1023
|
+
const aliasToTarget = /* @__PURE__ */ new Map();
|
|
1024
|
+
const allAliasSlugs = /* @__PURE__ */ new Set();
|
|
1025
|
+
const issues = [];
|
|
1026
|
+
const canonicalSlugsByType = /* @__PURE__ */ new Map();
|
|
1027
|
+
for (const type of config.types) {
|
|
1028
|
+
const slugs = listEnSlugs(config.rootDir, type.contentDir);
|
|
1029
|
+
canonicalSlugsByType.set(type.id, new Set(slugs));
|
|
1030
|
+
}
|
|
1031
|
+
for (const type of config.types) {
|
|
1032
|
+
if (!type.path) continue;
|
|
1033
|
+
const canonicalSlugs = canonicalSlugsByType.get(type.id) ?? /* @__PURE__ */ new Set();
|
|
1034
|
+
for (const enSlug of canonicalSlugs) {
|
|
1035
|
+
const doc = readEnDocument(config, type, enSlug);
|
|
1036
|
+
if (!doc) continue;
|
|
1037
|
+
for (const alias of doc.aliases) {
|
|
1038
|
+
if (alias === enSlug) {
|
|
1039
|
+
issues.push({
|
|
1040
|
+
contentTypeId: type.id,
|
|
1041
|
+
enSlug,
|
|
1042
|
+
field: "aliases",
|
|
1043
|
+
message: `Alias "${alias}" must not equal the canonical slug`,
|
|
1044
|
+
level: "error"
|
|
1045
|
+
});
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (canonicalSlugs.has(alias)) {
|
|
1049
|
+
issues.push({
|
|
1050
|
+
contentTypeId: type.id,
|
|
1051
|
+
enSlug,
|
|
1052
|
+
field: "aliases",
|
|
1053
|
+
message: `Alias "${alias}" collides with another document's canonical slug`,
|
|
1054
|
+
level: "error"
|
|
1055
|
+
});
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const existing = aliasToTarget.get(alias);
|
|
1059
|
+
if (existing) {
|
|
1060
|
+
issues.push({
|
|
1061
|
+
contentTypeId: type.id,
|
|
1062
|
+
enSlug,
|
|
1063
|
+
field: "aliases",
|
|
1064
|
+
message: `Alias "${alias}" is already claimed by ${existing.contentTypeId}/${existing.canonicalSlug}`,
|
|
1065
|
+
level: "error"
|
|
1066
|
+
});
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
aliasToTarget.set(alias, {
|
|
1070
|
+
contentTypeId: type.id,
|
|
1071
|
+
canonicalSlug: enSlug,
|
|
1072
|
+
path: type.path
|
|
1073
|
+
});
|
|
1074
|
+
allAliasSlugs.add(alias);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return { aliasToTarget, allAliasSlugs, issues };
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/redirects/build-redirects.ts
|
|
1082
|
+
function getRedirectSourceSlugs(project) {
|
|
1083
|
+
const aliasIndex = buildGlobalAliasIndex(project);
|
|
1084
|
+
const outboundByType = /* @__PURE__ */ new Map();
|
|
1085
|
+
for (const type of project.config.types) {
|
|
1086
|
+
const outbound = /* @__PURE__ */ new Set();
|
|
1087
|
+
for (const enSlug of listEnSlugs(project.config.rootDir, type.contentDir)) {
|
|
1088
|
+
const doc = readEnDocument(project.config, type, enSlug);
|
|
1089
|
+
if (doc?.redirectTo) outbound.add(enSlug);
|
|
1090
|
+
}
|
|
1091
|
+
outboundByType.set(type.id, outbound);
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
aliasSlugs: aliasIndex.allAliasSlugs,
|
|
1095
|
+
outboundByType
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/sitemap/join-base-url.ts
|
|
1100
|
+
function joinBaseUrl(baseUrl, pathname) {
|
|
1101
|
+
const origin = baseUrl.replace(/\/$/, "");
|
|
1102
|
+
const path5 = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
1103
|
+
return `${origin}${path5}`;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/sitemap/generate-sitemap.ts
|
|
1107
|
+
function shouldIncludeEnDoc(enDoc, enSlug, redirectSources, contentTypeId, excludeNoindex) {
|
|
1108
|
+
if (enDoc.redirectTo) return false;
|
|
1109
|
+
if (redirectSources.aliasSlugs.has(enSlug)) return false;
|
|
1110
|
+
const outbound = redirectSources.outboundByType.get(contentTypeId);
|
|
1111
|
+
if (outbound?.has(enSlug)) return false;
|
|
1112
|
+
if (excludeNoindex && enDoc.noindex) return false;
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
function resolveSitemapOptions(options) {
|
|
1116
|
+
return {
|
|
1117
|
+
...options,
|
|
1118
|
+
resolveUrl: options.resolveUrl ?? ((_locale, pathname) => joinBaseUrl(options.baseUrl, pathname))
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
async function buildClusterEntry(type, enSlug, enDoc, allDocs, config, options) {
|
|
1122
|
+
const { defaultLocale, locales } = config;
|
|
1123
|
+
const excludeNoindex = options.excludeNoindex !== false;
|
|
1124
|
+
const includeXDefault = options.includeXDefault !== false;
|
|
1125
|
+
const typeDefaults = options.typeDefaults?.[type.id];
|
|
1126
|
+
const pathnameFor = (doc) => options.resolvePathname?.(type, doc, defaultLocale) ?? resolveCanonicalPathname(type, doc, defaultLocale);
|
|
1127
|
+
const alternates = {};
|
|
1128
|
+
for (const locale of locales) {
|
|
1129
|
+
const idx = allDocs.get(locale);
|
|
1130
|
+
if (!idx) continue;
|
|
1131
|
+
const doc = locale === defaultLocale ? idx.bySlug.get(enSlug) : idx.byEnSlug.get(enSlug);
|
|
1132
|
+
if (!doc) continue;
|
|
1133
|
+
if (excludeNoindex && doc.noindex) continue;
|
|
1134
|
+
const pathname = pathnameFor(doc);
|
|
1135
|
+
alternates[locale] = await options.resolveUrl(locale, pathname);
|
|
1136
|
+
}
|
|
1137
|
+
if (Object.keys(alternates).length === 0) return null;
|
|
1138
|
+
const mainUrl = alternates[defaultLocale] ?? alternates[Object.keys(alternates)[0]];
|
|
1139
|
+
if (includeXDefault && alternates[defaultLocale]) {
|
|
1140
|
+
alternates["x-default"] = alternates[defaultLocale];
|
|
1141
|
+
}
|
|
1142
|
+
const lastModified = documentLastModified(enDoc);
|
|
1143
|
+
return {
|
|
1144
|
+
url: mainUrl,
|
|
1145
|
+
...lastModified ? { lastModified } : {},
|
|
1146
|
+
...typeDefaults?.changeFrequency ? { changeFrequency: typeDefaults.changeFrequency } : {},
|
|
1147
|
+
...typeDefaults?.priority !== void 0 ? { priority: typeDefaults.priority } : {},
|
|
1148
|
+
alternates: { languages: alternates }
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
async function generateSitemap(project, options) {
|
|
1152
|
+
const resolvedOptions = resolveSitemapOptions(options);
|
|
1153
|
+
const { config } = project;
|
|
1154
|
+
const redirectSources = getRedirectSourceSlugs(project);
|
|
1155
|
+
const excludeNoindex = options.excludeNoindex !== false;
|
|
1156
|
+
const typeIds = options.contentTypes ?? config.types.filter(isRoutableType).map((t) => t.id);
|
|
1157
|
+
const entries = [];
|
|
1158
|
+
for (const typeId of typeIds) {
|
|
1159
|
+
const runtime = project.getType(typeId);
|
|
1160
|
+
const type = runtime.config;
|
|
1161
|
+
if (!isRoutableType(type)) continue;
|
|
1162
|
+
const allDocs = runtime.load();
|
|
1163
|
+
const enIndex = allDocs.get(config.defaultLocale);
|
|
1164
|
+
if (!enIndex) continue;
|
|
1165
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1166
|
+
for (const [enSlug, enDoc] of enIndex.bySlug) {
|
|
1167
|
+
if (processed.has(enSlug)) continue;
|
|
1168
|
+
processed.add(enSlug);
|
|
1169
|
+
if (!shouldIncludeEnDoc(enDoc, enSlug, redirectSources, typeId, excludeNoindex)) {
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
const entry = await buildClusterEntry(
|
|
1173
|
+
type,
|
|
1174
|
+
enSlug,
|
|
1175
|
+
enDoc,
|
|
1176
|
+
allDocs,
|
|
1177
|
+
config,
|
|
1178
|
+
resolvedOptions
|
|
1179
|
+
);
|
|
1180
|
+
if (entry) entries.push(entry);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return entries;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/create-scribe.ts
|
|
1187
|
+
function createScribe(input) {
|
|
1188
|
+
const config = resolveConfig(input);
|
|
1189
|
+
const project = createProject(config);
|
|
1190
|
+
const scribe = {
|
|
1191
|
+
config,
|
|
1192
|
+
project,
|
|
1193
|
+
getType: project.getType,
|
|
1194
|
+
listTypes: project.listTypes,
|
|
1195
|
+
sitemap(options) {
|
|
1196
|
+
return generateSitemap(project, options);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
for (const type of config.types) {
|
|
1200
|
+
scribe[type.id] = project.getType(type.id);
|
|
1201
|
+
}
|
|
1202
|
+
return scribe;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
export { createScribe };
|
|
1206
|
+
//# sourceMappingURL=runtime.js.map
|
|
1207
|
+
//# sourceMappingURL=runtime.js.map
|