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.
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var path3 = require('path');
5
5
  var fs2 = require('fs');
6
6
  var matter = require('gray-matter');
7
7
  var Database = require('better-sqlite3');
8
+ var remarkMdx = require('remark-mdx');
9
+ var remarkParse = require('remark-parse');
10
+ var unified = require('unified');
8
11
  var jiti = require('jiti');
9
12
  var crypto = require('crypto');
10
13
  var genai = require('@google/genai');
@@ -15,6 +18,8 @@ var path3__default = /*#__PURE__*/_interopDefault(path3);
15
18
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
16
19
  var matter__default = /*#__PURE__*/_interopDefault(matter);
17
20
  var Database__default = /*#__PURE__*/_interopDefault(Database);
21
+ var remarkMdx__default = /*#__PURE__*/_interopDefault(remarkMdx);
22
+ var remarkParse__default = /*#__PURE__*/_interopDefault(remarkParse);
18
23
 
19
24
  // ../../node_modules/tsup/assets/cjs_shims.js
20
25
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
@@ -698,6 +703,135 @@ function bulkLoadTranslations(db) {
698
703
  return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
699
704
  }
700
705
 
706
+ // src/translate/normalize-mdx-body.ts
707
+ function needsMdxEscapeNormalization(body) {
708
+ return /\\n[ \t/>]|<[\w.-][^>]*\\n/.test(body);
709
+ }
710
+ function normalizeTranslatedMdxBody(body) {
711
+ if (!needsMdxEscapeNormalization(body)) {
712
+ return { body, adjusted: false };
713
+ }
714
+ const normalized = body.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
715
+ return { body: normalized, adjusted: normalized !== body };
716
+ }
717
+
718
+ // src/translate/sanitize-mdx-jsx.ts
719
+ function sanitizeMdxJsxAttributeQuotes(body) {
720
+ let adjusted = false;
721
+ let out = "";
722
+ let i = 0;
723
+ while (i < body.length) {
724
+ if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
725
+ out += body[i];
726
+ i += 1;
727
+ continue;
728
+ }
729
+ const tagStart = i;
730
+ i += 1;
731
+ while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
732
+ const tagName = body.slice(tagStart + 1, i);
733
+ let tagOut = `<${tagName}`;
734
+ let tagAdjusted = false;
735
+ while (i < body.length && body[i] !== ">" && body[i] !== "/") {
736
+ while (i < body.length && /\s/.test(body[i] ?? "")) {
737
+ tagOut += body[i];
738
+ i += 1;
739
+ }
740
+ if (i >= body.length || body[i] === ">" || body[i] === "/") break;
741
+ const attrStart = i;
742
+ while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
743
+ body.slice(attrStart, i);
744
+ while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
745
+ if (body[i] !== "=") {
746
+ tagOut += body.slice(attrStart, i);
747
+ continue;
748
+ }
749
+ tagOut += body.slice(attrStart, i);
750
+ tagOut += "=";
751
+ i += 1;
752
+ while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
753
+ const quote = body[i];
754
+ if (quote !== '"' && quote !== "'") {
755
+ continue;
756
+ }
757
+ if (quote === "'") {
758
+ const valStart2 = i + 1;
759
+ i += 1;
760
+ while (i < body.length) {
761
+ if (body[i] === "\\") {
762
+ i += 2;
763
+ continue;
764
+ }
765
+ if (body[i] === "'") break;
766
+ i += 1;
767
+ }
768
+ tagOut += body.slice(valStart2 - 1, i + 1);
769
+ i += 1;
770
+ continue;
771
+ }
772
+ const valStart = i + 1;
773
+ i += 1;
774
+ let closeIdx = -1;
775
+ for (let scan = valStart; scan < body.length; scan += 1) {
776
+ if (body[scan] !== '"') continue;
777
+ let j = scan + 1;
778
+ while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
779
+ if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
780
+ closeIdx = scan;
781
+ break;
782
+ }
783
+ }
784
+ if (closeIdx === -1) {
785
+ tagOut += body.slice(valStart - 1, i);
786
+ break;
787
+ }
788
+ const value = body.slice(valStart, closeIdx);
789
+ const hasInternalQuote = value.includes('"');
790
+ if (hasInternalQuote) {
791
+ const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
792
+ tagOut += `'${escaped}'`;
793
+ tagAdjusted = true;
794
+ } else {
795
+ tagOut += `"${value}"`;
796
+ }
797
+ i = closeIdx + 1;
798
+ }
799
+ if (tagAdjusted) adjusted = true;
800
+ tagOut += body[i] ?? "";
801
+ out += tagOut;
802
+ i += 1;
803
+ }
804
+ return { body: out, adjusted };
805
+ }
806
+
807
+ // src/translate/validate-mdx-body.ts
808
+ var parser = unified.unified().use(remarkParse__default.default).use(remarkMdx__default.default);
809
+ function prepareTranslatedMdxBody(body) {
810
+ const normalized = normalizeTranslatedMdxBody(body);
811
+ const sanitized = sanitizeMdxJsxAttributeQuotes(normalized.body);
812
+ return {
813
+ body: sanitized.body,
814
+ adjusted: normalized.adjusted || sanitized.adjusted
815
+ };
816
+ }
817
+ function validateMdxBody(body) {
818
+ try {
819
+ parser.parse(body);
820
+ return { ok: true };
821
+ } catch (error) {
822
+ const message = error instanceof Error ? error.message : String(error);
823
+ return { ok: false, error: message };
824
+ }
825
+ }
826
+ function assertValidTranslatedMdxBody(body) {
827
+ const prepared = prepareTranslatedMdxBody(body);
828
+ const validated = validateMdxBody(prepared.body);
829
+ if (!validated.ok) {
830
+ throw new Error(`MDX validation failed: ${validated.error}`);
831
+ }
832
+ return prepared;
833
+ }
834
+
701
835
  // src/loader/normalize-en.ts
