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/cli/index.cjs +174 -75
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +174 -76
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +135 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +135 -69
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +2 -0
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +2 -0
- package/dist/runtime.js.map +1 -1
- package/dist/src/config/resolve-config.d.ts.map +1 -1
- package/dist/src/core/types.d.ts +4 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/validate/validate-assets.d.ts +20 -0
- package/dist/src/validate/validate-assets.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts +1 -1
- package/dist/src/validate/validate-project.d.ts.map +1 -1
- package/dist/src/version.d.ts +4 -0
- package/dist/src/version.d.ts.map +1 -0
- package/package.json +1 -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(
|
|
132
|
-
if (!
|
|
133
|
-
throw new Error(`Content type "${typeId}": path must start with / (got "${
|
|
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 = (
|
|
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 "${
|
|
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
|
|
263
|
-
const value = getAtPath(data,
|
|
264
|
+
for (const path11 of paths) {
|
|
265
|
+
const value = getAtPath(data, path11);
|
|
264
266
|
if (value !== void 0) {
|
|
265
|
-
setAtPath(out,
|
|
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,
|
|
285
|
+
function getAtPath(obj, path11) {
|
|
284
286
|
let current = obj;
|
|
285
|
-
for (const segment of
|
|
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,
|
|
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,
|
|
298
|
-
if (
|
|
299
|
-
if (
|
|
300
|
-
obj[
|
|
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] =
|
|
305
|
+
const [head, ...rest] = path11;
|
|
304
306
|
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
305
307
|
obj[head] = {};
|
|
306
308
|
}
|
|
@@ -1571,8 +1573,8 @@ function getRedirectSourceSlugs(project) {
|
|
|
1571
1573
|
// src/sitemap/join-base-url.ts
|
|
1572
1574
|
function joinBaseUrl(baseUrl, pathname) {
|
|
1573
1575
|
const origin = baseUrl.replace(/\/$/, "");
|
|
1574
|
-
const
|
|
1575
|
-
return `${origin}${
|
|
1576
|
+
const path11 = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
1577
|
+
return `${origin}${path11}`;
|
|
1576
1578
|
}
|
|
1577
1579
|
|
|
1578
1580
|
// src/sitemap/generate-sitemap.ts
|
|
@@ -1716,36 +1718,6 @@ function loadConfigSync(options = {}) {
|
|
|
1716
1718
|
}
|
|
1717
1719
|
return resolveConfig(config, path3.dirname(configPath));
|
|
1718
1720
|
}
|
|
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
1721
|
function getAtPath2(obj, fieldPath) {
|
|
1750
1722
|
let current = obj;
|
|
1751
1723
|
for (const segment of fieldPath) {
|
|
@@ -1825,6 +1797,72 @@ function validateRelations(project) {
|
|
|
1825
1797
|
}
|
|
1826
1798
|
return issues;
|
|
1827
1799
|
}
|
|
1800
|
+
var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
|
|
1801
|
+
var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
|
|
1802
|
+
var MARKDOWN_IMAGE_RE = new RegExp(
|
|
1803
|
+
String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
|
|
1804
|
+
"gi"
|
|
1805
|
+
);
|
|
1806
|
+
var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
|
|
1807
|
+
function isImageWebPath(value) {
|
|
1808
|
+
return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
|
|
1809
|
+
}
|
|
1810
|
+
function isSiteAssetPath(webPath) {
|
|
1811
|
+
const segment = webPath.split("/")[1];
|
|
1812
|
+
return Boolean(segment && !segment.includes("."));
|
|
1813
|
+
}
|
|
1814
|
+
function addImagePath(webPath, out) {
|
|
1815
|
+
if (isSiteAssetPath(webPath)) out.add(webPath);
|
|
1816
|
+
}
|
|
1817
|
+
function collectBodyImagePaths(body, out) {
|
|
1818
|
+
for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
|
|
1819
|
+
for (const match of body.matchAll(re)) {
|
|
1820
|
+
addImagePath(match[1], out);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
function collectFrontmatterImagePaths(value, out) {
|
|
1825
|
+
if (typeof value === "string") {
|
|
1826
|
+
if (isImageWebPath(value)) addImagePath(value, out);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if (Array.isArray(value)) {
|
|
1830
|
+
for (const item of value) collectFrontmatterImagePaths(item, out);
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (value && typeof value === "object") {
|
|
1834
|
+
for (const nested of Object.values(value)) {
|
|
1835
|
+
collectFrontmatterImagePaths(nested, out);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function collectImagePaths(frontmatter, body) {
|
|
1840
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1841
|
+
collectFrontmatterImagePaths(frontmatter, paths);
|
|
1842
|
+
collectBodyImagePaths(body, paths);
|
|
1843
|
+
return [...paths].sort();
|
|
1844
|
+
}
|
|
1845
|
+
function assetFilePath(assetsPath, webPath) {
|
|
1846
|
+
return path3.join(assetsPath, webPath.replace(/^\//, ""));
|
|
1847
|
+
}
|
|
1848
|
+
function validateDocumentAssets(config, input) {
|
|
1849
|
+
const assetsPath = config.assetsPath;
|
|
1850
|
+
if (!assetsPath) return [];
|
|
1851
|
+
const issues = [];
|
|
1852
|
+
for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
|
|
1853
|
+
const filePath = assetFilePath(assetsPath, webPath);
|
|
1854
|
+
if (fs2.existsSync(filePath)) continue;
|
|
1855
|
+
issues.push({
|
|
1856
|
+
level: "warning",
|
|
1857
|
+
contentType: input.contentType,
|
|
1858
|
+
enSlug: input.enSlug,
|
|
1859
|
+
locale: input.locale,
|
|
1860
|
+
field: "asset",
|
|
1861
|
+
message: `Missing image asset ${webPath} (expected ${filePath})`
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
return issues;
|
|
1865
|
+
}
|
|
1828
1866
|
|
|
1829
1867
|
// src/validate/validate-slug-suffix.ts
|
|
1830
1868
|
function validateTranslationSlugSuffixes(config, db) {
|
|
@@ -1887,8 +1925,6 @@ function validateProject(config) {
|
|
|
1887
1925
|
});
|
|
1888
1926
|
continue;
|
|
1889
1927
|
}
|
|
1890
|
-
const payload = getTranslatablePayload(enDoc, type);
|
|
1891
|
-
const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
|
|
1892
1928
|
const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
|
|
1893
1929
|
locale: config.defaultLocale,
|
|
1894
1930
|
defaultLocale: config.defaultLocale,
|
|
@@ -1906,29 +1942,18 @@ function validateProject(config) {
|
|
|
1906
1942
|
message: issue.message
|
|
1907
1943
|
});
|
|
1908
1944
|
}
|
|
1945
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1946
|
+
contentType: type.id,
|
|
1947
|
+
enSlug,
|
|
1948
|
+
frontmatter: enDoc.frontmatter,
|
|
1949
|
+
body: enDoc.content
|
|
1950
|
+
})) {
|
|
1951
|
+
issues.push(issue);
|
|
1952
|
+
}
|
|
1909
1953
|
for (const locale of config.locales) {
|
|
1910
1954
|
if (locale === config.defaultLocale) continue;
|
|
1911
1955
|
const row = getTranslation(db, type.id, enSlug, locale);
|
|
1912
1956
|
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
1957
|
const localeFm = JSON.parse(row.frontmatter_json);
|
|
1933
1958
|
for (const issue of validateLocaleBuiltinFields(localeFm)) {
|
|
1934
1959
|
issues.push({
|
|
@@ -1940,6 +1965,15 @@ function validateProject(config) {
|
|
|
1940
1965
|
message: issue.message
|
|
1941
1966
|
});
|
|
1942
1967
|
}
|
|
1968
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1969
|
+
contentType: type.id,
|
|
1970
|
+
enSlug,
|
|
1971
|
+
locale,
|
|
1972
|
+
frontmatter: localeFm,
|
|
1973
|
+
body: row.body
|
|
1974
|
+
})) {
|
|
1975
|
+
issues.push(issue);
|
|
1976
|
+
}
|
|
1943
1977
|
}
|
|
1944
1978
|
}
|
|
1945
1979
|
try {
|
|
@@ -2007,6 +2041,18 @@ function validateProject(config) {
|
|
|
2007
2041
|
}
|
|
2008
2042
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
2009
2043
|
}
|
|
2044
|
+
function sha256(input) {
|
|
2045
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
2046
|
+
}
|
|
2047
|
+
function computePageEnHash(translatableFrontmatter, body) {
|
|
2048
|
+
const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
|
|
2049
|
+
return sha256(payload);
|
|
2050
|
+
}
|
|
2051
|
+
function computeBodyHash(body) {
|
|
2052
|
+
return sha256(body);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// src/translate/worklist.ts
|
|
2010
2056
|
function listEnSlugs3(rootDir, contentDir) {
|
|
2011
2057
|
const dir = path3.join(rootDir, contentDir);
|
|
2012
2058
|
if (!fs2.existsSync(dir)) return [];
|
|
@@ -2065,6 +2111,26 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
|
|
|
2065
2111
|
return config.locales.filter((l) => l !== config.defaultLocale);
|
|
2066
2112
|
}
|
|
2067
2113
|
|
|
2114
|
+
// src/history/record-revision.ts
|
|
2115
|
+
function recordRevision(config, input) {
|
|
2116
|
+
const db = openStore(config, "readwrite");
|
|
2117
|
+
const id = appendRevision(db, {
|
|
2118
|
+
contentType: input.contentType,
|
|
2119
|
+
enSlug: input.enSlug,
|
|
2120
|
+
locale: input.locale,
|
|
2121
|
+
revisionKind: input.revisionKind,
|
|
2122
|
+
enHash: input.enHash,
|
|
2123
|
+
bodyHash: computeBodyHash(input.body),
|
|
2124
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2125
|
+
model: input.model,
|
|
2126
|
+
bodyPreview: input.body.slice(0, 200),
|
|
2127
|
+
frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
|
|
2128
|
+
body: input.body
|
|
2129
|
+
});
|
|
2130
|
+
db.close();
|
|
2131
|
+
return id;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2068
2134
|
// src/translate/gemini-models.ts
|
|
2069
2135
|
var GEMINI_MODEL_IDS = {
|
|
2070
2136
|
"gemini-2.5-pro": "gemini-2.5-pro",
|