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/cli/index.cjs
CHANGED
|
@@ -13,6 +13,7 @@ var dotenv = require('dotenv');
|
|
|
13
13
|
var nodeServer = require('@hono/node-server');
|
|
14
14
|
var hono = require('hono');
|
|
15
15
|
var prompts = require('@inquirer/prompts');
|
|
16
|
+
var url = require('url');
|
|
16
17
|
|
|
17
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
19
|
|
|
@@ -232,14 +233,14 @@ var SLUG_PLACEHOLDER = "{slug}";
|
|
|
232
233
|
function isRoutableType(type) {
|
|
233
234
|
return typeof type.path === "string" && type.path.length > 0;
|
|
234
235
|
}
|
|
235
|
-
function assertValidPathTemplate(
|
|
236
|
-
if (!
|
|
237
|
-
throw new Error(`Content type "${typeId}": path must start with / (got "${
|
|
236
|
+
function assertValidPathTemplate(path13, typeId) {
|
|
237
|
+
if (!path13.startsWith("/")) {
|
|
238
|
+
throw new Error(`Content type "${typeId}": path must start with / (got "${path13}")`);
|
|
238
239
|
}
|
|
239
|
-
const count = (
|
|
240
|
+
const count = (path13.match(/\{slug\}/g) ?? []).length;
|
|
240
241
|
if (count !== 1) {
|
|
241
242
|
throw new Error(
|
|
242
|
-
`Content type "${typeId}": path must contain exactly one {slug} (got "${
|
|
243
|
+
`Content type "${typeId}": path must contain exactly one {slug} (got "${path13}")`
|
|
243
244
|
);
|
|
244
245
|
}
|
|
245
246
|
}
|
|
@@ -291,6 +292,7 @@ function resolveConfig(input, baseDir) {
|
|
|
291
292
|
const projectRoot = path3__default.default.resolve(baseDir ?? process.cwd(), raw.rootDir);
|
|
292
293
|
const contentRoot = path3__default.default.resolve(projectRoot, raw.contentDir ?? "content");
|
|
293
294
|
const storePath = path3__default.default.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
295
|
+
const assetsPath = raw.assetsDir ? path3__default.default.resolve(projectRoot, raw.assetsDir) : void 0;
|
|
294
296
|
const seenIds = /* @__PURE__ */ new Set();
|
|
295
297
|
const types = raw.types.map((type) => {
|
|
296
298
|
if (seenIds.has(type.id)) {
|
|
@@ -311,6 +313,7 @@ function resolveConfig(input, baseDir) {
|
|
|
311
313
|
const config = {
|
|
312
314
|
rootDir: contentRoot,
|
|
313
315
|
storePath,
|
|
316
|
+
assetsPath,
|
|
314
317
|
locales: [...raw.locales],
|
|
315
318
|
defaultLocale,
|
|
316
319
|
localePresets: raw.localePresets,
|
|
@@ -367,10 +370,10 @@ function introspectSchema(schema, prefix = []) {
|
|
|
367
370
|
}
|
|
368
371
|
function extractByPaths(data, paths) {
|
|
369
372
|
const out = {};
|
|
370
|
-
for (const
|
|
371
|
-
const value = getAtPath(data,
|
|
373
|
+
for (const path13 of paths) {
|
|
374
|
+
const value = getAtPath(data, path13);
|
|
372
375
|
if (value !== void 0) {
|
|
373
|
-
setAtPath(out,
|
|
376
|
+
setAtPath(out, path13.filter((p) => p !== "*"), value);
|
|
374
377
|
}
|
|
375
378
|
}
|
|
376
379
|
return out;
|
|
@@ -388,13 +391,13 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
|
388
391
|
const translatable = pickTranslatable(localeData, schema);
|
|
389
392
|
return deepMerge(structural, translatable);
|
|
390
393
|
}
|
|
391
|
-
function getAtPath(obj,
|
|
394
|
+
function getAtPath(obj, path13) {
|
|
392
395
|
let current = obj;
|
|
393
|
-
for (const segment of
|
|
396
|
+
for (const segment of path13) {
|
|
394
397
|
if (segment === "*") {
|
|
395
398
|
if (!Array.isArray(current)) return void 0;
|
|
396
399
|
return current.map(
|
|
397
|
-
(item) => typeof item === "object" && item !== null ? getAtPath(item,
|
|
400
|
+
(item) => typeof item === "object" && item !== null ? getAtPath(item, path13.slice(path13.indexOf("*") + 1)) : void 0
|
|
398
401
|
);
|
|
399
402
|
}
|
|
400
403
|
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
@@ -402,13 +405,13 @@ function getAtPath(obj, path11) {
|
|
|
402
405
|
}
|
|
403
406
|
return current;
|
|
404
407
|
}
|
|
405
|
-
function setAtPath(obj,
|
|
406
|
-
if (
|
|
407
|
-
if (
|
|
408
|
-
obj[
|
|
408
|
+
function setAtPath(obj, path13, value) {
|
|
409
|
+
if (path13.length === 0) return;
|
|
410
|
+
if (path13.length === 1) {
|
|
411
|
+
obj[path13[0]] = value;
|
|
409
412
|
return;
|
|
410
413
|
}
|
|
411
|
-
const [head, ...rest] =
|
|
414
|
+
const [head, ...rest] = path13;
|
|
412
415
|
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
413
416
|
obj[head] = {};
|
|
414
417
|
}
|
|
@@ -1480,43 +1483,6 @@ function loadConfigSync(options = {}) {
|
|
|
1480
1483
|
|
|
1481
1484
|
// src/validate/validate-project.ts
|
|
1482
1485
|
init_cjs_shims();
|
|
1483
|
-
|
|
1484
|
-
// src/hash/page-hash.ts
|
|
1485
|
-
init_cjs_shims();
|
|
1486
|
-
function sha256(input) {
|
|
1487
|
-
return crypto.createHash("sha256").update(input, "utf8").digest("hex");
|
|
1488
|
-
}
|
|
1489
|
-
function computePageEnHash(translatableFrontmatter, body) {
|
|
1490
|
-
const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
|
|
1491
|
-
return sha256(payload);
|
|
1492
|
-
}
|
|
1493
|
-
function computeBodyHash(body) {
|
|
1494
|
-
return sha256(body);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// src/history/record-revision.ts
|
|
1498
|
-
init_cjs_shims();
|
|
1499
|
-
init_sqlite();
|
|
1500
|
-
function recordRevision(config, input) {
|
|
1501
|
-
const db = openStore(config, "readwrite");
|
|
1502
|
-
const id = appendRevision(db, {
|
|
1503
|
-
contentType: input.contentType,
|
|
1504
|
-
enSlug: input.enSlug,
|
|
1505
|
-
locale: input.locale,
|
|
1506
|
-
revisionKind: input.revisionKind,
|
|
1507
|
-
enHash: input.enHash,
|
|
1508
|
-
bodyHash: computeBodyHash(input.body),
|
|
1509
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1510
|
-
model: input.model,
|
|
1511
|
-
bodyPreview: input.body.slice(0, 200),
|
|
1512
|
-
frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
|
|
1513
|
-
body: input.body
|
|
1514
|
-
});
|
|
1515
|
-
db.close();
|
|
1516
|
-
return id;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// src/validate/validate-project.ts
|
|
1520
1486
|
init_sqlite();
|
|
1521
1487
|
|
|
1522
1488
|
// src/validate/validate-relations.ts
|
|
@@ -1601,6 +1567,75 @@ function validateRelations(project) {
|
|
|
1601
1567
|
return issues;
|
|
1602
1568
|
}
|
|
1603
1569
|
|
|
1570
|
+
// src/validate/validate-assets.ts
|
|
1571
|
+
init_cjs_shims();
|
|
1572
|
+
var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
|
|
1573
|
+
var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
|
|
1574
|
+
var MARKDOWN_IMAGE_RE = new RegExp(
|
|
1575
|
+
String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
|
|
1576
|
+
"gi"
|
|
1577
|
+
);
|
|
1578
|
+
var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
|
|
1579
|
+
function isImageWebPath(value) {
|
|
1580
|
+
return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
|
|
1581
|
+
}
|
|
1582
|
+
function isSiteAssetPath(webPath) {
|
|
1583
|
+
const segment = webPath.split("/")[1];
|
|
1584
|
+
return Boolean(segment && !segment.includes("."));
|
|
1585
|
+
}
|
|
1586
|
+
function addImagePath(webPath, out) {
|
|
1587
|
+
if (isSiteAssetPath(webPath)) out.add(webPath);
|
|
1588
|
+
}
|
|
1589
|
+
function collectBodyImagePaths(body, out) {
|
|
1590
|
+
for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
|
|
1591
|
+
for (const match of body.matchAll(re)) {
|
|
1592
|
+
addImagePath(match[1], out);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function collectFrontmatterImagePaths(value, out) {
|
|
1597
|
+
if (typeof value === "string") {
|
|
1598
|
+
if (isImageWebPath(value)) addImagePath(value, out);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (Array.isArray(value)) {
|
|
1602
|
+
for (const item of value) collectFrontmatterImagePaths(item, out);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (value && typeof value === "object") {
|
|
1606
|
+
for (const nested of Object.values(value)) {
|
|
1607
|
+
collectFrontmatterImagePaths(nested, out);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function collectImagePaths(frontmatter, body) {
|
|
1612
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1613
|
+
collectFrontmatterImagePaths(frontmatter, paths);
|
|
1614
|
+
collectBodyImagePaths(body, paths);
|
|
1615
|
+
return [...paths].sort();
|
|
1616
|
+
}
|
|
1617
|
+
function assetFilePath(assetsPath, webPath) {
|
|
1618
|
+
return path3__default.default.join(assetsPath, webPath.replace(/^\//, ""));
|
|
1619
|
+
}
|
|
1620
|
+
function validateDocumentAssets(config, input) {
|
|
1621
|
+
const assetsPath = config.assetsPath;
|
|
1622
|
+
if (!assetsPath) return [];
|
|
1623
|
+
const issues = [];
|
|
1624
|
+
for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
|
|
1625
|
+
const filePath = assetFilePath(assetsPath, webPath);
|
|
1626
|
+
if (fs2__default.default.existsSync(filePath)) continue;
|
|
1627
|
+
issues.push({
|
|
1628
|
+
level: "warning",
|
|
1629
|
+
contentType: input.contentType,
|
|
1630
|
+
enSlug: input.enSlug,
|
|
1631
|
+
locale: input.locale,
|
|
1632
|
+
field: "asset",
|
|
1633
|
+
message: `Missing image asset ${webPath} (expected ${filePath})`
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
return issues;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1604
1639
|
// src/validate/validate-slug-suffix.ts
|
|
1605
1640
|
init_cjs_shims();
|
|
1606
1641
|
function validateTranslationSlugSuffixes(config, db) {
|
|
@@ -1663,8 +1698,6 @@ function validateProject(config) {
|
|
|
1663
1698
|
});
|
|
1664
1699
|
continue;
|
|
1665
1700
|
}
|
|
1666
|
-
const payload = getTranslatablePayload(enDoc, type);
|
|
1667
|
-
const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
|
|
1668
1701
|
const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
|
|
1669
1702
|
locale: config.defaultLocale,
|
|
1670
1703
|
defaultLocale: config.defaultLocale,
|
|
@@ -1682,29 +1715,18 @@ function validateProject(config) {
|
|
|
1682
1715
|
message: issue.message
|
|
1683
1716
|
});
|
|
1684
1717
|
}
|
|
1718
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1719
|
+
contentType: type.id,
|
|
1720
|
+
enSlug,
|
|
1721
|
+
frontmatter: enDoc.frontmatter,
|
|
1722
|
+
body: enDoc.content
|
|
1723
|
+
})) {
|
|
1724
|
+
issues.push(issue);
|
|
1725
|
+
}
|
|
1685
1726
|
for (const locale of config.locales) {
|
|
1686
1727
|
if (locale === config.defaultLocale) continue;
|
|
1687
1728
|
const row = getTranslation(db, type.id, enSlug, locale);
|
|
1688
1729
|
if (!row) continue;
|
|
1689
|
-
if (row.en_hash !== currentEnHash) {
|
|
1690
|
-
issues.push({
|
|
1691
|
-
level: "warning",
|
|
1692
|
-
contentType: type.id,
|
|
1693
|
-
enSlug,
|
|
1694
|
-
locale,
|
|
1695
|
-
field: "en_hash",
|
|
1696
|
-
message: "Translation is stale (en_hash mismatch)"
|
|
1697
|
-
});
|
|
1698
|
-
recordRevision(config, {
|
|
1699
|
-
contentType: type.id,
|
|
1700
|
-
enSlug,
|
|
1701
|
-
locale,
|
|
1702
|
-
revisionKind: "en_edit_detected",
|
|
1703
|
-
enHash: currentEnHash,
|
|
1704
|
-
body: row.body,
|
|
1705
|
-
frontmatter: JSON.parse(row.frontmatter_json)
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
1730
|
const localeFm = JSON.parse(row.frontmatter_json);
|
|
1709
1731
|
for (const issue of validateLocaleBuiltinFields(localeFm)) {
|
|
1710
1732
|
issues.push({
|
|
@@ -1716,6 +1738,15 @@ function validateProject(config) {
|
|
|
1716
1738
|
message: issue.message
|
|
1717
1739
|
});
|
|
1718
1740
|
}
|
|
1741
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1742
|
+
contentType: type.id,
|
|
1743
|
+
enSlug,
|
|
1744
|
+
locale,
|
|
1745
|
+
frontmatter: localeFm,
|
|
1746
|
+
body: row.body
|
|
1747
|
+
})) {
|
|
1748
|
+
issues.push(issue);
|
|
1749
|
+
}
|
|
1719
1750
|
}
|
|
1720
1751
|
}
|
|
1721
1752
|
try {
|
|
@@ -1786,6 +1817,21 @@ function validateProject(config) {
|
|
|
1786
1817
|
|
|
1787
1818
|
// src/translate/worklist.ts
|
|
1788
1819
|
init_cjs_shims();
|
|
1820
|
+
|
|
1821
|
+
// src/hash/page-hash.ts
|
|
1822
|
+
init_cjs_shims();
|
|
1823
|
+
function sha256(input) {
|
|
1824
|
+
return crypto.createHash("sha256").update(input, "utf8").digest("hex");
|
|
1825
|
+
}
|
|
1826
|
+
function computePageEnHash(translatableFrontmatter, body) {
|
|
1827
|
+
const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
|
|
1828
|
+
return sha256(payload);
|
|
1829
|
+
}
|
|
1830
|
+
function computeBodyHash(body) {
|
|
1831
|
+
return sha256(body);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/translate/worklist.ts
|
|
1789
1835
|
init_sqlite();
|
|
1790
1836
|
function listEnSlugs3(rootDir, contentDir) {
|
|
1791
1837
|
const dir = path3__default.default.join(rootDir, contentDir);
|
|
@@ -1847,6 +1893,30 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
|
|
|
1847
1893
|
|
|
1848
1894
|
// src/translate/page-translator.ts
|
|
1849
1895
|
init_cjs_shims();
|
|
1896
|
+
|
|
1897
|
+
// src/history/record-revision.ts
|
|
1898
|
+
init_cjs_shims();
|
|
1899
|
+
init_sqlite();
|
|
1900
|
+
function recordRevision(config, input) {
|
|
1901
|
+
const db = openStore(config, "readwrite");
|
|
1902
|
+
const id = appendRevision(db, {
|
|
1903
|
+
contentType: input.contentType,
|
|
1904
|
+
enSlug: input.enSlug,
|
|
1905
|
+
locale: input.locale,
|
|
1906
|
+
revisionKind: input.revisionKind,
|
|
1907
|
+
enHash: input.enHash,
|
|
1908
|
+
bodyHash: computeBodyHash(input.body),
|
|
1909
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1910
|
+
model: input.model,
|
|
1911
|
+
bodyPreview: input.body.slice(0, 200),
|
|
1912
|
+
frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
|
|
1913
|
+
body: input.body
|
|
1914
|
+
});
|
|
1915
|
+
db.close();
|
|
1916
|
+
return id;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// src/translate/page-translator.ts
|
|
1850
1920
|
init_sqlite();
|
|
1851
1921
|
|
|
1852
1922
|
// src/translate/gemini-client.ts
|
|
@@ -3178,6 +3248,24 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
3178
3248
|
};
|
|
3179
3249
|
}
|
|
3180
3250
|
|
|
3251
|
+
// src/version.ts
|
|
3252
|
+
init_cjs_shims();
|
|
3253
|
+
function readScribeVersion() {
|
|
3254
|
+
let dir = path3__default.default.dirname(url.fileURLToPath(importMetaUrl));
|
|
3255
|
+
for (; ; ) {
|
|
3256
|
+
const candidate = path3__default.default.join(dir, "package.json");
|
|
3257
|
+
if (fs2__default.default.existsSync(candidate)) {
|
|
3258
|
+
const pkg = JSON.parse(fs2__default.default.readFileSync(candidate, "utf8"));
|
|
3259
|
+
if (pkg.name === "scribe-cms" && pkg.version) return pkg.version;
|
|
3260
|
+
}
|
|
3261
|
+
const parent = path3__default.default.dirname(dir);
|
|
3262
|
+
if (parent === dir) break;
|
|
3263
|
+
dir = parent;
|
|
3264
|
+
}
|
|
3265
|
+
return "unknown";
|
|
3266
|
+
}
|
|
3267
|
+
var SCRIBE_VERSION = readScribeVersion();
|
|
3268
|
+
|
|
3181
3269
|
// cli/index.ts
|
|
3182
3270
|
function parseArgs(argv) {
|
|
3183
3271
|
const options = { cwd: process.cwd() };
|
|
@@ -3280,7 +3368,13 @@ function loadProject(options) {
|
|
|
3280
3368
|
return createProject(config);
|
|
3281
3369
|
}
|
|
3282
3370
|
async function main() {
|
|
3283
|
-
const
|
|
3371
|
+
const argv = process.argv.slice(2);
|
|
3372
|
+
const first = argv[0];
|
|
3373
|
+
if (first === "--version" || first === "-V" || first === "version") {
|
|
3374
|
+
console.log(SCRIBE_VERSION);
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
const { command, options, rest } = parseArgs(argv);
|
|
3284
3378
|
loadEnvFromCwd(options.cwd);
|
|
3285
3379
|
if (command === "help" || command === "--help") {
|
|
3286
3380
|
console.log(`Usage: scribe <command>
|
|
@@ -3292,6 +3386,7 @@ Commands:
|
|
|
3292
3386
|
translate Translate stale/missing locale pages
|
|
3293
3387
|
history <type> <slug> Show revision timeline
|
|
3294
3388
|
studio Start read-only local studio
|
|
3389
|
+
version Print scribe-cms version
|
|
3295
3390
|
|
|
3296
3391
|
Export-static flags:
|
|
3297
3392
|
--out <dir> Output directory (default: public)
|
|
@@ -3313,6 +3408,10 @@ Translate flags:
|
|
|
3313
3408
|
`);
|
|
3314
3409
|
return;
|
|
3315
3410
|
}
|
|
3411
|
+
if (command === "version") {
|
|
3412
|
+
console.log(SCRIBE_VERSION);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3316
3415
|
const project = loadProject(options);
|
|
3317
3416
|
const config = project.config;
|
|
3318
3417
|
switch (command) {
|