702
836
  function normalizeEnFrontmatter(data) {
703
837
  const out = { ...data };
@@ -818,7 +952,7 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
818
952
  noindex: seo.noindex,
819
953
  canonicalPathOverride: seo.canonicalPathOverride,
820
954
  frontmatter,
821
- content: row.body
955
+ content: prepareTranslatedMdxBody(row.body).body
822
956
  };
823
957
  }
824
958
  var DEV_REVALIDATE_MS = 1500;
@@ -2159,15 +2293,27 @@ function validateProject(config) {
2159
2293
  message: issue.message
2160
2294
  });
2161
2295
  }
2296
+ const preparedBody = prepareTranslatedMdxBody(row.body).body;
2162
2297
  for (const issue of validateDocumentAssets(config, {
2163
2298
  contentType: type.id,
2164
2299
  enSlug,
2165
2300
  locale,
2166
2301
  frontmatter: localeFm,
2167
- body: row.body
2302
+ body: preparedBody
2168
2303
  })) {
2169
2304
  issues.push(issue);
2170
2305
  }
2306
+ const mdxValidation = validateMdxBody(preparedBody);
2307
+ if (!mdxValidation.ok) {
2308
+ issues.push({
2309
+ level: "error",
2310
+ contentType: type.id,
2311
+ enSlug,
2312
+ locale,
2313
+ field: "body",
2314
+ message: `Invalid MDX: ${mdxValidation.error}`
2315
+ });
2316
+ }
2171
2317
  }
2172
2318
  }
2173
2319
  try {
@@ -2499,24 +2645,21 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
2499
2645
  }
2500
2646
 
2501
2647
  // src/translate/resolve-translate-config.ts
