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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path4 from 'path';
|
|
3
|
-
import 'url';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
4
|
import fs2 from 'fs';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -218,14 +218,14 @@ var SLUG_PLACEHOLDER = "{slug}";
|
|
|
218
218
|
function isRoutableType(type) {
|
|
219
219
|
return typeof type.path === "string" && type.path.length > 0;
|
|
220
220
|
}
|
|
221
|
-
function assertValidPathTemplate(
|
|
222
|
-
if (!
|
|
223
|
-
throw new Error(`Content type "${typeId}": path must start with / (got "${
|
|
221
|
+
function assertValidPathTemplate(path14, typeId) {
|
|
222
|
+
if (!path14.startsWith("/")) {
|
|
223
|
+
throw new Error(`Content type "${typeId}": path must start with / (got "${path14}")`);
|
|
224
224
|
}
|
|
225
|
-
const count = (
|
|
225
|
+
const count = (path14.match(/\{slug\}/g) ?? []).length;
|
|
226
226
|
if (count !== 1) {
|
|
227
227
|
throw new Error(
|
|
228
|
-
`Content type "${typeId}": path must contain exactly one {slug} (got "${
|
|
228
|
+
`Content type "${typeId}": path must contain exactly one {slug} (got "${path14}")`
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
@@ -277,6 +277,7 @@ function resolveConfig(input, baseDir) {
|
|
|
277
277
|
const projectRoot = path4.resolve(baseDir ?? process.cwd(), raw.rootDir);
|
|
278
278
|
const contentRoot = path4.resolve(projectRoot, raw.contentDir ?? "content");
|
|
279
279
|
const storePath = path4.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
280
|
+
const assetsPath = raw.assetsDir ? path4.resolve(projectRoot, raw.assetsDir) : void 0;
|
|
280
281
|
const seenIds = /* @__PURE__ */ new Set();
|
|
281
282
|
const types = raw.types.map((type) => {
|
|
282
283
|
if (seenIds.has(type.id)) {
|
|
@@ -297,6 +298,7 @@ function resolveConfig(input, baseDir) {
|
|
|
297
298
|
const config = {
|
|
298
299
|
rootDir: contentRoot,
|
|
299
300
|
storePath,
|
|
301
|
+
assetsPath,
|
|
300
302
|
locales: [...raw.locales],
|
|
301
303
|
defaultLocale,
|
|
302
304
|
localePresets: raw.localePresets,
|
|
@@ -353,10 +355,10 @@ function introspectSchema(schema, prefix = []) {
|
|
|
353
355
|
}
|
|
354
356
|
function extractByPaths(data, paths) {
|
|
355
357
|
const out = {};
|
|
356
|
-
for (const
|
|
357
|
-
const value = getAtPath(data,
|
|
358
|
+
for (const path14 of paths) {
|
|
359
|
+
const value = getAtPath(data, path14);
|
|
358
360
|
if (value !== void 0) {
|
|
359
|
-
setAtPath(out,
|
|
361
|
+
setAtPath(out, path14.filter((p) => p !== "*"), value);
|
|
360
362
|
}
|
|
361
363
|
}
|
|
362
364
|
return out;
|
|
@@ -374,13 +376,13 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
|
374
376
|
const translatable = pickTranslatable(localeData, schema);
|
|
375
377
|
return deepMerge(structural, translatable);
|
|
376
378
|
}
|
|
377
|
-
function getAtPath(obj,
|
|
379
|
+
function getAtPath(obj, path14) {
|
|
378
380
|
let current = obj;
|
|
379
|
-
for (const segment of
|
|
381
|
+
for (const segment of path14) {
|
|
380
382
|
if (segment === "*") {
|
|
381
383
|
if (!Array.isArray(current)) return void 0;
|
|
382
384
|
return current.map(
|
|
383
|
-
(item) => typeof item === "object" && item !== null ? getAtPath(item,
|
|
385
|
+
(item) => typeof item === "object" && item !== null ? getAtPath(item, path14.slice(path14.indexOf("*") + 1)) : void 0
|
|
384
386
|
);
|
|
385
387
|
}
|
|
386
388
|
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
@@ -388,13 +390,13 @@ function getAtPath(obj, path12) {
|
|
|
388
390
|
}
|
|
389
391
|
return current;
|
|
390
392
|
}
|
|
391
|
-
function setAtPath(obj,
|
|
392
|
-
if (
|
|
393
|
-
if (
|
|
394
|
-
obj[
|
|
393
|
+
function setAtPath(obj, path14, value) {
|
|
394
|
+
if (path14.length === 0) return;
|
|
395
|
+
if (path14.length === 1) {
|
|
396
|
+
obj[path14[0]] = value;
|
|
395
397
|
return;
|
|
396
398
|
}
|
|
397
|
-
const [head, ...rest] =
|
|
399
|
+
const [head, ...rest] = path14;
|
|
398
400
|
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
399
401
|
obj[head] = {};
|
|
400
402
|
}
|
|
@@ -1466,43 +1468,6 @@ function loadConfigSync(options = {}) {
|
|
|
1466
1468
|
|
|
1467
1469
|
// src/validate/validate-project.ts
|
|
1468
1470
|
init_esm_shims();
|
|
1469
|
-
|
|
1470
|
-
// src/hash/page-hash.ts
|
|
1471
|
-
init_esm_shims();
|
|
1472
|
-
function sha256(input) {
|
|
1473
|
-
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
1474
|
-
}
|
|
1475
|
-
function computePageEnHash(translatableFrontmatter, body) {
|
|
1476
|
-
const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
|
|
1477
|
-
return sha256(payload);
|
|
1478
|
-
}
|
|
1479
|
-
function computeBodyHash(body) {
|
|
1480
|
-
return sha256(body);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// src/history/record-revision.ts
|
|
1484
|
-
init_esm_shims();
|
|
1485
|
-
init_sqlite();
|
|
1486
|
-
function recordRevision(config, input) {
|
|
1487
|
-
const db = openStore(config, "readwrite");
|
|
1488
|
-
const id = appendRevision(db, {
|
|
1489
|
-
contentType: input.contentType,
|
|
1490
|
-
enSlug: input.enSlug,
|
|
1491
|
-
locale: input.locale,
|
|
1492
|
-
revisionKind: input.revisionKind,
|
|
1493
|
-
enHash: input.enHash,
|
|
1494
|
-
bodyHash: computeBodyHash(input.body),
|
|
1495
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1496
|
-
model: input.model,
|
|
1497
|
-
bodyPreview: input.body.slice(0, 200),
|
|
1498
|
-
frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
|
|
1499
|
-
body: input.body
|
|
1500
|
-
});
|
|
1501
|
-
db.close();
|
|
1502
|
-
return id;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// src/validate/validate-project.ts
|
|
1506
1471
|
init_sqlite();
|
|
1507
1472
|
|
|
1508
1473
|
// src/validate/validate-relations.ts
|
|
@@ -1587,6 +1552,75 @@ function validateRelations(project) {
|
|
|
1587
1552
|
return issues;
|
|
1588
1553
|
}
|
|
1589
1554
|
|
|
1555
|
+
// src/validate/validate-assets.ts
|
|
1556
|
+
init_esm_shims();
|
|
1557
|
+
var IMAGE_EXT = String.raw`(?:png|jpe?g|webp|gif|svg)`;
|
|
1558
|
+
var IMAGE_WEB_PATH = String.raw`\/[\w\-./]+\.${IMAGE_EXT}`;
|
|
1559
|
+
var MARKDOWN_IMAGE_RE = new RegExp(
|
|
1560
|
+
String.raw`!\[[^\]]*\]\((${IMAGE_WEB_PATH})\)`,
|
|
1561
|
+
"gi"
|
|
1562
|
+
);
|
|
1563
|
+
var HTML_SRC_RE = new RegExp(String.raw`src=["'](${IMAGE_WEB_PATH})["']`, "gi");
|
|
1564
|
+
function isImageWebPath(value) {
|
|
1565
|
+
return new RegExp(String.raw`^${IMAGE_WEB_PATH}$`, "i").test(value);
|
|
1566
|
+
}
|
|
1567
|
+
function isSiteAssetPath(webPath) {
|
|
1568
|
+
const segment = webPath.split("/")[1];
|
|
1569
|
+
return Boolean(segment && !segment.includes("."));
|
|
1570
|
+
}
|
|
1571
|
+
function addImagePath(webPath, out) {
|
|
1572
|
+
if (isSiteAssetPath(webPath)) out.add(webPath);
|
|
1573
|
+
}
|
|
1574
|
+
function collectBodyImagePaths(body, out) {
|
|
1575
|
+
for (const re of [MARKDOWN_IMAGE_RE, HTML_SRC_RE]) {
|
|
1576
|
+
for (const match of body.matchAll(re)) {
|
|
1577
|
+
addImagePath(match[1], out);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function collectFrontmatterImagePaths(value, out) {
|
|
1582
|
+
if (typeof value === "string") {
|
|
1583
|
+
if (isImageWebPath(value)) addImagePath(value, out);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
if (Array.isArray(value)) {
|
|
1587
|
+
for (const item of value) collectFrontmatterImagePaths(item, out);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (value && typeof value === "object") {
|
|
1591
|
+
for (const nested of Object.values(value)) {
|
|
1592
|
+
collectFrontmatterImagePaths(nested, out);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function collectImagePaths(frontmatter, body) {
|
|
1597
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1598
|
+
collectFrontmatterImagePaths(frontmatter, paths);
|
|
1599
|
+
collectBodyImagePaths(body, paths);
|
|
1600
|
+
return [...paths].sort();
|
|
1601
|
+
}
|
|
1602
|
+
function assetFilePath(assetsPath, webPath) {
|
|
1603
|
+
return path4.join(assetsPath, webPath.replace(/^\//, ""));
|
|
1604
|
+
}
|
|
1605
|
+
function validateDocumentAssets(config, input) {
|
|
1606
|
+
const assetsPath = config.assetsPath;
|
|
1607
|
+
if (!assetsPath) return [];
|
|
1608
|
+
const issues = [];
|
|
1609
|
+
for (const webPath of collectImagePaths(input.frontmatter, input.body)) {
|
|
1610
|
+
const filePath = assetFilePath(assetsPath, webPath);
|
|
1611
|
+
if (fs2.existsSync(filePath)) continue;
|
|
1612
|
+
issues.push({
|
|
1613
|
+
level: "warning",
|
|
1614
|
+
contentType: input.contentType,
|
|
1615
|
+
enSlug: input.enSlug,
|
|
1616
|
+
locale: input.locale,
|
|
1617
|
+
field: "asset",
|
|
1618
|
+
message: `Missing image asset ${webPath} (expected ${filePath})`
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
return issues;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1590
1624
|
// src/validate/validate-slug-suffix.ts
|
|
1591
1625
|
init_esm_shims();
|
|
1592
1626
|
function validateTranslationSlugSuffixes(config, db) {
|
|
@@ -1649,8 +1683,6 @@ function validateProject(config) {
|
|
|
1649
1683
|
});
|
|
1650
1684
|
continue;
|
|
1651
1685
|
}
|
|
1652
|
-
const payload = getTranslatablePayload(enDoc, type);
|
|
1653
|
-
const currentEnHash = computePageEnHash(payload.frontmatter, payload.body);
|
|
1654
1686
|
const crossIssues = type.crossValidate?.(enDoc.frontmatter, {
|
|
1655
1687
|
locale: config.defaultLocale,
|
|
1656
1688
|
defaultLocale: config.defaultLocale,
|
|
@@ -1668,29 +1700,18 @@ function validateProject(config) {
|
|
|
1668
1700
|
message: issue.message
|
|
1669
1701
|
});
|
|
1670
1702
|
}
|
|
1703
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1704
|
+
contentType: type.id,
|
|
1705
|
+
enSlug,
|
|
1706
|
+
frontmatter: enDoc.frontmatter,
|
|
1707
|
+
body: enDoc.content
|
|
1708
|
+
})) {
|
|
1709
|
+
issues.push(issue);
|
|
1710
|
+
}
|
|
1671
1711
|
for (const locale of config.locales) {
|
|
1672
1712
|
if (locale === config.defaultLocale) continue;
|
|
1673
1713
|
const row = getTranslation(db, type.id, enSlug, locale);
|
|
1674
1714
|
if (!row) continue;
|
|
1675
|
-
if (row.en_hash !== currentEnHash) {
|
|
1676
|
-
issues.push({
|
|
1677
|
-
level: "warning",
|
|
1678
|
-
contentType: type.id,
|
|
1679
|
-
enSlug,
|
|
1680
|
-
locale,
|
|
1681
|
-
field: "en_hash",
|
|
1682
|
-
message: "Translation is stale (en_hash mismatch)"
|
|
1683
|
-
});
|
|
1684
|
-
recordRevision(config, {
|
|
1685
|
-
contentType: type.id,
|
|
1686
|
-
enSlug,
|
|
1687
|
-
locale,
|
|
1688
|
-
revisionKind: "en_edit_detected",
|
|
1689
|
-
enHash: currentEnHash,
|
|
1690
|
-
body: row.body,
|
|
1691
|
-
frontmatter: JSON.parse(row.frontmatter_json)
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
1715
|
const localeFm = JSON.parse(row.frontmatter_json);
|
|
1695
1716
|
for (const issue of validateLocaleBuiltinFields(localeFm)) {
|
|
1696
1717
|
issues.push({
|
|
@@ -1702,6 +1723,15 @@ function validateProject(config) {
|
|
|
1702
1723
|
message: issue.message
|
|
1703
1724
|
});
|
|
1704
1725
|
}
|
|
1726
|
+
for (const issue of validateDocumentAssets(config, {
|
|
1727
|
+
contentType: type.id,
|
|
1728
|
+
enSlug,
|
|
1729
|
+
locale,
|
|
1730
|
+
frontmatter: localeFm,
|
|
1731
|
+
body: row.body
|
|
1732
|
+
})) {
|
|
1733
|
+
issues.push(issue);
|
|
1734
|
+
}
|
|
1705
1735
|
}
|
|
1706
1736
|
}
|
|
1707
1737
|
try {
|
|
@@ -1772,6 +1802,21 @@ function validateProject(config) {
|
|
|
1772
1802
|
|
|
1773
1803
|
// src/translate/worklist.ts
|
|
1774
1804
|
init_esm_shims();
|
|
1805
|
+
|
|
1806
|
+
// src/hash/page-hash.ts
|
|
1807
|
+
init_esm_shims();
|
|
1808
|
+
function sha256(input) {
|
|
1809
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
1810
|
+
}
|
|
1811
|
+
function computePageEnHash(translatableFrontmatter, body) {
|
|
1812
|
+
const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
|
|
1813
|
+
return sha256(payload);
|
|
1814
|
+
}
|
|
1815
|
+
function computeBodyHash(body) {
|
|
1816
|
+
return sha256(body);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// src/translate/worklist.ts
|
|
1775
1820
|
init_sqlite();
|
|
1776
1821
|
function listEnSlugs3(rootDir, contentDir) {
|
|
1777
1822
|
const dir = path4.join(rootDir, contentDir);
|
|
@@ -1833,6 +1878,30 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
|
|
|
1833
1878
|
|
|
1834
1879
|
// src/translate/page-translator.ts
|
|
1835
1880
|
init_esm_shims();
|
|
1881
|
+
|
|
1882
|
+
// src/history/record-revision.ts
|
|
1883
|
+
init_esm_shims();
|
|
1884
|
+
init_sqlite();
|
|
1885
|
+
function recordRevision(config, input) {
|
|
1886
|
+
const db = openStore(config, "readwrite");
|
|
1887
|
+
const id = appendRevision(db, {
|
|
1888
|
+
contentType: input.contentType,
|
|
1889
|
+
enSlug: input.enSlug,
|
|
1890
|
+
locale: input.locale,
|
|
1891
|
+
revisionKind: input.revisionKind,
|
|
1892
|
+
enHash: input.enHash,
|
|
1893
|
+
bodyHash: computeBodyHash(input.body),
|
|
1894
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1895
|
+
model: input.model,
|
|
1896
|
+
bodyPreview: input.body.slice(0, 200),
|
|
1897
|
+
frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
|
|
1898
|
+
body: input.body
|
|
1899
|
+
});
|
|
1900
|
+
db.close();
|
|
1901
|
+
return id;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// src/translate/page-translator.ts
|
|
1836
1905
|
init_sqlite();
|
|
1837
1906
|
|
|
1838
1907
|
// src/translate/gemini-client.ts
|
|
@@ -3164,6 +3233,24 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
3164
3233
|
};
|
|
3165
3234
|
}
|
|
3166
3235
|
|
|
3236
|
+
// src/version.ts
|
|
3237
|
+
init_esm_shims();
|
|
3238
|
+
function readScribeVersion() {
|
|
3239
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
3240
|
+
for (; ; ) {
|
|
3241
|
+
const candidate = path4.join(dir, "package.json");
|
|
3242
|
+
if (fs2.existsSync(candidate)) {
|
|
3243
|
+
const pkg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
3244
|
+
if (pkg.name === "scribe-cms" && pkg.version) return pkg.version;
|
|
3245
|
+
}
|
|
3246
|
+
const parent = path4.dirname(dir);
|
|
3247
|
+
if (parent === dir) break;
|
|
3248
|
+
dir = parent;
|
|
3249
|
+
}
|
|
3250
|
+
return "unknown";
|
|
3251
|
+
}
|
|
3252
|
+
var SCRIBE_VERSION = readScribeVersion();
|
|
3253
|
+
|
|
3167
3254
|
// cli/index.ts
|
|
3168
3255
|
function parseArgs(argv) {
|
|
3169
3256
|
const options = { cwd: process.cwd() };
|
|
@@ -3266,7 +3353,13 @@ function loadProject(options) {
|
|
|
3266
3353
|
return createProject(config);
|
|
3267
3354
|
}
|
|
3268
3355
|
async function main() {
|
|
3269
|
-
const
|
|
3356
|
+
const argv = process.argv.slice(2);
|
|
3357
|
+
const first = argv[0];
|
|
3358
|
+
if (first === "--version" || first === "-V" || first === "version") {
|
|
3359
|
+
console.log(SCRIBE_VERSION);
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
const { command, options, rest } = parseArgs(argv);
|
|
3270
3363
|
loadEnvFromCwd(options.cwd);
|
|
3271
3364
|
if (command === "help" || command === "--help") {
|
|
3272
3365
|
console.log(`Usage: scribe <command>
|
|
@@ -3278,6 +3371,7 @@ Commands:
|
|
|
3278
3371
|
translate Translate stale/missing locale pages
|
|
3279
3372
|
history <type> <slug> Show revision timeline
|
|
3280
3373
|
studio Start read-only local studio
|
|
3374
|
+
version Print scribe-cms version
|
|
3281
3375
|
|
|
3282
3376
|
Export-static flags:
|
|
3283
3377
|
--out <dir> Output directory (default: public)
|
|
@@ -3299,6 +3393,10 @@ Translate flags:
|
|
|
3299
3393
|
`);
|
|
3300
3394
|
return;
|
|
3301
3395
|
}
|
|
3396
|
+
if (command === "version") {
|
|
3397
|
+
console.log(SCRIBE_VERSION);
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3302
3400
|
const project = loadProject(options);
|
|
3303
3401
|
const config = project.config;
|
|
3304
3402
|
switch (command) {
|