scribe-cms 0.0.4 → 0.0.6

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 (35) hide show
  1. package/dist/cli/index.cjs +272 -180
  2. package/dist/cli/index.cjs.map +1 -1
  3. package/dist/cli/index.js +272 -181
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/index.cjs +188 -123
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.js +188 -123
  8. package/dist/index.js.map +1 -1
  9. package/dist/runtime.cjs +25 -18
  10. package/dist/runtime.cjs.map +1 -1
  11. package/dist/runtime.js +25 -18
  12. package/dist/runtime.js.map +1 -1
  13. package/dist/src/config/resolve-config.d.ts.map +1 -1
  14. package/dist/src/core/types.d.ts +4 -0
  15. package/dist/src/core/types.d.ts.map +1 -1
  16. package/dist/src/history/record-snapshot.d.ts +11 -0
  17. package/dist/src/history/record-snapshot.d.ts.map +1 -0
  18. package/dist/src/storage/sqlite.d.ts.map +1 -1
  19. package/dist/src/storage/translations.d.ts +14 -21
  20. package/dist/src/storage/translations.d.ts.map +1 -1
  21. package/dist/src/translate/page-translator.d.ts.map +1 -1
  22. package/dist/src/validate/validate-assets.d.ts +20 -0
  23. package/dist/src/validate/validate-assets.d.ts.map +1 -0
  24. package/dist/src/validate/validate-project.d.ts +1 -1
  25. package/dist/src/validate/validate-project.d.ts.map +1 -1
  26. package/dist/src/version.d.ts +4 -0
  27. package/dist/src/version.d.ts.map +1 -0
  28. package/dist/studio/server.cjs +51 -64
  29. package/dist/studio/server.cjs.map +1 -1
  30. package/dist/studio/server.d.ts.map +1 -1
  31. package/dist/studio/server.js +51 -64
  32. package/dist/studio/server.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/src/history/record-revision.d.ts +0 -14
  35. package/dist/src/history/record-revision.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -128,14 +128,14 @@ var SLUG_PLACEHOLDER = "{slug}";
128
128
  function isRoutableType(type) {
129
129
  return typeof type.path === "string" && type.path.length > 0;
130
130
  }
