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.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(
|
|
143
|
-
if (!
|
|
144
|
-
throw new Error(`Content type "${typeId}": path must start with / (got "${
|
|
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 = (
|
|
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 "${
|
|
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
|
|
274
|
-
const value = getAtPath(data,
|
|
275
|
+
for (const path11 of paths) {
|
|
276
|
+
const value = getAtPath(data, path11);
|
|
275
277
|
if (value !== void 0) {
|
|
276
|
-
setAtPath(out,
|
|
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,
|
|
296
|
+
function getAtPath(obj, path11) {
|
|
295
297
|
let current = obj;
|
|
296
|
-
for (const segment of
|
|
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,
|
|
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,
|
|
309
|
-
if (
|
|
310
|
-
if (
|
|
311
|
-
obj[
|
|
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] =
|
|
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
|
|
1586
|
-
return `${origin}${
|
|
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",
|