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.cjs CHANGED
@@ -139,14 +139,14 @@ var SLUG_PLACEHOLDER = "{slug}";
139
139
  function isRoutableType(type) {
140
140
  return typeof type.path === "string" && type.path.length > 0;
141
141
  }
142
- function assertValidPathTemplate(path10, typeId) {
143
- if (!path10.startsWith("/")) {
144
- throw new Error(`Content type "${typeId}": path must start with / (got "${path10}")`);
142
+ function assertValidPathTemplate(path11, typeId) {
143
+ if (!path11.startsWith("/")) {
144
+ throw new Error(`Content type "${typeId}": path must start with / (got "${path11}")`);
145
145
  }
146
- const count = (path10.match(/\{slug\}/g) ?? []).length;
146
+ const count = (path11.match(/\{slug\}/g) ?? []).length;
147
147
  if (count !== 1) {
148
148
  throw new Error(
149
- `Content type "${typeId}": path must contain exactly one {slug} (got "${path10}")`
149
+ `Content type "${typeId}": path must contain exactly one {slug} (got "${path11}")`
150
150
  );
151
151
  }
152
152
  }
@@ -198,6 +198,7 @@ function resolveConfig(input, baseDir) {
198
198
  const projectRoot = path3__default.default.resolve(baseDir ?? process.cwd(), raw.rootDir);
199
199
  const contentRoot = path3__default.default.resolve(projectRoot, raw.contentDir ?? "content");
200
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;
201
202
  const seenIds = /* @__PURE__ */ new Set();
202
203
  const types = raw.types.map((type) => {
203
204
  if (seenIds.has(type.id)) {
@@ -218,6 +219,7 @@ function resolveConfig(input, baseDir) {
218
219
  const config = {
219
220
  rootDir: contentRoot,
220
221
  storePath,
222
+ assetsPath,
221
223
  locales: [...raw.locales],
222
224
  defaultLocale,
223
225
  localePresets: raw.localePresets,
@@ -270,10 +272,10 @@ function introspectSchema(schema, prefix = []) {
270
272
  }
271
273
  function extractByPaths(data, paths) {
272
274
  const out = {};
273
- for (const path10 of paths) {
274
- const value = getAtPath(data, path10);
275
+ for (const path11 of paths) {
276
+ const value = getAtPath(data, path11);
275
277
  if (value !== void 0) {
276
- setAtPath(out, path10.filter((p) => p !== "*"), value);
278
+ setAtPath(out, path11.filter((p) => p !== "*"), value);
277
279
  }
278
280
  }
279
281
  return out;
@@ -291,13 +293,13 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
291
293
  const translatable = pickTranslatable(localeData, schema);
292
294
  return deepMerge(structural, translatable);
293
295
  }
294
- function getAtPath(obj, path10) {
296
+ function getAtPath(obj, path11) {
295
297
  let current = obj;
296
- for (const segment of path10) {
298
+ for (const segment of path11) {
297
299
  if (segment === "*") {
298
300
  if (!Array.isArray(current)) return void 0;
299
301
  return current.map(
300
- (item) => typeof item === "object" && item !== null ? getAtPath(item, path10.slice(path10.indexOf("*") + 1)) : void 0
302
+ (item) => typeof item === "object" && item !== null ? getAtPath(item, path11.slice(path11.indexOf("*") + 1)) : void 0
301
303
  );
302
304
  }
303
305
  if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
@@ -305,13 +307,13 @@ function getAtPath(obj, path10) {
305
307
  }
306
308
  return current;
307
309
  }
308
- function setAtPath(obj, path10, value) {
309
- if (path10.length === 0) return;
310
- if (path10.length === 1) {
311
- obj[path10[0]] = value;
310
+ function setAtPath(obj, path11, value) {
311
+ if (path11.length === 0) return;
312
+ if (path11.length === 1) {
313
+ obj[path11[0]] = value;
312
314
  return;
313
315
  }
314
- const [head, ...rest] = path10;
316
+ const [head, ...rest] = path11;
315
317
  if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
316
318
  obj[head] = {};
317
319
  }
@@ -507,7 +509,7 @@ function seoFieldsFromEn(enDoc) {
507
509
  redirectTo: enDoc.redirectTo
508
510
  };
509
511
  }
510
- var SCHEMA_VERSION = 3;
512
+ var SCHEMA_VERSION = 4;
511
513
  var MIGRATIONS = [
512
514
  `CREATE TABLE IF NOT EXISTS meta (
513
515
  key TEXT PRIMARY KEY,
@@ -527,20 +529,6 @@ var MIGRATIONS = [
527
529
  )`,
528
530
  `CREATE INDEX IF NOT EXISTS idx_translations_type_locale
529
531
  ON translations(content_type, locale)`,
530
- `CREATE TABLE IF NOT EXISTS revisions (
531
- id INTEGER PRIMARY KEY AUTOINCREMENT,
532
- content_type TEXT NOT NULL,
533
- en_slug TEXT NOT NULL,
534
- locale TEXT,
535
- revision_kind TEXT NOT NULL,
536
- en_hash TEXT NOT NULL,
537
- body_hash TEXT NOT NULL,
538
- created_at TEXT NOT NULL,
539
- model TEXT,
540
- body_preview TEXT
541
- )`,
542
- `CREATE INDEX IF NOT EXISTS idx_revisions_lookup
543
- ON revisions(content_type, en_slug, locale, created_at DESC)`,
544
532
  `CREATE TABLE IF NOT EXISTS slug_aliases (
545
533
  content_type TEXT NOT NULL,
546
534
  canonical_en_slug TEXT NOT NULL,
@@ -557,7 +545,19 @@ var MIGRATIONS = [
557
545
  locale_slug TEXT NOT NULL,
558
546
  captured_at TEXT NOT NULL,
559
547
  PRIMARY KEY (content_type, alias_en_slug, locale)
560
- )`
548
+ )`,
549
+ `CREATE TABLE IF NOT EXISTS en_snapshots (
550
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
551
+ content_type TEXT NOT NULL,
552
+ en_slug TEXT NOT NULL,
553
+ en_hash TEXT NOT NULL,
554
+ frontmatter_json TEXT NOT NULL,
555
+ body TEXT NOT NULL,
556
+ created_at TEXT NOT NULL,
557
+ UNIQUE (content_type, en_slug, en_hash)
558
+ )`,
559
+ `CREATE INDEX IF NOT EXISTS idx_en_snapshots_lookup
560
+ ON en_snapshots(content_type, en_slug, created_at DESC)`
561
561
  ];
562
562
  function resolveStorePath(config) {
563
563
  return config.storePath;
@@ -579,12 +579,19 @@ function addColumnIfMissing(db, table, column, ddlType) {
579
579
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddlType}`);
580
580
  }
581
581
  }
582
+ function readSchemaVersion(db) {
583
+ const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
584
+ return row ? Number.parseInt(row.value, 10) || 0 : 0;
585
+ }
582
586
  function migrate(db) {
587
+ const previousVersion = readSchemaVersion(db);
583
588
  for (const sql of MIGRATIONS) {
584
589
  db.exec(sql);
585
590
  }
586
- addColumnIfMissing(db, "revisions", "frontmatter_json", "TEXT");
587
- addColumnIfMissing(db, "revisions", "body", "TEXT");
591
+ if (previousVersion < 4) {
592
+ db.exec(`DROP TABLE IF EXISTS revisions`);
593
+ }
594
+ addColumnIfMissing(db, "translations", "snapshot_id", "INTEGER");
588
595
  db.prepare(
589
596
  `INSERT INTO meta(key, value) VALUES('schema_version', ?)
590
597
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
@@ -592,18 +599,38 @@ function migrate(db) {
592
599
  }
593
600
 
594
601
  // src/storage/translations.ts
602
+ function getOrCreateEnSnapshot(db, input) {
603
+ db.prepare(
604
+ `INSERT OR IGNORE INTO en_snapshots (
605
+ content_type, en_slug, en_hash, frontmatter_json, body, created_at
606
+ ) VALUES (?, ?, ?, ?, ?, ?)`
607
+ ).run(
608
+ input.contentType,
609
+ input.enSlug,
610
+ input.enHash,
611
+ JSON.stringify(input.frontmatter),
612
+ input.body,
613
+ input.createdAt
614
+ );
615
+ const row = db.prepare(
616
+ `SELECT id FROM en_snapshots
617
+ WHERE content_type = ? AND en_slug = ? AND en_hash = ?`
618
+ ).get(input.contentType, input.enSlug, input.enHash);
619
+ return row.id;
620
+ }
595
621
  function upsertTranslation(db, input) {
596
622
  db.prepare(
597
623
  `INSERT INTO translations (
598
- content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model
599
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
624
+ content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model, snapshot_id
625
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
600
626
  ON CONFLICT(content_type, en_slug, locale) DO UPDATE SET
601
627
  slug = excluded.slug,
602
628
  frontmatter_json = excluded.frontmatter_json,
603
629
  body = excluded.body,
604
630
  en_hash = excluded.en_hash,
605
631
  translated_at = excluded.translated_at,
606
- model = excluded.model`
632
+ model = excluded.model,
633
+ snapshot_id = excluded.snapshot_id`
607
634
  ).run(
608
635
  input.contentType,
609
636
  input.enSlug,
@@ -613,7 +640,8 @@ function upsertTranslation(db, input) {
613
640
  input.body,
614
641
  input.enHash,
615
642
  input.translatedAt,
616
- input.model
643
+ input.model,
644
+ input.snapshotId
617
645
  );
618
646
  }
619
647
  function getTranslation(db, contentType, enSlug, locale) {
@@ -632,27 +660,6 @@ function listTranslationsForEnSlug(db, contentType, enSlug) {
632
660
  function bulkLoadTranslations(db) {
633
661
  return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
634
662
  }
635
- function appendRevision(db, input) {
636
- const result = db.prepare(
637
- `INSERT INTO revisions (
638
- content_type, en_slug, locale, revision_kind, en_hash, body_hash, created_at,
639
- model, body_preview, frontmatter_json, body
640
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
641
- ).run(
642
- input.contentType,
643
- input.enSlug,
644
- input.locale,
645
- input.revisionKind,
646
- input.enHash,
647
- input.bodyHash,
648
- input.createdAt,
649
- input.model ?? null,
650
- input.bodyPreview ?? null,
651
- input.frontmatterJson ?? null,
652
- input.body ?? null
653
- );
654
- return Number(result.lastInsertRowid);
655
- }
656
663
 
657
664
  // src/loader/normalize-en.ts
658
665
  function normalizeEnFrontmatter(data) {
@@ -1582,8 +1589,8 @@ function getRedirectSourceSlugs(project) {
1582
1589
  // src/sitemap/join-base-url.ts
1583
1590
  function joinBaseUrl(baseUrl, pathname) {
1584
1591
  const origin = baseUrl.replace(/\/$/, "");
1585
- const path10 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1586
- return `${origin}${path10}`;
1592
+ const path11 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1593
+ return `${origin}${path11}`;
1587
1594
  }
1588
1595
 
1589
1596
  // src/sitemap/generate-sitemap.ts
@@ -1727,36 +1734,6 @@ function loadConfigSync(options = {}) {
1727
1734
  }
1728
1735
  return resolveConfig(config, path3__default.default.dirname(configPath));
1729
1736
  }
1730
- function sha256(input) {
1731
- return crypto.createHash("sha256").update(input, "utf8").digest("hex");
1732
- }
1733
- function computePageEnHash(translatableFrontmatter, body) {
1734
- const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
1735
- return sha256(payload);
1736
- }
1737
- function computeBodyHash(body) {
1738
- return sha256(body);
1739
- }
1740
-
1741
- // src/history/record-revision.ts
1742
- function recordRevision(config, input) {
1743
- const db = openStore(config, "readwrite");
1744
- const id = appendRevision(db, {
1745
- contentType: input.contentType,
1746
- enSlug: input.enSlug,
1747
- locale: input.locale,
1748
- revisionKind: input.revisionKind,
1749
- enHash: input.enHash,
1750
- bodyHash: computeBodyHash(input.body),
1751
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1752
- model: input.model,
1753
- bodyPreview: input.body.slice(0, 200),
1754
- frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
1755
- body: input.body
1756
- });
1757
- db.close();
1758
- return id;
1759
- }
1760
1737
  function getAtPath2(obj, fieldPath) {
1761
1738
  let current = obj;
1762
1739
  for (const segment of fieldPath) {
@@ -1836,6 +1813,72 @@ function validateRelations(project) {
1836
1813
  }
1837
1814
  return issues;
1838
1815
  }
1816
+ var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
1817
+ var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
1818
+ var MARKDOWN_IMAGE_RE = new RegExp(
1819
+ String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
1820
+ "gi"
1821
+ );
1822
+ var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
1823
+ function isImageWebPath(value) {
1824
+ return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
1825
+ }
1826
+ function isSiteAssetPath(webPath) {
1827
+ const segment = webPath.split("/")[1];
1828
+ return Boolean(segment && !segment.includes("."));
1829
+ }
1830
+ function addImagePath(webPath, out) {
1831
+ if (isSiteAssetPath(webPath)) out.add(webPath);
1832
+ }
1833
+ function collectBodyImagePaths(body, out) {
1834
+ for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
1835
+ for (const match of body.matchAll(re)) {
1836
+ addImagePath(match[1], out);
1837
+ }
1838
+ }
1839
+ }
1840
+ function collectFrontmatterImagePaths(value, out) {
1841
+ if (typeof value === "string") {
1842
+ if (isImageWebPath(value)) addImagePath(value, out);
1843
+ return;
1844
+ }
1845
+ if (Array.isArray(value)) {
1846
+ for (const item of value) collectFrontmatterImagePaths(item, out);
1847
+ return;
1848
+ }
1849
+ if (value && typeof value === "object") {
1850
+ for (const nested of Object.values(value)) {
1851
+ collectFrontmatterImagePaths(nested, out);
1852
+ }
1853
+ }
1854
+ }
1855
+ function collectImagePaths(frontmatter, body) {
1856
+ const paths = /* @__PURE__ */ new Set();
1857
+ collectFrontmatterImagePaths(frontmatter, paths);
1858
+ collectBodyImagePaths(body, paths);
1859
+ return [...paths].sort();
1860
+ }
1861
+ function assetFilePath(assetsPath, webPath) {
1862
+ return path3__default.default.join(assetsPath, webPath.replace(/^\//, ""));
1863
+ }
1864
+ function validateDocumentAssets(config, input) {
1865
+ const assetsPath = config.assetsPath;
1866
+ if (!assetsPath) return [];
1867
+ const issues = [];
1868
+ for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
1869
+ const filePath = assetFilePath(assetsPath, webPath);
1870
+ if (fs2__default.default.existsSync(filePath)) continue;
1871
+ issues.push({
1872
+ level: "warning",
1873
+ contentType: input.contentType,
1874
+ enSlug: input.enSlug,
1875
+ locale: input.locale,
1876
+ field: "asset",
1877
+ message: `Missing image asset ${webPath} (expected ${filePath})`
1878
+ });
1879
+ }
1880
+ return issues;
1881
+ }
1839
1882
 
1840
1883
  // src/validate/validate-slug-suffix.ts
1841
1884
  function validateTranslationSlugSuffixes(config, db) {
@@ -1898,8 +1941,6 @@ function validateProject(config) {
1898
1941
  });
1899
1942
  continue;
1900
1943
  }
1901
- const payload = getTranslatablePayload(enDoc, type);
1902
- const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
1903
1944
  const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
1904
1945
  locale: config.defaultLocale,
1905
1946
  defaultLocale: config.defaultLocale,
@@ -1917,29 +1958,18 @@ function validateProject(config) {
1917
1958
  message: issue.message
1918
1959
  });
1919
1960
  }
1961
+ for (const issue of validateDocumentAssets(config, {
1962
+ contentType: type.id,
1963
+ enSlug,
1964
+ frontmatter: enDoc.frontmatter,
1965
+ body: enDoc.content
1966
+ })) {
1967
+ issues.push(issue);
1968
+ }
1920
1969
  for (const locale of config.locales) {
1921
1970
  if (locale === config.defaultLocale) continue;
1922
1971
  const row = getTranslation(db, type.id, enSlug, locale);
1923
1972
  if (!row) continue;
1924
- if (row.en_hash !== currentEnHash) {
1925
- issues.push({
1926
- level: "warning",
1927
- contentType: type.id,
1928
- enSlug,
1929
- locale,
1930
- field: "en_hash",
1931
- message: "Translation is stale (en_hash mismatch)"
1932
- });
1933
- recordRevision(config, {
1934
- contentType: type.id,
1935
- enSlug,
1936
- locale,
1937
- revisionKind: "en_edit_detected",
1938
- enHash: currentEnHash,
1939
- body: row.body,
1940
- frontmatter: JSON.parse(row.frontmatter_json)
1941
- });
1942
- }
1943
1973
  const localeFm = JSON.parse(row.frontmatter_json);
1944
1974
  for (const issue of validateLocaleBuiltinFields(localeFm)) {
1945
1975
  issues.push({
@@ -1951,6 +1981,15 @@ function validateProject(config) {
1951
1981
  message: issue.message
1952
1982
  });
1953
1983
  }
1984
+ for (const issue of validateDocumentAssets(config, {
1985
+ contentType: type.id,
1986
+ enSlug,
1987
+ locale,
1988
+ frontmatter: localeFm,
1989
+ body: row.body
1990
+ })) {
1991
+ issues.push(issue);
1992
+ }
1954
1993
  }
1955
1994
  }
1956
1995
  try {
@@ -2018,6 +2057,15 @@ function validateProject(config) {
2018
2057
  }
2019
2058
  return { ok: issues.every((i) => i.level !== "error"), issues };
2020
2059
  }
2060
+ function sha256(input) {
2061
+ return crypto.createHash("sha256").update(input, "utf8").digest("hex");
2062
+ }
2063
+ function computePageEnHash(translatableFrontmatter, body) {
2064
+ const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
2065
+ return sha256(payload);
2066
+ }
2067
+
2068
+ // src/translate/worklist.ts
2021
2069
  function listEnSlugs3(rootDir, contentDir) {
2022
2070
  const dir = path3__default.default.join(rootDir, contentDir);
2023
2071
  if (!fs2__default.default.existsSync(dir)) return [];
@@ -2076,6 +2124,21 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
2076
2124
  return config.locales.filter((l) => l !== config.defaultLocale);
2077
2125
  }
2078
2126
 
2127
+ // src/history/record-snapshot.ts
2128
+ function recordEnSnapshot(config, input, db) {
2129
+ const ownDb = db ?? openStore(config, "readwrite");
2130
+ const id = getOrCreateEnSnapshot(ownDb, {
2131
+ contentType: input.contentType,
2132
+ enSlug: input.enSlug,
2133
+ enHash: input.enHash,
2134
+ frontmatter: input.frontmatter,
2135
+ body: input.body,
2136
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2137
+ });
2138
+ if (!db) ownDb.close();
2139
+ return id;
2140
+ }
2141
+
2079
2142
  // src/translate/gemini-models.ts
2080
2143
  var GEMINI_MODEL_IDS = {
2081
2144
  "gemini-2.5-pro": "gemini-2.5-pro",
@@ -2449,6 +2512,17 @@ async function translatePage(config, item, options = {}) {
2449
2512
  throw new Error(`Translation validation failed: ${validated.error}`);
2450
2513
  }
2451
2514
  const writeDb = openStore(config, "readwrite");
2515
+ const snapshotId = recordEnSnapshot(
2516
+ config,
2517
+ {
2518
+ contentType: type.id,
2519
+ enSlug: item.enSlug,
2520
+ enHash: currentEnHash,
2521
+ frontmatter: payload.frontmatter,
2522
+ body: payload.body
2523
+ },
2524
+ writeDb
2525
+ );
2452
2526
  upsertTranslation(writeDb, {
2453
2527
  contentType: type.id,
2454
2528
  enSlug: item.enSlug,
@@ -2458,19 +2532,10 @@ async function translatePage(config, item, options = {}) {
2458
2532
  body: result.parsed.body,
2459
2533
  enHash: currentEnHash,
2460
2534
  translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2461
- model: result.model
2535
+ model: result.model,
2536
+ snapshotId
2462
2537
  });
2463
2538
  writeDb.close();
2464
- recordRevision(config, {
2465
- contentType: type.id,
2466
- enSlug: item.enSlug,
2467
- locale: item.locale,
2468
- revisionKind: "translation",
2469
- enHash: currentEnHash,
2470
- body: result.parsed.body,
2471
- frontmatter: validated.frontmatter,
2472
- model: result.model
2473
- });
2474
2539
  const estimatedCostUsd = estimateTranslationCostUsd(
2475
2540
  normalizeGeminiDisplayName(result.model),
2476
2541
  result.usage.inputTokens,