scribe-cms 0.0.4 → 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.
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
  }
@@ -1582,8 +1584,8 @@ function getRedirectSourceSlugs(project) {
1582
1584
  // src/sitemap/join-base-url.ts
1583
1585
  function joinBaseUrl(baseUrl, pathname) {
1584
1586
  const origin = baseUrl.replace(/\/$/, "");
1585
- const path10 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1586
- return `${origin}${path10}`;
1587
+ const path11 = pathname.startsWith("/") ? pathname : `/${pathname}`;
1588
+ return `${origin}${path11}`;
1587
1589
  }
1588
1590
 
1589
1591
  // src/sitemap/generate-sitemap.ts
@@ -1727,36 +1729,6 @@ function loadConfigSync(options = {}) {
1727
1729
  }
1728
1730
  return resolveConfig(config, path3__default.default.dirname(configPath));
1729
1731
  }
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
1732
  function getAtPath2(obj, fieldPath) {
1761
1733
  let current = obj;
1762
1734
  for (const segment of fieldPath) {
@@ -1836,6 +1808,72 @@ function validateRelations(project) {
1836
1808
  }
1837
1809
  return issues;
1838
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
+ }
1839
1877
 
1840
1878
  // src/validate/validate-slug-suffix.ts
1841
1879
  function validateTranslationSlugSuffixes(config, db) {
@@ -1898,8 +1936,6 @@ function validateProject(config) {
1898
1936
  });
1899
1937
  continue;
1900
1938
  }
1901
- const payload = getTranslatablePayload(enDoc, type);
1902
- const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
1903
1939
  const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
1904
1940
  locale: config.defaultLocale,
1905
1941
  defaultLocale: config.defaultLocale,
@@ -1917,29 +1953,18 @@ function validateProject(config) {
1917
1953
  message: issue.message
1918
1954
  });
1919
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
+ }
1920
1964
  for (const locale of config.locales) {
1921
1965
  if (locale === config.defaultLocale) continue;
1922
1966
  const row = getTranslation(db, type.id, enSlug, locale);
1923
1967
  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
1968
  const localeFm = JSON.parse(row.frontmatter_json);
1944
1969
  for (const issue of validateLocaleBuiltinFields(localeFm)) {
1945
1970
  issues.push({
@@ -1951,6 +1976,15 @@ function validateProject(config) {
1951
1976
  message: issue.message
1952
1977
  });
1953
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
+ }
1954
1988
  }
1955
1989
  }
1956
1990
  try {
@@ -2018,6 +2052,18 @@ function validateProject(config) {
2018
2052
  }
2019
2053
  return { ok: issues.every((i) => i.level !== "error"), issues };
2020
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
2021
2067
  function listEnSlugs3(rootDir, contentDir) {
2022
2068
  const dir = path3__default.default.join(rootDir, contentDir);
2023
2069
  if (!fs2__default.default.existsSync(dir)) return [];
@@ -2076,6 +2122,26 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
2076
2122
  return config.locales.filter((l) => l !== config.defaultLocale);
2077
2123
  }
2078
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
+
2079
2145
  // src/translate/gemini-models.ts
2080
2146
  var GEMINI_MODEL_IDS = {
2081
2147
  "gemini-2.5-pro": "gemini-2.5-pro",