@bobbykim/manguito-cms-cli 0.8.1 → 0.8.2
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.js +105 -31
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -22839,13 +22839,89 @@ function checkRelationInput(fields, body, registry, path5 = "") {
|
|
|
22839
22839
|
}
|
|
22840
22840
|
return errors;
|
|
22841
22841
|
}
|
|
22842
|
+
function paragraphItemsIn(fields, body, registry, path5) {
|
|
22843
|
+
const out = [];
|
|
22844
|
+
for (const field of fields) {
|
|
22845
|
+
if (field.field_type !== "paragraph" || !(field.name in body)) continue;
|
|
22846
|
+
const comp = field.ui_component;
|
|
22847
|
+
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
22848
|
+
if (!pType) continue;
|
|
22849
|
+
const value = body[field.name];
|
|
22850
|
+
const name = `${path5}${field.name}`;
|
|
22851
|
+
if (relationCardinality(field) === "one") {
|
|
22852
|
+
if (isPlainObject(value)) out.push({ item: value, fields: pType.fields, prefix: `${name}.` });
|
|
22853
|
+
} else if (Array.isArray(value)) {
|
|
22854
|
+
value.forEach((item, i) => {
|
|
22855
|
+
if (isPlainObject(item)) out.push({ item, fields: pType.fields, prefix: `${name}[${i}].` });
|
|
22856
|
+
});
|
|
22857
|
+
}
|
|
22858
|
+
}
|
|
22859
|
+
return out;
|
|
22860
|
+
}
|
|
22861
|
+
function isMissing(value) {
|
|
22862
|
+
if (value === null || value === void 0) return true;
|
|
22863
|
+
if (typeof value === "string" && value.trim() === "") return true;
|
|
22864
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
22865
|
+
return false;
|
|
22866
|
+
}
|
|
22867
|
+
function checkRequiredInParagraphItems(fields, body, registry, path5 = "") {
|
|
22868
|
+
const errors = [];
|
|
22869
|
+
for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path5)) {
|
|
22870
|
+
for (const f of itemFields) {
|
|
22871
|
+
if (!f.required || f.removed === true || !f.db_column || f.db_column.junction) continue;
|
|
22872
|
+
if (isMissing(item[f.name])) {
|
|
22873
|
+
errors.push({ field: `${prefix}${f.name}`, message: `${prefix}${f.name} is required.` });
|
|
22874
|
+
}
|
|
22875
|
+
}
|
|
22876
|
+
errors.push(...checkRequiredInParagraphItems(itemFields, item, registry, prefix));
|
|
22877
|
+
}
|
|
22878
|
+
return errors;
|
|
22879
|
+
}
|
|
22842
22880
|
function quoteIdent2(name) {
|
|
22843
22881
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
22844
22882
|
return `"${name}"`;
|
|
22845
22883
|
}
|
|
22884
|
+
function collectReferences(fields, body, registry, path5, out) {
|
|
22885
|
+
for (const f of fields) {
|
|
22886
|
+
if (!(f.name in body) || !f.db_column) continue;
|
|
22887
|
+
const table = f.db_column.junction ? f.db_column.junction.right_table : f.db_column.foreign_key?.table;
|
|
22888
|
+
if (!table) continue;
|
|
22889
|
+
const value = body[f.name];
|
|
22890
|
+
const name = `${path5}${f.name}`;
|
|
22891
|
+
if (typeof value === "string") {
|
|
22892
|
+
out.push({ table, id: value, field: name });
|
|
22893
|
+
} else if (Array.isArray(value)) {
|
|
22894
|
+
value.forEach((v2, i) => {
|
|
22895
|
+
if (typeof v2 === "string") out.push({ table, id: v2, field: `${name}[${i}]` });
|
|
22896
|
+
});
|
|
22897
|
+
}
|
|
22898
|
+
}
|
|
22899
|
+
for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path5)) {
|
|
22900
|
+
collectReferences(itemFields, item, registry, prefix, out);
|
|
22901
|
+
}
|
|
22902
|
+
}
|
|
22903
|
+
async function findMissingReferences(db, fields, body, registry) {
|
|
22904
|
+
const uses = [];
|
|
22905
|
+
collectReferences(fields, body, registry, "", uses);
|
|
22906
|
+
if (uses.length === 0) return [];
|
|
22907
|
+
const lookups = [...new Set(uses.map((u) => u.table))].map(async (table) => {
|
|
22908
|
+
const ids = [...new Set(uses.filter((u) => u.table === table && isUuid(u.id)).map((u) => u.id.toLowerCase()))];
|
|
22909
|
+
if (ids.length === 0) return [table, /* @__PURE__ */ new Set()];
|
|
22910
|
+
const result = await db.execute(
|
|
22911
|
+
sql`SELECT id FROM ${sql.raw(quoteIdent2(table))} WHERE id IN (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})`
|
|
22912
|
+
);
|
|
22913
|
+
return [table, new Set(result.rows.map((r) => String(r.id).toLowerCase()))];
|
|
22914
|
+
});
|
|
22915
|
+
const found = new Map(await Promise.all(lookups));
|
|
22916
|
+
return uses.filter((u) => !found.get(u.table).has(u.id.toLowerCase())).map((u) => ({ field: u.field, message: `${u.field} refers to an item that does not exist.` }));
|
|
22917
|
+
}
|
|
22918
|
+
function quoteIdent3(name) {
|
|
22919
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
22920
|
+
return `"${name}"`;
|
|
22921
|
+
}
|
|
22846
22922
|
async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
22847
22923
|
const result = await db.execute(
|
|
22848
|
-
sql`SELECT * FROM ${sql.raw(
|
|
22924
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent3(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
22849
22925
|
);
|
|
22850
22926
|
const rows = result.rows;
|
|
22851
22927
|
for (const nf of pType.fields) {
|
|
@@ -22870,22 +22946,20 @@ async function lookupBasePathId(db, pathOrName) {
|
|
|
22870
22946
|
return r.rows[0]?.id ?? null;
|
|
22871
22947
|
}
|
|
22872
22948
|
var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
22873
|
-
function isEmpty(value) {
|
|
22874
|
-
if (value === null || value === void 0) return true;
|
|
22875
|
-
if (typeof value === "string" && value.trim() === "") return true;
|
|
22876
|
-
if (Array.isArray(value) && value.length === 0) return true;
|
|
22877
|
-
return false;
|
|
22878
|
-
}
|
|
22879
22949
|
function checkRequiredFields(fields, data) {
|
|
22880
|
-
return fields.filter((f) => f.required &&
|
|
22881
|
-
}
|
|
22882
|
-
function
|
|
22883
|
-
const
|
|
22884
|
-
|
|
22885
|
-
return
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
)
|
|
22950
|
+
return fields.filter((f) => f.required && isMissing(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
22951
|
+
}
|
|
22952
|
+
async function relationInputError(c, fields, body, registry, db) {
|
|
22953
|
+
const refuse = (message, details) => c.json({ ok: false, error: { code: "VALIDATION_ERROR", message, details } }, 422);
|
|
22954
|
+
const shape = checkRelationInput(fields, body, registry);
|
|
22955
|
+
if (shape.length > 0) return refuse("Relation fields have the wrong shape", shape);
|
|
22956
|
+
const required = checkRequiredInParagraphItems(fields, body, registry);
|
|
22957
|
+
if (required.length > 0) return refuse("Required fields are missing", required);
|
|
22958
|
+
if (db) {
|
|
22959
|
+
const missing = await findMissingReferences(db, fields, body, registry);
|
|
22960
|
+
if (missing.length > 0) return refuse("Relation fields refer to items that do not exist", missing);
|
|
22961
|
+
}
|
|
22962
|
+
return null;
|
|
22889
22963
|
}
|
|
22890
22964
|
function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
|
|
22891
22965
|
const searchParams = new URL(url).searchParams;
|
|
@@ -23032,7 +23106,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23032
23106
|
} else if (f.db_column.junction) {
|
|
23033
23107
|
const j = f.db_column.junction;
|
|
23034
23108
|
const r = await db.execute(
|
|
23035
|
-
sql`SELECT ${sql.raw(
|
|
23109
|
+
sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}`
|
|
23036
23110
|
);
|
|
23037
23111
|
row[f.name] = r.rows.map(
|
|
23038
23112
|
(row2) => row2[j.right_column]
|
|
@@ -23108,8 +23182,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23108
23182
|
}
|
|
23109
23183
|
);
|
|
23110
23184
|
const writeNewItem = async (c, body) => {
|
|
23111
|
-
const
|
|
23112
|
-
if (
|
|
23185
|
+
const inputError = await relationInputError(c, contentType.fields, body, registry, db);
|
|
23186
|
+
if (inputError) return inputError;
|
|
23113
23187
|
const storageBody = fieldKeys.toStorage(body);
|
|
23114
23188
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
23115
23189
|
if (fieldErrors.length > 0) {
|
|
@@ -23244,8 +23318,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23244
23318
|
}
|
|
23245
23319
|
);
|
|
23246
23320
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
23247
|
-
const
|
|
23248
|
-
if (
|
|
23321
|
+
const inputError = await relationInputError(c, contentType.fields, body, registry, db);
|
|
23322
|
+
if (inputError) return inputError;
|
|
23249
23323
|
const storageBody = fieldKeys.toStorage(body);
|
|
23250
23324
|
if (body["published"] === true) {
|
|
23251
23325
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
@@ -23443,8 +23517,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23443
23517
|
requirePermission("content:create"),
|
|
23444
23518
|
async (c) => {
|
|
23445
23519
|
const body = await c.req.json();
|
|
23446
|
-
const
|
|
23447
|
-
if (
|
|
23520
|
+
const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
|
|
23521
|
+
if (inputError) return inputError;
|
|
23448
23522
|
const storageBody = fieldKeys.toStorage(body);
|
|
23449
23523
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
23450
23524
|
if (fieldErrors.length > 0) {
|
|
@@ -23507,8 +23581,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23507
23581
|
404
|
|
23508
23582
|
);
|
|
23509
23583
|
}
|
|
23510
|
-
const
|
|
23511
|
-
if (
|
|
23584
|
+
const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
|
|
23585
|
+
if (inputError) return inputError;
|
|
23512
23586
|
if (body["published"] === true) {
|
|
23513
23587
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
23514
23588
|
});
|
|
@@ -24545,7 +24619,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
24545
24619
|
}
|
|
24546
24620
|
var API_VERSION = (() => {
|
|
24547
24621
|
try {
|
|
24548
|
-
return "0.8.
|
|
24622
|
+
return "0.8.1";
|
|
24549
24623
|
} catch {
|
|
24550
24624
|
return "0.0.0";
|
|
24551
24625
|
}
|
|
@@ -24654,7 +24728,7 @@ function codeError(code, message) {
|
|
|
24654
24728
|
err.code = code;
|
|
24655
24729
|
return err;
|
|
24656
24730
|
}
|
|
24657
|
-
function
|
|
24731
|
+
function quoteIdent4(name) {
|
|
24658
24732
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
24659
24733
|
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
24660
24734
|
}
|
|
@@ -24705,7 +24779,7 @@ function whereFragment(conds) {
|
|
|
24705
24779
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
24706
24780
|
const { relations = {}, publishedRelations = false, sortableColumns } = options;
|
|
24707
24781
|
function tableRaw() {
|
|
24708
|
-
return sql.raw(
|
|
24782
|
+
return sql.raw(quoteIdent4(tableName));
|
|
24709
24783
|
}
|
|
24710
24784
|
async function resolveRows(rows, include, cache) {
|
|
24711
24785
|
if (rows.length === 0) return rows;
|
|
@@ -24748,7 +24822,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
24748
24822
|
const offset = (page - 1) * per_page;
|
|
24749
24823
|
const conds = buildConditions(published_only, filters, search);
|
|
24750
24824
|
const where = whereFragment(conds);
|
|
24751
|
-
const sortCol = sql.raw(
|
|
24825
|
+
const sortCol = sql.raw(quoteIdent4(sort_by));
|
|
24752
24826
|
const sortDir = sql.raw(sort_order === "desc" ? "DESC" : "ASC");
|
|
24753
24827
|
const countResult = await db.execute(
|
|
24754
24828
|
sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
|
|
@@ -24802,7 +24876,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
24802
24876
|
updated_at: /* @__PURE__ */ new Date()
|
|
24803
24877
|
};
|
|
24804
24878
|
const entries = Object.entries(record).filter(([, v2]) => v2 !== void 0);
|
|
24805
|
-
const colsSql = sql.join(entries.map(([k]) => sql.raw(
|
|
24879
|
+
const colsSql = sql.join(entries.map(([k]) => sql.raw(quoteIdent4(k))), sql`, `);
|
|
24806
24880
|
const valsSql = sql.join(entries.map(([, v2]) => sql`${v2}`), sql`, `);
|
|
24807
24881
|
const result = await db.execute(
|
|
24808
24882
|
sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
@@ -24819,7 +24893,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
24819
24893
|
return this.findOne(id);
|
|
24820
24894
|
}
|
|
24821
24895
|
const setClauses = sql.join(
|
|
24822
|
-
entries.map(([k, v2]) => sql`${sql.raw(
|
|
24896
|
+
entries.map(([k, v2]) => sql`${sql.raw(quoteIdent4(k))} = ${v2}`),
|
|
24823
24897
|
sql`, `
|
|
24824
24898
|
);
|
|
24825
24899
|
const result = await db.execute(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobbykim/manguito-cms-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/bobbykim89/manguito-cms",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"vite": "^8.1.3",
|
|
32
32
|
"@bobbykim/manguito-cms-admin": "0.5.1",
|
|
33
33
|
"@bobbykim/manguito-cms-core": "0.7.0",
|
|
34
|
-
"@bobbykim/manguito-cms-
|
|
35
|
-
"@bobbykim/manguito-cms-
|
|
34
|
+
"@bobbykim/manguito-cms-db": "0.1.7",
|
|
35
|
+
"@bobbykim/manguito-cms-api": "0.8.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.9.5",
|