scribe-cms 0.0.2 → 0.0.5

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.
Files changed (64) hide show
  1. package/README.md +2 -3
  2. package/bin/scribe.js +2 -11
  3. package/dist/cli/index.cjs +1437 -225
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +1437 -226
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/prompt-translate.d.ts +16 -0
  8. package/dist/cli/prompt-translate.d.ts.map +1 -0
  9. package/dist/cli/translate-progress.d.ts +11 -0
  10. package/dist/cli/translate-progress.d.ts.map +1 -0
  11. package/dist/index.cjs +591 -147
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.js +587 -148
  14. package/dist/index.js.map +1 -1
  15. package/dist/runtime.cjs +14 -2
  16. package/dist/runtime.cjs.map +1 -1
  17. package/dist/runtime.js +14 -2
  18. package/dist/runtime.js.map +1 -1
  19. package/dist/src/config/resolve-config.d.ts.map +1 -1
  20. package/dist/src/core/field.d.ts +2 -0
  21. package/dist/src/core/field.d.ts.map +1 -1
  22. package/dist/src/core/types.d.ts +5 -1
  23. package/dist/src/core/types.d.ts.map +1 -1
  24. package/dist/src/export/build-static-raw-exports.d.ts +37 -0
  25. package/dist/src/export/build-static-raw-exports.d.ts.map +1 -0
  26. package/dist/src/export/write-static-raw-exports.d.ts +12 -0
  27. package/dist/src/export/write-static-raw-exports.d.ts.map +1 -0
  28. package/dist/src/history/record-revision.d.ts +2 -0
  29. package/dist/src/history/record-revision.d.ts.map +1 -1
  30. package/dist/src/index.d.ts +7 -0
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/storage/sqlite.d.ts.map +1 -1
  33. package/dist/src/storage/translations.d.ts +6 -0
  34. package/dist/src/storage/translations.d.ts.map +1 -1
  35. package/dist/src/translate/gemini-client.d.ts +7 -0
  36. package/dist/src/translate/gemini-client.d.ts.map +1 -1
  37. package/dist/src/translate/gemini-models.d.ts +8 -0
  38. package/dist/src/translate/gemini-models.d.ts.map +1 -0
  39. package/dist/src/translate/gemini-pricing.d.ts +11 -0
  40. package/dist/src/translate/gemini-pricing.d.ts.map +1 -0
  41. package/dist/src/translate/page-translator.d.ts +38 -1
  42. package/dist/src/translate/page-translator.d.ts.map +1 -1
  43. package/dist/src/translate/prompts/translation-prompt.d.ts +1 -2
  44. package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -1
  45. package/dist/src/translate/response-schema.d.ts +7 -0
  46. package/dist/src/translate/response-schema.d.ts.map +1 -0
  47. package/dist/src/translate/validate-translation.d.ts +14 -0
  48. package/dist/src/translate/validate-translation.d.ts.map +1 -0
  49. package/dist/src/translate/worklist.d.ts +3 -0
  50. package/dist/src/translate/worklist.d.ts.map +1 -1
  51. package/dist/src/validate/validate-assets.d.ts +20 -0
  52. package/dist/src/validate/validate-assets.d.ts.map +1 -0
  53. package/dist/src/validate/validate-project.d.ts +1 -1
  54. package/dist/src/validate/validate-project.d.ts.map +1 -1
  55. package/dist/src/version.d.ts +4 -0
  56. package/dist/src/version.d.ts.map +1 -0
  57. package/dist/studio/server.cjs +611 -194
  58. package/dist/studio/server.cjs.map +1 -1
  59. package/dist/studio/server.d.ts +1 -1
  60. package/dist/studio/server.d.ts.map +1 -1
  61. package/dist/studio/server.js +611 -194
  62. package/dist/studio/server.js.map +1 -1
  63. package/package.json +16 -5
  64. package/bin/ensure-built.mjs +0 -31
package/dist/index.cjs CHANGED
@@ -109,6 +109,22 @@ function unwrapSchema(schema) {
109
109
  }
110
110
  return current;
111
111
  }
112
+ function peelOptionalWrappers(schema) {
113
+ let current = schema;
114
+ for (let i = 0; i < 8; i++) {
115
+ const type = current._def?.type;
116
+ if (type === "optional" || type === "nullable") {
117
+ current = current.unwrap();
118
+ continue;
119
+ }
120
+ if (type === "default") {
121
+ current = current.removeDefault();
122
+ continue;
123
+ }
124
+ break;
125
+ }
126
+ return current;
127
+ }
112
128
 
113
129
  // src/core/types.ts