2502
- function slugStrategyRules(slugStrategy, preserveTerms) {
2648
+ var BUILTIN_TRANSLATE_RULES = [
2649
+ "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.",
2650
+ "Return the MDX body with real line breaks; do not use JSON escape sequences like \\n or \\t in the body string.",
2651
+ '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 \\".'
2652
+ ];
2653
+ function slugStrategyRules(slugStrategy) {
2503
2654
  if (slugStrategy === "localized") {
2504
- const rules = [
2655
+ return [
2505
2656
  "Provide a URL slug in JSON field `slug` that is Localized into the target language.",
2506
2657
  "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2507
2658
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2508
2659
  "For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
2509
2660
  "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.",
2510
- '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 \\".'
2661
+ "Preserve 4-digit years and proper nouns in slugs."
2511
2662
  ];
2512
- if (preserveTerms?.length) {
2513
- rules.push(
2514
- `Preserve these terms verbatim in slugs (lowercase): ${preserveTerms.join(", ")}.`
2515
- );
2516
- } else {
2517
- rules.push("Preserve 4-digit years and proper nouns in slugs.");
2518
- }
2519
- return rules;
2520
2663
  }
2521
2664
  return ["Do NOT output a slug \u2014 slugStrategy is fixed."];
2522
2665
  }
@@ -2529,9 +2672,10 @@ function mergeTranslateConfig(project, type, slugStrategy) {
2529
2672
  promptOverride: type?.prompt ?? project?.prompt,
2530
2673
  context: mergeContext(project, type),
2531
2674
  rules: [
2675
+ ...BUILTIN_TRANSLATE_RULES,
2532
2676
  ...project?.rules ?? [],
2533
2677
  ...type?.rules ?? [],
2534
- ...slugStrategyRules(slugStrategy, project?.slugPreserveTerms)
2678
+ ...slugStrategyRules(slugStrategy)
2535
2679
  ],
2536
2680
  model: type?.model ?? project?.defaultModel
2537
2681
  };
@@ -2540,95 +2684,6 @@ function resolveTranslateConfig(config, type) {
2540
2684
  return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
2541
2685
  }
2542
2686
 
2543
- // src/translate/sanitize-mdx-jsx.ts
2544
- function sanitizeMdxJsxAttributeQuotes(body) {
2545
- let adjusted = false;
2546
- let out = "";
2547
- let i = 0;
2548
- while (i < body.length) {
2549
- if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
2550
- out += body[i];
2551
- i += 1;
2552
- continue;
2553
- }
2554
- const tagStart = i;
2555
- i += 1;
2556
- while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
2557
- const tagName = body.slice(tagStart + 1, i);
2558
- let tagOut = `<${tagName}`;
2559
- let tagAdjusted = false;
2560
- while (i < body.length && body[i] !== ">" && body[i] !== "/") {
2561
- while (i < body.length && /\s/.test(body[i] ?? "")) {
2562
- tagOut += body[i];
2563
- i += 1;
2564
- }
2565
- if (i >= body.length || body[i] === ">" || body[i] === "/") break;
2566
- const attrStart = i;
2567
- while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
2568
- body.slice(attrStart, i);
2569
- while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2570
- if (body[i] !== "=") {
2571
- tagOut += body.slice(attrStart, i);
2572
- continue;
2573
- }
2574
- tagOut += body.slice(attrStart, i);
2575
- tagOut += "=";
2576
- i += 1;
2577
- while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2578
- const quote = body[i];
2579
- if (quote !== '"' && quote !== "'") {
2580
- continue;
2581
- }
2582
- if (quote === "'") {
2583
- const valStart2 = i + 1;
2584
- i += 1;
2585
- while (i < body.length) {
2586
- if (body[i] === "\\") {
2587
- i += 2;
2588
- continue;
2589
- }
2590
- if (body[i] === "'") break;
2591
- i += 1;
2592
- }
2593
- tagOut += body.slice(valStart2 - 1, i + 1);
2594
- i += 1;
2595
- continue;
2596
- }
2597
- const valStart = i + 1;
2598
- i += 1;
2599
- let closeIdx = -1;
2600
- for (let scan = valStart; scan < body.length; scan += 1) {
2601
- if (body[scan] !== '"') continue;
2602
- let j = scan + 1;
2603
- while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
2604
- if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
2605
- closeIdx = scan;
2606
- break;
2607
- }
2608
- }
2609
- if (closeIdx === -1) {
2610
- tagOut += body.slice(valStart - 1, i);
2611
- break;
2612
- }
2613
- const value = body.slice(valStart, closeIdx);
2614
- const hasInternalQuote = value.includes('"');
2615
- if (hasInternalQuote) {
2616
- const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2617
- tagOut += `'${escaped}'`;
2618
- tagAdjusted = true;
2619
- } else {
2620
- tagOut += `"${value}"`;
2621
- }
2622
- i = closeIdx + 1;
2623
- }
2624
- if (tagAdjusted) adjusted = true;
2625
- tagOut += body[i] ?? "";
2626
- out += tagOut;
2627
- i += 1;
2628
- }
2629
- return { body: out, adjusted };
2630
- }
2631
-
2632
2687
  // src/translate/validate-translation.ts
2633
2688
  function formatZodIssues(error) {
2634
2689
  return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
@@ -2762,7 +2817,7 @@ async function translatePage(config, item, options = {}) {
2762
2817
  if (!validated.ok) {
2763
2818
  throw new Error(`Translation validation failed: ${validated.error}`);
2764
2819
  }
2765
- const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
2820
+ const { body: translatedBody, adjusted: mdxAdjusted } = assertValidTranslatedMdxBody(
2766
2821
  result.parsed.body
2767
2822
  );
2768
2823
  const writeDb = openStore(config, "readwrite");