scribe-cms 0.0.10 → 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.
@@ -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;
@@ -1840,15 +1979,27 @@ function validateProject(config) {
1840
1979
  message: issue.message
1841
1980
  });
1842
1981
  }
1982
+ const preparedBody = prepareTranslatedMdxBody(row.body).body;
1843
1983
  for (const issue of validateDocumentAssets(config, {
1844
1984
  contentType: type.id,
1845
1985
  enSlug,
1846
1986
  locale,
1847
1987
  frontmatter: localeFm,
1848
- body: row.body
1988
+ body: preparedBody
1849
1989
  })) {
1850
1990
  issues.push(issue);
1851
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
+ }
1852
2003
  }
1853
2004
  }
1854
2005
  try {
@@ -2216,24 +2367,21 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
2216
2367
 
2217
2368
  // src/translate/resolve-translate-config.ts
2218
2369
  init_cjs_shims();
2219
- function slugStrategyRules(slugStrategy, preserveTerms) {
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) {
2220
2376
  if (slugStrategy === "localized") {
2221
- const rules = [
2377
+ return [
2222
2378
  "Provide a URL slug in JSON field `slug` that is Localized into the target language.",
2223
2379
  "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2224
2380
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2225
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).",
2226
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.",
2227
- '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 \\".'
2383
+ "Preserve 4-digit years and proper nouns in slugs."
2228
2384
  ];
2229
- if (preserveTerms?.length) {
2230
- rules.push(
2231
- `Preserve these terms verbatim in slugs (lowercase): ${preserveTerms.join(", ")}.`
2232
- );
2233
- } else {
2234
- rules.push("Preserve 4-digit years and proper nouns in slugs.");
2235
- }
2236
- return rules;
2237
2385
  }
2238
2386
  return ["Do NOT output a slug \u2014 slugStrategy is fixed."];
2239
2387
  }
@@ -2246,9 +2394,10 @@ function mergeTranslateConfig(project, type, slugStrategy) {
2246
2394
  promptOverride: type?.prompt ?? project?.prompt,
2247
2395
  context: mergeContext(project, type),
2248
2396
  rules: [
2397
+ ...BUILTIN_TRANSLATE_RULES,
2249
2398
  ...project?.rules ?? [],
2250
2399
  ...type?.rules ?? [],
2251
- ...slugStrategyRules(slugStrategy, project?.slugPreserveTerms)
2400
+ ...slugStrategyRules(slugStrategy)
2252
2401
  ],
2253
2402
  model: type?.model ?? project?.defaultModel
2254
2403
  };
@@ -2257,96 +2406,6 @@ function resolveTranslateConfig(config, type) {
2257
2406
  return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
2258
2407
  }
2259
2408
 
2260
- // src/translate/sanitize-mdx-jsx.ts
2261
- init_cjs_shims();
2262
- function sanitizeMdxJsxAttributeQuotes(body) {
2263
- let adjusted = false;
2264
- let out = "";
2265
- let i = 0;
2266
- while (i < body.length) {
2267
- if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
2268
- out += body[i];
2269
- i += 1;
2270
- continue;
2271
- }
2272
- const tagStart = i;
2273
- i += 1;
2274
- while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
2275
- const tagName = body.slice(tagStart + 1, i);
2276
- let tagOut = `<${tagName}`;
2277
- let tagAdjusted = false;
2278
- while (i < body.length && body[i] !== ">" && body[i] !== "/") {
2279
- while (i < body.length && /\s/.test(body[i] ?? "")) {
2280
- tagOut += body[i];
2281
- i += 1;
2282
- }
2283
- if (i >= body.length || body[i] === ">" || body[i] === "/") break;
2284
- const attrStart = i;
2285
- while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
2286
- body.slice(attrStart, i);
2287
- while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2288
- if (body[i] !== "=") {
2289
- tagOut += body.slice(attrStart, i);
2290
- continue;
2291
- }
2292
- tagOut += body.slice(attrStart, i);
2293
- tagOut += "=";
2294
- i += 1;
2295
- while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2296
- const quote = body[i];
2297
- if (quote !== '"' && quote !== "'") {
2298
- continue;
2299
- }
2300
- if (quote === "'") {
2301
- const valStart2 = i + 1;
2302
- i += 1;
2303
- while (i < body.length) {
2304
- if (body[i] === "\\") {
2305
- i += 2;
2306
- continue;
2307
- }
2308
- if (body[i] === "'") break;
2309
- i += 1;
2310
- }
2311
- tagOut += body.slice(valStart2 - 1, i + 1);
2312
- i += 1;
2313
- continue;
2314
- }
2315
- const valStart = i + 1;
2316
- i += 1;
2317
- let closeIdx = -1;
2318
- for (let scan = valStart; scan < body.length; scan += 1) {
2319
- if (body[scan] !== '"') continue;
2320
- let j = scan + 1;
2321
- while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
2322
- if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
2323
- closeIdx = scan;
2324
- break;
2325
- }
2326
- }
2327
- if (closeIdx === -1) {
2328
- tagOut += body.slice(valStart - 1, i);
2329
- break;
2330
- }
2331
- const value = body.slice(valStart, closeIdx);
2332
- const hasInternalQuote = value.includes('"');
2333
- if (hasInternalQuote) {
2334
- const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2335
- tagOut += `'${escaped}'`;
2336
- tagAdjusted = true;
2337
- } else {
2338
- tagOut += `"${value}"`;
2339
- }
2340
- i = closeIdx + 1;
2341
- }
2342
- if (tagAdjusted) adjusted = true;
2343
- tagOut += body[i] ?? "";
2344
- out += tagOut;
2345
- i += 1;
2346
- }
2347
- return { body: out, adjusted };
2348
- }
2349
-
2350
2409
  // src/translate/validate-translation.ts
2351
2410
  init_cjs_shims();
2352
2411
  function formatZodIssues(error) {
@@ -2481,7 +2540,7 @@ async function translatePage(config, item, options = {}) {
2481
2540
  if (!validated.ok) {
2482
2541
  throw new Error(`Translation validation failed: ${validated.error}`);
2483
2542
  }
2484
- const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
2543
+ const { body: translatedBody, adjusted: mdxAdjusted } = assertValidTranslatedMdxBody(
2485
2544
  result.parsed.body
2486
2545
  );
2487
2546
  const writeDb = openStore(config, "readwrite");