114
130
  function defineConfig(config) {
@@ -123,14 +139,14 @@ var SLUG_PLACEHOLDER = "{slug}";
123
139
  function isRoutableType(type) {
124
140
  return typeof type.path === "string" && type.path.length > 0;
125
141
  }
126
- function assertValidPathTemplate(path9, typeId) {
127
- if (!path9.startsWith("/")) {
128
- throw new Error(`Content type "${typeId}": path must start with / (got "${path9}")`);
142
+ function assertValidPathTemplate(path11, typeId) {
143
+ if (!path11.startsWith("/")) {
144
+ throw new Error(`Content type "${typeId}": path must start with / (got "${path11}")`);
129
145
  }
130
- const count = (path9.match(/\{slug\}/g) ?? []).length;
146
+ const count = (path11.match(/\{slug\}/g) ?? []).length;
131
147
  if (count !== 1) {
132
148
  throw new Error(
133
- `Content type "${typeId}": path must contain exactly one {slug} (got "${path9}")`
149
+ `Content type "${typeId}": path must contain exactly one {slug} (got "${path11}")`
134
150
  );
135
151
  }
136
152
  }
@@ -182,6 +198,7 @@ function resolveConfig(input, baseDir) {
182
198
  const projectRoot = path3__default.default.resolve(baseDir ?? process.cwd(), raw.rootDir);
183
199
  const contentRoot = path3__default.default.resolve(projectRoot, raw.contentDir ?? "content");
184
200
  const storePath = path3__default.default.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
201
+ const assetsPath = raw.assetsDir ? path3__default.default.resolve(projectRoot, raw.assetsDir) : void 0;
185
202
  const seenIds = /* @__PURE__ */ new Set();
186
203
  const types = raw.types.map((type) => {
187
204
  if (seenIds.has(type.id)) {
@@ -202,6 +219,7 @@ function resolveConfig(input, baseDir) {
202
219
  const config = {
203
220
  rootDir: contentRoot,
204
221
  storePath,
222
+ assetsPath,
205
223
  locales: [...raw.locales],
206
224
  defaultLocale,
207
225
  localePresets: raw.localePresets,
@@ -254,10 +272,10 @@ function introspectSchema(schema, prefix = []) {
254
272
  }
255
273
  function extractByPaths(data, paths) {
256
274
  const out = {};
257
- for (const path9 of paths) {
258
- const value = getAtPath(data, path9);
275
+ for (const path11 of paths) {
276
+ const value = getAtPath(data, path11);
259
277
  if (value !== void 0) {
260
- setAtPath(out, path9.filter((p) => p !== "*"), value);
278
+ setAtPath(out, path11.filter((p) => p !== "*"), value);
261
279
  }
262
280
  }
263
281
  return out;
@@ -275,13 +293,13 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
275
293
  const translatable = pickTranslatable(localeData, schema);
276
294
  return deepMerge(structural, translatable);
277
295
  }
278
- function getAtPath(obj, path9) {
296
+ function getAtPath(obj, path11) {
279
297
  let current = obj;
280
- for (const segment of path9) {
298
+ for (const segment of path11) {
281
299
  if (segment === "*") {
282
300
  if (!Array.isArray(current)) return void 0;
283
301
  return current.map(
284
- (item) => typeof item === "object" && item !== null ? getAtPath(item, path9.slice(path9.indexOf("*") + 1)) : void 0
302
+ (item) => typeof item === "object" && item !== null ? getAtPath(item, path11.slice(path11.indexOf("*") + 1)) : void 0
285
303
  );
286
304
  }
287
305
  if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
@@ -289,13 +307,13 @@ function getAtPath(obj, path9) {
289
307
  }
290
308
  return current;
291
309
  }
292
- function setAtPath(obj, path9, value) {
293
- if (path9.length === 0) return;
294
- if (path9.length === 1) {
295
- obj[path9[0]] = value;
310
+ function setAtPath(obj, path11, value) {
311
+ if (path11.length === 0) return;
312
+ if (path11.length === 1) {
313
+ obj[path11[0]] = value;
296
314
  return;
297
315
  }
298
- const [head, ...rest] = path9;
316
+ const [head, ...rest] = path11;
299
317
  if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
300
318
  obj[head] = {};
301
319
  }
@@ -491,7 +509,7 @@ function seoFieldsFromEn(enDoc) {
491
509
  redirectTo: enDoc.redirectTo
492
510
  };
493
511
  }
494
- var SCHEMA_VERSION = 2;
512
+ var SCHEMA_VERSION = 3;
495
513
  var MIGRATIONS = [
496
514
  `CREATE TABLE IF NOT EXISTS meta (
497
515
  key TEXT PRIMARY KEY,
@@ -548,17 +566,27 @@ function resolveStorePath(config) {
548
566
  }
549
567
  function openStore(config, mode = "readwrite") {
550
568
  const storePath = resolveStorePath(config);
551
- fs2__default.default.mkdirSync(path3__default.default.dirname(storePath), { recursive: true });
569
+ if (mode === "readwrite") {
570
+ fs2__default.default.mkdirSync(path3__default.default.dirname(storePath), { recursive: true });
571
+ }
552
572
  const db = new Database__default.default(storePath, { readonly: mode === "readonly" });
553
573
  if (mode === "readwrite") {
554
574
  migrate(db);
555
575
  }
556
576
  return db;
557
577
  }
578
+ function addColumnIfMissing(db, table, column, ddlType) {
579
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
580
+ if (!columns.some((c) => c.name === column)) {
581
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddlType}`);
582
+ }
583
+ }
558
584
  function migrate(db) {
559
585
  for (const sql of MIGRATIONS) {
560
586
  db.exec(sql);
561
587
  }
588
+ addColumnIfMissing(db, "revisions", "frontmatter_json", "TEXT");
589
+ addColumnIfMissing(db, "revisions", "body", "TEXT");
562
590
  db.prepare(
563
591
  `INSERT INTO meta(key, value) VALUES('schema_version', ?)
564
592
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
@@ -609,8 +637,9 @@ function bulkLoadTranslations(db) {
609
637
  function appendRevision(db, input) {
610
638
  const result = db.prepare(
611
639
  `INSERT INTO revisions (
612
- content_type, en_slug, locale, revision_kind, en_hash, body_hash, created_at, model, body_preview
613
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
640
+ content_type, en_slug, locale, revision_kind, en_hash, body_hash, created_at,
641
+ model, body_preview, frontmatter_json, body
642
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
614
643
  ).run(
615
644
  input.contentType,
616
645
  input.enSlug,
@@ -620,7 +649,9 @@ function appendRevision(db, input) {
620
649
  input.bodyHash,
621
650
  input.createdAt,
622
651
  input.model ?? null,
623
- input.bodyPreview ?? null
652
+ input.bodyPreview ?? null,
653
+ input.frontmatterJson ?? null,
654
+ input.body ?? null
624
655
  );
625
656
  return Number(result.lastInsertRowid);
626
657
  }
@@ -1553,8 +1584,8 @@ function getRedirectSourceSlugs(project) {
1553
1584
  // src/sitemap/join-base-url.ts
1554
1585
  function joinBaseUrl(baseUrl, pathname) {
1555
1586
  const origin = baseUrl.replace(/\/$/, "");
1556
- const path9 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1557
- return `${origin}${path9}`;
1587
+ const path11 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1588
+ return `${origin}${path11}`;
1558
1589
  }
1559
1590
 
1560
1591
  // src/sitemap/generate-sitemap.ts
@@ -1698,34 +1729,6 @@ function loadConfigSync(options = {}) {
1698
1729
  }
1699
1730
  return resolveConfig(config, path3__default.default.dirname(configPath));
1700
1731
  }
1701
- function sha256(input) {
1702
- return crypto.createHash("sha256").update(input, "utf8").digest("hex");
1703
- }
1704
- function computePageEnHash(translatableFrontmatter, body) {
1705
- const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
1706
- return sha256(payload);
1707
- }
1708
- function computeBodyHash(body) {
1709
- return sha256(body);
1710
- }
1711
-
1712
- // src/history/record-revision.ts
1713
- function recordRevision(config, input) {
1714
- const db = openStore(config, "readwrite");
1715
- const id = appendRevision(db, {
1716
- contentType: input.contentType,
1717
- enSlug: input.enSlug,
1718
- locale: input.locale,
1719
- revisionKind: input.revisionKind,
1720
- enHash: input.enHash,
1721
- bodyHash: computeBodyHash(input.body),
1722
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1723
- model: input.model,
1724
- bodyPreview: input.body.slice(0, 200)
1725
- });
1726
- db.close();
1727
- return id;
1728
- }
1729
1732
  function getAtPath2(obj, fieldPath) {
1730
1733
  let current = obj;
1731
1734
  for (const segment of fieldPath) {
@@ -1805,6 +1808,72 @@ function validateRelations(project) {
1805
1808
  }
1806
1809
  return issues;
1807
1810
  }
1811
+ var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
1812
+ var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
1813
+ var MARKDOWN_IMAGE_RE = new RegExp(
1814
+ String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
1815
+ "gi"
1816
+ );
1817
+ var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
1818
+ function isImageWebPath(value) {
1819
+ return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
1820
+ }
1821
+ function isSiteAssetPath(webPath) {
1822
+ const segment = webPath.split("/")[1];
1823
+ return Boolean(segment && !segment.includes("."));
1824
+ }
1825
+ function addImagePath(webPath, out) {
1826
+ if (isSiteAssetPath(webPath)) out.add(webPath);
1827
+ }
1828
+ function collectBodyImagePaths(body, out) {
1829
+ for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
1830
+ for (const match of body.matchAll(re)) {
1831
+ addImagePath(match[1], out);
1832
+ }
1833
+ }
1834
+ }
1835
+ function collectFrontmatterImagePaths(value, out) {
1836
+ if (typeof value === "string") {
1837
+ if (isImageWebPath(value)) addImagePath(value, out);
1838
+ return;
1839
+ }
1840
+ if (Array.isArray(value)) {
1841
+ for (const item of value) collectFrontmatterImagePaths(item, out);
1842
+ return;
1843
+ }
1844
+ if (value && typeof value === "object") {
1845
+ for (const nested of Object.values(value)) {
1846
+ collectFrontmatterImagePaths(nested, out);
1847
+ }
1848
+ }
1849
+ }
1850
+ function collectImagePaths(frontmatter, body) {
1851
+ const paths = /* @__PURE__ */ new Set();
1852
+ collectFrontmatterImagePaths(frontmatter, paths);
1853
+ collectBodyImagePaths(body, paths);
1854
+ return [...paths].sort();
1855
+ }
1856
+ function assetFilePath(assetsPath, webPath) {
1857
+ return path3__default.default.join(assetsPath, webPath.replace(/^\//, ""));
1858
+ }
1859
+ function validateDocumentAssets(config, input) {
1860
+ const assetsPath = config.assetsPath;
1861
+ if (!assetsPath) return [];
1862
+ const issues = [];
1863
+ for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
1864
+ const filePath = assetFilePath(assetsPath, webPath);
1865
+ if (fs2__default.default.existsSync(filePath)) continue;
1866
+ issues.push({
1867
+ level: "warning",
1868
+ contentType: input.contentType,
1869
+ enSlug: input.enSlug,
1870
+ locale: input.locale,
1871
+ field: "asset",
1872
+ message: `Missing image asset ${webPath} (expected ${filePath})`
1873
+ });
1874
+ }
1875
+ return issues;
1876
+ }
1808
1877
 
1809
1878
  // src/validate/validate-slug-suffix.ts
1810
1879
  function validateTranslationSlugSuffixes(config, db) {
@@ -1867,8 +1936,6 @@ function validateProject(config) {
1867
1936
  });
1868
1937
  continue;
1869
1938
  }
1870
- const payload = getTranslatablePayload(enDoc, type);
1871
- const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
1872
1939
  const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
1873
1940
  locale: config.defaultLocale,
1874
1941
  defaultLocale: config.defaultLocale,
@@ -1886,28 +1953,18 @@ function validateProject(config) {
1886
1953
  message: issue.message
1887
1954
  });
1888
1955
  }
1956
+ for (const issue of validateDocumentAssets(config, {
1957
+ contentType: type.id,
1958
+ enSlug,
1959
+ frontmatter: enDoc.frontmatter,
1960
+ body: enDoc.content
1961
+ })) {
1962
+ issues.push(issue);
1963
+ }
1889
1964
  for (const locale of config.locales) {
1890
1965
  if (locale === config.defaultLocale) continue;
1891
1966
  const row = getTranslation(db, type.id, enSlug, locale);
1892
1967
  if (!row) continue;
1893
- if (row.en_hash !== currentEnHash) {
1894
- issues.push({
1895
- level: "warning",
1896
- contentType: type.id,
1897
- enSlug,
1898
- locale,
1899
- field: "en_hash",
1900
- message: "Translation is stale (en_hash mismatch)"
1901
- });
1902
- recordRevision(config, {
1903
- contentType: type.id,
1904
- enSlug,
1905
- locale,
1906
- revisionKind: "en_edit_detected",
1907
- enHash: currentEnHash,
1908
- body: row.body
1909
- });
1910
- }
1911
1968
  const localeFm = JSON.parse(row.frontmatter_json);
1912
1969
  for (const issue of validateLocaleBuiltinFields(localeFm)) {
1913
1970
  issues.push({
@@ -1919,6 +1976,15 @@ function validateProject(config) {
1919
1976
  message: issue.message
1920
1977
  });
1921
1978
  }
1979
+ for (const issue of validateDocumentAssets(config, {
1980
+ contentType: type.id,
1981
+ enSlug,
1982
+ locale,
1983
+ frontmatter: localeFm,
1984
+ body: row.body
1985
+ })) {
1986
+ issues.push(issue);
1987
+ }
1922
1988
  }
1923
1989
  }
1924
1990
  try {
@@ -1986,6 +2052,18 @@ function validateProject(config) {
1986
2052
  }
1987
2053
  return { ok: issues.every((i) => i.level !== "error"), issues };
1988
2054
  }
2055
+ function sha256(input) {
2056
+ return crypto.createHash("sha256").update(input, "utf8").digest("hex");
2057
+ }
2058
+ function computePageEnHash(translatableFrontmatter, body) {
2059
+ const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
2060
+ return sha256(payload);
2061
+ }
2062
+ function computeBodyHash(body) {
2063
+ return sha256(body);
2064
+ }
2065
+
2066
+ // src/translate/worklist.ts
1989
2067
  function listEnSlugs3(rootDir, contentDir) {
1990
2068
  const dir = path3__default.default.join(rootDir, contentDir);
1991
2069
  if (!fs2__default.default.existsSync(dir)) return [];
@@ -2030,6 +2108,10 @@ function buildWorklist(config, options = {}) {
2030
2108
  }
2031
2109
  }
2032
2110
  db.close();
2111
+ const strategy = options.strategy ?? "all";
2112
+ if (strategy === "missing-only") {
2113
+ return items.filter((item) => item.reason === "missing");
2114
+ }
2033
2115
  return items;
2034
2116
  }
2035
2117
  function resolveLocalesFromPreset(config, preset, explicitLocales) {
@@ -2039,7 +2121,50 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
2039
2121
  }
2040
2122
  return config.locales.filter((l) => l !== config.defaultLocale);
2041
2123
  }
2042
- var DEFAULT_MODEL = "gemini-2.5-pro";
2124
+
2125
+ // src/history/record-revision.ts
2126
+ function recordRevision(config, input) {
2127
+ const db = openStore(config, "readwrite");
2128
+ const id = appendRevision(db, {
2129
+ contentType: input.contentType,
2130
+ enSlug: input.enSlug,
2131
+ locale: input.locale,
2132
+ revisionKind: input.revisionKind,
2133
+ enHash: input.enHash,
2134
+ bodyHash: computeBodyHash(input.body),
2135
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2136
+ model: input.model,
2137
+ bodyPreview: input.body.slice(0, 200),
2138
+ frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
2139
+ body: input.body
2140
+ });
2141
+ db.close();
2142
+ return id;
2143
+ }
2144
+
2145
+ // src/translate/gemini-models.ts
2146
+ var GEMINI_MODEL_IDS = {
2147
+ "gemini-2.5-pro": "gemini-2.5-pro",
2148
+ "gemini-3.1-pro": "gemini-3.1-pro-preview"
2149
+ };
2150
+ var DEFAULT_GEMINI_MODEL = "gemini-3.1-pro";
2151
+ function stripModelsPrefix(model) {
2152
+ return model.replace(/^models\//, "");
2153
+ }
2154
+ function resolveGeminiModelId(model) {
2155
+ const name = stripModelsPrefix(model);
2156
+ return GEMINI_MODEL_IDS[name] ?? name;
2157
+ }
2158
+ function normalizeGeminiDisplayName(model) {
2159
+ const name = stripModelsPrefix(model);
2160
+ if (name in GEMINI_MODEL_IDS) return name;
2161
+ const alias = Object.entries(GEMINI_MODEL_IDS).find(([, apiId]) => apiId === name);
2162
+ if (alias) return alias[0];
2163
+ return name;
2164
+ }
2165
+
2166
+ // src/translate/gemini-client.ts
2167
+ var DEFAULT_MODEL = DEFAULT_GEMINI_MODEL;
2043
2168
  function extractJson(text) {
2044
2169
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
2045
2170
  if (fenced?.[1]) return fenced[1].trim();
@@ -2053,13 +2178,17 @@ async function translatePageWithGemini(input) {
2053
2178
  if (!apiKey) {
2054
2179
  throw new Error("GEMINI_API_KEY is required for scribe translate");
2055
2180
  }
2056
- const model = input.model ?? process.env.PROSE_GEMINI_MODEL ?? DEFAULT_MODEL;
2181
+ const displayModel = normalizeGeminiDisplayName(
2182
+ input.model ?? process.env.PROSE_GEMINI_MODEL ?? DEFAULT_MODEL
2183
+ );
2184
+ const apiModel = resolveGeminiModelId(displayModel);
2057
2185
  const ai = new genai.GoogleGenAI({ apiKey });
2058
2186
  const response = await ai.models.generateContent({
2059
- model,
2187
+ model: apiModel,
2060
2188
  contents: input.prompt,
2061
2189
  config: {
2062
- responseMimeType: "application/json"
2190
+ responseMimeType: "application/json",
2191
+ ...input.responseSchema ? { responseSchema: input.responseSchema } : {}
2063
2192
  }
2064
2193
  });
2065
2194
  const raw = response.text ?? "";
@@ -2067,7 +2196,38 @@ async function translatePageWithGemini(input) {
2067
2196
  if (!parsed.frontmatter || typeof parsed.body !== "string") {
2068
2197
  throw new Error("Gemini response missing frontmatter/body");
2069
2198
  }
2070
- return { model, raw, parsed };
2199
+ const usageMetadata = response.usageMetadata;
2200
+ const usage = {
2201
+ inputTokens: usageMetadata?.promptTokenCount ?? 0,
2202
+ outputTokens: usageMetadata?.candidatesTokenCount ?? 0,
2203
+ totalTokens: usageMetadata?.totalTokenCount ?? 0
2204
+ };
2205
+ return { model: displayModel, raw, parsed, usage };
2206
+ }
2207
+
2208
+ // src/translate/gemini-pricing.ts
2209
+ var CONTEXT_TIER_TOKENS = 2e5;
2210
+ var GEMINI_PRICING_USD = {
2211
+ "gemini-3.1-pro": { input: [2, 4], output: [12, 18] }
2212
+ };
2213
+ function normalizeModelId(model) {
2214
+ return model.replace(/^models\//, "").toLowerCase();
2215
+ }
2216
+ function tierRate(tokens, tiers) {
2217
+ return tokens <= CONTEXT_TIER_TOKENS ? tiers[0] : tiers[1];
2218
+ }
2219
+ function resolveModelPricing(model) {
2220
+ const id = normalizeModelId(model);
2221
+ if (GEMINI_PRICING_USD[id]) return GEMINI_PRICING_USD[id];
2222
+ const match = Object.entries(GEMINI_PRICING_USD).find(([key]) => id.includes(key));
2223
+ return match?.[1];
2224
+ }
2225
+ function estimateTranslationCostUsd(model, inputTokens, outputTokens) {
2226
+ const pricing = resolveModelPricing(model);
2227
+ if (!pricing) return void 0;
2228
+ const inputRate = tierRate(inputTokens, pricing.input);
2229
+ const outputRate = tierRate(outputTokens, pricing.output);
2230
+ return inputTokens / 1e6 * inputRate + outputTokens / 1e6 * outputRate;
2071
2231
  }
2072
2232
 
2073
2233
  // src/translate/prompts/translation-prompt.ts
@@ -2103,6 +2263,7 @@ function buildPageTranslationPrompt(input) {
2103
2263
  prompt,
2104
2264
  "",
2105
2265
  ...input.resolved.context ? ["## Context", input.resolved.context, ""] : [],
2266
+ ...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
2106
2267
  "## Rules",
2107
2268
  ...input.resolved.rules.map((rule) => `- ${rule}`),
2108
2269
  "",
@@ -2110,10 +2271,6 @@ function buildPageTranslationPrompt(input) {
2110
2271
  "Return ONLY valid JSON with keys:",
2111
2272
  input.slugStrategy === "localized" ? "`frontmatter` (object with translated frontmatter fields), `body` (string, full MDX body), `slug` (string)." : "`frontmatter` (object), `body` (string).",
2112
2273
  "",
2113
- "## EN metadata",
2114
- `title: ${input.enTitle}`,
2115
- `description: ${input.enDescription}`,
2116
- "",
2117
2274
  "## EN translatable frontmatter (JSON)",
2118
2275
  JSON.stringify(input.translatableFrontmatter, null, 2),
2119
2276
  "",
@@ -2122,6 +2279,71 @@ function buildPageTranslationPrompt(input) {
2122
2279
  ];
2123
2280
  return lines.join("\n");
2124
2281
  }
2282
+ function getObjectShape(schema) {
2283
+ const base = peelOptionalWrappers(schema);
2284
+ if (base instanceof Object && "shape" in base) {
2285
+ return base.shape;
2286
+ }
2287
+ return null;
2288
+ }
2289
+ function getArraySchema(schema) {
2290
+ const base = peelOptionalWrappers(schema);
2291
+ if (base instanceof Object && "element" in base && base._def?.type === "array") {
2292
+ return base;
2293
+ }
2294
+ return null;
2295
+ }
2296
+ function extractTranslatableFromStructural(schema) {
2297
+ const arraySchema = getArraySchema(schema);
2298
+ if (arraySchema) {
2299
+ const inner = buildTranslatableSubschema(arraySchema.element);
2300
+ if (inner) return zod.z.array(inner);
2301
+ return null;
2302
+ }
2303
+ if (getObjectShape(schema)) {
2304
+ return buildTranslatableSubschema(schema);
2305
+ }
2306
+ return null;
2307
+ }
2308
+ function buildTranslatableSubschema(schema) {
2309
+ const shape = getObjectShape(schema);
2310
+ if (!shape) return null;
2311
+ const out = {};
2312
+ for (const [key, child] of Object.entries(shape)) {
2313
+ const childSchema = child;
2314
+ const kind = getFieldKind(childSchema);
2315
+ if (kind === "translatable") {
2316
+ out[key] = peelOptionalWrappers(childSchema);
2317
+ } else if (kind === "structural") {
2318
+ const nested = extractTranslatableFromStructural(childSchema);
2319
+ if (nested) out[key] = nested;
2320
+ }
2321
+ }
2322
+ if (Object.keys(out).length === 0) return null;
2323
+ return zod.z.object(out);
2324
+ }
2325
+ function buildGeminiResponseSchema(schema, slugStrategy) {
2326
+ try {
2327
+ const translatable = buildTranslatableSubschema(schema);
2328
+ if (!translatable) return null;
2329
+ const responseShape = {
2330
+ frontmatter: translatable,
2331
+ body: zod.z.string()
2332
+ };
2333
+ if (slugStrategy === "localized") {
2334
+ responseShape.slug = zod.z.string();
2335
+ }
2336
+ const jsonSchema = zod.z.toJSONSchema(zod.z.object(responseShape), {
2337
+ target: "draft-2020-12",
2338
+ unrepresentable: "any",
2339
+ io: "output"
2340
+ });
2341
+ delete jsonSchema.$schema;
2342
+ return jsonSchema;
2343
+ } catch {
2344
+ return null;
2345
+ }
2346
+ }
2125
2347
 
2126
2348
  // src/translate/resolve-translate-config.ts
2127
2349
  function slugStrategyRules(slugStrategy, preserveTerms) {
@@ -2162,107 +2384,329 @@ function resolveTranslateConfig(config, type) {
2162
2384
  return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
2163
2385
  }
2164
2386
 
2387
+ // src/translate/validate-translation.ts
2388
+ function formatZodIssues(error) {
2389
+ return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
2390
+ }
2391
+ function sanitizeTranslatedFrontmatter(rawFrontmatter, schema) {
2392
+ return pickTranslatable(rawFrontmatter, schema);
2393
+ }
2394
+ function validateTranslatedFrontmatter(enDoc, localeFrontmatter, typeSchema) {
2395
+ const frontmatter = sanitizeTranslatedFrontmatter(localeFrontmatter, typeSchema);
2396
+ const merged = mergeStructuralOntoLocale(
2397
+ frontmatter,
2398
+ enDoc.frontmatter,
2399
+ typeSchema
2400
+ );
2401
+ const parsed = typeSchema.safeParse(merged);
2402
+ if (!parsed.success) {
2403
+ return { ok: false, error: formatZodIssues(parsed.error) };
2404
+ }
2405
+ return { ok: true, frontmatter };
2406
+ }
2407
+
2165
2408
  // src/translate/page-translator.ts
2409
+ function summarizeResults(results, durationMs) {
2410
+ return results.reduce(
2411
+ (totals, result) => {
2412
+ if (result.failed) totals.failed += 1;
2413
+ else if (result.skipped) totals.skipped += 1;
2414
+ else totals.translated += 1;
2415
+ totals.inputTokens += result.usage?.inputTokens ?? 0;
2416
+ totals.outputTokens += result.usage?.outputTokens ?? 0;
2417
+ totals.estimatedCostUsd += result.estimatedCostUsd ?? 0;
2418
+ return totals;
2419
+ },
2420
+ {
2421
+ translated: 0,
2422
+ skipped: 0,
2423
+ failed: 0,
2424
+ inputTokens: 0,
2425
+ outputTokens: 0,
2426
+ estimatedCostUsd: 0,
2427
+ durationMs
2428
+ }
2429
+ );
2430
+ }
2431
+ function formatTranslateError(error) {
2432
+ const message = error instanceof Error ? error.message : String(error);
2433
+ try {
2434
+ const parsed = JSON.parse(message);
2435
+ if (parsed.error?.message) return parsed.error.message;
2436
+ } catch {
2437
+ }
2438
+ const embedded = message.match(/"message"\s*:\s*"((?:\\.|[^"\\])*)"/);
2439
+ if (embedded?.[1]) {
2440
+ return embedded[1].replace(/\\"/g, '"').replace(/\\n/g, "\n");
2441
+ }
2442
+ return message.length > 160 ? `${message.slice(0, 157)}\u2026` : message;
2443
+ }
2444
+ async function runPool(items, concurrency, worker) {
2445
+ if (items.length === 0) return;
2446
+ let nextIndex = 0;
2447
+ const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
2448
+ while (nextIndex < items.length) {
2449
+ const index = nextIndex++;
2450
+ await worker(items[index], index);
2451
+ }
2452
+ });
2453
+ await Promise.all(runners);
2454
+ }
2455
+ function resolveContextLabel(enDoc, enSlug) {
2456
+ const fm = enDoc.frontmatter;
2457
+ for (const key of ["title", "name", "h1"]) {
2458
+ const value = fm[key];
2459
+ if (typeof value === "string" && value.trim()) return value;
2460
+ }
2461
+ return enSlug;
2462
+ }
2166
2463
  async function translatePage(config, item, options = {}) {
2167
- const type = config.types.find((t) => t.id === item.contentType);
2168
- if (!type) throw new Error(`Unknown content type ${item.contentType}`);
2169
- const enDoc = readEnDocument(config, type, item.enSlug);
2170
- if (!enDoc) throw new Error(`EN document not found: ${item.enSlug}`);
2171
- const payload = getTranslatablePayload(enDoc, type);
2172
- const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
2173
- const db = openStore(config, "readonly");
2174
- const existing = getTranslation(db, type.id, item.enSlug, item.locale);
2175
- db.close();
2176
- if (!options.force && existing && existing.en_hash === currentEnHash) {
2177
- return {
2178
- contentType: item.contentType,
2464
+ const startedAt = Date.now();
2465
+ const base = {
2466
+ contentType: item.contentType,
2467
+ enSlug: item.enSlug,
2468
+ locale: item.locale
2469
+ };
2470
+ try {
2471
+ const type = config.types.find((t) => t.id === item.contentType);
2472
+ if (!type) throw new Error(`Unknown content type ${item.contentType}`);
2473
+ const enDoc = readEnDocument(config, type, item.enSlug);
2474
+ if (!enDoc) throw new Error(`EN document not found: ${item.enSlug}`);
2475
+ const payload = getTranslatablePayload(enDoc, type);
2476
+ const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
2477
+ const db = openStore(config, "readonly");
2478
+ const existing = getTranslation(db, type.id, item.enSlug, item.locale);
2479
+ db.close();
2480
+ if (!options.force && existing && existing.en_hash === currentEnHash) {
2481
+ return {
2482
+ ...base,
2483
+ skipped: true,
2484
+ reason: "fresh",
2485
+ durationMs: Date.now() - startedAt
2486
+ };
2487
+ }
2488
+ const resolvedTranslate = resolveTranslateConfig(config, type);
2489
+ const model = options.model ?? resolvedTranslate.model;
2490
+ const prompt = buildPageTranslationPrompt({
2491
+ resolved: resolvedTranslate,
2492
+ targetLocale: item.locale,
2493
+ contextLabel: resolveContextLabel(enDoc, item.enSlug),
2494
+ translatableFrontmatter: payload.frontmatter,
2495
+ enBody: payload.body,
2496
+ slugStrategy: type.slugStrategy
2497
+ });
2498
+ if (options.dryRun) {
2499
+ return {
2500
+ ...base,
2501
+ skipped: false,
2502
+ model,
2503
+ durationMs: Date.now() - startedAt
2504
+ };
2505
+ }
2506
+ const responseSchema = buildGeminiResponseSchema(type.schema, type.slugStrategy);
2507
+ const result = await translatePageWithGemini({
2508
+ prompt,
2509
+ model,
2510
+ responseSchema: responseSchema ?? void 0
2511
+ });
2512
+ const slug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2513
+ const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
2514
+ if (!validated.ok) {
2515
+ throw new Error(`Translation validation failed: ${validated.error}`);
2516
+ }
2517
+ const writeDb = openStore(config, "readwrite");
2518
+ upsertTranslation(writeDb, {
2519
+ contentType: type.id,
2179
2520
  enSlug: item.enSlug,
2180
2521
  locale: item.locale,
2181
- skipped: true,
2182
- reason: "fresh"
2183
- };
2184
- }
2185
- const resolvedTranslate = resolveTranslateConfig(config, type);
2186
- const prompt = buildPageTranslationPrompt({
2187
- resolved: resolvedTranslate,
2188
- targetLocale: item.locale,
2189
- enTitle: String(enDoc.frontmatter.title ?? item.enSlug),
2190
- enDescription: String(enDoc.frontmatter.description ?? ""),
2191
- translatableFrontmatter: payload.frontmatter,
2192
- enBody: payload.body,
2193
- slugStrategy: type.slugStrategy
2194
- });
2195
- if (options.dryRun) {
2196
- return {
2197
- contentType: item.contentType,
2522
+ slug,
2523
+ frontmatter: validated.frontmatter,
2524
+ body: result.parsed.body,
2525
+ enHash: currentEnHash,
2526
+ translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2527
+ model: result.model
2528
+ });
2529
+ writeDb.close();
2530
+ recordRevision(config, {
2531
+ contentType: type.id,
2198
2532
  enSlug: item.enSlug,
2199
2533
  locale: item.locale,
2534
+ revisionKind: "translation",
2535
+ enHash: currentEnHash,
2536
+ body: result.parsed.body,
2537
+ frontmatter: validated.frontmatter,
2538
+ model: result.model
2539
+ });
2540
+ const estimatedCostUsd = estimateTranslationCostUsd(
2541
+ normalizeGeminiDisplayName(result.model),
2542
+ result.usage.inputTokens,
2543
+ result.usage.outputTokens
2544
+ );
2545
+ return {
2546
+ ...base,
2200
2547
  skipped: false,
2201
- model: options.model
2548
+ model: result.model,
2549
+ usage: result.usage,
2550
+ estimatedCostUsd,
2551
+ durationMs: Date.now() - startedAt
2552
+ };
2553
+ } catch (error) {
2554
+ return {
2555
+ ...base,
2556
+ skipped: false,
2557
+ failed: true,
2558
+ error: formatTranslateError(error),
2559
+ durationMs: Date.now() - startedAt
2202
2560
  };
2203
2561
  }
2204
- const result = await translatePageWithGemini({
2205
- prompt,
2206
- model: options.model ?? resolvedTranslate.model
2207
- });
2208
- const slug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2209
- const writeDb = openStore(config, "readwrite");
2210
- upsertTranslation(writeDb, {
2211
- contentType: type.id,
2212
- enSlug: item.enSlug,
2213
- locale: item.locale,
2214
- slug,
2215
- frontmatter: result.parsed.frontmatter,
2216
- body: result.parsed.body,
2217
- enHash: currentEnHash,
2218
- translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2219
- model: result.model
2562
+ }
2563
+ function labelForItem(item) {
2564
+ return `${item.contentType}/${item.enSlug}@${item.locale}`;
2565
+ }
2566
+ async function translateWorklist(config, items, options = {}) {
2567
+ const concurrency = Math.max(1, options.concurrency ?? 3);
2568
+ const startedAt = Date.now();
2569
+ const results = new Array(items.length);
2570
+ const active = /* @__PURE__ */ new Set();
2571
+ options.onProgress?.({
2572
+ type: "start",
2573
+ total: items.length,
2574
+ concurrency,
2575
+ dryRun: Boolean(options.dryRun),
2576
+ model: options.model
2220
2577
  });
2221
- writeDb.close();
2222
- recordRevision(config, {
2223
- contentType: type.id,
2224
- enSlug: item.enSlug,
2225
- locale: item.locale,
2226
- revisionKind: "translation",
2227
- enHash: currentEnHash,
2228
- body: result.parsed.body,
2229
- model: result.model
2578
+ await runPool(items, concurrency, async (item, index) => {
2579
+ const label = labelForItem(item);
2580
+ active.add(label);
2581
+ options.onProgress?.({ type: "item-start", item, active: [...active] });
2582
+ const result = await translatePage(config, item, options);
2583
+ results[index] = result;
2584
+ active.delete(label);
2585
+ options.onProgress?.({ type: "item-done", result });
2230
2586
  });
2231
- return {
2232
- contentType: item.contentType,
2233
- enSlug: item.enSlug,
2234
- locale: item.locale,
2235
- skipped: false,
2236
- model: result.model
2237
- };
2587
+ const totals = summarizeResults(results, Date.now() - startedAt);
2588
+ options.onProgress?.({ type: "done", results, totals });
2589
+ return results;
2238
2590
  }
2239
- async function translateWorklist(config, items, options = {}) {
2240
- const results = [];
2241
- for (const item of items) {
2242
- results.push(await translatePage(config, item, options));
2591
+ function serializeMdx(frontmatter, content) {
2592
+ return matter__default.default.stringify(content, frontmatter);
2593
+ }
2594
+
2595
+ // src/export/build-static-raw-exports.ts
2596
+ function normalizeExtension(ext) {
2597
+ return ext.startsWith(".") ? ext : `.${ext}`;
2598
+ }
2599
+ function exportDirSegment(pathTemplate) {
2600
+ const prefix = pathPrefix(pathTemplate);
2601
+ return prefix.replace(/^\/+|\/+$/g, "");
2602
+ }
2603
+ function getStaticExportRoots(project, options = {}) {
2604
+ const { config } = project;
2605
+ const locales = options.locales ?? config.locales;
2606
+ const typeFilter = options.types ? new Set(options.types) : null;
2607
+ const roots = /* @__PURE__ */ new Set();
2608
+ for (const type of project.listTypes()) {
2609
+ if (!isRoutableType(type.config)) continue;
2610
+ if (typeFilter && !typeFilter.has(type.id)) continue;
2611
+ const segment = exportDirSegment(type.config.path);
2612
+ roots.add(`${segment}/`);
2613
+ for (const locale of locales) {
2614
+ if (locale === config.defaultLocale) continue;
2615
+ roots.add(`${locale}/${segment}/`);
2616
+ }
2243
2617
  }
2244
- return results;
2618
+ return [...roots].sort();
2619
+ }
2620
+ function buildStaticRawExports(project, options = {}) {
2621
+ const { config } = project;
2622
+ const extension = normalizeExtension(options.extension ?? ".mdx");
2623
+ const locales = options.locales ?? config.locales;
2624
+ const excludeRedirected = options.excludeRedirected ?? true;
2625
+ const excludeNoindex = options.excludeNoindex ?? false;
2626
+ const typeFilter = options.types ? new Set(options.types) : null;
2627
+ const out = [];
2628
+ for (const type of project.listTypes()) {
2629
+ if (!isRoutableType(type.config)) continue;
2630
+ if (typeFilter && !typeFilter.has(type.id)) continue;
2631
+ const pathTemplate = type.config.path;
2632
+ const all = type.load();
2633
+ const enIdx = all.get(config.defaultLocale);
2634
+ if (!enIdx) continue;
2635
+ for (const locale of locales) {
2636
+ for (const enDoc of enIdx.bySlug.values()) {
2637
+ const resolved = type.resolve(enDoc.slug, locale);
2638
+ if (!resolved.document) continue;
2639
+ if (excludeRedirected && resolved.shouldRedirectTo) continue;
2640
+ if (excludeNoindex && resolved.document.noindex) continue;
2641
+ const doc = resolved.document;
2642
+ const slugWithExt = `${doc.slug}${extension}`;
2643
+ const urlPath = resolvePath(pathTemplate, slugWithExt, locale, config.defaultLocale);
2644
+ out.push({
2645
+ relativePath: urlPath.slice(1),
2646
+ urlPath,
2647
+ locale,
2648
+ typeId: type.id,
2649
+ enSlug: doc.enSlug,
2650
+ source: serializeMdx(doc.frontmatter, doc.content)
2651
+ });
2652
+ }
2653
+ }
2654
+ }
2655
+ return out;
2656
+ }
2657
+ function rmDirIfExists(dir) {
2658
+ if (fs2__default.default.existsSync(dir)) {
2659
+ fs2__default.default.rmSync(dir, { recursive: true, force: true });
2660
+ }
2661
+ }
2662
+ function writeStaticRawExports(project, options = {}) {
2663
+ const outDir = path3__default.default.resolve(options.outDir ?? "public");
2664
+ const typeFilter = options.types;
2665
+ for (const root of getStaticExportRoots(project, {
2666
+ types: typeFilter,
2667
+ locales: options.locales
2668
+ })) {
2669
+ rmDirIfExists(path3__default.default.join(outDir, root));
2670
+ }
2671
+ const exports = buildStaticRawExports(project, options);
2672
+ const counts = /* @__PURE__ */ new Map();
2673
+ for (const item of exports) {
2674
+ const filePath = path3__default.default.join(outDir, item.relativePath);
2675
+ fs2__default.default.mkdirSync(path3__default.default.dirname(filePath), { recursive: true });
2676
+ fs2__default.default.writeFileSync(filePath, item.source, "utf8");
2677
+ const key = `${item.typeId}/${item.locale}`;
2678
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2679
+ }
2680
+ for (const [key, count] of [...counts.entries()].sort()) {
2681
+ console.log(` ${key}: ${count}`);
2682
+ }
2683
+ return { exports, written: exports.length };
2245
2684
  }
2246
2685
 
2247
2686
  exports.buildAllContentRedirects = buildAllContentRedirects;
2687
+ exports.buildStaticRawExports = buildStaticRawExports;
2248
2688
  exports.buildWorklist = buildWorklist;
2249
2689
  exports.createProject = createProject;
2250
2690
  exports.createScribe = createScribe;
2251
2691
  exports.defineConfig = defineConfig;
2252
2692
  exports.defineContentType = defineContentType;
2693
+ exports.exportDirSegment = exportDirSegment;
2253
2694
  exports.field = field;
2254
2695
  exports.findConfigPath = findConfigPath;
2255
2696
  exports.generateSitemap = generateSitemap;
2256
2697
  exports.getFieldKind = getFieldKind;
2257
2698
  exports.getRedirectSourceSlugs = getRedirectSourceSlugs;
2258
2699
  exports.getRelationTarget = getRelationTarget;
2700
+ exports.getStaticExportRoots = getStaticExportRoots;
2259
2701
  exports.isResolvedConfig = isResolvedConfig;
2260
2702
  exports.loadConfigSync = loadConfigSync;
2261
2703
  exports.resolveConfig = resolveConfig;
2262
2704
  exports.resolveLocalesFromPreset = resolveLocalesFromPreset;
2705
+ exports.serializeMdx = serializeMdx;
2263
2706
  exports.translatePage = translatePage;
2264
2707
  exports.translateWorklist = translateWorklist;
2265
2708
  exports.unwrapSchema = unwrapSchema;
2266
2709
  exports.validateProject = validateProject;
2710
+ exports.writeStaticRawExports = writeStaticRawExports;
2267
2711
  //# sourceMappingURL=index.cjs.map
2268
2712
  //# sourceMappingURL=index.cjs.map