scribe-cms 0.0.16 → 0.0.20
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 +10 -0
- package/dist/cli/index.cjs +4139 -542
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4137 -540
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1512 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1497 -231
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +435 -33
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +434 -32
- package/dist/runtime.js.map +1 -1
- package/dist/src/config/resolve-config.d.ts.map +1 -1
- package/dist/src/core/builtin-fields.d.ts +10 -1
- package/dist/src/core/builtin-fields.d.ts.map +1 -1
- package/dist/src/core/field.d.ts +70 -0
- package/dist/src/core/field.d.ts.map +1 -1
- package/dist/src/core/introspect-schema.d.ts +31 -1
- package/dist/src/core/introspect-schema.d.ts.map +1 -1
- package/dist/src/core/managed-roots.d.ts +15 -0
- package/dist/src/core/managed-roots.d.ts.map +1 -0
- package/dist/src/core/types.d.ts +56 -4
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/create-project.d.ts +11 -1
- package/dist/src/create-project.d.ts.map +1 -1
- package/dist/src/create-scribe.d.ts.map +1 -1
- package/dist/src/delete/execute.d.ts +21 -0
- package/dist/src/delete/execute.d.ts.map +1 -0
- package/dist/src/delete/plan.d.ts +85 -0
- package/dist/src/delete/plan.d.ts.map +1 -0
- package/dist/src/delete/render-text.d.ts +7 -0
- package/dist/src/delete/render-text.d.ts.map +1 -0
- package/dist/src/hash/page-hash.d.ts +9 -0
- package/dist/src/hash/page-hash.d.ts.map +1 -1
- package/dist/src/index.d.ts +12 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/inline/resolve-tokens.d.ts +31 -0
- package/dist/src/inline/resolve-tokens.d.ts.map +1 -0
- package/dist/src/inline/tokens.d.ts +95 -0
- package/dist/src/inline/tokens.d.ts.map +1 -0
- package/dist/src/loader/create-loader.d.ts +7 -1
- package/dist/src/loader/create-loader.d.ts.map +1 -1
- package/dist/src/loader/resolve-assets.d.ts +16 -0
- package/dist/src/loader/resolve-assets.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/storage/translations.d.ts +16 -0
- package/dist/src/storage/translations.d.ts.map +1 -1
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -1
- package/dist/src/translate/response-schema.d.ts +7 -1
- package/dist/src/translate/response-schema.d.ts.map +1 -1
- package/dist/src/translate/translate-core.d.ts +6 -0
- package/dist/src/translate/translate-core.d.ts.map +1 -1
- package/dist/src/translate/validate-translation.d.ts.map +1 -1
- package/dist/src/translate/worklist.d.ts +12 -2
- package/dist/src/translate/worklist.d.ts.map +1 -1
- package/dist/src/validate/validate-assets.d.ts +26 -2
- package/dist/src/validate/validate-assets.d.ts.map +1 -1
- package/dist/src/validate/validate-inline-tokens.d.ts +10 -0
- package/dist/src/validate/validate-inline-tokens.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts.map +1 -1
- package/dist/src/validate/validate-relations.d.ts +2 -1
- package/dist/src/validate/validate-relations.d.ts.map +1 -1
- package/dist/studio/asset-serve.d.ts +39 -0
- package/dist/studio/asset-serve.d.ts.map +1 -0
- package/dist/studio/asset-views.d.ts +4 -0
- package/dist/studio/asset-views.d.ts.map +1 -0
- package/dist/studio/content-views.d.ts +85 -0
- package/dist/studio/content-views.d.ts.map +1 -0
- package/dist/studio/introspect-fields.d.ts +111 -0
- package/dist/studio/introspect-fields.d.ts.map +1 -0
- package/dist/studio/mdx-preview.d.ts +3 -0
- package/dist/studio/mdx-preview.d.ts.map +1 -0
- package/dist/studio/preview-tokens.d.ts +16 -0
- package/dist/studio/preview-tokens.d.ts.map +1 -0
- package/dist/studio/search.d.ts +34 -0
- package/dist/studio/search.d.ts.map +1 -0
- package/dist/studio/server.cjs +4806 -281
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.d.ts.map +1 -1
- package/dist/studio/server.js +4804 -279
- package/dist/studio/server.js.map +1 -1
- package/dist/studio/shared.d.ts +20 -0
- package/dist/studio/shared.d.ts.map +1 -0
- package/dist/studio/studio-cache.d.ts +93 -0
- package/dist/studio/studio-cache.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/runtime.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var path = require('path');
|
|
4
4
|
var zod = require('zod');
|
|
5
|
-
var
|
|
5
|
+
var fs3 = require('fs');
|
|
6
6
|
var matter = require('gray-matter');
|
|
7
7
|
var Database = require('better-sqlite3');
|
|
8
8
|
var remarkMdx = require('remark-mdx');
|
|
@@ -12,7 +12,7 @@ var unified = require('unified');
|
|
|
12
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
13
|
|
|
14
14
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
15
|
-
var
|
|
15
|
+
var fs3__default = /*#__PURE__*/_interopDefault(fs3);
|
|
16
16
|
var matter__default = /*#__PURE__*/_interopDefault(matter);
|
|
17
17
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
18
18
|
var remarkMdx__default = /*#__PURE__*/_interopDefault(remarkMdx);
|
|
@@ -142,7 +142,15 @@ function resolveConfig(input, baseDir) {
|
|
|
142
142
|
const projectRoot = path__default.default.resolve(process.cwd(), raw.rootDir);
|
|
143
143
|
const contentRoot = path__default.default.resolve(projectRoot, raw.contentDir ?? "content");
|
|
144
144
|
const storePath = path__default.default.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
145
|
-
|
|
145
|
+
let assetsPath;
|
|
146
|
+
let assets;
|
|
147
|
+
if (raw.assets !== void 0 || raw.assetsDir !== void 0) {
|
|
148
|
+
const dir = raw.assets?.dir ?? raw.assetsDir ?? "public";
|
|
149
|
+
const publicPath = raw.assets?.publicPath ?? "/";
|
|
150
|
+
const managedDirs = raw.assets?.managedDirs ?? [];
|
|
151
|
+
assetsPath = path__default.default.resolve(projectRoot, dir);
|
|
152
|
+
assets = { assetsPath, publicPath, managedDirs };
|
|
153
|
+
}
|
|
146
154
|
const seenIds = /* @__PURE__ */ new Set();
|
|
147
155
|
const types = raw.types.map((type) => {
|
|
148
156
|
if (seenIds.has(type.id)) {
|
|
@@ -157,13 +165,17 @@ function resolveConfig(input, baseDir) {
|
|
|
157
165
|
contentDir: type.contentDir ?? type.id,
|
|
158
166
|
label: type.label ?? type.id.charAt(0).toUpperCase() + type.id.slice(1),
|
|
159
167
|
slugStrategy: type.slugStrategy ?? "fixed",
|
|
160
|
-
indexFallback: type.indexFallback ?? (type.path ? "en" : "none")
|
|
168
|
+
indexFallback: type.indexFallback ?? (type.path ? "en" : "none"),
|
|
169
|
+
// `body` defaults to true (fully backwards compatible). A resolved type
|
|
170
|
+
// always carries an explicit boolean so consumers read one source of truth.
|
|
171
|
+
body: type.body ?? true
|
|
161
172
|
};
|
|
162
173
|
});
|
|
163
174
|
const config = {
|
|
164
175
|
rootDir: contentRoot,
|
|
165
176
|
storePath,
|
|
166
177
|
assetsPath,
|
|
178
|
+
assets,
|
|
167
179
|
locales: [...raw.locales],
|
|
168
180
|
defaultLocale,
|
|
169
181
|
localeRouting,
|
|
@@ -199,6 +211,7 @@ function deriveLocaleFallbacks(locales, defaultLocale) {
|
|
|
199
211
|
}
|
|
200
212
|
var FIELD_KIND = /* @__PURE__ */ Symbol.for("@genlook/scribe/fieldKind");
|
|
201
213
|
var RELATION_META = /* @__PURE__ */ Symbol.for("@genlook/scribe/relationMeta");
|
|
214
|
+
var ASSET_META = /* @__PURE__ */ Symbol.for("@genlook/scribe/assetMeta");
|
|
202
215
|
function getFieldKind(schema) {
|
|
203
216
|
const tagged = schema;
|
|
204
217
|
return tagged[FIELD_KIND] ?? "structural";
|
|
@@ -226,6 +239,29 @@ function getRelationTarget(schema) {
|
|
|
226
239
|
}
|
|
227
240
|
return null;
|
|
228
241
|
}
|
|
242
|
+
function getAssetMeta(schema) {
|
|
243
|
+
let current = schema;
|
|
244
|
+
for (let i = 0; i < 8; i++) {
|
|
245
|
+
const tagged = current;
|
|
246
|
+
if (tagged[ASSET_META]) {
|
|
247
|
+
return tagged[ASSET_META];
|
|
248
|
+
}
|
|
249
|
+
if (typeof tagged.unwrap === "function") {
|
|
250
|
+
current = tagged.unwrap();
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (typeof tagged.removeDefault === "function") {
|
|
254
|
+
current = tagged.removeDefault();
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (tagged._def?.innerType) {
|
|
258
|
+
current = tagged._def.innerType;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
229
265
|
function peelOptionalWrappers(schema) {
|
|
230
266
|
let current = schema;
|
|
231
267
|
for (let i = 0; i < 8; i++) {
|
|
@@ -259,7 +295,23 @@ function introspectSchema(schema, prefix = []) {
|
|
|
259
295
|
kind: "relation",
|
|
260
296
|
relationTarget: relation.typeId,
|
|
261
297
|
relationMultiple: relation.multiple,
|
|
262
|
-
relationOptional: relation.optional
|
|
298
|
+
relationOptional: relation.optional,
|
|
299
|
+
relationOnTargetDelete: relation.onTargetDelete
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
const asset = getAssetMeta(schema);
|
|
304
|
+
if (asset && prefix.length > 0) {
|
|
305
|
+
return [
|
|
306
|
+
{
|
|
307
|
+
path: prefix,
|
|
308
|
+
kind: "asset",
|
|
309
|
+
assetDir: asset.dir,
|
|
310
|
+
assetTemplate: asset.template,
|
|
311
|
+
assetFormats: asset.formats,
|
|
312
|
+
assetMaxKB: asset.maxKB,
|
|
313
|
+
assetOptional: asset.optional,
|
|
314
|
+
assetOnDelete: asset.onDelete
|
|
263
315
|
}
|
|
264
316
|
];
|
|
265
317
|
}
|
|
@@ -337,7 +389,7 @@ function pickTranslatable(data, schema) {
|
|
|
337
389
|
return extractByPaths(data, translatablePaths);
|
|
338
390
|
}
|
|
339
391
|
function pickStructural(data, schema) {
|
|
340
|
-
const structuralPaths = introspectSchema(schema).filter((f) => f.kind === "structural" || f.kind === "relation").map((f) => f.path);
|
|
392
|
+
const structuralPaths = introspectSchema(schema).filter((f) => f.kind === "structural" || f.kind === "relation" || f.kind === "asset").map((f) => f.path);
|
|
341
393
|
return extractByPaths(data, structuralPaths);
|
|
342
394
|
}
|
|
343
395
|
function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
@@ -352,7 +404,7 @@ function deepMerge(base, overlay) {
|
|
|
352
404
|
out[key] = deepMerge(out[key], value);
|
|
353
405
|
} else if (Array.isArray(value) && Array.isArray(out[key])) {
|
|
354
406
|
out[key] = mergeArrayOverlay(out[key], value);
|
|
355
|
-
} else {
|
|
407
|
+
} else if (value !== void 0) {
|
|
356
408
|
out[key] = value;
|
|
357
409
|
}
|
|
358
410
|
}
|
|
@@ -370,6 +422,9 @@ function mergeArrayOverlay(base, overlay) {
|
|
|
370
422
|
function listRelationFields(schema) {
|
|
371
423
|
return introspectSchema(schema).filter((f) => f.kind === "relation");
|
|
372
424
|
}
|
|
425
|
+
function listAssetFields(schema) {
|
|
426
|
+
return introspectSchema(schema).filter((f) => f.kind === "asset");
|
|
427
|
+
}
|
|
373
428
|
var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
374
429
|
var slugPatternSchema = zod.z.string().regex(SLUG_PATTERN, "slug must be lowercase-kebab-case");
|
|
375
430
|
var isoDateSchema = zod.z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]*)?$/, "Use ISO date YYYY-MM-DD or full ISO 8601");
|
|
@@ -450,12 +505,18 @@ function extractBuiltinEnFields(data, _pathTemplate, _enSlug, _defaultLocale) {
|
|
|
450
505
|
delete rest[internalKey];
|
|
451
506
|
}
|
|
452
507
|
}
|
|
508
|
+
let vars;
|
|
509
|
+
if (rest.vars !== void 0) {
|
|
510
|
+
vars = rest.vars;
|
|
511
|
+
delete rest.vars;
|
|
512
|
+
}
|
|
453
513
|
return {
|
|
454
514
|
builtin: {
|
|
455
515
|
publishedAt,
|
|
456
516
|
updatedAt,
|
|
457
517
|
noindex,
|
|
458
|
-
canonicalPathOverride
|
|
518
|
+
canonicalPathOverride,
|
|
519
|
+
vars
|
|
459
520
|
},
|
|
460
521
|
rest,
|
|
461
522
|
issues
|
|
@@ -485,6 +546,7 @@ function mergeBuiltinsIntoFrontmatter(frontmatter, doc, type, defaultLocale, loc
|
|
|
485
546
|
if (doc.updatedAt !== void 0) out.updatedAt = doc.updatedAt;
|
|
486
547
|
out.noindex = doc.noindex;
|
|
487
548
|
out.canonicalPath = resolveCanonicalPathname(type, doc, defaultLocale, localeRouting);
|
|
549
|
+
if (doc.vars !== void 0) out.vars = doc.vars;
|
|
488
550
|
return out;
|
|
489
551
|
}
|
|
490
552
|
function seoFieldsFromEn(enDoc) {
|
|
@@ -574,7 +636,7 @@ function resolveStorePath(config) {
|
|
|
574
636
|
function openStore(config, mode = "readwrite") {
|
|
575
637
|
const storePath = resolveStorePath(config);
|
|
576
638
|
if (mode === "readwrite") {
|
|
577
|
-
|
|
639
|
+
fs3__default.default.mkdirSync(path__default.default.dirname(storePath), { recursive: true });
|
|
578
640
|
}
|
|
579
641
|
const db = new Database__default.default(storePath, { readonly: mode === "readonly" });
|
|
580
642
|
if (mode === "readwrite") {
|
|
@@ -613,6 +675,9 @@ function migrate(db) {
|
|
|
613
675
|
function listTranslationsForType(db, contentType) {
|
|
614
676
|
return db.prepare(`SELECT * FROM translations WHERE content_type = ? ORDER BY en_slug, locale`).all(contentType);
|
|
615
677
|
}
|
|
678
|
+
function bulkLoadTranslations(db) {
|
|
679
|
+
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
680
|
+
}
|
|
616
681
|
|
|
617
682
|
// src/translate/normalize-mdx-body.ts
|
|
618
683
|
function needsMdxEscapeNormalization(body) {
|
|
@@ -729,6 +794,279 @@ function prepareTranslatedMdxBody(body) {
|
|
|
729
794
|
};
|
|
730
795
|
}
|
|
731
796
|
|
|
797
|
+
// src/loader/resolve-assets.ts
|
|
798
|
+
function joinPublicPath(publicPath, webPath) {
|
|
799
|
+
if (publicPath === "" || publicPath === "/") return webPath;
|
|
800
|
+
const suffix = webPath.startsWith("/") ? webPath : `/${webPath}`;
|
|
801
|
+
return publicPath.replace(/\/+$/, "") + suffix;
|
|
802
|
+
}
|
|
803
|
+
function materializeTemplate(template, enSlug) {
|
|
804
|
+
return template.split("{slug}").join(enSlug);
|
|
805
|
+
}
|
|
806
|
+
function setAssetAtPath(container, path6, transform) {
|
|
807
|
+
const [head, ...rest] = path6;
|
|
808
|
+
if (head === void 0) return;
|
|
809
|
+
if (rest.length === 0) {
|
|
810
|
+
const current = container[head];
|
|
811
|
+
const next = transform(typeof current === "string" ? current : void 0);
|
|
812
|
+
if (next !== void 0) container[head] = next;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (rest[0] === "*") {
|
|
816
|
+
const arr = container[head];
|
|
817
|
+
if (!Array.isArray(arr)) return;
|
|
818
|
+
for (const item of arr) {
|
|
819
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
820
|
+
setAssetAtPath(item, rest.slice(1), transform);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const child = container[head];
|
|
826
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
827
|
+
setAssetAtPath(child, rest, transform);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function resolveDocumentAssets(doc, assetFields, assets) {
|
|
831
|
+
const frontmatter = doc.frontmatter;
|
|
832
|
+
for (const f of assetFields) {
|
|
833
|
+
setAssetAtPath(frontmatter, f.path, (current) => {
|
|
834
|
+
let value = current;
|
|
835
|
+
if (value === void 0) {
|
|
836
|
+
if (!f.assetTemplate) return void 0;
|
|
837
|
+
value = materializeTemplate(f.assetTemplate, doc.enSlug);
|
|
838
|
+
}
|
|
839
|
+
return joinPublicPath(assets.publicPath, value);
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/inline/tokens.ts
|
|
845
|
+
var TOKEN_OPEN = "${{";
|
|
846
|
+
var ESCAPE_SEQUENCE = "$\\{{";
|
|
847
|
+
var ESCAPE_RESULT = "${{";
|
|
848
|
+
function placeholderMarker(n) {
|
|
849
|
+
return `%%${n}%%`;
|
|
850
|
+
}
|
|
851
|
+
function findTokenEnd(body, start) {
|
|
852
|
+
let i = start;
|
|
853
|
+
let inString = false;
|
|
854
|
+
while (i < body.length) {
|
|
855
|
+
const ch = body[i];
|
|
856
|
+
if (ch === "\n") return -1;
|
|
857
|
+
if (inString) {
|
|
858
|
+
if (ch === "\\") {
|
|
859
|
+
i += 2;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (ch === '"') inString = false;
|
|
863
|
+
i += 1;
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (ch === '"') {
|
|
867
|
+
inString = true;
|
|
868
|
+
i += 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (ch === "}" && body[i + 1] === "}") return i;
|
|
872
|
+
i += 1;
|
|
873
|
+
}
|
|
874
|
+
return -1;
|
|
875
|
+
}
|
|
876
|
+
function parseTokenInner(inner) {
|
|
877
|
+
const colon = inner.indexOf(":");
|
|
878
|
+
if (colon === -1) {
|
|
879
|
+
return { ok: false, reason: `missing kind separator ":"` };
|
|
880
|
+
}
|
|
881
|
+
const kind = inner.slice(0, colon);
|
|
882
|
+
const rest = inner.slice(colon + 1);
|
|
883
|
+
switch (kind) {
|
|
884
|
+
case "static": {
|
|
885
|
+
let text;
|
|
886
|
+
try {
|
|
887
|
+
text = JSON.parse(rest);
|
|
888
|
+
} catch {
|
|
889
|
+
return { ok: false, reason: `static value is not a valid JSON string` };
|
|
890
|
+
}
|
|
891
|
+
if (typeof text !== "string") {
|
|
892
|
+
return { ok: false, reason: `static value must be a JSON string` };
|
|
893
|
+
}
|
|
894
|
+
return { ok: true, token: { kind: "static", text } };
|
|
895
|
+
}
|
|
896
|
+
case "relation": {
|
|
897
|
+
const parts = rest.split(":");
|
|
898
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
899
|
+
return {
|
|
900
|
+
ok: false,
|
|
901
|
+
reason: `relation must be relation:<typeId>:<enSlug> or relation:<typeId>:<enSlug>:slug`
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
const [targetTypeId, enSlug, mode] = parts;
|
|
905
|
+
if (!targetTypeId || !enSlug) {
|
|
906
|
+
return { ok: false, reason: `relation is missing a typeId or enSlug` };
|
|
907
|
+
}
|
|
908
|
+
if (mode !== void 0 && mode !== "slug") {
|
|
909
|
+
return { ok: false, reason: `relation mode must be "slug" (got "${mode}")` };
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
ok: true,
|
|
913
|
+
token: {
|
|
914
|
+
kind: "relation",
|
|
915
|
+
targetTypeId,
|
|
916
|
+
enSlug,
|
|
917
|
+
mode: mode === "slug" ? "slug" : "url"
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
case "asset": {
|
|
922
|
+
if (!rest.startsWith("/")) {
|
|
923
|
+
return { ok: false, reason: `asset path must start with "/" (got "${rest}")` };
|
|
924
|
+
}
|
|
925
|
+
return { ok: true, token: { kind: "asset", webPath: rest } };
|
|
926
|
+
}
|
|
927
|
+
case "var": {
|
|
928
|
+
if (!rest) return { ok: false, reason: `var is missing a key` };
|
|
929
|
+
return { ok: true, token: { kind: "var", key: rest } };
|
|
930
|
+
}
|
|
931
|
+
default:
|
|
932
|
+
return { ok: false, reason: `unknown token kind "${kind}"` };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function extractInlineTokens(body) {
|
|
936
|
+
const tokens = [];
|
|
937
|
+
const malformed = [];
|
|
938
|
+
let out = "";
|
|
939
|
+
let i = 0;
|
|
940
|
+
while (i < body.length) {
|
|
941
|
+
const open = body.indexOf(TOKEN_OPEN, i);
|
|
942
|
+
if (open === -1) {
|
|
943
|
+
out += body.slice(i);
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
out += body.slice(i, open);
|
|
947
|
+
const contentStart = open + TOKEN_OPEN.length;
|
|
948
|
+
const end = findTokenEnd(body, contentStart);
|
|
949
|
+
if (end === -1) {
|
|
950
|
+
malformed.push({
|
|
951
|
+
raw: body.slice(open),
|
|
952
|
+
reason: `unterminated token (missing "}}")`,
|
|
953
|
+
index: open
|
|
954
|
+
});
|
|
955
|
+
out += body.slice(open);
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
const raw = body.slice(open, end + 2);
|
|
959
|
+
const inner = body.slice(contentStart, end);
|
|
960
|
+
const parsed = parseTokenInner(inner);
|
|
961
|
+
if (parsed.ok) {
|
|
962
|
+
tokens.push({ ...parsed.token, raw });
|
|
963
|
+
out += placeholderMarker(tokens.length);
|
|
964
|
+
} else {
|
|
965
|
+
malformed.push({ raw, reason: parsed.reason, index: open });
|
|
966
|
+
out += raw;
|
|
967
|
+
}
|
|
968
|
+
i = end + 2;
|
|
969
|
+
}
|
|
970
|
+
return { placeholderBody: out, tokens, malformed };
|
|
971
|
+
}
|
|
972
|
+
function fillPlaceholders(body, resolvedValues) {
|
|
973
|
+
return body.replace(/%%(\d+)%%/g, (match, n) => {
|
|
974
|
+
const idx = Number(n) - 1;
|
|
975
|
+
return idx >= 0 && idx < resolvedValues.length ? resolvedValues[idx] : match;
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function unescapeInlineTokens(body) {
|
|
979
|
+
return body.split(ESCAPE_SEQUENCE).join(ESCAPE_RESULT);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/inline/resolve-tokens.ts
|
|
983
|
+
function slugKey(typeId, enSlug, locale) {
|
|
984
|
+
return `${typeId}\0${enSlug}\0${locale}`;
|
|
985
|
+
}
|
|
986
|
+
function isStringRecord(value) {
|
|
987
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
988
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
989
|
+
}
|
|
990
|
+
function createInlineResolver(config) {
|
|
991
|
+
const urlBuilder = createUrlBuilder(config);
|
|
992
|
+
const typeById = new Map(config.types.map((t) => [t.id, t]));
|
|
993
|
+
const storePath = resolveStorePath(config);
|
|
994
|
+
let slugCache = null;
|
|
995
|
+
const localizedSlugIndex = () => {
|
|
996
|
+
let mtime = 0;
|
|
997
|
+
try {
|
|
998
|
+
mtime = fs3__default.default.statSync(storePath).mtimeMs;
|
|
999
|
+
} catch {
|
|
1000
|
+
mtime = 0;
|
|
1001
|
+
}
|
|
1002
|
+
if (slugCache && slugCache.mtime === mtime) return slugCache.index;
|
|
1003
|
+
const index = /* @__PURE__ */ new Map();
|
|
1004
|
+
try {
|
|
1005
|
+
const db = openStore(config, "readonly");
|
|
1006
|
+
try {
|
|
1007
|
+
for (const row of bulkLoadTranslations(db)) {
|
|
1008
|
+
index.set(slugKey(row.content_type, row.en_slug, row.locale), row.slug);
|
|
1009
|
+
}
|
|
1010
|
+
} finally {
|
|
1011
|
+
db.close();
|
|
1012
|
+
}
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
slugCache = { mtime, index };
|
|
1016
|
+
return index;
|
|
1017
|
+
};
|
|
1018
|
+
const localizedSlug = (typeId, enSlug, locale) => {
|
|
1019
|
+
if (locale === config.defaultLocale) return enSlug;
|
|
1020
|
+
const index = localizedSlugIndex();
|
|
1021
|
+
const direct = index.get(slugKey(typeId, enSlug, locale));
|
|
1022
|
+
if (direct) return direct;
|
|
1023
|
+
for (const fb of config.localeFallbacks?.[locale] ?? []) {
|
|
1024
|
+
const hit = index.get(slugKey(typeId, enSlug, fb));
|
|
1025
|
+
if (hit) return hit;
|
|
1026
|
+
}
|
|
1027
|
+
return enSlug;
|
|
1028
|
+
};
|
|
1029
|
+
return {
|
|
1030
|
+
resolve(token, enFrontmatter, locale) {
|
|
1031
|
+
switch (token.kind) {
|
|
1032
|
+
case "static":
|
|
1033
|
+
return token.text;
|
|
1034
|
+
case "var": {
|
|
1035
|
+
const vars = enFrontmatter.vars;
|
|
1036
|
+
if (isStringRecord(vars) && typeof vars[token.key] === "string") {
|
|
1037
|
+
return vars[token.key];
|
|
1038
|
+
}
|
|
1039
|
+
return "";
|
|
1040
|
+
}
|
|
1041
|
+
case "asset": {
|
|
1042
|
+
const assets = config.assets;
|
|
1043
|
+
return assets ? joinPublicPath(assets.publicPath, token.webPath) : token.webPath;
|
|
1044
|
+
}
|
|
1045
|
+
case "relation": {
|
|
1046
|
+
const type = typeById.get(token.targetTypeId);
|
|
1047
|
+
if (!type) return "";
|
|
1048
|
+
if (token.mode === "slug") return token.enSlug;
|
|
1049
|
+
if (!isRoutableType(type)) return "";
|
|
1050
|
+
const slug = localizedSlug(token.targetTypeId, token.enSlug, locale);
|
|
1051
|
+
return urlBuilder.resolvePath(type.path, slug, locale);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function substituteEnInlineBody(rawBody, enFrontmatter, defaultLocale, resolver) {
|
|
1058
|
+
const { placeholderBody, tokens } = extractInlineTokens(rawBody);
|
|
1059
|
+
if (tokens.length === 0) return unescapeInlineTokens(placeholderBody);
|
|
1060
|
+
const values = tokens.map((t) => resolver.resolve(t, enFrontmatter, defaultLocale));
|
|
1061
|
+
return unescapeInlineTokens(fillPlaceholders(placeholderBody, values));
|
|
1062
|
+
}
|
|
1063
|
+
function fillTranslatedInlineBody(translatedBody, enRawBody, enFrontmatter, locale, resolver) {
|
|
1064
|
+
const { tokens } = extractInlineTokens(enRawBody);
|
|
1065
|
+
if (tokens.length === 0) return unescapeInlineTokens(translatedBody);
|
|
1066
|
+
const values = tokens.map((t) => resolver.resolve(t, enFrontmatter, locale));
|
|
1067
|
+
return unescapeInlineTokens(fillPlaceholders(translatedBody, values));
|
|
1068
|
+
}
|
|
1069
|
+
|
|
732
1070
|
// src/loader/normalize-en.ts
|
|
733
1071
|
function normalizeEnFrontmatter(data) {
|
|
734
1072
|
const out = { ...data };
|
|
@@ -754,14 +1092,14 @@ function isPostFile(name) {
|
|
|
754
1092
|
return isPublishableContentFile(name);
|
|
755
1093
|
}
|
|
756
1094
|
function listEnFiles(contentDir) {
|
|
757
|
-
if (!
|
|
758
|
-
return
|
|
1095
|
+
if (!fs3__default.default.existsSync(contentDir)) return [];
|
|
1096
|
+
return fs3__default.default.readdirSync(contentDir).filter(isPostFile).map((f) => path__default.default.join(contentDir, f));
|
|
759
1097
|
}
|
|
760
1098
|
function slugFromPath(filePath) {
|
|
761
1099
|
return path__default.default.basename(filePath).replace(/\.(md|mdx)$/, "");
|
|
762
1100
|
}
|
|
763
1101
|
function parseEnMdx(filePath, config, type) {
|
|
764
|
-
const raw =
|
|
1102
|
+
const raw = fs3__default.default.readFileSync(filePath, "utf8");
|
|
765
1103
|
const parsed = matter__default.default(raw);
|
|
766
1104
|
const slug = slugFromPath(filePath);
|
|
767
1105
|
const normalized = normalizeEnFrontmatter(parsed.data);
|
|
@@ -800,6 +1138,7 @@ function parseEnMdx(filePath, config, type) {
|
|
|
800
1138
|
updatedAt: builtin.updatedAt,
|
|
801
1139
|
noindex: builtin.noindex,
|
|
802
1140
|
canonicalPathOverride: builtin.canonicalPathOverride,
|
|
1141
|
+
vars: builtin.vars,
|
|
803
1142
|
slug,
|
|
804
1143
|
locale: config.defaultLocale
|
|
805
1144
|
},
|
|
@@ -824,12 +1163,15 @@ function parseEnMdx(filePath, config, type) {
|
|
|
824
1163
|
noindex: builtin.noindex,
|
|
825
1164
|
canonicalPathOverride: builtin.canonicalPathOverride,
|
|
826
1165
|
frontmatter,
|
|
827
|
-
|
|
1166
|
+
// Bodyless types (`body: false`) never carry a body: the loader skips the
|
|
1167
|
+
// MDX body entirely so runtimes, exports, and hashing all see an empty body.
|
|
1168
|
+
// A stray body is reported separately by `scribe validate`.
|
|
1169
|
+
content: type.body === false ? "" : parsed.content,
|
|
828
1170
|
filePath
|
|
829
1171
|
};
|
|
830
1172
|
return { document, issues };
|
|
831
1173
|
}
|
|
832
|
-
function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
1174
|
+
function buildDocumentFromTranslation(row, enDoc, type, config, inlineResolver) {
|
|
833
1175
|
const localeFm = JSON.parse(row.frontmatter_json);
|
|
834
1176
|
const merged = mergeStructuralOntoLocale(localeFm, enDoc.frontmatter, type.schema);
|
|
835
1177
|
const seo = seoFieldsFromEn(enDoc);
|
|
@@ -840,6 +1182,14 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
|
840
1182
|
config.defaultLocale,
|
|
841
1183
|
config.localeRouting
|
|
842
1184
|
);
|
|
1185
|
+
const preparedBody = prepareTranslatedMdxBody(row.body).body;
|
|
1186
|
+
const content = inlineResolver ? fillTranslatedInlineBody(
|
|
1187
|
+
preparedBody,
|
|
1188
|
+
enDoc.content,
|
|
1189
|
+
enDoc.frontmatter,
|
|
1190
|
+
row.locale,
|
|
1191
|
+
inlineResolver
|
|
1192
|
+
) : preparedBody;
|
|
843
1193
|
return {
|
|
844
1194
|
slug: row.slug,
|
|
845
1195
|
enSlug: row.en_slug,
|
|
@@ -849,14 +1199,16 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
|
849
1199
|
noindex: seo.noindex,
|
|
850
1200
|
canonicalPathOverride: seo.canonicalPathOverride,
|
|
851
1201
|
frontmatter,
|
|
852
|
-
content
|
|
1202
|
+
content
|
|
853
1203
|
};
|
|
854
1204
|
}
|
|
855
1205
|
var DEV_REVALIDATE_MS = 1500;
|
|
856
|
-
|
|
1206
|
+
var contentVersion = 0;
|
|
1207
|
+
function createContentLoader(config, type, options = {}) {
|
|
857
1208
|
let cached = null;
|
|
858
1209
|
let signature = "";
|
|
859
1210
|
let lastCheck = 0;
|
|
1211
|
+
let builtVersion = contentVersion;
|
|
860
1212
|
const contentDir = path__default.default.join(
|
|
861
1213
|
/* turbopackIgnore: true */
|
|
862
1214
|
config.rootDir,
|
|
@@ -864,18 +1216,34 @@ function createContentLoader(config, type) {
|
|
|
864
1216
|
);
|
|
865
1217
|
const storePath = resolveStorePath(config);
|
|
866
1218
|
const isProd = process.env.NODE_ENV === "production";
|
|
1219
|
+
const assets = config.assets;
|
|
1220
|
+
const assetFields = assets && options.resolveAssets ? listAssetFields(type.schema) : [];
|
|
1221
|
+
const resolveAssets = (doc) => {
|
|
1222
|
+
if (assetFields.length > 0 && assets) resolveDocumentAssets(doc, assetFields, assets);
|
|
1223
|
+
};
|
|
1224
|
+
const inlineResolver = options.inlineResolver;
|
|
1225
|
+
const resolveEnInline = (doc) => {
|
|
1226
|
+
if (inlineResolver) {
|
|
1227
|
+
doc.content = substituteEnInlineBody(
|
|
1228
|
+
doc.content,
|
|
1229
|
+
doc.frontmatter,
|
|
1230
|
+
config.defaultLocale,
|
|
1231
|
+
inlineResolver
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
867
1235
|
function computeSignature() {
|
|
868
1236
|
const files = listEnFiles(contentDir);
|
|
869
1237
|
let newest = 0;
|
|
870
1238
|
for (const f of files) {
|
|
871
1239
|
try {
|
|
872
|
-
newest = Math.max(newest,
|
|
1240
|
+
newest = Math.max(newest, fs3__default.default.statSync(f).mtimeMs);
|
|
873
1241
|
} catch {
|
|
874
1242
|
}
|
|
875
1243
|
}
|
|
876
1244
|
let store = 0;
|
|
877
1245
|
try {
|
|
878
|
-
store =
|
|
1246
|
+
store = fs3__default.default.statSync(storePath).mtimeMs;
|
|
879
1247
|
} catch {
|
|
880
1248
|
}
|
|
881
1249
|
return `${files.length}:${newest}:${store}`;
|
|
@@ -921,16 +1289,28 @@ function createContentLoader(config, type) {
|
|
|
921
1289
|
for (const row of rowsByLocale.get(locale) ?? []) {
|
|
922
1290
|
const enDoc = englishBySlug.get(row.en_slug);
|
|
923
1291
|
if (!enDoc) continue;
|
|
924
|
-
const doc = buildDocumentFromTranslation(row, enDoc, type, config);
|
|
1292
|
+
const doc = buildDocumentFromTranslation(row, enDoc, type, config, inlineResolver);
|
|
1293
|
+
resolveAssets(doc);
|
|
925
1294
|
bySlug.set(doc.slug, doc);
|
|
926
1295
|
byEnSlug.set(row.en_slug, doc);
|
|
927
1296
|
}
|
|
928
1297
|
out.set(locale, { bySlug, byEnSlug });
|
|
929
1298
|
}
|
|
1299
|
+
for (const doc of englishBySlug.values()) {
|
|
1300
|
+
resolveAssets(doc);
|
|
1301
|
+
resolveEnInline(doc);
|
|
1302
|
+
}
|
|
930
1303
|
return out;
|
|
931
1304
|
}
|
|
932
1305
|
return () => {
|
|
933
1306
|
if (cached) {
|
|
1307
|
+
if (contentVersion !== builtVersion) {
|
|
1308
|
+
cached = build();
|
|
1309
|
+
builtVersion = contentVersion;
|
|
1310
|
+
lastCheck = Date.now();
|
|
1311
|
+
signature = isProd ? "" : computeSignature();
|
|
1312
|
+
return cached;
|
|
1313
|
+
}
|
|
934
1314
|
if (isProd) return cached;
|
|
935
1315
|
const now = Date.now();
|
|
936
1316
|
if (now - lastCheck < DEV_REVALIDATE_MS) return cached;
|
|
@@ -943,6 +1323,7 @@ function createContentLoader(config, type) {
|
|
|
943
1323
|
}
|
|
944
1324
|
lastCheck = Date.now();
|
|
945
1325
|
cached = build();
|
|
1326
|
+
builtVersion = contentVersion;
|
|
946
1327
|
signature = isProd ? "" : computeSignature();
|
|
947
1328
|
return cached;
|
|
948
1329
|
};
|
|
@@ -1032,8 +1413,11 @@ function comparatorFor(orderBy) {
|
|
|
1032
1413
|
return (a, b) => a.slug.localeCompare(b.slug);
|
|
1033
1414
|
}
|
|
1034
1415
|
}
|
|
1035
|
-
function buildRuntime(config, type, getRuntime) {
|
|
1036
|
-
const load = createContentLoader(config, type
|
|
1416
|
+
function buildRuntime(config, type, getRuntime, options = {}) {
|
|
1417
|
+
const load = createContentLoader(config, type, {
|
|
1418
|
+
resolveAssets: options.resolveAssets,
|
|
1419
|
+
inlineResolver: options.inlineResolver
|
|
1420
|
+
});
|
|
1037
1421
|
const relationFields = new Map(
|
|
1038
1422
|
listRelationFields(type.schema).filter((f) => f.path.length === 1).map((f) => [f.path[0], f])
|
|
1039
1423
|
);
|
|
@@ -1050,12 +1434,12 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1050
1434
|
id: type.id,
|
|
1051
1435
|
config: type,
|
|
1052
1436
|
load,
|
|
1053
|
-
list(locale = config.defaultLocale,
|
|
1437
|
+
list(locale = config.defaultLocale, options2 = {}) {
|
|
1054
1438
|
const idx = load().get(locale);
|
|
1055
1439
|
if (!idx) return [];
|
|
1056
1440
|
const docs = Array.from(idx.bySlug.values());
|
|
1057
|
-
docs.sort(comparatorFor(
|
|
1058
|
-
return
|
|
1441
|
+
docs.sort(comparatorFor(options2.orderBy ?? type.orderBy ?? "slug"));
|
|
1442
|
+
return options2.limit !== void 0 ? docs.slice(0, options2.limit) : docs;
|
|
1059
1443
|
},
|
|
1060
1444
|
get(slug, locale = config.defaultLocale) {
|
|
1061
1445
|
return load().get(locale)?.bySlug.get(slug) ?? null;
|
|
@@ -1068,7 +1452,7 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1068
1452
|
load(),
|
|
1069
1453
|
type,
|
|
1070
1454
|
config.localeRouting,
|
|
1071
|
-
config.localeFallbacks[locale] ?? []
|
|
1455
|
+
config.localeFallbacks?.[locale] ?? []
|
|
1072
1456
|
);
|
|
1073
1457
|
if (result.document && type.path) {
|
|
1074
1458
|
return {
|
|
@@ -1082,15 +1466,15 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1082
1466
|
}
|
|
1083
1467
|
return result;
|
|
1084
1468
|
},
|
|
1085
|
-
staticParams(
|
|
1469
|
+
staticParams(options2 = {}) {
|
|
1086
1470
|
assertRoutable("staticParams");
|
|
1087
1471
|
const all = load();
|
|
1088
1472
|
const enIdx = all.get(config.defaultLocale);
|
|
1089
1473
|
if (!enIdx) return [];
|
|
1090
1474
|
const params = [];
|
|
1091
|
-
for (const locale of
|
|
1475
|
+
for (const locale of options2.locales ?? config.locales) {
|
|
1092
1476
|
const localeIdx = all.get(locale);
|
|
1093
|
-
const fallbacks = config.localeFallbacks[locale] ?? [];
|
|
1477
|
+
const fallbacks = config.localeFallbacks?.[locale] ?? [];
|
|
1094
1478
|
for (const doc of enIdx.bySlug.values()) {
|
|
1095
1479
|
let slug;
|
|
1096
1480
|
if (locale === config.defaultLocale) {
|
|
@@ -1169,7 +1553,7 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1169
1553
|
};
|
|
1170
1554
|
return runtime;
|
|
1171
1555
|
}
|
|
1172
|
-
function createProject(config) {
|
|
1556
|
+
function createProject(config, options = {}) {
|
|
1173
1557
|
const runtimes = /* @__PURE__ */ new Map();
|
|
1174
1558
|
const getRuntime = (id) => {
|
|
1175
1559
|
const runtime = runtimes.get(id);
|
|
@@ -1178,8 +1562,15 @@ function createProject(config) {
|
|
|
1178
1562
|
}
|
|
1179
1563
|
return runtime;
|
|
1180
1564
|
};
|
|
1565
|
+
const inlineResolver = options.resolveInlineTokens ? createInlineResolver(config) : void 0;
|
|
1181
1566
|
for (const type of config.types) {
|
|
1182
|
-
runtimes.set(
|
|
1567
|
+
runtimes.set(
|
|
1568
|
+
type.id,
|
|
1569
|
+
buildRuntime(config, type, getRuntime, {
|
|
1570
|
+
resolveAssets: options.resolveAssets,
|
|
1571
|
+
inlineResolver
|
|
1572
|
+
})
|
|
1573
|
+
);
|
|
1183
1574
|
}
|
|
1184
1575
|
return {
|
|
1185
1576
|
config,
|
|
@@ -1272,10 +1663,10 @@ function redirectsFilePath(config, type) {
|
|
|
1272
1663
|
}
|
|
1273
1664
|
function loadTypeRedirectsFile(config, type) {
|
|
1274
1665
|
const filePath = redirectsFilePath(config, type);
|
|
1275
|
-
if (!
|
|
1666
|
+
if (!fs3__default.default.existsSync(filePath)) return null;
|
|
1276
1667
|
let raw;
|
|
1277
1668
|
try {
|
|
1278
|
-
raw = JSON.parse(
|
|
1669
|
+
raw = JSON.parse(fs3__default.default.readFileSync(filePath, "utf8"));
|
|
1279
1670
|
} catch (error) {
|
|
1280
1671
|
throw new Error(
|
|
1281
1672
|
`${type.id}: failed to parse ${TYPE_REDIRECTS_FILENAME}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1431,7 +1822,7 @@ async function generateSitemap(project, options) {
|
|
|
1431
1822
|
// src/create-scribe.ts
|
|
1432
1823
|
function createScribe(input) {
|
|
1433
1824
|
const config = resolveConfig(input);
|
|
1434
|
-
const project = createProject(config);
|
|
1825
|
+
const project = createProject(config, { resolveAssets: true, resolveInlineTokens: true });
|
|
1435
1826
|
const scribe = {
|
|
1436
1827
|
config,
|
|
1437
1828
|
project,
|
|
@@ -1440,6 +1831,17 @@ function createScribe(input) {
|
|
|
1440
1831
|
listRoutableTypes: project.listRoutableTypes,
|
|
1441
1832
|
sitemap(options) {
|
|
1442
1833
|
return generateSitemap(project, options);
|
|
1834
|
+
},
|
|
1835
|
+
assets: {
|
|
1836
|
+
url(ref, opts) {
|
|
1837
|
+
if (opts && Object.keys(opts).length > 0) {
|
|
1838
|
+
throw new Error(
|
|
1839
|
+
`scribe.assets.url: options are reserved for a future pipeline; got ${Object.keys(opts).join(", ")}`
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
const assetsConfig = config.assets;
|
|
1843
|
+
return assetsConfig ? joinPublicPath(assetsConfig.publicPath, ref) : ref;
|
|
1844
|
+
}
|
|
1443
1845
|
}
|
|
1444
1846
|
};
|
|
1445
1847
|
for (const type of config.types) {
|