scribe-cms 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +176 -116
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +174 -116
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +171 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +169 -115
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +118 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +116 -1
- package/dist/runtime.js.map +1 -1
- package/dist/src/core/types.d.ts +0 -2
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/loader/create-loader.d.ts.map +1 -1
- package/dist/src/translate/normalize-mdx-body.d.ts +9 -0
- package/dist/src/translate/normalize-mdx-body.d.ts.map +1 -0
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -1
- package/dist/src/translate/validate-mdx-body.d.ts +16 -0
- package/dist/src/translate/validate-mdx-body.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts.map +1 -1
- package/dist/src/validate/validate-redirects.d.ts.map +1 -1
- package/dist/studio/server.cjs +6 -0
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.js +4 -0
- package/dist/studio/server.js.map +1 -1
- package/package.json +5 -2
package/dist/cli/index.cjs
CHANGED
|
@@ -6,6 +6,9 @@ var path3 = require('path');
|
|
|
6
6
|
var Database = require('better-sqlite3');
|
|
7
7
|
var zod = require('zod');
|
|
8
8
|
var matter = require('gray-matter');
|
|
9
|
+
var remarkMdx = require('remark-mdx');
|
|
10
|
+
var remarkParse = require('remark-parse');
|
|
11
|
+
var unified = require('unified');
|
|
9
12
|
var jiti = require('jiti');
|
|
10
13
|
var crypto = require('crypto');
|
|
11
14
|
var genai = require('@google/genai');
|
|
@@ -22,6 +25,8 @@ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
|
22
25
|
var path3__default = /*#__PURE__*/_interopDefault(path3);
|
|
23
26
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
24
27
|
var matter__default = /*#__PURE__*/_interopDefault(matter);
|
|
28
|
+
var remarkMdx__default = /*#__PURE__*/_interopDefault(remarkMdx);
|
|
29
|
+
var remarkParse__default = /*#__PURE__*/_interopDefault(remarkParse);
|
|
25
30
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
26
31
|
|
|
27
32
|
var __defProp = Object.defineProperty;
|
|
@@ -727,6 +732,140 @@ function bulkLoadTranslations(db) {
|
|
|
727
732
|
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
728
733
|
}
|
|
729
734
|
|
|
735
|
+
// src/translate/validate-mdx-body.ts
|
|
736
|
+
init_cjs_shims();
|
|
737
|
+
|
|
738
|
+
// src/translate/normalize-mdx-body.ts
|
|
739
|
+
init_cjs_shims();
|
|
740
|
+
function needsMdxEscapeNormalization(body) {
|
|
741
|
+
return /\\n[ \t/>]|<[\w.-][^>]*\\n/.test(body);
|
|
742
|
+
}
|
|
743
|
+
function normalizeTranslatedMdxBody(body) {
|
|
744
|
+
if (!needsMdxEscapeNormalization(body)) {
|
|
745
|
+
return { body, adjusted: false };
|
|
746
|
+
}
|
|
747
|
+
const normalized = body.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
748
|
+
return { body: normalized, adjusted: normalized !== body };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/translate/sanitize-mdx-jsx.ts
|
|
752
|
+
init_cjs_shims();
|
|
753
|
+
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
754
|
+
let adjusted = false;
|
|
755
|
+
let out = "";
|
|
756
|
+
let i = 0;
|
|
757
|
+
while (i < body.length) {
|
|
758
|
+
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
759
|
+
out += body[i];
|
|
760
|
+
i += 1;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const tagStart = i;
|
|
764
|
+
i += 1;
|
|
765
|
+
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
766
|
+
const tagName = body.slice(tagStart + 1, i);
|
|
767
|
+
let tagOut = `<${tagName}`;
|
|
768
|
+
let tagAdjusted = false;
|
|
769
|
+
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
770
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
771
|
+
tagOut += body[i];
|
|
772
|
+
i += 1;
|
|
773
|
+
}
|
|
774
|
+
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
775
|
+
const attrStart = i;
|
|
776
|
+
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
777
|
+
body.slice(attrStart, i);
|
|
778
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
779
|
+
if (body[i] !== "=") {
|
|
780
|
+
tagOut += body.slice(attrStart, i);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
tagOut += body.slice(attrStart, i);
|
|
784
|
+
tagOut += "=";
|
|
785
|
+
i += 1;
|
|
786
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
787
|
+
const quote = body[i];
|
|
788
|
+
if (quote !== '"' && quote !== "'") {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (quote === "'") {
|
|
792
|
+
const valStart2 = i + 1;
|
|
793
|
+
i += 1;
|
|
794
|
+
while (i < body.length) {
|
|
795
|
+
if (body[i] === "\\") {
|
|
796
|
+
i += 2;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (body[i] === "'") break;
|
|
800
|
+
i += 1;
|
|
801
|
+
}
|
|
802
|
+
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
803
|
+
i += 1;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const valStart = i + 1;
|
|
807
|
+
i += 1;
|
|
808
|
+
let closeIdx = -1;
|
|
809
|
+
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
810
|
+
if (body[scan] !== '"') continue;
|
|
811
|
+
let j = scan + 1;
|
|
812
|
+
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
813
|
+
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
814
|
+
closeIdx = scan;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (closeIdx === -1) {
|
|
819
|
+
tagOut += body.slice(valStart - 1, i);
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
const value = body.slice(valStart, closeIdx);
|
|
823
|
+
const hasInternalQuote = value.includes('"');
|
|
824
|
+
if (hasInternalQuote) {
|
|
825
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
826
|
+
tagOut += `'${escaped}'`;
|
|
827
|
+
tagAdjusted = true;
|
|
828
|
+
} else {
|
|
829
|
+
tagOut += `"${value}"`;
|
|
830
|
+
}
|
|
831
|
+
i = closeIdx + 1;
|
|
832
|
+
}
|
|
833
|
+
if (tagAdjusted) adjusted = true;
|
|
834
|
+
tagOut += body[i] ?? "";
|
|
835
|
+
out += tagOut;
|
|
836
|
+
i += 1;
|
|
837
|
+
}
|
|
838
|
+
return { body: out, adjusted };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/translate/validate-mdx-body.ts
|
|
842
|
+
var parser = unified.unified().use(remarkParse__default.default).use(remarkMdx__default.default);
|
|
843
|
+
function prepareTranslatedMdxBody(body) {
|
|
844
|
+
const normalized = normalizeTranslatedMdxBody(body);
|
|
845
|
+
const sanitized = sanitizeMdxJsxAttributeQuotes(normalized.body);
|
|
846
|
+
return {
|
|
847
|
+
body: sanitized.body,
|
|
848
|
+
adjusted: normalized.adjusted || sanitized.adjusted
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function validateMdxBody(body) {
|
|
852
|
+
try {
|
|
853
|
+
parser.parse(body);
|
|
854
|
+
return { ok: true };
|
|
855
|
+
} catch (error) {
|
|
856
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
857
|
+
return { ok: false, error: message };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function assertValidTranslatedMdxBody(body) {
|
|
861
|
+
const prepared = prepareTranslatedMdxBody(body);
|
|
862
|
+
const validated = validateMdxBody(prepared.body);
|
|
863
|
+
if (!validated.ok) {
|
|
864
|
+
throw new Error(`MDX validation failed: ${validated.error}`);
|
|
865
|
+
}
|
|
866
|
+
return prepared;
|
|
867
|
+
}
|
|
868
|
+
|
|
730
869
|
// src/loader/normalize-en.ts
|
|
731
870
|
init_cjs_shims();
|
|
732
871
|
function normalizeEnFrontmatter(data) {
|
|
@@ -848,7 +987,7 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
|
848
987
|
noindex: seo.noindex,
|
|
849
988
|
canonicalPathOverride: seo.canonicalPathOverride,
|
|
850
989
|
frontmatter,
|
|
851
|
-
content: row.body
|
|
990
|
+
content: prepareTranslatedMdxBody(row.body).body
|
|
852
991
|
};
|
|
853
992
|
}
|
|
854
993
|
var DEV_REVALIDATE_MS = 1500;
|
|
@@ -1450,7 +1589,7 @@ function validateTypeRedirects(project) {
|
|
|
1450
1589
|
}
|
|
1451
1590
|
for (const entry of loaded.entries) {
|
|
1452
1591
|
for (const from of entry.fromSlugs) {
|
|
1453
|
-
if (from === entry.toSlug) {
|
|
1592
|
+
if (entry.kind === "same-type" && from === entry.toSlug) {
|
|
1454
1593
|
issues.push({
|
|
1455
1594
|
level: "error",
|
|
1456
1595
|
contentType: type.id,
|
|
@@ -1544,16 +1683,17 @@ function validateTypeRedirects(project) {
|
|
|
1544
1683
|
if (!targetType?.path) continue;
|
|
1545
1684
|
for (const from of entry.fromSlugs) {
|
|
1546
1685
|
if (globalFrom.get(from)?.typeId !== file.contentTypeId) continue;
|
|
1547
|
-
const
|
|
1548
|
-
if (
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1686
|
+
const targetAsSource = globalFrom.get(entry.toSlug);
|
|
1687
|
+
if (!targetAsSource) continue;
|
|
1688
|
+
const sameSlugCrossType = entry.kind === "cross-type" && entry.fromSlugs.includes(entry.toSlug) && targetAsSource.typeId === file.contentTypeId;
|
|
1689
|
+
if (sameSlugCrossType) continue;
|
|
1690
|
+
issues.push({
|
|
1691
|
+
level: "error",
|
|
1692
|
+
contentType: file.contentTypeId,
|
|
1693
|
+
enSlug: from,
|
|
1694
|
+
field: TYPE_REDIRECTS_FILENAME,
|
|
1695
|
+
message: `Redirect chain detected: "${from}" \u2192 "${entry.toSlug}" \u2192 existing redirect source`
|
|
1696
|
+
});
|
|
1557
1697
|
}
|
|
1558
1698
|
}
|
|
1559
1699
|
}
|
|
@@ -1839,15 +1979,27 @@ function validateProject(config) {
|
|
|
1839
1979
|
message: issue.message
|
|
1840
1980
|
});
|
|
1841
1981
|
}
|
|
1982
|
+
const preparedBody = prepareTranslatedMdxBody(row.body).body;
|
|
1842
1983
|
for (const issue of validateDocumentAssets(config, {
|
|
1843
1984
|
contentType: type.id,
|
|
1844
1985
|
enSlug,
|
|
1845
1986
|
locale,
|
|
1846
1987
|
frontmatter: localeFm,
|
|
1847
|
-
body:
|
|
1988
|
+
body: preparedBody
|
|
1848
1989
|
})) {
|
|
1849
1990
|
issues.push(issue);
|
|
1850
1991
|
}
|
|
1992
|
+
const mdxValidation = validateMdxBody(preparedBody);
|
|
1993
|
+
if (!mdxValidation.ok) {
|
|
1994
|
+
issues.push({
|
|
1995
|
+
level: "error",
|
|
1996
|
+
contentType: type.id,
|
|
1997
|
+
enSlug,
|
|
1998
|
+
locale,
|
|
1999
|
+
field: "body",
|
|
2000
|
+
message: `Invalid MDX: ${mdxValidation.error}`
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
1851
2003
|
}
|
|
1852
2004
|
}
|
|
1853
2005
|
try {
|
|
@@ -2215,24 +2367,21 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
|
|
|
2215
2367
|
|
|
2216
2368
|
// src/translate/resolve-translate-config.ts
|
|
2217
2369
|
init_cjs_shims();
|
|
2218
|
-
|
|
2370
|
+
var BUILTIN_TRANSLATE_RULES = [
|
|
2371
|
+
"Do not translate brand or product names unless the brand has a well-known localized name in the target market (rare). Keep the original spelling and capitalization.",
|
|
2372
|
+
"Return the MDX body with real line breaks; do not use JSON escape sequences like \\n or \\t in the body string.",
|
|
2373
|
+
'In JSX attributes (e.g. FaqItem question="..."), use single quotes when the value contains double-quote characters (e.g. Hebrew \u05D3\u05D5\u05D0"\u05DC), or escape them as \\".'
|
|
2374
|
+
];
|
|
2375
|
+
function slugStrategyRules(slugStrategy) {
|
|
2219
2376
|
if (slugStrategy === "localized") {
|
|
2220
|
-
|
|
2377
|
+
return [
|
|
2221
2378
|
"Provide a URL slug in JSON field `slug` that is Localized into the target language.",
|
|
2222
2379
|
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2223
2380
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2224
2381
|
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2225
2382
|
"Do NOT append locale codes to the slug (e.g. -fr, -he, -zh-cn). Locale routing is handled by the URL prefix, not the slug.",
|
|
2226
|
-
|
|
2383
|
+
"Preserve 4-digit years and proper nouns in slugs."
|
|
2227
2384
|
];
|
|
2228
|
-
if (preserveTerms?.length) {
|
|
2229
|
-
rules.push(
|
|
2230
|
-
`Preserve these terms verbatim in slugs (lowercase): ${preserveTerms.join(", ")}.`
|
|
2231
|
-
);
|
|
2232
|
-
} else {
|
|
2233
|
-
rules.push("Preserve 4-digit years and proper nouns in slugs.");
|
|
2234
|
-
}
|
|
2235
|
-
return rules;
|
|
2236
2385
|
}
|
|
2237
2386
|
return ["Do NOT output a slug \u2014 slugStrategy is fixed."];
|
|
2238
2387
|
}
|
|
@@ -2245,9 +2394,10 @@ function mergeTranslateConfig(project, type, slugStrategy) {
|
|
|
2245
2394
|
promptOverride: type?.prompt ?? project?.prompt,
|
|
2246
2395
|
context: mergeContext(project, type),
|
|
2247
2396
|
rules: [
|
|
2397
|
+
...BUILTIN_TRANSLATE_RULES,
|
|
2248
2398
|
...project?.rules ?? [],
|
|
2249
2399
|
...type?.rules ?? [],
|
|
2250
|
-
...slugStrategyRules(slugStrategy
|
|
2400
|
+
...slugStrategyRules(slugStrategy)
|
|
2251
2401
|
],
|
|
2252
2402
|
model: type?.model ?? project?.defaultModel
|
|
2253
2403
|
};
|
|
@@ -2256,96 +2406,6 @@ function resolveTranslateConfig(config, type) {
|
|
|
2256
2406
|
return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
|
|
2257
2407
|
}
|
|
2258
2408
|
|
|
2259
|
-
// src/translate/sanitize-mdx-jsx.ts
|
|
2260
|
-
init_cjs_shims();
|
|
2261
|
-
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
2262
|
-
let adjusted = false;
|
|
2263
|
-
let out = "";
|
|
2264
|
-
let i = 0;
|
|
2265
|
-
while (i < body.length) {
|
|
2266
|
-
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
2267
|
-
out += body[i];
|
|
2268
|
-
i += 1;
|
|
2269
|
-
continue;
|
|
2270
|
-
}
|
|
2271
|
-
const tagStart = i;
|
|
2272
|
-
i += 1;
|
|
2273
|
-
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
2274
|
-
const tagName = body.slice(tagStart + 1, i);
|
|
2275
|
-
let tagOut = `<${tagName}`;
|
|
2276
|
-
let tagAdjusted = false;
|
|
2277
|
-
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
2278
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
2279
|
-
tagOut += body[i];
|
|
2280
|
-
i += 1;
|
|
2281
|
-
}
|
|
2282
|
-
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
2283
|
-
const attrStart = i;
|
|
2284
|
-
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
2285
|
-
body.slice(attrStart, i);
|
|
2286
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2287
|
-
if (body[i] !== "=") {
|
|
2288
|
-
tagOut += body.slice(attrStart, i);
|
|
2289
|
-
continue;
|
|
2290
|
-
}
|
|
2291
|
-
tagOut += body.slice(attrStart, i);
|
|
2292
|
-
tagOut += "=";
|
|
2293
|
-
i += 1;
|
|
2294
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2295
|
-
const quote = body[i];
|
|
2296
|
-
if (quote !== '"' && quote !== "'") {
|
|
2297
|
-
continue;
|
|
2298
|
-
}
|
|
2299
|
-
if (quote === "'") {
|
|
2300
|
-
const valStart2 = i + 1;
|
|
2301
|
-
i += 1;
|
|
2302
|
-
while (i < body.length) {
|
|
2303
|
-
if (body[i] === "\\") {
|
|
2304
|
-
i += 2;
|
|
2305
|
-
continue;
|
|
2306
|
-
}
|
|
2307
|
-
if (body[i] === "'") break;
|
|
2308
|
-
i += 1;
|
|
2309
|
-
}
|
|
2310
|
-
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
2311
|
-
i += 1;
|
|
2312
|
-
continue;
|
|
2313
|
-
}
|
|
2314
|
-
const valStart = i + 1;
|
|
2315
|
-
i += 1;
|
|
2316
|
-
let closeIdx = -1;
|
|
2317
|
-
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
2318
|
-
if (body[scan] !== '"') continue;
|
|
2319
|
-
let j = scan + 1;
|
|
2320
|
-
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
2321
|
-
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
2322
|
-
closeIdx = scan;
|
|
2323
|
-
break;
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
if (closeIdx === -1) {
|
|
2327
|
-
tagOut += body.slice(valStart - 1, i);
|
|
2328
|
-
break;
|
|
2329
|
-
}
|
|
2330
|
-
const value = body.slice(valStart, closeIdx);
|
|
2331
|
-
const hasInternalQuote = value.includes('"');
|
|
2332
|
-
if (hasInternalQuote) {
|
|
2333
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2334
|
-
tagOut += `'${escaped}'`;
|
|
2335
|
-
tagAdjusted = true;
|
|
2336
|
-
} else {
|
|
2337
|
-
tagOut += `"${value}"`;
|
|
2338
|
-
}
|
|
2339
|
-
i = closeIdx + 1;
|
|
2340
|
-
}
|
|
2341
|
-
if (tagAdjusted) adjusted = true;
|
|
2342
|
-
tagOut += body[i] ?? "";
|
|
2343
|
-
out += tagOut;
|
|
2344
|
-
i += 1;
|
|
2345
|
-
}
|
|
2346
|
-
return { body: out, adjusted };
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
2409
|
// src/translate/validate-translation.ts
|
|
2350
2410
|
init_cjs_shims();
|
|
2351
2411
|
function formatZodIssues(error) {
|
|
@@ -2480,7 +2540,7 @@ async function translatePage(config, item, options = {}) {
|
|
|
2480
2540
|
if (!validated.ok) {
|
|
2481
2541
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
2482
2542
|
}
|
|
2483
|
-
const { body: translatedBody, adjusted: mdxAdjusted } =
|
|
2543
|
+
const { body: translatedBody, adjusted: mdxAdjusted } = assertValidTranslatedMdxBody(
|
|
2484
2544
|
result.parsed.body
|
|
2485
2545
|
);
|
|
2486
2546
|
const writeDb = openStore(config, "readwrite");
|