131
- function assertValidPathTemplate(path10, typeId) {
132
- if (!path10.startsWith("/")) {
133
- throw new Error(`Content type "${typeId}": path must start with / (got "${path10}")`);
131
+ function assertValidPathTemplate(path11, typeId) {
132
+ if (!path11.startsWith("/")) {
133
+ throw new Error(`Content type "${typeId}": path must start with / (got "${path11}")`);
134
134
  }
135
- const count = (path10.match(/\{slug\}/g) ?? []).length;
135
+ const count = (path11.match(/\{slug\}/g) ?? []).length;
136
136
  if (count !== 1) {
137
137
  throw new Error(
138
- `Content type "${typeId}": path must contain exactly one {slug} (got "${path10}")`
138
+ `Content type "${typeId}": path must contain exactly one {slug} (got "${path11}")`
139
139
  );
140
140
  }
141
141
  }
@@ -187,6 +187,7 @@ function resolveConfig(input, baseDir) {
187
187
  const projectRoot = path3.resolve(baseDir ?? process.cwd(), raw.rootDir);
188
188
  const contentRoot = path3.resolve(projectRoot, raw.contentDir ?? "content");
189
189
  const storePath = path3.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
190
+ const assetsPath = raw.assetsDir ? path3.resolve(projectRoot, raw.assetsDir) : void 0;
190
191
  const seenIds = /* @__PURE__ */ new Set();
191
192
  const types = raw.types.map((type) => {
192
193
  if (seenIds.has(type.id)) {
@@ -207,6 +208,7 @@ function resolveConfig(input, baseDir) {
207
208
  const config = {
208
209
  rootDir: contentRoot,
209
210
  storePath,
211
+ assetsPath,
210
212
  locales: [...raw.locales],
211
213
  defaultLocale,
212
214
  localePresets: raw.localePresets,
@@ -259,10 +261,10 @@ function introspectSchema(schema, prefix = []) {
259
261
  }
260
262
  function extractByPaths(data, paths) {
261
263
  const out = {};
262
- for (const path10 of paths) {
263
- const value = getAtPath(data, path10);
264
+ for (const path11 of paths) {
265
+ const value = getAtPath(data, path11);
264
266
  if (value !== void 0) {
265
- setAtPath(out, path10.filter((p) => p !== "*"), value);
267
+ setAtPath(out, path11.filter((p) => p !== "*"), value);
266
268
  }
267
269
  }
268
270
  return out;
@@ -280,13 +282,13 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
280
282
  const translatable = pickTranslatable(localeData, schema);
281
283
  return deepMerge(structural, translatable);
282
284
  }
283
- function getAtPath(obj, path10) {
285
+ function getAtPath(obj, path11) {
284
286
  let current = obj;
285
- for (const segment of path10) {
287
+ for (const segment of path11) {
286
288
  if (segment === "*") {
287
289
  if (!Array.isArray(current)) return void 0;
288
290
  return current.map(
289
- (item) => typeof item === "object" && item !== null ? getAtPath(item, path10.slice(path10.indexOf("*") + 1)) : void 0
291
+ (item) => typeof item === "object" && item !== null ? getAtPath(item, path11.slice(path11.indexOf("*") + 1)) : void 0
290
292
  );
291
293
  }
292
294
  if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
@@ -294,13 +296,13 @@ function getAtPath(obj, path10) {
294
296
  }
295
297
  return current;
296
298
  }
297
- function setAtPath(obj, path10, value) {
298
- if (path10.length === 0) return;
299
- if (path10.length === 1) {
300
- obj[path10[0]] = value;
299
+ function setAtPath(obj, path11, value) {
300
+ if (path11.length === 0) return;
301
+ if (path11.length === 1) {
302
+ obj[path11[0]] = value;
301
303
  return;
302
304
  }
303
- const [head, ...rest] = path10;
305
+ const [head, ...rest] = path11;
304
306
  if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
305
307
  obj[head] = {};
306
308
  }
@@ -496,7 +498,7 @@ function seoFieldsFromEn(enDoc) {
496
498
  redirectTo: enDoc.redirectTo
497
499
  };
498
500
  }
499
- var SCHEMA_VERSION = 3;
501
+ var SCHEMA_VERSION = 4;
500
502
  var MIGRATIONS = [
501
503
  `CREATE TABLE IF NOT EXISTS meta (
502
504
  key TEXT PRIMARY KEY,
@@ -516,20 +518,6 @@ var MIGRATIONS = [
516
518
  )`,
517
519
  `CREATE INDEX IF NOT EXISTS idx_translations_type_locale
518
520
  ON translations(content_type, locale)`,
519
- `CREATE TABLE IF NOT EXISTS revisions (
520
- id INTEGER PRIMARY KEY AUTOINCREMENT,
521
- content_type TEXT NOT NULL,
522
- en_slug TEXT NOT NULL,
523
- locale TEXT,
524
- revision_kind TEXT NOT NULL,
525
- en_hash TEXT NOT NULL,
526
- body_hash TEXT NOT NULL,
527
- created_at TEXT NOT NULL,
528
- model TEXT,
529
- body_preview TEXT
530
- )`,
531
- `CREATE INDEX IF NOT EXISTS idx_revisions_lookup
532
- ON revisions(content_type, en_slug, locale, created_at DESC)`,
533
521
  `CREATE TABLE IF NOT EXISTS slug_aliases (
534
522
  content_type TEXT NOT NULL,
535
523
  canonical_en_slug TEXT NOT NULL,
@@ -546,7 +534,19 @@ var MIGRATIONS = [
546
534
  locale_slug TEXT NOT NULL,
547
535
  captured_at TEXT NOT NULL,
548
536
  PRIMARY KEY (content_type, alias_en_slug, locale)
549
- )`
537
+ )`,
538
+ `CREATE TABLE IF NOT EXISTS en_snapshots (
539
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
540
+ content_type TEXT NOT NULL,
541
+ en_slug TEXT NOT NULL,
542
+ en_hash TEXT NOT NULL,
543
+ frontmatter_json TEXT NOT NULL,
544
+ body TEXT NOT NULL,
545
+ created_at TEXT NOT NULL,
546
+ UNIQUE (content_type, en_slug, en_hash)
547
+ )`,
548
+ `CREATE INDEX IF NOT EXISTS idx_en_snapshots_lookup
549
+ ON en_snapshots(content_type, en_slug, created_at DESC)`
550
550
  ];
551
551
  function resolveStorePath(config) {
552
552
  return config.storePath;
@@ -568,12 +568,19 @@ function addColumnIfMissing(db, table, column, ddlType) {
568
568
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddlType}`);
569
569
  }
570
570
  }
571
+ function readSchemaVersion(db) {
572
+ const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
573
+ return row ? Number.parseInt(row.value, 10) || 0 : 0;
574
+ }
571
575
  function migrate(db) {
576
+ const previousVersion = readSchemaVersion(db);
572
577
  for (const sql of MIGRATIONS) {
573
578
  db.exec(sql);
574
579
  }
575
- addColumnIfMissing(db, "revisions", "frontmatter_json", "TEXT");
576
- addColumnIfMissing(db, "revisions", "body", "TEXT");
580
+ if (previousVersion < 4) {
581
+ db.exec(`DROP TABLE IF EXISTS revisions`);
582
+ }
583
+ addColumnIfMissing(db, "translations", "snapshot_id", "INTEGER");
577
584
  db.prepare(
578
585
  `INSERT INTO meta(key, value) VALUES('schema_version', ?)
579
586
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
@@ -581,18 +588,38 @@ function migrate(db) {
581
588
  }
582
589
 
583
590
  // src/storage/translations.ts
591
+ function getOrCreateEnSnapshot(db, input) {
592
+ db.prepare(
593
+ `INSERT OR IGNORE INTO en_snapshots (
594
+ content_type, en_slug, en_hash, frontmatter_json, body, created_at
595
+ ) VALUES (?, ?, ?, ?, ?, ?)`
596
+ ).run(
597
+ input.contentType,
598
+ input.enSlug,
599
+ input.enHash,
600
+ JSON.stringify(input.frontmatter),
601
+ input.body,
602
+ input.createdAt
603
+ );
604
+ const row = db.prepare(
605
+ `SELECT id FROM en_snapshots
606
+ WHERE content_type = ? AND en_slug = ? AND en_hash = ?`
607
+ ).get(input.contentType, input.enSlug, input.enHash);
608
+ return row.id;
609
+ }
584
610
  function upsertTranslation(db, input) {
585
611
  db.prepare(
586
612
  `INSERT INTO translations (
587
- content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model
588
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
613
+ content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model, snapshot_id
614
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
589
615
  ON CONFLICT(content_type, en_slug, locale) DO UPDATE SET
590
616
  slug = excluded.slug,
591
617
  frontmatter_json = excluded.frontmatter_json,
592
618
  body = excluded.body,
593
619
  en_hash = excluded.en_hash,
594
620
  translated_at = excluded.translated_at,
595
- model = excluded.model`
621
+ model = excluded.model,
622
+ snapshot_id = excluded.snapshot_id`
596
623
  ).run(
597
624
  input.contentType,
598
625
  input.enSlug,
@@ -602,7 +629,8 @@ function upsertTranslation(db, input) {
602
629
  input.body,
603
630
  input.enHash,
604
631
  input.translatedAt,
605
- input.model
632
+ input.model,
633
+ input.snapshotId
606
634
  );
607
635
  }
608
636
  function getTranslation(db, contentType, enSlug, locale) {
@@ -621,27 +649,6 @@ function listTranslationsForEnSlug(db, contentType, enSlug) {
621
649
  function bulkLoadTranslations(db) {
622
650
  return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
623
651
  }
624
- function appendRevision(db, input) {
625
- const result = db.prepare(
626
- `INSERT INTO revisions (
627
- content_type, en_slug, locale, revision_kind, en_hash, body_hash, created_at,
628
- model, body_preview, frontmatter_json, body
629
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
630
- ).run(
631
- input.contentType,
632
- input.enSlug,
633
- input.locale,
634
- input.revisionKind,
635
- input.enHash,
636
- input.bodyHash,
637
- input.createdAt,
638
- input.model ?? null,
639
- input.bodyPreview ?? null,
640
- input.frontmatterJson ?? null,
641
- input.body ?? null
642
- );
643
- return Number(result.lastInsertRowid);
644
- }
645
652
 
646
653
  // src/loader/normalize-en.ts
647
654
  function normalizeEnFrontmatter(data) {
@@ -1571,8 +1578,8 @@ function getRedirectSourceSlugs(project) {
1571
1578
  // src/sitemap/join-base-url.ts
1572
1579
  function joinBaseUrl(baseUrl, pathname) {
1573
1580
  const origin = baseUrl.replace(/\/$/, "");
1574
- const path10 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1575
- return `${origin}${path10}`;
1581
+ const path11 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1582
+ return `${origin}${path11}`;
1576
1583
  }
1577
1584
 
1578
1585
  // src/sitemap/generate-sitemap.ts
@@ -1716,36 +1723,6 @@ function loadConfigSync(options = {}) {
1716
1723
  }
1717
1724
  return resolveConfig(config, path3.dirname(configPath));
1718
1725
  }
1719
- function sha256(input) {
1720
- return createHash("sha256").update(input, "utf8").digest("hex");
1721
- }
1722
- function computePageEnHash(translatableFrontmatter, body) {
1723
- const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
1724
- return sha256(payload);
1725
- }
1726
- function computeBodyHash(body) {
1727
- return sha256(body);
1728
- }
1729
-
1730
- // src/history/record-revision.ts
1731
- function recordRevision(config, input) {
1732
- const db = openStore(config, "readwrite");
1733
- const id = appendRevision(db, {
1734
- contentType: input.contentType,
1735
- enSlug: input.enSlug,
1736
- locale: input.locale,
1737
- revisionKind: input.revisionKind,
1738
- enHash: input.enHash,
1739
- bodyHash: computeBodyHash(input.body),
1740
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1741
- model: input.model,
1742
- bodyPreview: input.body.slice(0, 200),
1743
- frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
1744
- body: input.body
1745
- });
1746
- db.close();
1747
- return id;
1748
- }
1749
1726
  function getAtPath2(obj, fieldPath) {
1750
1727
  let current = obj;
1751
1728
  for (const segment of fieldPath) {
@@ -1825,6 +1802,72 @@ function validateRelations(project) {
1825
1802
  }
1826
1803
  return issues;
1827
1804
  }
1805
+ var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
1806
+ var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
1807
+ var MARKDOWN_IMAGE_RE = new RegExp(
1808
+ String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
1809
+ "gi"
1810
+ );
1811
+ var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
1812
+ function isImageWebPath(value) {
1813
+ return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
1814
+ }
1815
+ function isSiteAssetPath(webPath) {
1816
+ const segment = webPath.split("/")[1];
1817
+ return Boolean(segment && !segment.includes("."));
1818
+ }
1819
+ function addImagePath(webPath, out) {
1820
+ if (isSiteAssetPath(webPath)) out.add(webPath);
1821
+ }
1822
+ function collectBodyImagePaths(body, out) {
1823
+ for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
1824
+ for (const match of body.matchAll(re)) {
1825
+ addImagePath(match[1], out);
1826
+ }
1827
+ }
1828
+ }
1829
+ function collectFrontmatterImagePaths(value, out) {
1830
+ if (typeof value === "string") {
1831
+ if (isImageWebPath(value)) addImagePath(value, out);
1832
+ return;
1833
+ }
1834
+ if (Array.isArray(value)) {
1835
+ for (const item of value) collectFrontmatterImagePaths(item, out);
1836
+ return;
1837
+ }
1838
+ if (value && typeof value === "object") {
1839
+ for (const nested of Object.values(value)) {
1840
+ collectFrontmatterImagePaths(nested, out);
1841
+ }
1842
+ }
1843
+ }
1844
+ function collectImagePaths(frontmatter, body) {
1845
+ const paths = /* @__PURE__ */ new Set();
1846
+ collectFrontmatterImagePaths(frontmatter, paths);
1847
+ collectBodyImagePaths(body, paths);
1848
+ return [...paths].sort();
1849
+ }
1850
+ function assetFilePath(assetsPath, webPath) {
1851
+ return path3.join(assetsPath, webPath.replace(/^\//, ""));
1852
+ }
1853
+ function validateDocumentAssets(config, input) {
1854
+ const assetsPath = config.assetsPath;
1855
+ if (!assetsPath) return [];
1856
+ const issues = [];
1857
+ for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
1858
+ const filePath = assetFilePath(assetsPath, webPath);
1859
+ if (fs2.existsSync(filePath)) continue;
1860
+ issues.push({
1861
+ level: "warning",
1862
+ contentType: input.contentType,
1863
+ enSlug: input.enSlug,
1864
+ locale: input.locale,
1865
+ field: "asset",
1866
+ message: `Missing image asset ${webPath} (expected ${filePath})`
1867
+ });
1868
+ }
1869
+ return issues;
1870
+ }
1828
1871
 
1829
1872
  // src/validate/validate-slug-suffix.ts
1830
1873
  function validateTranslationSlugSuffixes(config, db) {
@@ -1887,8 +1930,6 @@ function validateProject(config) {
1887
1930
  });
1888
1931
  continue;
1889
1932
  }
1890
- const payload = getTranslatablePayload(enDoc, type);
1891
- const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
1892
1933
  const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
1893
1934
  locale: config.defaultLocale,
1894
1935
  defaultLocale: config.defaultLocale,
@@ -1906,29 +1947,18 @@ function validateProject(config) {
1906
1947
  message: issue.message
1907
1948
  });
1908
1949
  }
1950
+ for (const issue of validateDocumentAssets(config, {
1951
+ contentType: type.id,
1952
+ enSlug,
1953
+ frontmatter: enDoc.frontmatter,
1954
+ body: enDoc.content
1955
+ })) {
1956
+ issues.push(issue);
1957
+ }
1909
1958
  for (const locale of config.locales) {
1910
1959
  if (locale === config.defaultLocale) continue;
1911
1960
  const row = getTranslation(db, type.id, enSlug, locale);
1912
1961
  if (!row) continue;
1913
- if (row.en_hash !== currentEnHash) {
1914
- issues.push({
1915
- level: "warning",
1916
- contentType: type.id,
1917
- enSlug,
1918
- locale,
1919
- field: "en_hash",
1920
- message: "Translation is stale (en_hash mismatch)"
1921
- });
1922
- recordRevision(config, {
1923
- contentType: type.id,
1924
- enSlug,
1925
- locale,
1926
- revisionKind: "en_edit_detected",
1927
- enHash: currentEnHash,
1928
- body: row.body,
1929
- frontmatter: JSON.parse(row.frontmatter_json)
1930
- });
1931
- }
1932
1962
  const localeFm = JSON.parse(row.frontmatter_json);
1933
1963
  for (const issue of validateLocaleBuiltinFields(localeFm)) {
1934
1964
  issues.push({
@@ -1940,6 +1970,15 @@ function validateProject(config) {
1940
1970
  message: issue.message
1941
1971
  });
1942
1972
  }
1973
+ for (const issue of validateDocumentAssets(config, {
1974
+ contentType: type.id,
1975
+ enSlug,
1976
+ locale,
1977
+ frontmatter: localeFm,
1978
+ body: row.body
1979
+ })) {
1980
+ issues.push(issue);
1981
+ }
1943
1982
  }
1944
1983
  }
1945
1984
  try {
@@ -2007,6 +2046,15 @@ function validateProject(config) {
2007
2046
  }
2008
2047
  return { ok: issues.every((i) => i.level !== "error"), issues };
2009
2048
  }
2049
+ function sha256(input) {
2050
+ return createHash("sha256").update(input, "utf8").digest("hex");
2051
+ }
2052
+ function computePageEnHash(translatableFrontmatter, body) {
2053
+ const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
2054
+ return sha256(payload);
2055
+ }
2056
+
2057
+ // src/translate/worklist.ts
2010
2058
  function listEnSlugs3(rootDir, contentDir) {
2011
2059
  const dir = path3.join(rootDir, contentDir);
2012
2060
  if (!fs2.existsSync(dir)) return [];
@@ -2065,6 +2113,21 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
2065
2113
  return config.locales.filter((l) => l !== config.defaultLocale);
2066
2114
  }
2067
2115
 
2116
+ // src/history/record-snapshot.ts
2117
+ function recordEnSnapshot(config, input, db) {
2118
+ const ownDb = db ?? openStore(config, "readwrite");
2119
+ const id = getOrCreateEnSnapshot(ownDb, {
2120
+ contentType: input.contentType,
2121
+ enSlug: input.enSlug,
2122
+ enHash: input.enHash,
2123
+ frontmatter: input.frontmatter,
2124
+ body: input.body,
2125
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2126
+ });
2127
+ if (!db) ownDb.close();
2128
+ return id;
2129
+ }
2130
+
2068
2131
  // src/translate/gemini-models.ts
2069
2132
  var GEMINI_MODEL_IDS = {
2070
2133
  "gemini-2.5-pro": "gemini-2.5-pro",
@@ -2438,6 +2501,17 @@ async function translatePage(config, item, options = {}) {
2438
2501
  throw new Error(`Translation validation failed: ${validated.error}`);
2439
2502
  }
2440
2503
  const writeDb = openStore(config, "readwrite");
2504
+ const snapshotId = recordEnSnapshot(
2505
+ config,
2506
+ {
2507
+ contentType: type.id,
2508
+ enSlug: item.enSlug,
2509
+ enHash: currentEnHash,
2510
+ frontmatter: payload.frontmatter,
2511
+ body: payload.body
2512
+ },
2513
+ writeDb
2514
+ );
2441
2515
  upsertTranslation(writeDb, {
2442
2516
  contentType: type.id,
2443
2517
  enSlug: item.enSlug,
@@ -2447,19 +2521,10 @@ async function translatePage(config, item, options = {}) {
2447
2521
  body: result.parsed.body,
2448
2522
  enHash: currentEnHash,
2449
2523
  translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2450
- model: result.model
2524
+ model: result.model,
2525
+ snapshotId
2451
2526
  });
2452
2527
  writeDb.close();
2453
- recordRevision(config, {
2454
- contentType: type.id,
2455
- enSlug: item.enSlug,
2456
- locale: item.locale,
2457
- revisionKind: "translation",
2458
- enHash: currentEnHash,
2459
- body: result.parsed.body,
2460
- frontmatter: validated.frontmatter,
2461
- model: result.model
2462
- });
2463
2528
  const estimatedCostUsd = estimateTranslationCostUsd(
2464
2529
  normalizeGeminiDisplayName(result.model),
2465
2530
  result.usage.inputTokens,