@bobbykim/manguito-cms-cli 0.10.1 → 0.11.1

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.
@@ -9270,7 +9270,7 @@ var require_bcryptjs = __commonJS({
9270
9270
  }
9271
9271
  });
9272
9272
 
9273
- // ../api/dist/chunk-IHEIX6P4.mjs
9273
+ // ../api/dist/chunk-GUZZQZSN.mjs
9274
9274
  init_esm_shims();
9275
9275
  import crypto from "crypto";
9276
9276
 
@@ -25795,7 +25795,8 @@ var fieldTypeRegistry = {
25795
25795
  left_column: "left_id",
25796
25796
  right_column: "right_id",
25797
25797
  right_table: targetTableName,
25798
- order_column: false
25798
+ // Every list keeps the order the editor gave it (#57).
25799
+ order_column: true
25799
25800
  }
25800
25801
  } : {
25801
25802
  // FK column on the owning table. An optional reference is cleared when
@@ -26617,7 +26618,7 @@ function describeSchemaChange(input) {
26617
26618
  return { from: from?.version ?? null, to: to.version, types, identical };
26618
26619
  }
26619
26620
 
26620
- // ../api/dist/chunk-IHEIX6P4.mjs
26621
+ // ../api/dist/chunk-GUZZQZSN.mjs
26621
26622
  function isColumnBacked2(field) {
26622
26623
  const col = field.db_column;
26623
26624
  if (col === null) return false;
@@ -26845,10 +26846,28 @@ async function persistJunctionField(db, itemId, junction, relatedIds) {
26845
26846
  const tbl = sql.raw(quoteIdent(junction.table_name));
26846
26847
  const left = sql.raw(quoteIdent(junction.left_column));
26847
26848
  const right = sql.raw(quoteIdent(junction.right_column));
26849
+ const seen = /* @__PURE__ */ new Set();
26850
+ const ids = relatedIds.filter((id) => {
26851
+ const key = id.toLowerCase();
26852
+ if (seen.has(key)) return false;
26853
+ seen.add(key);
26854
+ return true;
26855
+ });
26848
26856
  await db.execute(sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
26849
- for (const rid of relatedIds) {
26850
- await db.execute(sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
26851
- }
26857
+ if (ids.length === 0) return;
26858
+ const rows = sql.join(
26859
+ ids.map(
26860
+ (rid, position) => junction.order_column ? sql`(${itemId}, ${rid}, ${position})` : sql`(${itemId}, ${rid})`
26861
+ ),
26862
+ sql`, `
26863
+ );
26864
+ await db.execute(
26865
+ junction.order_column ? sql`INSERT INTO ${tbl} (${left}, ${right}, "order") VALUES ${rows}` : sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES ${rows}`
26866
+ );
26867
+ }
26868
+ function junctionOrderBy(junction) {
26869
+ const right = sql.raw(quoteIdent(junction.right_column));
26870
+ return junction.order_column ? sql` ORDER BY "order" ASC, ${right} ASC` : sql` ORDER BY ${right} ASC`;
26852
26871
  }
26853
26872
  function oneOrMany(cardinality, list) {
26854
26873
  return cardinality === "one" ? list[0] ?? null : list;
@@ -26950,9 +26969,8 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
26950
26969
  } else if (rel.type === "junction") {
26951
26970
  const parentIds = batch.map((r) => r["id"]);
26952
26971
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
26953
- const orderBy = rel.order_column ? sql` ORDER BY "order" ASC` : sql``;
26954
26972
  const junctionResult = await db.execute(
26955
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
26973
+ sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
26956
26974
  );
26957
26975
  const jRows = junctionResult.rows;
26958
26976
  const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
@@ -27011,7 +27029,7 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
27011
27029
  const parentIds = rows.map((r) => r["id"]);
27012
27030
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
27013
27031
  const result = await db.execute(
27014
- sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})`
27032
+ sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
27015
27033
  );
27016
27034
  const byLeft = groupBy(result.rows, "left_id");
27017
27035
  for (const row of rows) {
@@ -27071,6 +27089,7 @@ export {
27071
27089
  persistParagraphField,
27072
27090
  deleteParagraphField,
27073
27091
  persistJunctionField,
27092
+ junctionOrderBy,
27074
27093
  oneOrMany,
27075
27094
  paragraphItems,
27076
27095
  buildRelationsMap,
@@ -12,7 +12,7 @@ import {
12
12
  isColumnBacked,
13
13
  relationCardinality,
14
14
  resolveRelationField
15
- } from "./chunk-X7D2QPPO.js";
15
+ } from "./chunk-WTZBHFCV.js";
16
16
 
17
17
  // ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
18
18
  var require_shouldSkipPonyfill = __commonJS({
@@ -10787,10 +10787,10 @@ var require_parser = __commonJS({
10787
10787
  }
10788
10788
  });
10789
10789
 
10790
- // ../api/dist/handler-VEOWJRAL.mjs
10790
+ // ../api/dist/handler-MFF32JB4.mjs
10791
10791
  init_esm_shims();
10792
10792
 
10793
- // ../api/dist/chunk-Y5SWT7PO.mjs
10793
+ // ../api/dist/chunk-ITOSMGDT.mjs
10794
10794
  init_esm_shims();
10795
10795
 
10796
10796
  // ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
@@ -30123,7 +30123,7 @@ function createYoga(options) {
30123
30123
  });
30124
30124
  }
30125
30125
 
30126
- // ../api/dist/chunk-Y5SWT7PO.mjs
30126
+ // ../api/dist/chunk-ITOSMGDT.mjs
30127
30127
  var import_dataloader = __toESM(require_dataloader(), 1);
30128
30128
 
30129
30129
  // ../../node_modules/.pnpm/@escape.tech+graphql-armor@3.2.0_@envelop+core@5.5.1_@escape.tech+graphql-armor-types@0.7.0/node_modules/@escape.tech/graphql-armor/dist/graphql-armor.esm.js
@@ -30881,7 +30881,7 @@ var EnvelopArmor = class {
30881
30881
  }
30882
30882
  };
30883
30883
 
30884
- // ../api/dist/chunk-Y5SWT7PO.mjs
30884
+ // ../api/dist/chunk-ITOSMGDT.mjs
30885
30885
  var DateTimeScalar = new GraphQLScalarType({
30886
30886
  name: "DateTime",
30887
30887
  description: "ISO-8601 date-time string",
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  isColumnBacked,
38
38
  isDriverValueEncoder,
39
39
  isSQLWrapper,
40
+ junctionOrderBy,
40
41
  loadSchemaFile,
41
42
  loadVersionSnapshots,
42
43
  oneOrMany,
@@ -54,7 +55,7 @@ import {
54
55
  validateCrossReferences,
55
56
  verifyPassword,
56
57
  walkSchemaDirectory
57
- } from "./chunk-X7D2QPPO.js";
58
+ } from "./chunk-WTZBHFCV.js";
58
59
 
59
60
  // ../../node_modules/.pnpm/postgres-array@2.0.0/node_modules/postgres-array/index.js
60
61
  var require_postgres_array = __commonJS({
@@ -17216,27 +17217,59 @@ async function runDevMigration(configPath) {
17216
17217
  env: resolvedPathEnv()
17217
17218
  });
17218
17219
  }
17219
- async function generateMigration(configPath, migrationsFolder) {
17220
+ async function runGenerate(configPath, outFolder, cwd) {
17220
17221
  let beforeFiles = [];
17221
17222
  try {
17222
- const entries = await fs.readdir(migrationsFolder);
17223
+ const entries = await fs.readdir(outFolder);
17223
17224
  beforeFiles = entries.filter((f) => f.endsWith(".sql"));
17224
17225
  } catch {
17225
17226
  }
17226
17227
  execSync(`drizzle-kit generate --config=${configPath}`, {
17227
17228
  stdio: "inherit",
17228
- cwd: path.dirname(configPath),
17229
+ cwd,
17229
17230
  env: resolvedPathEnv()
17230
17231
  });
17231
17232
  let afterFiles = [];
17232
17233
  try {
17233
- const entries = await fs.readdir(migrationsFolder);
17234
+ const entries = await fs.readdir(outFolder);
17234
17235
  afterFiles = entries.filter((f) => f.endsWith(".sql"));
17235
17236
  } catch {
17236
17237
  }
17237
17238
  const beforeSet = new Set(beforeFiles);
17238
17239
  return afterFiles.filter((f) => !beforeSet.has(f));
17239
17240
  }
17241
+ async function generateMigration(configPath, migrationsFolder) {
17242
+ return runGenerate(configPath, migrationsFolder, path.dirname(configPath));
17243
+ }
17244
+ async function previewMigration(configPath, migrationsFolder) {
17245
+ const configDir = path.dirname(configPath);
17246
+ const tmp = await fs.mkdtemp(path.join(configDir, ".migration-preview-"));
17247
+ try {
17248
+ const out = path.join(tmp, "migrations");
17249
+ try {
17250
+ await fs.cp(migrationsFolder, out, { recursive: true });
17251
+ } catch (err) {
17252
+ if (err.code !== "ENOENT") throw err;
17253
+ }
17254
+ const base = `./${path.basename(configPath).replace(/\.[cm]?[jt]s$/, "")}`;
17255
+ const tmpConfig = path.join(tmp, "drizzle.config.ts");
17256
+ await fs.writeFile(
17257
+ tmpConfig,
17258
+ [
17259
+ `import base from '.${base}'`,
17260
+ // Relative to the real config's folder, where drizzle-kit runs.
17261
+ `export default { ...base, out: './${path.relative(configDir, out).split(path.sep).join("/")}' }`,
17262
+ ""
17263
+ ].join("\n")
17264
+ );
17265
+ const names = await runGenerate(tmpConfig, out, configDir);
17266
+ return await Promise.all(
17267
+ names.map(async (name) => ({ name, sql: await fs.readFile(path.join(out, name), "utf8") }))
17268
+ );
17269
+ } finally {
17270
+ await fs.rm(tmp, { recursive: true, force: true });
17271
+ }
17272
+ }
17240
17273
  async function applyMigrations(configPath, db, options) {
17241
17274
  const { migrationsTable, migrationsFolder } = options;
17242
17275
  await db.execute(
@@ -17378,11 +17411,14 @@ function formatAlterColumnType(line2) {
17378
17411
  return `ALTER COLUMN ${tableName}.${colName} TYPE ${newType}`;
17379
17412
  }
17380
17413
  function scanMigrationFiles(filePaths) {
17414
+ return scanMigrationSql(
17415
+ filePaths.map((filePath) => ({ name: path2.basename(filePath), sql: readFileSync(filePath, "utf-8") }))
17416
+ );
17417
+ }
17418
+ function scanMigrationSql(migrations) {
17381
17419
  const operations = [];
17382
- for (const filePath of filePaths) {
17383
- const content = readFileSync(filePath, "utf-8");
17420
+ for (const { name: filename, sql: content } of migrations) {
17384
17421
  const lines = content.split("\n");
17385
- const filename = path2.basename(filePath);
17386
17422
  for (const line2 of lines) {
17387
17423
  const upper = line2.trim().toUpperCase();
17388
17424
  if (upper.includes("DROP COLUMN")) {
@@ -22848,8 +22884,7 @@ function checkRelationInput(fields, body, registry, path5 = "") {
22848
22884
  });
22849
22885
  continue;
22850
22886
  }
22851
- const comp = field.ui_component;
22852
- const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
22887
+ const pType = paragraphTypeOf(field, registry);
22853
22888
  if (!pType) continue;
22854
22889
  items.forEach((item, i) => {
22855
22890
  const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
@@ -22858,12 +22893,15 @@ function checkRelationInput(fields, body, registry, path5 = "") {
22858
22893
  }
22859
22894
  return errors;
22860
22895
  }
22896
+ function paragraphTypeOf(field, registry) {
22897
+ const comp = field.ui_component;
22898
+ return comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
22899
+ }
22861
22900
  function paragraphItemsIn(fields, body, registry, path5) {
22862
22901
  const out = [];
22863
22902
  for (const field of fields) {
22864
22903
  if (field.field_type !== "paragraph" || !(field.name in body)) continue;
22865
- const comp = field.ui_component;
22866
- const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
22904
+ const pType = paragraphTypeOf(field, registry);
22867
22905
  if (!pType) continue;
22868
22906
  const value = body[field.name];
22869
22907
  const name = `${path5}${field.name}`;
@@ -22934,6 +22972,131 @@ async function findMissingReferences(db, fields, body, registry) {
22934
22972
  const found = new Map(await Promise.all(lookups));
22935
22973
  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.` }));
22936
22974
  }
22975
+ var VARCHAR_DEFAULT_LIMIT = 255;
22976
+ var INTEGER_MIN = -2147483648;
22977
+ var INTEGER_MAX = 2147483647;
22978
+ var DECIMAL_LIMIT = 1e6;
22979
+ var ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
22980
+ var DECIMAL_STRING = /^-?\d+(?:\.\d+)?$/;
22981
+ var EMPTY_MEANS_NULL = /* @__PURE__ */ new Set(["date", "integer", "float", "enum"]);
22982
+ function emptyValuesToNull(fields, body, registry) {
22983
+ const out = { ...body };
22984
+ for (const f of fields) {
22985
+ if (!(f.name in out)) continue;
22986
+ const value = out[f.name];
22987
+ if (value === "" && EMPTY_MEANS_NULL.has(f.field_type)) {
22988
+ out[f.name] = null;
22989
+ continue;
22990
+ }
22991
+ if (f.field_type !== "paragraph") continue;
22992
+ const pType = paragraphTypeOf(f, registry);
22993
+ if (!pType) continue;
22994
+ if (isPlainObject(value)) out[f.name] = emptyValuesToNull(pType.fields, value, registry);
22995
+ else if (Array.isArray(value)) {
22996
+ out[f.name] = value.map((item) => isPlainObject(item) ? emptyValuesToNull(pType.fields, item, registry) : item);
22997
+ }
22998
+ }
22999
+ return out;
23000
+ }
23001
+ var compiled = /* @__PURE__ */ new Map();
23002
+ function patternOf(source) {
23003
+ if (!compiled.has(source)) {
23004
+ let re;
23005
+ try {
23006
+ re = new RegExp(source);
23007
+ } catch {
23008
+ re = null;
23009
+ }
23010
+ compiled.set(source, re);
23011
+ }
23012
+ return compiled.get(source) ?? null;
23013
+ }
23014
+ function isRealDate(value) {
23015
+ const m2 = ISO_TIMESTAMP.exec(value);
23016
+ if (!m2) return false;
23017
+ const [y, mo, d2, h = 0, mi2 = 0, sec = 0] = m2.slice(1).map((part) => part === void 0 ? void 0 : Number(part));
23018
+ if (y === void 0 || mo === void 0 || d2 === void 0 || y < 1) return false;
23019
+ const date2 = new Date(Date.UTC(y, mo - 1, d2));
23020
+ date2.setUTCFullYear(y);
23021
+ return date2.getUTCMonth() === mo - 1 && date2.getUTCDate() === d2 && h <= 23 && mi2 <= 59 && sec <= 59;
23022
+ }
23023
+ function valueError(f, value, name) {
23024
+ const v2 = f.validation;
23025
+ if (Array.isArray(value) && (f.field_type === "paragraph" || f.field_type === "reference")) {
23026
+ if (v2.max_items !== void 0 && value.length > v2.max_items) {
23027
+ return `${name} can hold at most ${v2.max_items} items.`;
23028
+ }
23029
+ return null;
23030
+ }
23031
+ const col = f.db_column;
23032
+ if (!col || col.junction || col.column_type === "uuid") return null;
23033
+ if (value === null || value === void 0) {
23034
+ if (col.nullable) return null;
23035
+ return f.required ? `${name} is required.` : `${name} cannot be empty.`;
23036
+ }
23037
+ switch (f.field_type) {
23038
+ case "text/plain": {
23039
+ if (typeof value !== "string") return `${name} must be text.`;
23040
+ if (value.includes("\0")) return `${name} contains a character that cannot be stored (U+0000).`;
23041
+ const limit = v2.limit ?? VARCHAR_DEFAULT_LIMIT;
23042
+ if ([...value].length > limit) return `${name} can be at most ${limit} characters.`;
23043
+ const pattern = v2.pattern === void 0 ? null : patternOf(v2.pattern);
23044
+ if (value !== "" && pattern && !pattern.test(value)) return `${name} is not in the correct format.`;
23045
+ return null;
23046
+ }
23047
+ case "text/rich":
23048
+ if (typeof value !== "string") return `${name} must be text.`;
23049
+ return value.includes("\0") ? `${name} contains a character that cannot be stored (U+0000).` : null;
23050
+ case "integer":
23051
+ if (typeof value !== "number" || !Number.isInteger(value)) return `${name} must be a whole number.`;
23052
+ if (value < INTEGER_MIN || value > INTEGER_MAX) {
23053
+ return `${name} must be between ${INTEGER_MIN} and ${INTEGER_MAX}.`;
23054
+ }
23055
+ return rangeError(name, value, v2.min, v2.max);
23056
+ case "float": {
23057
+ const n = typeof value === "number" ? value : typeof value === "string" && DECIMAL_STRING.test(value) ? Number(value) : NaN;
23058
+ if (!Number.isFinite(n)) return `${name} must be a number.`;
23059
+ if (roundedSize(typeof value === "string" ? value : String(n)) >= DECIMAL_LIMIT) {
23060
+ return `${name} must be less than ${DECIMAL_LIMIT} in size.`;
23061
+ }
23062
+ return rangeError(name, n, v2.min, v2.max);
23063
+ }
23064
+ case "boolean":
23065
+ return typeof value === "boolean" ? null : `${name} must be true or false.`;
23066
+ case "date":
23067
+ return typeof value === "string" && isRealDate(value) ? null : `${name} must be a date, such as 2026-10-11 or 2026-10-11T09:30:00Z.`;
23068
+ case "enum": {
23069
+ const allowed = v2.allowed_values ?? [];
23070
+ return typeof value === "string" && allowed.includes(value) ? null : `${name} must be one of: ${allowed.join(", ")}.`;
23071
+ }
23072
+ default:
23073
+ return null;
23074
+ }
23075
+ }
23076
+ function roundedSize(text2) {
23077
+ if (text2.includes("e")) return Math.abs(Number(text2));
23078
+ const [int = "0", frac = ""] = text2.replace("-", "").split(".");
23079
+ const kept = Number(`${int}.${frac.slice(0, 4) || "0"}`);
23080
+ return frac.length > 4 && frac.charAt(4) >= "5" ? kept + 1e-4 : kept;
23081
+ }
23082
+ function rangeError(name, value, min, max) {
23083
+ if (min !== void 0 && value < min) return `${name} must be at least ${min}.`;
23084
+ if (max !== void 0 && value > max) return `${name} must be at most ${max}.`;
23085
+ return null;
23086
+ }
23087
+ function checkFieldValues(fields, body, registry, path5 = "") {
23088
+ const errors = [];
23089
+ for (const f of fields) {
23090
+ if (!(f.name in body) || f.removed === true) continue;
23091
+ const name = `${path5}${f.name}`;
23092
+ const message = valueError(f, body[f.name], name);
23093
+ if (message) errors.push({ field: name, message });
23094
+ }
23095
+ for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path5)) {
23096
+ errors.push(...checkFieldValues(itemFields, item, registry, prefix));
23097
+ }
23098
+ return errors;
23099
+ }
22937
23100
  function quoteIdent3(name) {
22938
23101
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
22939
23102
  return `"${name}"`;
@@ -22969,7 +23132,7 @@ async function loadRelationsForEdit(db, registry, fields, id, row) {
22969
23132
  } else if (f.db_column.junction) {
22970
23133
  const j = f.db_column.junction;
22971
23134
  const r = await db.execute(
22972
- sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}`
23135
+ sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}${junctionOrderBy(j)}`
22973
23136
  );
22974
23137
  row[f.name] = r.rows.map((linked) => linked[j.right_column]);
22975
23138
  }
@@ -23012,6 +23175,8 @@ async function relationInputError(c, fields, body, registry, db) {
23012
23175
  const refuse = (message, details) => c.json({ ok: false, error: { code: "VALIDATION_ERROR", message, details } }, 422);
23013
23176
  const shape = checkRelationInput(fields, body, registry);
23014
23177
  if (shape.length > 0) return refuse("Relation fields have the wrong shape", shape);
23178
+ const values = checkFieldValues(fields, body, registry);
23179
+ if (values.length > 0) return refuse("Some values are not valid", values);
23015
23180
  const required = checkRequiredInParagraphItems(fields, body, registry);
23016
23181
  if (required.length > 0) return refuse("Required fields are missing", required);
23017
23182
  if (db) {
@@ -23162,7 +23327,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23162
23327
  `/admin/api/${basePath}`,
23163
23328
  requirePermission("content:create"),
23164
23329
  async (c) => {
23165
- const body = await c.req.json();
23330
+ const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
23166
23331
  if (contentType.only_one) {
23167
23332
  const existing = await repo.findMany({ page: 1, per_page: 1 });
23168
23333
  if (existing.meta.total > 0) {
@@ -23289,7 +23454,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23289
23454
  requirePermission("content:edit"),
23290
23455
  async (c) => {
23291
23456
  const id = c.req.param("id");
23292
- const body = await c.req.json();
23457
+ const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
23293
23458
  const existing = await repo.findOne(id);
23294
23459
  if (!existing) {
23295
23460
  return c.json(
@@ -23396,7 +23561,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23396
23561
  `/admin/api/${basePath}`,
23397
23562
  requirePermission("content:edit"),
23398
23563
  async (c) => {
23399
- const body = await c.req.json();
23564
+ const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
23400
23565
  const existingResult = await repo.findMany({ page: 1, per_page: 1 });
23401
23566
  const existing = existingResult.data[0];
23402
23567
  if (!existing) {
@@ -23523,7 +23688,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23523
23688
  `/admin/api/taxonomy/${typeName}`,
23524
23689
  requirePermission("content:create"),
23525
23690
  async (c) => {
23526
- const body = await c.req.json();
23691
+ const body = emptyValuesToNull(taxonomyType.fields, await c.req.json(), registry);
23527
23692
  const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
23528
23693
  if (inputError) return inputError;
23529
23694
  const storageBody = fieldKeys.toStorage(body);
@@ -23571,7 +23736,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23571
23736
  requirePermission("content:edit"),
23572
23737
  async (c) => {
23573
23738
  const id = c.req.param("id");
23574
- const body = await c.req.json();
23739
+ const body = emptyValuesToNull(taxonomyType.fields, await c.req.json(), registry);
23575
23740
  const storageBody = fieldKeys.toStorage(body);
23576
23741
  const existing = await repo.findOne(id);
23577
23742
  if (!existing) {
@@ -24609,7 +24774,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
24609
24774
  }
24610
24775
  var API_VERSION = (() => {
24611
24776
  try {
24612
- return "0.8.4";
24777
+ return "0.9.1";
24613
24778
  } catch {
24614
24779
  return "0.0.0";
24615
24780
  }
@@ -25292,7 +25457,7 @@ function createCmsApp(options) {
25292
25457
  handler: null,
25293
25458
  error: null
25294
25459
  }));
25295
- const ready = import("./handler-VEOWJRAL-OU66YS5U.js").then(({ createGraphQLHandler }) => {
25460
+ const ready = import("./handler-MFF32JB4-MEMKPHHQ.js").then(({ createGraphQLHandler }) => {
25296
25461
  gqlPasses.forEach((pass, i) => {
25297
25462
  const mount = mounts[i];
25298
25463
  try {
@@ -25994,6 +26159,16 @@ function buildDeps(options, cwd) {
25994
26159
  prompt: createPromptAdapter()
25995
26160
  };
25996
26161
  }
26162
+ function printDestructiveOperations(scan) {
26163
+ process.stdout.write("\u26A0 This migration contains destructive operations:\n");
26164
+ for (const op of scan.operations) process.stdout.write(` - ${op.operation}
26165
+ `);
26166
+ process.stdout.write("\n");
26167
+ }
26168
+ async function readPendingMigrations(db, migrationsTable, migrationsFolder) {
26169
+ const { pending } = await getMigrationStatus(db.getDb(), { migrationsTable, migrationsFolder });
26170
+ return Promise.all(pending.map(async (name) => ({ name, sql: await readFile2(join12(migrationsFolder, name), "utf-8") })));
26171
+ }
25997
26172
  async function runMigrate(options, deps) {
25998
26173
  if (options.status) {
25999
26174
  loadEnvFile(options.env);
@@ -26059,19 +26234,39 @@ async function runMigrate(options, deps) {
26059
26234
  printSuccess("Build complete");
26060
26235
  const generatedDir = resolve8(cwd, "dist/generated");
26061
26236
  await generateDrizzleConfig(config2, generatedDir);
26237
+ if (options.dryRun) {
26238
+ const pending2 = await readPendingMigrations(deps.db, migrationsTable, migrationsFolder);
26239
+ const preview = await previewMigration(deps.configPath, migrationsFolder);
26240
+ const all = [...pending2, ...preview];
26241
+ if (pending2.length > 0) {
26242
+ printSuccess(`Generated earlier, not yet applied: ${pending2.map((m2) => m2.name).join(", ")}`);
26243
+ }
26244
+ printSuccess(
26245
+ preview.length === 0 ? "No new migration would be generated" : `Would generate ${preview.length} migration file${preview.length !== 1 ? "s" : ""}: ${preview.map((m2) => m2.name).join(", ")}`
26246
+ );
26247
+ process.stdout.write("\nDry run \u2014 no migration files written, no changes applied.\n\n");
26248
+ if (all.length > 0) {
26249
+ process.stdout.write("SQL `manguito migrate` would apply:\n");
26250
+ process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
26251
+ for (const m2 of all) process.stdout.write(`-- ${m2.name}
26252
+ ${m2.sql}
26253
+ `);
26254
+ process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
26255
+ const scan = scanMigrationSql(all);
26256
+ if (scan.hasDestructiveOperations) printDestructiveOperations(scan);
26257
+ else process.stdout.write("No destructive operations detected.\n\n");
26258
+ process.stdout.write("Run `manguito migrate` to generate and apply these.\n");
26259
+ }
26260
+ process.exit(0);
26261
+ }
26062
26262
  const newFileNames = await generateMigration(deps.configPath, migrationsFolder);
26063
- const newFilePaths = newFileNames.map((f) => join12(migrationsFolder, f));
26064
26263
  printSuccess(
26065
26264
  newFileNames.length === 0 ? "No new migration files generated" : `Generated ${newFileNames.length} migration file${newFileNames.length !== 1 ? "s" : ""}: ${newFileNames.join(", ")}`
26066
26265
  );
26067
- const scanResult = newFilePaths.length > 0 ? scanMigrationFiles(newFilePaths) : { hasDestructiveOperations: false, operations: [] };
26266
+ const { pending } = await getMigrationStatus(deps.db.getDb(), { migrationsTable, migrationsFolder });
26267
+ const scanResult = scanMigrationFiles(pending.map((f) => join12(migrationsFolder, f)));
26068
26268
  if (scanResult.hasDestructiveOperations) {
26069
- process.stdout.write("\u26A0 This migration contains destructive operations:\n");
26070
- for (const op of scanResult.operations) {
26071
- process.stdout.write(` - ${op.operation}
26072
- `);
26073
- }
26074
- process.stdout.write("\n");
26269
+ printDestructiveOperations(scanResult);
26075
26270
  process.stdout.write(" These changes are irreversible and may cause data loss.\n");
26076
26271
  const uniqueFiles = [...new Set(scanResult.operations.map((op) => op.file))];
26077
26272
  for (const file of uniqueFiles) {
@@ -26089,25 +26284,6 @@ async function runMigrate(options, deps) {
26089
26284
  }
26090
26285
  }
26091
26286
  }
26092
- if (options.dryRun) {
26093
- process.stdout.write("\nDry run \u2014 no files will be written, no changes applied.\n\n");
26094
- if (newFilePaths.length > 0) {
26095
- process.stdout.write("Generated SQL:\n");
26096
- process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
26097
- for (const filePath of newFilePaths) {
26098
- const sql2 = await readFile2(filePath, "utf-8");
26099
- process.stdout.write(sql2);
26100
- }
26101
- process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
26102
- } else {
26103
- process.stdout.write("No SQL changes generated.\n\n");
26104
- }
26105
- if (!scanResult.hasDestructiveOperations) {
26106
- process.stdout.write("No destructive operations detected.\n\n");
26107
- }
26108
- process.stdout.write("Run `manguito migrate` to generate and apply this migration.\n");
26109
- process.exit(0);
26110
- }
26111
26287
  const migrationResult = await applyMigrations(deps.configPath, deps.db.getDb(), {
26112
26288
  migrationsTable,
26113
26289
  migrationsFolder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "license": "MIT",
5
5
  "author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
6
6
  "homepage": "https://github.com/bobbykim89/manguito-cms",
@@ -29,10 +29,10 @@
29
29
  "tsup": "^8.5.1",
30
30
  "typescript": "^6.0.3",
31
31
  "vite": "^8.1.3",
32
- "@bobbykim/manguito-cms-admin": "0.5.3",
33
- "@bobbykim/manguito-cms-api": "0.8.4",
34
- "@bobbykim/manguito-cms-db": "0.1.9",
35
- "@bobbykim/manguito-cms-core": "0.9.0"
32
+ "@bobbykim/manguito-cms-admin": "0.5.4",
33
+ "@bobbykim/manguito-cms-api": "0.9.1",
34
+ "@bobbykim/manguito-cms-db": "0.2.0",
35
+ "@bobbykim/manguito-cms-core": "0.10.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",