@bobbykim/manguito-cms-cli 0.4.6 → 0.6.0

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 CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  __toESM,
23
23
  buildRelationsMap,
24
24
  createFieldKeyMap,
25
+ createFieldKeyMapFromProjection,
25
26
  deleteParagraphField,
26
27
  entityKind,
27
28
  fillPlaceholders,
@@ -38,7 +39,7 @@ import {
38
39
  resolveRelationField,
39
40
  sql,
40
41
  tracer
41
- } from "./chunk-LFMOJRYP.js";
42
+ } from "./chunk-STFSR5LP.js";
42
43
 
43
44
  // ../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
44
45
  var require_identity = __commonJS({
@@ -119,17 +120,17 @@ var require_visit = __commonJS({
119
120
  visit.BREAK = BREAK;
120
121
  visit.SKIP = SKIP;
121
122
  visit.REMOVE = REMOVE;
122
- function visit_(key, node, visitor, path4) {
123
- const ctrl = callVisitor(key, node, visitor, path4);
123
+ function visit_(key, node, visitor, path7) {
124
+ const ctrl = callVisitor(key, node, visitor, path7);
124
125
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
125
- replaceNode(key, path4, ctrl);
126
- return visit_(key, ctrl, visitor, path4);
126
+ replaceNode(key, path7, ctrl);
127
+ return visit_(key, ctrl, visitor, path7);
127
128
  }
128
129
  if (typeof ctrl !== "symbol") {
129
130
  if (identity.isCollection(node)) {
130
- path4 = Object.freeze(path4.concat(node));
131
+ path7 = Object.freeze(path7.concat(node));
131
132
  for (let i = 0; i < node.items.length; ++i) {
132
- const ci = visit_(i, node.items[i], visitor, path4);
133
+ const ci = visit_(i, node.items[i], visitor, path7);
133
134
  if (typeof ci === "number")
134
135
  i = ci - 1;
135
136
  else if (ci === BREAK)
@@ -140,13 +141,13 @@ var require_visit = __commonJS({
140
141
  }
141
142
  }
142
143
  } else if (identity.isPair(node)) {
143
- path4 = Object.freeze(path4.concat(node));
144
- const ck = visit_("key", node.key, visitor, path4);
144
+ path7 = Object.freeze(path7.concat(node));
145
+ const ck = visit_("key", node.key, visitor, path7);
145
146
  if (ck === BREAK)
146
147
  return BREAK;
147
148
  else if (ck === REMOVE)
148
149
  node.key = null;
149
- const cv = visit_("value", node.value, visitor, path4);
150
+ const cv = visit_("value", node.value, visitor, path7);
150
151
  if (cv === BREAK)
151
152
  return BREAK;
152
153
  else if (cv === REMOVE)
@@ -167,17 +168,17 @@ var require_visit = __commonJS({
167
168
  visitAsync.BREAK = BREAK;
168
169
  visitAsync.SKIP = SKIP;
169
170
  visitAsync.REMOVE = REMOVE;
170
- async function visitAsync_(key, node, visitor, path4) {
171
- const ctrl = await callVisitor(key, node, visitor, path4);
171
+ async function visitAsync_(key, node, visitor, path7) {
172
+ const ctrl = await callVisitor(key, node, visitor, path7);
172
173
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
173
- replaceNode(key, path4, ctrl);
174
- return visitAsync_(key, ctrl, visitor, path4);
174
+ replaceNode(key, path7, ctrl);
175
+ return visitAsync_(key, ctrl, visitor, path7);
175
176
  }
176
177
  if (typeof ctrl !== "symbol") {
177
178
  if (identity.isCollection(node)) {
178
- path4 = Object.freeze(path4.concat(node));
179
+ path7 = Object.freeze(path7.concat(node));
179
180
  for (let i = 0; i < node.items.length; ++i) {
180
- const ci = await visitAsync_(i, node.items[i], visitor, path4);
181
+ const ci = await visitAsync_(i, node.items[i], visitor, path7);
181
182
  if (typeof ci === "number")
182
183
  i = ci - 1;
183
184
  else if (ci === BREAK)
@@ -188,13 +189,13 @@ var require_visit = __commonJS({
188
189
  }
189
190
  }
190
191
  } else if (identity.isPair(node)) {
191
- path4 = Object.freeze(path4.concat(node));
192
- const ck = await visitAsync_("key", node.key, visitor, path4);
192
+ path7 = Object.freeze(path7.concat(node));
193
+ const ck = await visitAsync_("key", node.key, visitor, path7);
193
194
  if (ck === BREAK)
194
195
  return BREAK;
195
196
  else if (ck === REMOVE)
196
197
  node.key = null;
197
- const cv = await visitAsync_("value", node.value, visitor, path4);
198
+ const cv = await visitAsync_("value", node.value, visitor, path7);
198
199
  if (cv === BREAK)
199
200
  return BREAK;
200
201
  else if (cv === REMOVE)
@@ -221,23 +222,23 @@ var require_visit = __commonJS({
221
222
  }
222
223
  return visitor;
223
224
  }
224
- function callVisitor(key, node, visitor, path4) {
225
+ function callVisitor(key, node, visitor, path7) {
225
226
  if (typeof visitor === "function")
226
- return visitor(key, node, path4);
227
+ return visitor(key, node, path7);
227
228
  if (identity.isMap(node))
228
- return visitor.Map?.(key, node, path4);
229
+ return visitor.Map?.(key, node, path7);
229
230
  if (identity.isSeq(node))
230
- return visitor.Seq?.(key, node, path4);
231
+ return visitor.Seq?.(key, node, path7);
231
232
  if (identity.isPair(node))
232
- return visitor.Pair?.(key, node, path4);
233
+ return visitor.Pair?.(key, node, path7);
233
234
  if (identity.isScalar(node))
234
- return visitor.Scalar?.(key, node, path4);
235
+ return visitor.Scalar?.(key, node, path7);
235
236
  if (identity.isAlias(node))
236
- return visitor.Alias?.(key, node, path4);
237
+ return visitor.Alias?.(key, node, path7);
237
238
  return void 0;
238
239
  }
239
- function replaceNode(key, path4, node) {
240
- const parent = path4[path4.length - 1];
240
+ function replaceNode(key, path7, node) {
241
+ const parent = path7[path7.length - 1];
241
242
  if (identity.isCollection(parent)) {
242
243
  parent.items[key] = node;
243
244
  } else if (identity.isPair(parent)) {
@@ -856,10 +857,10 @@ var require_Collection = __commonJS({
856
857
  var createNode = require_createNode();
857
858
  var identity = require_identity();
858
859
  var Node3 = require_Node();
859
- function collectionFromPath(schema, path4, value) {
860
+ function collectionFromPath(schema, path7, value) {
860
861
  let v2 = value;
861
- for (let i = path4.length - 1; i >= 0; --i) {
862
- const k = path4[i];
862
+ for (let i = path7.length - 1; i >= 0; --i) {
863
+ const k = path7[i];
863
864
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
864
865
  const a2 = [];
865
866
  a2[k] = v2;
@@ -878,7 +879,7 @@ var require_Collection = __commonJS({
878
879
  sourceObjects: /* @__PURE__ */ new Map()
879
880
  });
880
881
  }
881
- var isEmptyPath = (path4) => path4 == null || typeof path4 === "object" && !!path4[Symbol.iterator]().next().done;
882
+ var isEmptyPath = (path7) => path7 == null || typeof path7 === "object" && !!path7[Symbol.iterator]().next().done;
882
883
  var Collection = class extends Node3.NodeBase {
883
884
  constructor(type, schema) {
884
885
  super(type);
@@ -908,11 +909,11 @@ var require_Collection = __commonJS({
908
909
  * be a Pair instance or a `{ key, value }` object, which may not have a key
909
910
  * that already exists in the map.
910
911
  */
911
- addIn(path4, value) {
912
- if (isEmptyPath(path4))
912
+ addIn(path7, value) {
913
+ if (isEmptyPath(path7))
913
914
  this.add(value);
914
915
  else {
915
- const [key, ...rest] = path4;
916
+ const [key, ...rest] = path7;
916
917
  const node = this.get(key, true);
917
918
  if (identity.isCollection(node))
918
919
  node.addIn(rest, value);
@@ -926,8 +927,8 @@ var require_Collection = __commonJS({
926
927
  * Removes a value from the collection.
927
928
  * @returns `true` if the item was found and removed.
928
929
  */
929
- deleteIn(path4) {
930
- const [key, ...rest] = path4;
930
+ deleteIn(path7) {
931
+ const [key, ...rest] = path7;
931
932
  if (rest.length === 0)
932
933
  return this.delete(key);
933
934
  const node = this.get(key, true);
@@ -941,8 +942,8 @@ var require_Collection = __commonJS({
941
942
  * scalar values from their surrounding node; to disable set `keepScalar` to
942
943
  * `true` (collections are always returned intact).
943
944
  */
944
- getIn(path4, keepScalar) {
945
- const [key, ...rest] = path4;
945
+ getIn(path7, keepScalar) {
946
+ const [key, ...rest] = path7;
946
947
  const node = this.get(key, true);
947
948
  if (rest.length === 0)
948
949
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -960,8 +961,8 @@ var require_Collection = __commonJS({
960
961
  /**
961
962
  * Checks if the collection includes a value with the key `key`.
962
963
  */
963
- hasIn(path4) {
964
- const [key, ...rest] = path4;
964
+ hasIn(path7) {
965
+ const [key, ...rest] = path7;
965
966
  if (rest.length === 0)
966
967
  return this.has(key);
967
968
  const node = this.get(key, true);
@@ -971,8 +972,8 @@ var require_Collection = __commonJS({
971
972
  * Sets a value in this collection. For `!!set`, `value` needs to be a
972
973
  * boolean to add/remove the item from the set.
973
974
  */
974
- setIn(path4, value) {
975
- const [key, ...rest] = path4;
975
+ setIn(path7, value) {
976
+ const [key, ...rest] = path7;
976
977
  if (rest.length === 0) {
977
978
  this.set(key, value);
978
979
  } else {
@@ -3522,9 +3523,9 @@ var require_Document = __commonJS({
3522
3523
  this.contents.add(value);
3523
3524
  }
3524
3525
  /** Adds a value to the document. */
3525
- addIn(path4, value) {
3526
+ addIn(path7, value) {
3526
3527
  if (assertCollection(this.contents))
3527
- this.contents.addIn(path4, value);
3528
+ this.contents.addIn(path7, value);
3528
3529
  }
3529
3530
  /**
3530
3531
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -3599,14 +3600,14 @@ var require_Document = __commonJS({
3599
3600
  * Removes a value from the document.
3600
3601
  * @returns `true` if the item was found and removed.
3601
3602
  */
3602
- deleteIn(path4) {
3603
- if (Collection.isEmptyPath(path4)) {
3603
+ deleteIn(path7) {
3604
+ if (Collection.isEmptyPath(path7)) {
3604
3605
  if (this.contents == null)
3605
3606
  return false;
3606
3607
  this.contents = null;
3607
3608
  return true;
3608
3609
  }
3609
- return assertCollection(this.contents) ? this.contents.deleteIn(path4) : false;
3610
+ return assertCollection(this.contents) ? this.contents.deleteIn(path7) : false;
3610
3611
  }
3611
3612
  /**
3612
3613
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -3621,10 +3622,10 @@ var require_Document = __commonJS({
3621
3622
  * scalar values from their surrounding node; to disable set `keepScalar` to
3622
3623
  * `true` (collections are always returned intact).
3623
3624
  */
3624
- getIn(path4, keepScalar) {
3625
- if (Collection.isEmptyPath(path4))
3625
+ getIn(path7, keepScalar) {
3626
+ if (Collection.isEmptyPath(path7))
3626
3627
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3627
- return identity.isCollection(this.contents) ? this.contents.getIn(path4, keepScalar) : void 0;
3628
+ return identity.isCollection(this.contents) ? this.contents.getIn(path7, keepScalar) : void 0;
3628
3629
  }
3629
3630
  /**
3630
3631
  * Checks if the document includes a value with the key `key`.
@@ -3635,10 +3636,10 @@ var require_Document = __commonJS({
3635
3636
  /**
3636
3637
  * Checks if the document includes a value at `path`.
3637
3638
  */
3638
- hasIn(path4) {
3639
- if (Collection.isEmptyPath(path4))
3639
+ hasIn(path7) {
3640
+ if (Collection.isEmptyPath(path7))
3640
3641
  return this.contents !== void 0;
3641
- return identity.isCollection(this.contents) ? this.contents.hasIn(path4) : false;
3642
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path7) : false;
3642
3643
  }
3643
3644
  /**
3644
3645
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -3655,13 +3656,13 @@ var require_Document = __commonJS({
3655
3656
  * Sets a value in this document. For `!!set`, `value` needs to be a
3656
3657
  * boolean to add/remove the item from the set.
3657
3658
  */
3658
- setIn(path4, value) {
3659
- if (Collection.isEmptyPath(path4)) {
3659
+ setIn(path7, value) {
3660
+ if (Collection.isEmptyPath(path7)) {
3660
3661
  this.contents = value;
3661
3662
  } else if (this.contents == null) {
3662
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path4), value);
3663
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path7), value);
3663
3664
  } else if (assertCollection(this.contents)) {
3664
- this.contents.setIn(path4, value);
3665
+ this.contents.setIn(path7, value);
3665
3666
  }
3666
3667
  }
3667
3668
  /**
@@ -5641,9 +5642,9 @@ var require_cst_visit = __commonJS({
5641
5642
  visit.BREAK = BREAK;
5642
5643
  visit.SKIP = SKIP;
5643
5644
  visit.REMOVE = REMOVE;
5644
- visit.itemAtPath = (cst, path4) => {
5645
+ visit.itemAtPath = (cst, path7) => {
5645
5646
  let item = cst;
5646
- for (const [field, index] of path4) {
5647
+ for (const [field, index] of path7) {
5647
5648
  const tok = item?.[field];
5648
5649
  if (tok && "items" in tok) {
5649
5650
  item = tok.items[index];
@@ -5652,23 +5653,23 @@ var require_cst_visit = __commonJS({
5652
5653
  }
5653
5654
  return item;
5654
5655
  };
5655
- visit.parentCollection = (cst, path4) => {
5656
- const parent = visit.itemAtPath(cst, path4.slice(0, -1));
5657
- const field = path4[path4.length - 1][0];
5656
+ visit.parentCollection = (cst, path7) => {
5657
+ const parent = visit.itemAtPath(cst, path7.slice(0, -1));
5658
+ const field = path7[path7.length - 1][0];
5658
5659
  const coll = parent?.[field];
5659
5660
  if (coll && "items" in coll)
5660
5661
  return coll;
5661
5662
  throw new Error("Parent collection not found");
5662
5663
  };
5663
- function _visit(path4, item, visitor) {
5664
- let ctrl = visitor(item, path4);
5664
+ function _visit(path7, item, visitor) {
5665
+ let ctrl = visitor(item, path7);
5665
5666
  if (typeof ctrl === "symbol")
5666
5667
  return ctrl;
5667
5668
  for (const field of ["key", "value"]) {
5668
5669
  const token = item[field];
5669
5670
  if (token && "items" in token) {
5670
5671
  for (let i = 0; i < token.items.length; ++i) {
5671
- const ci = _visit(Object.freeze(path4.concat([[field, i]])), token.items[i], visitor);
5672
+ const ci = _visit(Object.freeze(path7.concat([[field, i]])), token.items[i], visitor);
5672
5673
  if (typeof ci === "number")
5673
5674
  i = ci - 1;
5674
5675
  else if (ci === BREAK)
@@ -5679,10 +5680,10 @@ var require_cst_visit = __commonJS({
5679
5680
  }
5680
5681
  }
5681
5682
  if (typeof ctrl === "function" && field === "key")
5682
- ctrl = ctrl(item, path4);
5683
+ ctrl = ctrl(item, path7);
5683
5684
  }
5684
5685
  }
5685
- return typeof ctrl === "function" ? ctrl(item, path4) : ctrl;
5686
+ return typeof ctrl === "function" ? ctrl(item, path7) : ctrl;
5686
5687
  }
5687
5688
  exports.visit = visit;
5688
5689
  }
@@ -6988,14 +6989,14 @@ var require_parser = __commonJS({
6988
6989
  case "scalar":
6989
6990
  case "single-quoted-scalar":
6990
6991
  case "double-quoted-scalar": {
6991
- const fs4 = this.flowScalar(this.type);
6992
+ const fs7 = this.flowScalar(this.type);
6992
6993
  if (atNextItem || it2.value) {
6993
- map2.items.push({ start, key: fs4, sep: [] });
6994
+ map2.items.push({ start, key: fs7, sep: [] });
6994
6995
  this.onKeyLine = true;
6995
6996
  } else if (it2.sep) {
6996
- this.stack.push(fs4);
6997
+ this.stack.push(fs7);
6997
6998
  } else {
6998
- Object.assign(it2, { key: fs4, sep: [] });
6999
+ Object.assign(it2, { key: fs7, sep: [] });
6999
7000
  this.onKeyLine = true;
7000
7001
  }
7001
7002
  return;
@@ -7123,13 +7124,13 @@ var require_parser = __commonJS({
7123
7124
  case "scalar":
7124
7125
  case "single-quoted-scalar":
7125
7126
  case "double-quoted-scalar": {
7126
- const fs4 = this.flowScalar(this.type);
7127
+ const fs7 = this.flowScalar(this.type);
7127
7128
  if (!it2 || it2.value)
7128
- fc.items.push({ start: [], key: fs4, sep: [] });
7129
+ fc.items.push({ start: [], key: fs7, sep: [] });
7129
7130
  else if (it2.sep)
7130
- this.stack.push(fs4);
7131
+ this.stack.push(fs7);
7131
7132
  else
7132
- Object.assign(it2, { key: fs4, sep: [] });
7133
+ Object.assign(it2, { key: fs7, sep: [] });
7133
7134
  return;
7134
7135
  }
7135
7136
  case "flow-map-end":
@@ -7521,13 +7522,13 @@ var require_bcrypt = __commonJS({
7521
7522
  throw Error("Illegal callback: " + typeof callback);
7522
7523
  _async(callback);
7523
7524
  } else
7524
- return new Promise(function(resolve10, reject) {
7525
+ return new Promise(function(resolve12, reject) {
7525
7526
  _async(function(err, res) {
7526
7527
  if (err) {
7527
7528
  reject(err);
7528
7529
  return;
7529
7530
  }
7530
- resolve10(res);
7531
+ resolve12(res);
7531
7532
  });
7532
7533
  });
7533
7534
  };
@@ -7556,13 +7557,13 @@ var require_bcrypt = __commonJS({
7556
7557
  throw Error("Illegal callback: " + typeof callback);
7557
7558
  _async(callback);
7558
7559
  } else
7559
- return new Promise(function(resolve10, reject) {
7560
+ return new Promise(function(resolve12, reject) {
7560
7561
  _async(function(err, res) {
7561
7562
  if (err) {
7562
7563
  reject(err);
7563
7564
  return;
7564
7565
  }
7565
- resolve10(res);
7566
+ resolve12(res);
7566
7567
  });
7567
7568
  });
7568
7569
  };
@@ -7607,13 +7608,13 @@ var require_bcrypt = __commonJS({
7607
7608
  throw Error("Illegal callback: " + typeof callback);
7608
7609
  _async(callback);
7609
7610
  } else
7610
- return new Promise(function(resolve10, reject) {
7611
+ return new Promise(function(resolve12, reject) {
7611
7612
  _async(function(err, res) {
7612
7613
  if (err) {
7613
7614
  reject(err);
7614
7615
  return;
7615
7616
  }
7616
- resolve10(res);
7617
+ resolve12(res);
7617
7618
  });
7618
7619
  });
7619
7620
  };
@@ -10880,15 +10881,15 @@ var require_pg_connection_string = __commonJS({
10880
10881
  if (config3.sslnegotiation === "direct" && config3.ssl === void 0) {
10881
10882
  config3.ssl = true;
10882
10883
  }
10883
- const fs4 = config3.sslcert || config3.sslkey || config3.sslrootcert ? __require("fs") : null;
10884
+ const fs7 = config3.sslcert || config3.sslkey || config3.sslrootcert ? __require("fs") : null;
10884
10885
  if (config3.sslcert) {
10885
- config3.ssl.cert = fs4.readFileSync(config3.sslcert).toString();
10886
+ config3.ssl.cert = fs7.readFileSync(config3.sslcert).toString();
10886
10887
  }
10887
10888
  if (config3.sslkey) {
10888
- config3.ssl.key = fs4.readFileSync(config3.sslkey).toString();
10889
+ config3.ssl.key = fs7.readFileSync(config3.sslkey).toString();
10889
10890
  }
10890
10891
  if (config3.sslrootcert) {
10891
- config3.ssl.ca = fs4.readFileSync(config3.sslrootcert).toString();
10892
+ config3.ssl.ca = fs7.readFileSync(config3.sslrootcert).toString();
10892
10893
  }
10893
10894
  if (options.useLibpqCompat && config3.uselibpqcompat) {
10894
10895
  throw new Error("Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.");
@@ -12334,7 +12335,7 @@ var require_dist2 = __commonJS({
12334
12335
  function parse4(stream, callback) {
12335
12336
  const parser = new parser_1.Parser();
12336
12337
  stream.on("data", (buffer) => parser.parse(buffer, callback));
12337
- return new Promise((resolve10) => stream.on("end", () => resolve10()));
12338
+ return new Promise((resolve12) => stream.on("end", () => resolve12()));
12338
12339
  }
12339
12340
  }
12340
12341
  });
@@ -12722,7 +12723,7 @@ var require_helper = __commonJS({
12722
12723
  "../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/helper.js"(exports, module) {
12723
12724
  "use strict";
12724
12725
  init_esm_shims();
12725
- var path4 = __require("path");
12726
+ var path7 = __require("path");
12726
12727
  var Stream = __require("stream").Stream;
12727
12728
  var split = require_split2();
12728
12729
  var util = __require("util");
@@ -12761,7 +12762,7 @@ var require_helper = __commonJS({
12761
12762
  };
12762
12763
  module.exports.getFileName = function(rawEnv) {
12763
12764
  var env = rawEnv || process.env;
12764
- var file2 = env.PGPASSFILE || (isWin ? path4.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path4.join(env.HOME || "./", ".pgpass"));
12765
+ var file2 = env.PGPASSFILE || (isWin ? path7.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path7.join(env.HOME || "./", ".pgpass"));
12765
12766
  return file2;
12766
12767
  };
12767
12768
  module.exports.usePgPass = function(stats, fname) {
@@ -12894,16 +12895,16 @@ var require_lib = __commonJS({
12894
12895
  "../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js"(exports, module) {
12895
12896
  "use strict";
12896
12897
  init_esm_shims();
12897
- var path4 = __require("path");
12898
- var fs4 = __require("fs");
12898
+ var path7 = __require("path");
12899
+ var fs7 = __require("fs");
12899
12900
  var helper = require_helper();
12900
12901
  module.exports = function(connInfo, cb) {
12901
12902
  var file2 = helper.getFileName();
12902
- fs4.stat(file2, function(err, stat) {
12903
+ fs7.stat(file2, function(err, stat) {
12903
12904
  if (err || !helper.usePgPass(stat, file2)) {
12904
12905
  return cb(void 0);
12905
12906
  }
12906
- var st = fs4.createReadStream(file2);
12907
+ var st = fs7.createReadStream(file2);
12907
12908
  helper.getPassword(connInfo, st, cb);
12908
12909
  });
12909
12910
  };
@@ -13103,12 +13104,12 @@ var require_client = __commonJS({
13103
13104
  this._connect(callback);
13104
13105
  return;
13105
13106
  }
13106
- return new this._Promise((resolve10, reject) => {
13107
+ return new this._Promise((resolve12, reject) => {
13107
13108
  this._connect((error51) => {
13108
13109
  if (error51) {
13109
13110
  reject(error51);
13110
13111
  } else {
13111
- resolve10(this);
13112
+ resolve12(this);
13112
13113
  }
13113
13114
  });
13114
13115
  });
@@ -13455,8 +13456,8 @@ var require_client = __commonJS({
13455
13456
  } else {
13456
13457
  query = new Query2(config3, values, callback);
13457
13458
  if (!query.callback) {
13458
- result = new this._Promise((resolve10, reject) => {
13459
- query.callback = (err, res) => err ? reject(err) : resolve10(res);
13459
+ result = new this._Promise((resolve12, reject) => {
13460
+ query.callback = (err, res) => err ? reject(err) : resolve12(res);
13460
13461
  }).catch((err) => {
13461
13462
  Error.captureStackTrace(err);
13462
13463
  throw err;
@@ -13540,8 +13541,8 @@ var require_client = __commonJS({
13540
13541
  if (cb) {
13541
13542
  this.connection.once("end", cb);
13542
13543
  } else {
13543
- return new this._Promise((resolve10) => {
13544
- this.connection.once("end", resolve10);
13544
+ return new this._Promise((resolve12) => {
13545
+ this.connection.once("end", resolve12);
13545
13546
  });
13546
13547
  }
13547
13548
  }
@@ -13591,8 +13592,8 @@ var require_pg_pool = __commonJS({
13591
13592
  const cb = function(err, client) {
13592
13593
  err ? rej(err) : res(client);
13593
13594
  };
13594
- const result = new Promise2(function(resolve10, reject) {
13595
- res = resolve10;
13595
+ const result = new Promise2(function(resolve12, reject) {
13596
+ res = resolve12;
13596
13597
  rej = reject;
13597
13598
  }).catch((err) => {
13598
13599
  Error.captureStackTrace(err);
@@ -13653,7 +13654,7 @@ var require_pg_pool = __commonJS({
13653
13654
  if (typeof Promise2.try === "function") {
13654
13655
  return Promise2.try(f);
13655
13656
  }
13656
- return new Promise2((resolve10) => resolve10(f()));
13657
+ return new Promise2((resolve12) => resolve12(f()));
13657
13658
  }
13658
13659
  _isFull() {
13659
13660
  return this._clients.length >= this.options.max;
@@ -14047,8 +14048,8 @@ var require_query2 = __commonJS({
14047
14048
  NativeQuery.prototype._getPromise = function() {
14048
14049
  if (this._promise) return this._promise;
14049
14050
  this._promise = new Promise(
14050
- function(resolve10, reject) {
14051
- this._once("end", resolve10);
14051
+ function(resolve12, reject) {
14052
+ this._once("end", resolve12);
14052
14053
  this._once("error", reject);
14053
14054
  }.bind(this)
14054
14055
  );
@@ -14227,12 +14228,12 @@ var require_client2 = __commonJS({
14227
14228
  this._connect(callback);
14228
14229
  return;
14229
14230
  }
14230
- return new this._Promise((resolve10, reject) => {
14231
+ return new this._Promise((resolve12, reject) => {
14231
14232
  this._connect((error51) => {
14232
14233
  if (error51) {
14233
14234
  reject(error51);
14234
14235
  } else {
14235
- resolve10(this);
14236
+ resolve12(this);
14236
14237
  }
14237
14238
  });
14238
14239
  });
@@ -14256,8 +14257,8 @@ var require_client2 = __commonJS({
14256
14257
  query = new NativeQuery(config3, values, callback);
14257
14258
  if (!query.callback) {
14258
14259
  let resolveOut, rejectOut;
14259
- result = new this._Promise((resolve10, reject) => {
14260
- resolveOut = resolve10;
14260
+ result = new this._Promise((resolve12, reject) => {
14261
+ resolveOut = resolve12;
14261
14262
  rejectOut = reject;
14262
14263
  }).catch((err) => {
14263
14264
  Error.captureStackTrace(err);
@@ -14320,8 +14321,8 @@ var require_client2 = __commonJS({
14320
14321
  }
14321
14322
  let result;
14322
14323
  if (!cb) {
14323
- result = new this._Promise(function(resolve10, reject) {
14324
- cb = (err) => err ? reject(err) : resolve10();
14324
+ result = new this._Promise(function(resolve12, reject) {
14325
+ cb = (err) => err ? reject(err) : resolve12();
14325
14326
  });
14326
14327
  }
14327
14328
  this.native.end(function() {
@@ -14468,8 +14469,8 @@ import { Command } from "commander";
14468
14469
 
14469
14470
  // src/commands/build.ts
14470
14471
  init_esm_shims();
14471
- import { mkdirSync, statSync as statSync2, existsSync as existsSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
14472
- import { resolve as resolve3, join as join7, dirname } from "path";
14472
+ import { mkdirSync, statSync as statSync2, existsSync as existsSync3, readdirSync as readdirSync3, writeFileSync } from "fs";
14473
+ import { resolve as resolve4, join as join9, dirname } from "path";
14473
14474
  import { createRequire } from "module";
14474
14475
  import { build as viteBuild } from "vite";
14475
14476
  import { build as tsupBuild } from "tsup";
@@ -15256,10 +15257,10 @@ function mergeDefs(...defs) {
15256
15257
  function cloneDef(schema) {
15257
15258
  return mergeDefs(schema._zod.def);
15258
15259
  }
15259
- function getElementAtPath(obj, path4) {
15260
- if (!path4)
15260
+ function getElementAtPath(obj, path7) {
15261
+ if (!path7)
15261
15262
  return obj;
15262
- return path4.reduce((acc, key) => acc?.[key], obj);
15263
+ return path7.reduce((acc, key) => acc?.[key], obj);
15263
15264
  }
15264
15265
  function promiseAllObject(promisesObj) {
15265
15266
  const keys = Object.keys(promisesObj);
@@ -15668,11 +15669,11 @@ function explicitlyAborted(x2, startIndex = 0) {
15668
15669
  }
15669
15670
  return false;
15670
15671
  }
15671
- function prefixIssues(path4, issues) {
15672
+ function prefixIssues(path7, issues) {
15672
15673
  return issues.map((iss) => {
15673
15674
  var _a3;
15674
15675
  (_a3 = iss).path ?? (_a3.path = []);
15675
- iss.path.unshift(path4);
15676
+ iss.path.unshift(path7);
15676
15677
  return iss;
15677
15678
  });
15678
15679
  }
@@ -15819,16 +15820,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
15819
15820
  }
15820
15821
  function formatError(error51, mapper = (issue2) => issue2.message) {
15821
15822
  const fieldErrors = { _errors: [] };
15822
- const processError = (error52, path4 = []) => {
15823
+ const processError = (error52, path7 = []) => {
15823
15824
  for (const issue2 of error52.issues) {
15824
15825
  if (issue2.code === "invalid_union" && issue2.errors.length) {
15825
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
15826
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
15826
15827
  } else if (issue2.code === "invalid_key") {
15827
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
15828
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
15828
15829
  } else if (issue2.code === "invalid_element") {
15829
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
15830
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
15830
15831
  } else {
15831
- const fullpath = [...path4, ...issue2.path];
15832
+ const fullpath = [...path7, ...issue2.path];
15832
15833
  if (fullpath.length === 0) {
15833
15834
  fieldErrors._errors.push(mapper(issue2));
15834
15835
  } else {
@@ -15855,17 +15856,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
15855
15856
  }
15856
15857
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
15857
15858
  const result = { errors: [] };
15858
- const processError = (error52, path4 = []) => {
15859
+ const processError = (error52, path7 = []) => {
15859
15860
  var _a3, _b;
15860
15861
  for (const issue2 of error52.issues) {
15861
15862
  if (issue2.code === "invalid_union" && issue2.errors.length) {
15862
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
15863
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
15863
15864
  } else if (issue2.code === "invalid_key") {
15864
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
15865
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
15865
15866
  } else if (issue2.code === "invalid_element") {
15866
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
15867
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
15867
15868
  } else {
15868
- const fullpath = [...path4, ...issue2.path];
15869
+ const fullpath = [...path7, ...issue2.path];
15869
15870
  if (fullpath.length === 0) {
15870
15871
  result.errors.push(mapper(issue2));
15871
15872
  continue;
@@ -15897,8 +15898,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
15897
15898
  }
15898
15899
  function toDotPath(_path) {
15899
15900
  const segs = [];
15900
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15901
- for (const seg of path4) {
15901
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15902
+ for (const seg of path7) {
15902
15903
  if (typeof seg === "number")
15903
15904
  segs.push(`[${seg}]`);
15904
15905
  else if (typeof seg === "symbol")
@@ -28671,13 +28672,13 @@ function resolveRef(ref, ctx) {
28671
28672
  if (!ref.startsWith("#")) {
28672
28673
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
28673
28674
  }
28674
- const path4 = ref.slice(1).split("/").filter(Boolean);
28675
- if (path4.length === 0) {
28675
+ const path7 = ref.slice(1).split("/").filter(Boolean);
28676
+ if (path7.length === 0) {
28676
28677
  return ctx.rootSchema;
28677
28678
  }
28678
28679
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
28679
- if (path4[0] === defsKey) {
28680
- const key = path4[1];
28680
+ if (path7[0] === defsKey) {
28681
+ const key = path7[1];
28681
28682
  if (!key || !ctx.defs[key]) {
28682
28683
  throw new Error(`Reference not found: ${ref}`);
28683
28684
  }
@@ -29093,6 +29094,8 @@ import * as path2 from "path";
29093
29094
  import * as fs from "fs";
29094
29095
  import * as path from "path";
29095
29096
  var import_bcryptjs = __toESM(require_bcryptjs(), 1);
29097
+ import * as fs3 from "fs";
29098
+ import * as path3 from "path";
29096
29099
  var snakeCaseName = external_exports.string().regex(/^[a-z][a-z0-9_]*$/, 'Must be snake_case (e.g. "blog_post")');
29097
29100
  var maxSizeString = external_exports.string().regex(
29098
29101
  /^\d+(\.\d+)?\s*(B|KB|MB|GB)$/i,
@@ -29839,8 +29842,8 @@ function getNameSegment(machineName) {
29839
29842
  const idx = machineName.indexOf("--");
29840
29843
  return idx === -1 ? machineName : machineName.slice(idx + 2);
29841
29844
  }
29842
- function zodPathToString(path4) {
29843
- return path4.reduce((acc, seg) => {
29845
+ function zodPathToString(path42) {
29846
+ return path42.reduce((acc, seg) => {
29844
29847
  const s = typeof seg === "symbol" ? String(seg) : seg;
29845
29848
  return typeof s === "number" ? `${acc}[${s}]` : acc ? `${acc}.${s}` : s;
29846
29849
  }, "");
@@ -30261,6 +30264,342 @@ async function hashPassword(password2) {
30261
30264
  async function verifyPassword(password2, stored) {
30262
30265
  return import_bcryptjs.default.compare(password2, stored);
30263
30266
  }
30267
+ var STRUCTURAL_KINDS = ["content_types", "taxonomy_types"];
30268
+ function typeEntries(registry2, kinds = STRUCTURAL_KINDS) {
30269
+ const out = [];
30270
+ for (const kind of kinds) {
30271
+ for (const [name, type] of Object.entries(registry2[kind])) {
30272
+ out.push([name, type.fields]);
30273
+ }
30274
+ }
30275
+ return out;
30276
+ }
30277
+ function fieldsByColumn(registry2, kinds = STRUCTURAL_KINDS) {
30278
+ const out = /* @__PURE__ */ new Map();
30279
+ for (const [typeName, fields] of typeEntries(registry2, kinds)) {
30280
+ const byColumn2 = /* @__PURE__ */ new Map();
30281
+ for (const f of fields) {
30282
+ if (!isColumnBacked2(f)) continue;
30283
+ byColumn2.set(f.db_column.column_name, f);
30284
+ }
30285
+ out.set(typeName, byColumn2);
30286
+ }
30287
+ return out;
30288
+ }
30289
+ function checkUnionCompleteness(input2) {
30290
+ const { union: union3, projections, currentVersion, snapshots } = input2;
30291
+ const errors = [];
30292
+ const unionColumns = fieldsByColumn(union3);
30293
+ for (const [version3, projection] of Object.entries(projections)) {
30294
+ if (version3 === currentVersion) continue;
30295
+ for (const [typeName, type] of Object.entries(projection.types)) {
30296
+ const columns = unionColumns.get(typeName);
30297
+ if (columns === void 0) {
30298
+ errors.push({
30299
+ file: `schemas/versions/${version3}`,
30300
+ code: "VERSION_COLUMN_MISSING",
30301
+ message: `Live version ${version3} exposes type "${typeName}", which the current schema no longer defines. Every column a live version serves must still exist. Keep "${typeName}" in the current schema with its fields marked "removed": true, or retire it with \`manguito version:retire ${version3}\` \u2014 which will refuse if ${version3} is the newest cut version.`
30302
+ });
30303
+ continue;
30304
+ }
30305
+ for (const f of type.fields) {
30306
+ if (columns.has(f.column_name)) continue;
30307
+ errors.push({
30308
+ file: `schemas/versions/${version3}`,
30309
+ code: "VERSION_COLUMN_MISSING",
30310
+ message: `Live version ${version3} exposes column "${f.column_name}" on "${typeName}" (as "${f.exposed_as}"), which the current schema neither exposes nor retains. Either add "column": "${f.column_name}" to the field that replaced it, or add a field { "name": "${f.exposed_as}", "column": "${f.column_name}", "removed": true } to retain the column while ${version3} is live \u2014 use a different "name" if "${f.exposed_as}" is still a live field.`
30311
+ });
30312
+ }
30313
+ }
30314
+ }
30315
+ const paragraphColumns = fieldsByColumn(union3, ["paragraph_types"]);
30316
+ for (const snap of snapshots) {
30317
+ for (const [typeName, fields] of typeEntries(snap.registry, ["paragraph_types"])) {
30318
+ const columns = paragraphColumns.get(typeName);
30319
+ if (columns === void 0) {
30320
+ errors.push({
30321
+ file: `schemas/versions/${snap.version}`,
30322
+ code: "VERSION_COLUMN_MISSING",
30323
+ message: `Live version ${snap.version} exposes paragraph type "${typeName}", which the current schema no longer defines. Every column a live version serves must still exist. Keep "${typeName}" in the current schema with its fields marked "removed": true, or retire it with \`manguito version:retire ${snap.version}\` \u2014 which will refuse if ${snap.version} is the newest cut version.`
30324
+ });
30325
+ continue;
30326
+ }
30327
+ for (const f of fields) {
30328
+ if (!isColumnBacked2(f) || f.removed === true) continue;
30329
+ const column = f.db_column.column_name;
30330
+ if (columns.has(column)) continue;
30331
+ errors.push({
30332
+ file: `schemas/versions/${snap.version}`,
30333
+ code: "VERSION_COLUMN_MISSING",
30334
+ message: `Live version ${snap.version} exposes column "${column}" on paragraph type "${typeName}" (as "${f.name}"), which the current schema neither exposes nor retains. Either add "column": "${column}" to the field that replaced it, or add a field { "name": "${f.name}", "column": "${column}", "removed": true } to retain the column while ${snap.version} is live \u2014 use a different "name" if "${f.name}" is still a live field.`
30335
+ });
30336
+ }
30337
+ }
30338
+ }
30339
+ return errors;
30340
+ }
30341
+ function checkOrphanedTombstones(input2) {
30342
+ const { current, projections, currentVersion, snapshots } = input2;
30343
+ const errors = [];
30344
+ const exposed = /* @__PURE__ */ new Set();
30345
+ for (const [version3, projection] of Object.entries(projections)) {
30346
+ if (version3 === currentVersion) continue;
30347
+ for (const [typeName, type] of Object.entries(projection.types)) {
30348
+ for (const f of type.fields) exposed.add(`${typeName}.${f.column_name}`);
30349
+ }
30350
+ }
30351
+ for (const [typeName, fields] of typeEntries(current)) {
30352
+ for (const f of fields) {
30353
+ if (f.removed !== true || !isColumnBacked2(f)) continue;
30354
+ if (exposed.has(`${typeName}.${f.db_column.column_name}`)) continue;
30355
+ errors.push({
30356
+ file: current.schemas[typeName]?.source_file ?? "",
30357
+ code: "ORPHANED_TOMBSTONE",
30358
+ message: `Field "${f.name}" on "${typeName}" is marked "removed": true, retaining column "${f.db_column.column_name}", but no live version exposes that column any more. It is a column nothing can read. Delete the field \u2014 that shrinks the union and lets db codegen drop the column.`
30359
+ });
30360
+ }
30361
+ }
30362
+ const paragraphExposed = /* @__PURE__ */ new Set();
30363
+ for (const snap of snapshots) {
30364
+ for (const [typeName, fields] of typeEntries(snap.registry, ["paragraph_types"])) {
30365
+ for (const f of fields) {
30366
+ if (!isColumnBacked2(f) || f.removed === true) continue;
30367
+ paragraphExposed.add(`${typeName}.${f.db_column.column_name}`);
30368
+ }
30369
+ }
30370
+ }
30371
+ for (const [typeName, fields] of typeEntries(current, ["paragraph_types"])) {
30372
+ for (const f of fields) {
30373
+ if (f.removed !== true || !isColumnBacked2(f)) continue;
30374
+ if (paragraphExposed.has(`${typeName}.${f.db_column.column_name}`)) continue;
30375
+ errors.push({
30376
+ file: current.schemas[typeName]?.source_file ?? "",
30377
+ code: "ORPHANED_TOMBSTONE",
30378
+ message: `Field "${f.name}" on paragraph type "${typeName}" is marked "removed": true, retaining column "${f.db_column.column_name}", but no live version exposes that column any more. It is a column nothing can read. Delete the field \u2014 that shrinks the union and lets db codegen drop the column.`
30379
+ });
30380
+ }
30381
+ }
30382
+ return errors;
30383
+ }
30384
+ function checkFieldTypeChangedWhileLive(input2) {
30385
+ const { current, snapshots } = input2;
30386
+ const errors = [];
30387
+ function compare(kinds, typeLabel) {
30388
+ const currentByType = fieldsByColumn(current, kinds);
30389
+ for (const snap of snapshots) {
30390
+ for (const [typeName, snapColumns] of fieldsByColumn(snap.registry, kinds)) {
30391
+ const currentColumns = currentByType.get(typeName);
30392
+ if (!currentColumns) continue;
30393
+ for (const [col, oldField] of snapColumns) {
30394
+ const currentField = currentColumns.get(col);
30395
+ if (!currentField) continue;
30396
+ if (currentField.field_type === oldField.field_type) continue;
30397
+ errors.push({
30398
+ file: `schemas/versions/${snap.version}`,
30399
+ code: "FIELD_TYPE_CHANGED_WHILE_LIVE",
30400
+ message: `Column "${col}" on ${typeLabel}"${typeName}" is exposed by live version ${snap.version} as ${oldField.field_type}, but the current schema now types it ${currentField.field_type}. A live version's contract cannot change type under its consumers \u2014 retire ${snap.version} first, or keep the field's type stable and introduce the change as a new column instead.`
30401
+ });
30402
+ }
30403
+ }
30404
+ }
30405
+ }
30406
+ compare(STRUCTURAL_KINDS, "");
30407
+ compare(["paragraph_types"], "paragraph type ");
30408
+ return errors;
30409
+ }
30410
+ function validateVersionModel(input2) {
30411
+ return [
30412
+ ...checkUnionCompleteness(input2),
30413
+ ...checkOrphanedTombstones(input2),
30414
+ ...checkFieldTypeChangedWhileLive(input2)
30415
+ ];
30416
+ }
30417
+ function collectFallbacks(current) {
30418
+ const out = /* @__PURE__ */ new Map();
30419
+ for (const [typeName, type] of [
30420
+ ...Object.entries(current.content_types),
30421
+ ...Object.entries(current.taxonomy_types)
30422
+ ]) {
30423
+ for (const f of type.fields) {
30424
+ if (f.fallback === void 0 || !isColumnBacked2(f)) continue;
30425
+ out.set(`${typeName}.${f.db_column.column_name}`, f.fallback);
30426
+ }
30427
+ }
30428
+ return out;
30429
+ }
30430
+ function buildProjections(input2) {
30431
+ const { current, currentVersion, snapshots } = input2;
30432
+ const byVersion = [
30433
+ ...snapshots,
30434
+ { version: currentVersion, registry: current }
30435
+ ];
30436
+ const fallbacks = collectFallbacks(current);
30437
+ const out = {};
30438
+ for (const { version: version3, registry: registry2 } of byVersion) {
30439
+ const types3 = {};
30440
+ for (const [typeName, type] of [
30441
+ ...Object.entries(registry2.content_types),
30442
+ ...Object.entries(registry2.taxonomy_types)
30443
+ ]) {
30444
+ const fields = type.fields.filter((f) => isColumnBacked2(f) && f.removed !== true).map((f) => {
30445
+ const column_name = f.db_column.column_name;
30446
+ const fallback = fallbacks.get(`${typeName}.${column_name}`);
30447
+ return fallback === void 0 ? { column_name, exposed_as: f.name } : { column_name, exposed_as: f.name, fallback };
30448
+ });
30449
+ types3[typeName] = { fields };
30450
+ }
30451
+ out[version3] = { version: version3, types: types3 };
30452
+ }
30453
+ return out;
30454
+ }
30455
+ function versionNumber(version3) {
30456
+ const n = Number.parseInt(version3.replace(/^v/, ""), 10);
30457
+ return Number.isNaN(n) ? 0 : n;
30458
+ }
30459
+ function computeVersionModel(input2) {
30460
+ const { current, snapshots } = input2;
30461
+ const highest = snapshots.reduce((max, s) => Math.max(max, versionNumber(s.version)), 0);
30462
+ const currentVersion = `v${highest + 1}`;
30463
+ const live = [
30464
+ ...new Set(snapshots.map((s) => s.version).sort((a2, b2) => versionNumber(a2) - versionNumber(b2))),
30465
+ currentVersion
30466
+ ];
30467
+ const union3 = current;
30468
+ const projections = buildProjections({ current, currentVersion, snapshots });
30469
+ const errors = validateVersionModel({ current, currentVersion, snapshots, union: union3, projections });
30470
+ if (errors.length > 0) return { ok: false, errors };
30471
+ return { ok: true, value: { current: currentVersion, live, union: union3, projections } };
30472
+ }
30473
+ function discoverSnapshotDirs(versionsDir) {
30474
+ const entries = fs3.readdirSync(versionsDir, { withFileTypes: true });
30475
+ return entries.filter((e) => e.isDirectory() && /^v\d+$/.test(e.name)).map((e) => ({ version: e.name, n: Number.parseInt(e.name.slice(1), 10) })).sort((a2, b2) => a2.n - b2.n).map(({ version: version3 }) => ({ version: version3, dir: path3.join(versionsDir, version3) }));
30476
+ }
30477
+ function walkSnapshotFolders(dir, folders) {
30478
+ const files = [];
30479
+ const errors = [];
30480
+ for (const [folderKey, schemaType] of Object.entries(FOLDER_KEY_TO_SCHEMA_TYPE)) {
30481
+ const folderPath = path3.join(dir, folders[folderKey]);
30482
+ if (!directoryExists(folderPath)) continue;
30483
+ let entries;
30484
+ try {
30485
+ entries = fs3.readdirSync(folderPath);
30486
+ } catch {
30487
+ entries = [];
30488
+ }
30489
+ for (const entry of entries) {
30490
+ if (!isSupportedExtension(entry)) continue;
30491
+ const filePath = path3.join(folderPath, entry);
30492
+ const result = loadSchemaFile(filePath);
30493
+ if (!result.ok) {
30494
+ errors.push(...result.errors);
30495
+ continue;
30496
+ }
30497
+ files.push({ path: filePath, raw: result.value, schema_type: schemaType });
30498
+ }
30499
+ }
30500
+ if (errors.length > 0) return { ok: false, errors };
30501
+ return { ok: true, value: files };
30502
+ }
30503
+ function wrapAsSnapshotInvalid(version3, errors) {
30504
+ return errors.map((e) => ({
30505
+ file: e.file,
30506
+ code: "VERSION_SNAPSHOT_INVALID",
30507
+ message: `Snapshot ${version3} failed to load: ${e.message}`,
30508
+ ...e.path !== void 0 ? { path: e.path } : {}
30509
+ }));
30510
+ }
30511
+ function loadSnapshot(version3, dir, folders, current) {
30512
+ const walked = walkSnapshotFolders(dir, folders);
30513
+ if (!walked.ok) return { ok: false, errors: wrapAsSnapshotInvalid(version3, walked.errors) };
30514
+ const schemas = [];
30515
+ const errors = [];
30516
+ for (const file2 of walked.value) {
30517
+ const parsed = parseSchema(file2.raw, file2.schema_type, file2.path);
30518
+ if (!parsed.ok) {
30519
+ errors.push(...parsed.errors);
30520
+ continue;
30521
+ }
30522
+ schemas.push(parsed.schema);
30523
+ }
30524
+ if (errors.length > 0) return { ok: false, errors: wrapAsSnapshotInvalid(version3, errors) };
30525
+ const registry2 = buildSchemaRegistry(schemas, current.routes, current.roles);
30526
+ return { ok: true, value: { version: version3, registry: registry2 } };
30527
+ }
30528
+ function loadVersionSnapshots(config3, current) {
30529
+ const versionsDir = path3.join(config3.base_path, "versions");
30530
+ if (!directoryExists(versionsDir)) return { ok: true, value: [] };
30531
+ const snapshots = [];
30532
+ const errors = [];
30533
+ for (const { version: version3, dir } of discoverSnapshotDirs(versionsDir)) {
30534
+ const result = loadSnapshot(version3, dir, config3.folders, current);
30535
+ if (!result.ok) {
30536
+ errors.push(...result.errors);
30537
+ continue;
30538
+ }
30539
+ snapshots.push(result.value);
30540
+ }
30541
+ if (errors.length > 0) return { ok: false, errors };
30542
+ return { ok: true, value: snapshots };
30543
+ }
30544
+ function typeEntries2(registry2) {
30545
+ return [
30546
+ ...Object.entries(registry2.content_types),
30547
+ ...Object.entries(registry2.taxonomy_types),
30548
+ ...Object.entries(registry2.paragraph_types)
30549
+ ].map(([name, type]) => [name, type.fields]);
30550
+ }
30551
+ function byColumn(fields) {
30552
+ const out = /* @__PURE__ */ new Map();
30553
+ for (const f of fields) {
30554
+ if (!isColumnBacked2(f)) continue;
30555
+ out.set(f.db_column.column_name, f);
30556
+ }
30557
+ return out;
30558
+ }
30559
+ function describeSchemaChange(input2) {
30560
+ const { from, to } = input2;
30561
+ const fromTypes = new Map(from === null ? [] : typeEntries2(from.registry));
30562
+ const types3 = [];
30563
+ for (const [typeName, toFields] of typeEntries2(to.registry)) {
30564
+ const fromFields = fromTypes.get(typeName);
30565
+ const fromByColumn = byColumn(fromFields ?? []);
30566
+ const fields = [];
30567
+ for (const [column, toField] of byColumn(toFields)) {
30568
+ const fromField = fromByColumn.get(column);
30569
+ if (fromField === void 0) {
30570
+ fields.push({ kind: "added", column, name: toField.name, field_type: toField.field_type });
30571
+ continue;
30572
+ }
30573
+ const wasTombstone = fromField.removed === true;
30574
+ const isTombstone = toField.removed === true;
30575
+ if (!wasTombstone && isTombstone) {
30576
+ fields.push({
30577
+ kind: "tombstoned",
30578
+ column,
30579
+ name: toField.name,
30580
+ // Omitted entirely rather than set undefined, so an equality check
30581
+ // against the no-fallback case is clean.
30582
+ ...toField.fallback !== void 0 && { fallback: toField.fallback }
30583
+ });
30584
+ continue;
30585
+ }
30586
+ if (wasTombstone && !isTombstone) {
30587
+ fields.push({ kind: "restored", column, name: toField.name });
30588
+ continue;
30589
+ }
30590
+ if (fromField.name !== toField.name) {
30591
+ fields.push({ kind: "renamed", column, from_name: fromField.name, to_name: toField.name });
30592
+ }
30593
+ }
30594
+ types3.push({
30595
+ type: typeName,
30596
+ status: fromFields === void 0 ? "added" : "present",
30597
+ fields
30598
+ });
30599
+ }
30600
+ const identical = types3.every((t) => t.status === "present" && t.fields.length === 0);
30601
+ return { from: from?.version ?? null, to: to.version, types: types3, identical };
30602
+ }
30264
30603
 
30265
30604
  // ../db/dist/index.mjs
30266
30605
  init_esm_shims();
@@ -30467,7 +30806,7 @@ init_esm_shims();
30467
30806
  function mapResultRow(columns, row, joinsNotNullableMap) {
30468
30807
  const nullifyMap = {};
30469
30808
  const result = columns.reduce(
30470
- (result2, { path: path4, field }, columnIndex) => {
30809
+ (result2, { path: path7, field }, columnIndex) => {
30471
30810
  let decoder;
30472
30811
  if (is(field, Column)) {
30473
30812
  decoder = field;
@@ -30479,8 +30818,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
30479
30818
  decoder = field.sql.decoder;
30480
30819
  }
30481
30820
  let node = result2;
30482
- for (const [pathChunkIndex, pathChunk] of path4.entries()) {
30483
- if (pathChunkIndex < path4.length - 1) {
30821
+ for (const [pathChunkIndex, pathChunk] of path7.entries()) {
30822
+ if (pathChunkIndex < path7.length - 1) {
30484
30823
  if (!(pathChunk in node)) {
30485
30824
  node[pathChunk] = {};
30486
30825
  }
@@ -30488,8 +30827,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
30488
30827
  } else {
30489
30828
  const rawValue = row[columnIndex];
30490
30829
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
30491
- if (joinsNotNullableMap && is(field, Column) && path4.length === 2) {
30492
- const objectName = path4[0];
30830
+ if (joinsNotNullableMap && is(field, Column) && path7.length === 2) {
30831
+ const objectName = path7[0];
30493
30832
  if (!(objectName in nullifyMap)) {
30494
30833
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
30495
30834
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -33857,7 +34196,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
33857
34196
  const baseTableName = this.tableName;
33858
34197
  const tableName = getTableLikeName(table);
33859
34198
  for (const item of extractUsedTable(table)) this.usedTables.add(item);
33860
- if (typeof tableName === "string" && this.config.joins?.some((join13) => join13.alias === tableName)) {
34199
+ if (typeof tableName === "string" && this.config.joins?.some((join15) => join15.alias === tableName)) {
33861
34200
  throw new Error(`Alias "${tableName}" is already used in this query`);
33862
34201
  }
33863
34202
  if (!this.isPartialSelect) {
@@ -35081,7 +35420,7 @@ var PgUpdateBase = class extends QueryPromise {
35081
35420
  createJoin(joinType) {
35082
35421
  return (table, on) => {
35083
35422
  const tableName = getTableLikeName(table);
35084
- if (typeof tableName === "string" && this.config.joins.some((join13) => join13.alias === tableName)) {
35423
+ if (typeof tableName === "string" && this.config.joins.some((join15) => join15.alias === tableName)) {
35085
35424
  throw new Error(`Alias "${tableName}" is already used in this query`);
35086
35425
  }
35087
35426
  if (typeof on === "function") {
@@ -35177,10 +35516,10 @@ var PgUpdateBase = class extends QueryPromise {
35177
35516
  const fromFields = this.getTableLikeFields(this.config.from);
35178
35517
  fields[tableName] = fromFields;
35179
35518
  }
35180
- for (const join13 of this.config.joins) {
35181
- const tableName2 = getTableLikeName(join13.table);
35182
- if (typeof tableName2 === "string" && !is(join13.table, SQL)) {
35183
- const fromFields = this.getTableLikeFields(join13.table);
35519
+ for (const join15 of this.config.joins) {
35520
+ const tableName2 = getTableLikeName(join15.table);
35521
+ if (typeof tableName2 === "string" && !is(join15.table, SQL)) {
35522
+ const fromFields = this.getTableLikeFields(join15.table);
35184
35523
  fields[tableName2] = fromFields;
35185
35524
  }
35186
35525
  }
@@ -38944,7 +39283,7 @@ var ds = T((th, ps) => {
38944
39283
  return /^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(r);
38945
39284
  }
38946
39285
  a(ls, "isBase64");
38947
- function fs4(r) {
39286
+ function fs7(r) {
38948
39287
  if (typeof r != "string") throw new TypeError("SASL: attribute pairs text must be a string");
38949
39288
  return new Map(r.split(",").map((e) => {
38950
39289
  if (!/^.=/.test(e)) throw new Error("SASL: Invalid attribute pair entry");
@@ -38952,9 +39291,9 @@ var ds = T((th, ps) => {
38952
39291
  return [t, n];
38953
39292
  }));
38954
39293
  }
38955
- a(fs4, "parseAttributePairs");
39294
+ a(fs7, "parseAttributePairs");
38956
39295
  function qu(r) {
38957
- let e = fs4(r), t = e.get("r");
39296
+ let e = fs7(r), t = e.get("r");
38958
39297
  if (t) {
38959
39298
  if (!Ou(t)) throw new Error("SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters");
38960
39299
  } else throw new Error("SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing");
@@ -38971,7 +39310,7 @@ var ds = T((th, ps) => {
38971
39310
  }
38972
39311
  a(qu, "parseServerFirstMessage");
38973
39312
  function Qu(r) {
38974
- let t = fs4(r).get("v");
39313
+ let t = fs7(r).get("v");
38975
39314
  if (t) {
38976
39315
  if (!ls(t)) throw new Error("SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64");
38977
39316
  } else throw new Error("SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing");
@@ -41707,8 +42046,8 @@ function drizzle2(...params) {
41707
42046
  // ../db/dist/index.mjs
41708
42047
  import { execSync, spawnSync } from "child_process";
41709
42048
  import crypto2 from "crypto";
41710
- import path3 from "path";
41711
- import fs3 from "fs/promises";
42049
+ import path4 from "path";
42050
+ import fs4 from "fs/promises";
41712
42051
  import { readFileSync as readFileSync2 } from "fs";
41713
42052
  import path22 from "path";
41714
42053
  function createPostgresAdapter(options = {}) {
@@ -42194,32 +42533,32 @@ async function seedSystemTables(db, registry2, options = {}) {
42194
42533
  }
42195
42534
  function resolvedPathEnv() {
42196
42535
  const root = process.cwd();
42197
- const localBin = path3.join(root, "node_modules", ".bin");
42198
- const existing = (process.env.PATH ?? "").split(path3.delimiter).filter(Boolean).map((entry) => path3.isAbsolute(entry) ? entry : path3.resolve(root, entry));
42199
- return { ...process.env, PATH: [localBin, ...existing].join(path3.delimiter) };
42536
+ const localBin = path4.join(root, "node_modules", ".bin");
42537
+ const existing = (process.env.PATH ?? "").split(path4.delimiter).filter(Boolean).map((entry) => path4.isAbsolute(entry) ? entry : path4.resolve(root, entry));
42538
+ return { ...process.env, PATH: [localBin, ...existing].join(path4.delimiter) };
42200
42539
  }
42201
42540
  async function runDevMigration(configPath) {
42202
42541
  execSync(`drizzle-kit push --config=${configPath}`, {
42203
42542
  stdio: "inherit",
42204
- cwd: path3.dirname(configPath),
42543
+ cwd: path4.dirname(configPath),
42205
42544
  env: resolvedPathEnv()
42206
42545
  });
42207
42546
  }
42208
42547
  async function generateMigration(configPath, migrationsFolder) {
42209
42548
  let beforeFiles = [];
42210
42549
  try {
42211
- const entries = await fs3.readdir(migrationsFolder);
42550
+ const entries = await fs4.readdir(migrationsFolder);
42212
42551
  beforeFiles = entries.filter((f) => f.endsWith(".sql"));
42213
42552
  } catch {
42214
42553
  }
42215
42554
  execSync(`drizzle-kit generate --config=${configPath}`, {
42216
42555
  stdio: "inherit",
42217
- cwd: path3.dirname(configPath),
42556
+ cwd: path4.dirname(configPath),
42218
42557
  env: resolvedPathEnv()
42219
42558
  });
42220
42559
  let afterFiles = [];
42221
42560
  try {
42222
- const entries = await fs3.readdir(migrationsFolder);
42561
+ const entries = await fs4.readdir(migrationsFolder);
42223
42562
  afterFiles = entries.filter((f) => f.endsWith(".sql"));
42224
42563
  } catch {
42225
42564
  }
@@ -42240,16 +42579,16 @@ async function applyMigrations(configPath, db, options) {
42240
42579
  if (statusBefore.pending.length > 0) {
42241
42580
  let journalEntries = [];
42242
42581
  try {
42243
- const journalPath = path3.join(migrationsFolder, "meta", "_journal.json");
42244
- const raw2 = await fs3.readFile(journalPath, "utf-8");
42582
+ const journalPath = path4.join(migrationsFolder, "meta", "_journal.json");
42583
+ const raw2 = await fs4.readFile(journalPath, "utf-8");
42245
42584
  journalEntries = JSON.parse(raw2).entries;
42246
42585
  } catch {
42247
42586
  }
42248
42587
  for (const pendingFile of statusBefore.pending) {
42249
- const sqlPath = path3.join(migrationsFolder, pendingFile);
42588
+ const sqlPath = path4.join(migrationsFolder, pendingFile);
42250
42589
  let sqlContent;
42251
42590
  try {
42252
- sqlContent = await fs3.readFile(sqlPath, "utf-8");
42591
+ sqlContent = await fs4.readFile(sqlPath, "utf-8");
42253
42592
  } catch {
42254
42593
  continue;
42255
42594
  }
@@ -42279,7 +42618,7 @@ async function applyMigrations(configPath, db, options) {
42279
42618
  "drizzle-kit",
42280
42619
  ["migrate", `--config=${configPath}`],
42281
42620
  {
42282
- cwd: path3.dirname(configPath),
42621
+ cwd: path4.dirname(configPath),
42283
42622
  // inherit lets drizzle-kit write its spinner and errors directly to the
42284
42623
  // terminal in real time — capturing stdout/stderr swallows ANSI overwrites
42285
42624
  // and hides the actual error message.
@@ -42305,8 +42644,8 @@ async function getMigrationStatus(db, options) {
42305
42644
  const { migrationsTable, migrationsFolder } = options;
42306
42645
  let journalEntries;
42307
42646
  try {
42308
- const journalPath = path3.join(migrationsFolder, "meta", "_journal.json");
42309
- const raw2 = await fs3.readFile(journalPath, "utf-8");
42647
+ const journalPath = path4.join(migrationsFolder, "meta", "_journal.json");
42648
+ const raw2 = await fs4.readFile(journalPath, "utf-8");
42310
42649
  const journal = JSON.parse(raw2);
42311
42650
  journalEntries = journal.entries;
42312
42651
  } catch {
@@ -42404,17 +42743,17 @@ function scanMigrationFiles(filePaths) {
42404
42743
  // src/codegen/registry.ts
42405
42744
  init_esm_shims();
42406
42745
  import { writeFile } from "fs/promises";
42407
- import { join as join2 } from "path";
42746
+ import { join as join3 } from "path";
42408
42747
  async function generateSchemaRegistry(registry2, targetDir) {
42409
42748
  const content = `export const schemaRegistry = ${JSON.stringify(registry2, null, 2)} as const
42410
42749
  `;
42411
- await writeFile(join2(targetDir, "schema-registry.ts"), content, "utf8");
42750
+ await writeFile(join3(targetDir, "schema-registry.ts"), content, "utf8");
42412
42751
  }
42413
42752
 
42414
42753
  // src/codegen/routes.ts
42415
42754
  init_esm_shims();
42416
42755
  import { writeFile as writeFile2 } from "fs/promises";
42417
- import { join as join3 } from "path";
42756
+ import { join as join4 } from "path";
42418
42757
 
42419
42758
  // ../api/dist/codegen/index.mjs
42420
42759
  init_esm_shims();
@@ -43087,13 +43426,13 @@ function generateRoutes(registry2) {
43087
43426
  // src/codegen/routes.ts
43088
43427
  async function generateRoutes2(registry2, targetDir) {
43089
43428
  const content = generateRoutes(registry2);
43090
- await writeFile2(join3(targetDir, "routes.ts"), content, "utf8");
43429
+ await writeFile2(join4(targetDir, "routes.ts"), content, "utf8");
43091
43430
  }
43092
43431
 
43093
43432
  // src/codegen/forms.ts
43094
43433
  init_esm_shims();
43095
43434
  import { mkdir, writeFile as writeFile3 } from "fs/promises";
43096
- import { join as join4 } from "path";
43435
+ import { join as join5 } from "path";
43097
43436
 
43098
43437
  // ../admin/dist/codegen/index.js
43099
43438
  init_esm_shims();
@@ -43261,24 +43600,25 @@ async function generateForms(registry2, formsDir) {
43261
43600
  ];
43262
43601
  for (const schema of schemas) {
43263
43602
  const content = generateFormComponent(schema);
43264
- await writeFile3(join4(formsDir, `${schema.name}.vue`), content, "utf8");
43603
+ await writeFile3(join5(formsDir, `${schema.name}.vue`), content, "utf8");
43265
43604
  }
43266
43605
  }
43267
43606
 
43268
43607
  // src/codegen/server-entries.ts
43269
43608
  init_esm_shims();
43270
43609
  import { writeFile as writeFile4 } from "fs/promises";
43271
- import { join as join5 } from "path";
43610
+ import { join as join6 } from "path";
43272
43611
  async function generateServerEntries(options, targetDir) {
43273
43612
  await Promise.all([
43274
- writeFile4(join5(targetDir, "server.ts"), serverEntry(options), "utf8"),
43275
- writeFile4(join5(targetDir, "handler.ts"), lambdaEntry(options), "utf8"),
43276
- writeFile4(join5(targetDir, "vercel.ts"), vercelEntry(options), "utf8")
43613
+ writeFile4(join6(targetDir, "server.ts"), serverEntry(options), "utf8"),
43614
+ writeFile4(join6(targetDir, "handler.ts"), lambdaEntry(options), "utf8"),
43615
+ writeFile4(join6(targetDir, "vercel.ts"), vercelEntry(options), "utf8")
43277
43616
  ]);
43278
43617
  }
43279
43618
  function appSetup() {
43280
43619
  return `import config from '../../manguito.config.js'
43281
43620
  import { schemaRegistry } from './schema-registry.js'
43621
+ import { versionModel } from './version-model.js'
43282
43622
  import { programmaticResolvers } from './programmatic-registry.js'
43283
43623
  import { createCmsApp } from '@bobbykim/manguito-cms-api'
43284
43624
  import { createPostgresAdapter } from '@bobbykim/manguito-cms-db'
@@ -43289,6 +43629,7 @@ await dbAdapter.connect()
43289
43629
  const { app } = createCmsApp({
43290
43630
  name: config.name,
43291
43631
  registry: schemaRegistry,
43632
+ versions: versionModel,
43292
43633
  db: dbAdapter.getDb(),
43293
43634
  storage: config.storage,
43294
43635
  prefix: config.api.prefix,
@@ -43457,7 +43798,7 @@ export default handle(app)
43457
43798
  // src/codegen/programmatic-registry.ts
43458
43799
  init_esm_shims();
43459
43800
  import { writeFile as writeFile5 } from "fs/promises";
43460
- import { join as join6, relative } from "path";
43801
+ import { join as join7, relative } from "path";
43461
43802
  function importSpecifier(targetDir, file2) {
43462
43803
  let rel = relative(targetDir, file2).replace(/\\/g, "/");
43463
43804
  if (!rel.startsWith(".")) rel = `./${rel}`;
@@ -43479,7 +43820,26 @@ export const programmaticResolvers = new Map<string, ProgrammaticFieldDefinition
43479
43820
  ${entries},
43480
43821
  ])
43481
43822
  `;
43482
- await writeFile5(join6(targetDir, "programmatic-registry.ts"), body, "utf8");
43823
+ await writeFile5(join7(targetDir, "programmatic-registry.ts"), body, "utf8");
43824
+ }
43825
+
43826
+ // src/codegen/version-model.ts
43827
+ init_esm_shims();
43828
+ import { writeFile as writeFile6 } from "fs/promises";
43829
+ import { join as join8 } from "path";
43830
+ function reduceVersionModel(model) {
43831
+ return { current: model.current, live: model.live, projections: model.projections };
43832
+ }
43833
+ async function generateVersionModel(model, targetDir) {
43834
+ const reduced = reduceVersionModel(model);
43835
+ const content = [
43836
+ "// GENERATED by manguito build \u2014 do not edit.",
43837
+ "import type { BakedVersionModel } from '@bobbykim/manguito-cms-api'",
43838
+ "",
43839
+ `export const versionModel: BakedVersionModel = ${JSON.stringify(reduced, null, 2)}`,
43840
+ ""
43841
+ ].join("\n");
43842
+ await writeFile6(join8(targetDir, "version-model.ts"), content, "utf8");
43483
43843
  }
43484
43844
 
43485
43845
  // src/utils/config.ts
@@ -43572,6 +43932,16 @@ function isValidConfig(value) {
43572
43932
  return value !== null && typeof value === "object" && "db" in value && "storage" in value && "server" in value && "api" in value && "admin" in value;
43573
43933
  }
43574
43934
 
43935
+ // src/utils/schema-config.ts
43936
+ init_esm_shims();
43937
+ import { resolve as resolve3 } from "path";
43938
+ function resolveSchemaConfig(cwd, config3) {
43939
+ return {
43940
+ base_path: resolve3(cwd, config3.schema.base_path),
43941
+ folders: config3.schema.folders
43942
+ };
43943
+ }
43944
+
43575
43945
  // src/utils/env.ts
43576
43946
  init_esm_shims();
43577
43947
  import { existsSync as existsSync2 } from "fs";
@@ -43613,7 +43983,7 @@ async function runBuild(options, deps) {
43613
43983
  }
43614
43984
  }
43615
43985
  }
43616
- const rolesPath = resolve3(cwd, config3.schema.base_path, "roles.json");
43986
+ const rolesPath = resolve4(cwd, config3.schema.base_path, "roles.json");
43617
43987
  const rolesLoadResult = loadSchemaFile(rolesPath);
43618
43988
  let parsedRoles = null;
43619
43989
  if (!rolesLoadResult.ok) {
@@ -43626,7 +43996,7 @@ async function runBuild(options, deps) {
43626
43996
  parsedRoles = rolesParseResult.value;
43627
43997
  }
43628
43998
  }
43629
- const routesPath = resolve3(cwd, config3.schema.base_path, "routes.json");
43999
+ const routesPath = resolve4(cwd, config3.schema.base_path, "routes.json");
43630
44000
  const routesLoadResult = loadSchemaFile(routesPath);
43631
44001
  let parsedRoutesDef = null;
43632
44002
  if (!routesLoadResult.ok) {
@@ -43657,13 +44027,26 @@ async function runBuild(options, deps) {
43657
44027
  printSuccess(
43658
44028
  `Schemas parsed (${contentCount} content types, ${paragraphCount} paragraph types, ${taxonomyCount} taxonomy types${enumPart})`
43659
44029
  );
43660
- const generatedDir = resolve3(cwd, "dist/generated");
44030
+ const generatedDir = resolve4(cwd, "dist/generated");
43661
44031
  mkdirSync(generatedDir, { recursive: true });
43662
- writeFileSync(join7(generatedDir, "schema.ts"), generateSchemaFile(registry2), "utf8");
44032
+ writeFileSync(join9(generatedDir, "schema.ts"), generateSchemaFile(registry2), "utf8");
43663
44033
  await generateSchemaRegistry(registry2, generatedDir);
43664
44034
  await generateRoutes2(registry2, generatedDir);
43665
- await generateForms(registry2, join7(generatedDir, "forms"));
43666
- const programmaticDir = resolve3(cwd, config3.programmatic.dir);
44035
+ await generateForms(registry2, join9(generatedDir, "forms"));
44036
+ const schema = resolveSchemaConfig(cwd, config3);
44037
+ const snapshots = loadVersionSnapshots(schema, registry2);
44038
+ if (!snapshots.ok) {
44039
+ printValidationErrors(snapshots.errors, "Snapshot errors", "manguito build");
44040
+ process.exit(1);
44041
+ }
44042
+ const versionModel = computeVersionModel({ current: registry2, snapshots: snapshots.value });
44043
+ if (!versionModel.ok) {
44044
+ printValidationErrors(versionModel.errors, "Version model errors", "manguito build");
44045
+ process.exit(1);
44046
+ }
44047
+ await generateVersionModel(versionModel.value, generatedDir);
44048
+ printSuccess(`Version model baked (live: ${versionModel.value.live.join(", ")})`);
44049
+ const programmaticDir = resolve4(cwd, config3.programmatic.dir);
43667
44050
  const resolverFiles = existsSync3(programmaticDir) ? walkFiles(programmaticDir).filter((f) => /\.(ts|mjs|js)$/.test(f)) : [];
43668
44051
  await generateProgrammaticRegistry(resolverFiles, generatedDir);
43669
44052
  await generateServerEntries(
@@ -43676,7 +44059,7 @@ async function runBuild(options, deps) {
43676
44059
  root: resolveAdminRoot(cwd),
43677
44060
  base: config3.admin.prefix + "/",
43678
44061
  build: {
43679
- outDir: resolve3(cwd, "dist/admin"),
44062
+ outDir: resolve4(cwd, "dist/admin"),
43680
44063
  emptyOutDir: true
43681
44064
  },
43682
44065
  define: {
@@ -43696,12 +44079,12 @@ async function runBuild(options, deps) {
43696
44079
  try {
43697
44080
  await tsupBuild({
43698
44081
  entry: {
43699
- server: resolve3(cwd, "dist/generated/server.ts"),
43700
- handler: resolve3(cwd, "dist/generated/handler.ts"),
43701
- vercel: resolve3(cwd, "dist/generated/vercel.ts")
44082
+ server: resolve4(cwd, "dist/generated/server.ts"),
44083
+ handler: resolve4(cwd, "dist/generated/handler.ts"),
44084
+ vercel: resolve4(cwd, "dist/generated/vercel.ts")
43702
44085
  },
43703
44086
  format: ["esm"],
43704
- outDir: resolve3(cwd, "dist"),
44087
+ outDir: resolve4(cwd, "dist"),
43705
44088
  dts: false,
43706
44089
  silent: true,
43707
44090
  // hono/aws-lambda and hono/vercel are optional peer deps — mark external
@@ -43719,24 +44102,24 @@ async function runBuild(options, deps) {
43719
44102
  process.stdout.write("\nBuild complete \u2192 dist/\n");
43720
44103
  }
43721
44104
  async function needsRebuild(cwd) {
43722
- const sentinel = resolve3(cwd, "dist/generated/schema.ts");
44105
+ const sentinel = resolve4(cwd, "dist/generated/schema.ts");
43723
44106
  if (!existsSync3(sentinel)) return true;
43724
44107
  const sentinelMtime = statSync2(sentinel).mtimeMs;
43725
- const schemasDir = resolve3(cwd, "schemas");
44108
+ const schemasDir = resolve4(cwd, "schemas");
43726
44109
  if (!existsSync3(schemasDir)) return false;
43727
44110
  return walkFiles(schemasDir).some(
43728
44111
  (file2) => statSync2(file2).mtimeMs > sentinelMtime
43729
44112
  );
43730
44113
  }
43731
44114
  function resolveAdminRoot(cwd) {
43732
- const cwdRequire = createRequire(join7(cwd, "package.json"));
44115
+ const cwdRequire = createRequire(join9(cwd, "package.json"));
43733
44116
  const adminPkgJson = cwdRequire.resolve("@bobbykim/manguito-cms-admin/package.json");
43734
44117
  return dirname(adminPkgJson);
43735
44118
  }
43736
44119
  function walkFiles(dir) {
43737
44120
  const files = [];
43738
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
43739
- const full = join7(dir, entry.name);
44121
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
44122
+ const full = join9(dir, entry.name);
43740
44123
  if (entry.isDirectory()) {
43741
44124
  files.push(...walkFiles(full));
43742
44125
  } else {
@@ -43750,8 +44133,8 @@ function walkFiles(dir) {
43750
44133
  init_esm_shims();
43751
44134
  import { createServer as createHttpServer } from "http";
43752
44135
  import { existsSync as existsSync5 } from "fs";
43753
- import { resolve as resolve6, join as join11, dirname as dirname2, extname as extname4 } from "path";
43754
- import { mkdir as mkdir2, writeFile as writeFile8, watch, readFile } from "fs/promises";
44136
+ import { resolve as resolve7, join as join13, dirname as dirname2, extname as extname4 } from "path";
44137
+ import { mkdir as mkdir2, writeFile as writeFile9, watch, readFile } from "fs/promises";
43755
44138
  import { createRequire as createRequire2 } from "module";
43756
44139
  import { createServer as createViteServer, searchForWorkspaceRoot } from "vite";
43757
44140
 
@@ -43927,26 +44310,26 @@ var handleParsingNestedValues = (form, key, value) => {
43927
44310
 
43928
44311
  // ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/url.js
43929
44312
  init_esm_shims();
43930
- var splitPath = (path4) => {
43931
- const paths = path4.split("/");
44313
+ var splitPath = (path7) => {
44314
+ const paths = path7.split("/");
43932
44315
  if (paths[0] === "") {
43933
44316
  paths.shift();
43934
44317
  }
43935
44318
  return paths;
43936
44319
  };
43937
44320
  var splitRoutingPath = (routePath) => {
43938
- const { groups, path: path4 } = extractGroupsFromPath(routePath);
43939
- const paths = splitPath(path4);
44321
+ const { groups, path: path7 } = extractGroupsFromPath(routePath);
44322
+ const paths = splitPath(path7);
43940
44323
  return replaceGroupMarks(paths, groups);
43941
44324
  };
43942
- var extractGroupsFromPath = (path4) => {
44325
+ var extractGroupsFromPath = (path7) => {
43943
44326
  const groups = [];
43944
- path4 = path4.replace(/\{[^}]+\}/g, (match2, index) => {
44327
+ path7 = path7.replace(/\{[^}]+\}/g, (match2, index) => {
43945
44328
  const mark = `@${index}`;
43946
44329
  groups.push([mark, match2]);
43947
44330
  return mark;
43948
44331
  });
43949
- return { groups, path: path4 };
44332
+ return { groups, path: path7 };
43950
44333
  };
43951
44334
  var replaceGroupMarks = (paths, groups) => {
43952
44335
  for (let i = groups.length - 1; i >= 0; i--) {
@@ -44003,8 +44386,8 @@ var getPath = (request) => {
44003
44386
  const queryIndex = url2.indexOf("?", i);
44004
44387
  const hashIndex = url2.indexOf("#", i);
44005
44388
  const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
44006
- const path4 = url2.slice(start, end);
44007
- return tryDecodeURI(path4.includes("%25") ? path4.replace(/%25/g, "%2525") : path4);
44389
+ const path7 = url2.slice(start, end);
44390
+ return tryDecodeURI(path7.includes("%25") ? path7.replace(/%25/g, "%2525") : path7);
44008
44391
  } else if (charCode === 63 || charCode === 35) {
44009
44392
  break;
44010
44393
  }
@@ -44021,11 +44404,11 @@ var mergePath = (base, sub, ...rest) => {
44021
44404
  }
44022
44405
  return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
44023
44406
  };
44024
- var checkOptionalParameter = (path4) => {
44025
- if (path4.charCodeAt(path4.length - 1) !== 63 || !path4.includes(":")) {
44407
+ var checkOptionalParameter = (path7) => {
44408
+ if (path7.charCodeAt(path7.length - 1) !== 63 || !path7.includes(":")) {
44026
44409
  return null;
44027
44410
  }
44028
- const segments = path4.split("/");
44411
+ const segments = path7.split("/");
44029
44412
  const results = [];
44030
44413
  let basePath = "";
44031
44414
  segments.forEach((segment) => {
@@ -44166,9 +44549,9 @@ var HonoRequest = class {
44166
44549
  */
44167
44550
  path;
44168
44551
  bodyCache = {};
44169
- constructor(request, path4 = "/", matchResult = [[]]) {
44552
+ constructor(request, path7 = "/", matchResult = [[]]) {
44170
44553
  this.raw = request;
44171
- this.path = path4;
44554
+ this.path = path7;
44172
44555
  this.#matchResult = matchResult;
44173
44556
  this.#validatedData = {};
44174
44557
  }
@@ -44923,8 +45306,8 @@ var Hono = class _Hono {
44923
45306
  return this;
44924
45307
  };
44925
45308
  });
44926
- this.on = (method, path4, ...handlers) => {
44927
- for (const p2 of [path4].flat()) {
45309
+ this.on = (method, path7, ...handlers) => {
45310
+ for (const p2 of [path7].flat()) {
44928
45311
  this.#path = p2;
44929
45312
  for (const m2 of [method].flat()) {
44930
45313
  handlers.map((handler) => {
@@ -44981,8 +45364,8 @@ var Hono = class _Hono {
44981
45364
  * app.route("/api", app2) // GET /api/user
44982
45365
  * ```
44983
45366
  */
44984
- route(path4, app) {
44985
- const subApp = this.basePath(path4);
45367
+ route(path7, app) {
45368
+ const subApp = this.basePath(path7);
44986
45369
  app.routes.map((r) => {
44987
45370
  let handler;
44988
45371
  if (app.errorHandler === errorHandler) {
@@ -45008,9 +45391,9 @@ var Hono = class _Hono {
45008
45391
  * const api = new Hono().basePath('/api')
45009
45392
  * ```
45010
45393
  */
45011
- basePath(path4) {
45394
+ basePath(path7) {
45012
45395
  const subApp = this.#clone();
45013
- subApp._basePath = mergePath(this._basePath, path4);
45396
+ subApp._basePath = mergePath(this._basePath, path7);
45014
45397
  return subApp;
45015
45398
  }
45016
45399
  /**
@@ -45084,7 +45467,7 @@ var Hono = class _Hono {
45084
45467
  * })
45085
45468
  * ```
45086
45469
  */
45087
- mount(path4, applicationHandler, options) {
45470
+ mount(path7, applicationHandler, options) {
45088
45471
  let replaceRequest;
45089
45472
  let optionHandler;
45090
45473
  if (options) {
@@ -45111,7 +45494,7 @@ var Hono = class _Hono {
45111
45494
  return [c.env, executionContext];
45112
45495
  };
45113
45496
  replaceRequest ||= (() => {
45114
- const mergedPath = mergePath(this._basePath, path4);
45497
+ const mergedPath = mergePath(this._basePath, path7);
45115
45498
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
45116
45499
  return (request) => {
45117
45500
  const url2 = new URL(request.url);
@@ -45126,19 +45509,19 @@ var Hono = class _Hono {
45126
45509
  }
45127
45510
  await next();
45128
45511
  };
45129
- this.#addRoute(METHOD_NAME_ALL, mergePath(path4, "*"), handler);
45512
+ this.#addRoute(METHOD_NAME_ALL, mergePath(path7, "*"), handler);
45130
45513
  return this;
45131
45514
  }
45132
- #addRoute(method, path4, handler, baseRoutePath) {
45515
+ #addRoute(method, path7, handler, baseRoutePath) {
45133
45516
  method = method.toUpperCase();
45134
- path4 = mergePath(this._basePath, path4);
45517
+ path7 = mergePath(this._basePath, path7);
45135
45518
  const r = {
45136
45519
  basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
45137
- path: path4,
45520
+ path: path7,
45138
45521
  method,
45139
45522
  handler
45140
45523
  };
45141
- this.router.add(method, path4, [handler, r]);
45524
+ this.router.add(method, path7, [handler, r]);
45142
45525
  this.routes.push(r);
45143
45526
  }
45144
45527
  #handleError(err, c) {
@@ -45151,10 +45534,10 @@ var Hono = class _Hono {
45151
45534
  if (method === "HEAD") {
45152
45535
  return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
45153
45536
  }
45154
- const path4 = this.getPath(request, { env });
45155
- const matchResult = this.router.match(method, path4);
45537
+ const path7 = this.getPath(request, { env });
45538
+ const matchResult = this.router.match(method, path7);
45156
45539
  const c = new Context(request, {
45157
- path: path4,
45540
+ path: path7,
45158
45541
  matchResult,
45159
45542
  env,
45160
45543
  executionCtx,
@@ -45261,7 +45644,7 @@ init_esm_shims();
45261
45644
  // ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/matcher.js
45262
45645
  init_esm_shims();
45263
45646
  var emptyParam = [];
45264
- function match(method, path4) {
45647
+ function match(method, path7) {
45265
45648
  const matchers = this.buildAllMatchers();
45266
45649
  const match2 = ((method2, path23) => {
45267
45650
  const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
@@ -45277,7 +45660,7 @@ function match(method, path4) {
45277
45660
  return [matcher[1][index], match3];
45278
45661
  });
45279
45662
  this.match = match2;
45280
- return match2(method, path4);
45663
+ return match2(method, path7);
45281
45664
  }
45282
45665
 
45283
45666
  // ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/node.js
@@ -45394,12 +45777,12 @@ init_esm_shims();
45394
45777
  var Trie = class {
45395
45778
  #context = { varIndex: 0 };
45396
45779
  #root = new Node();
45397
- insert(path4, index, pathErrorCheckOnly) {
45780
+ insert(path7, index, pathErrorCheckOnly) {
45398
45781
  const paramAssoc = [];
45399
45782
  const groups = [];
45400
45783
  for (let i = 0; ; ) {
45401
45784
  let replaced = false;
45402
- path4 = path4.replace(/\{[^}]+\}/g, (m2) => {
45785
+ path7 = path7.replace(/\{[^}]+\}/g, (m2) => {
45403
45786
  const mark = `@\\${i}`;
45404
45787
  groups[i] = [mark, m2];
45405
45788
  i++;
@@ -45410,7 +45793,7 @@ var Trie = class {
45410
45793
  break;
45411
45794
  }
45412
45795
  }
45413
- const tokens = path4.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
45796
+ const tokens = path7.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
45414
45797
  for (let i = groups.length - 1; i >= 0; i--) {
45415
45798
  const [mark] = groups[i];
45416
45799
  for (let j = tokens.length - 1; j >= 0; j--) {
@@ -45449,9 +45832,9 @@ var Trie = class {
45449
45832
  // ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/router.js
45450
45833
  var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
45451
45834
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
45452
- function buildWildcardRegExp(path4) {
45453
- return wildcardRegExpCache[path4] ??= new RegExp(
45454
- path4 === "*" ? "" : `^${path4.replace(
45835
+ function buildWildcardRegExp(path7) {
45836
+ return wildcardRegExpCache[path7] ??= new RegExp(
45837
+ path7 === "*" ? "" : `^${path7.replace(
45455
45838
  /\/\*$|([.\\+*[^\]$()])/g,
45456
45839
  (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)"
45457
45840
  )}$`
@@ -45473,17 +45856,17 @@ function buildMatcherFromPreprocessedRoutes(routes) {
45473
45856
  );
45474
45857
  const staticMap = /* @__PURE__ */ Object.create(null);
45475
45858
  for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
45476
- const [pathErrorCheckOnly, path4, handlers] = routesWithStaticPathFlag[i];
45859
+ const [pathErrorCheckOnly, path7, handlers] = routesWithStaticPathFlag[i];
45477
45860
  if (pathErrorCheckOnly) {
45478
- staticMap[path4] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
45861
+ staticMap[path7] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
45479
45862
  } else {
45480
45863
  j++;
45481
45864
  }
45482
45865
  let paramAssoc;
45483
45866
  try {
45484
- paramAssoc = trie.insert(path4, j, pathErrorCheckOnly);
45867
+ paramAssoc = trie.insert(path7, j, pathErrorCheckOnly);
45485
45868
  } catch (e) {
45486
- throw e === PATH_ERROR ? new UnsupportedPathError(path4) : e;
45869
+ throw e === PATH_ERROR ? new UnsupportedPathError(path7) : e;
45487
45870
  }
45488
45871
  if (pathErrorCheckOnly) {
45489
45872
  continue;
@@ -45517,12 +45900,12 @@ function buildMatcherFromPreprocessedRoutes(routes) {
45517
45900
  }
45518
45901
  return [regexp, handlerMap, staticMap];
45519
45902
  }
45520
- function findMiddleware(middleware, path4) {
45903
+ function findMiddleware(middleware, path7) {
45521
45904
  if (!middleware) {
45522
45905
  return void 0;
45523
45906
  }
45524
45907
  for (const k of Object.keys(middleware).sort((a2, b2) => b2.length - a2.length)) {
45525
- if (buildWildcardRegExp(k).test(path4)) {
45908
+ if (buildWildcardRegExp(k).test(path7)) {
45526
45909
  return [...middleware[k]];
45527
45910
  }
45528
45911
  }
@@ -45536,7 +45919,7 @@ var RegExpRouter = class {
45536
45919
  this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
45537
45920
  this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
45538
45921
  }
45539
- add(method, path4, handler) {
45922
+ add(method, path7, handler) {
45540
45923
  const middleware = this.#middleware;
45541
45924
  const routes = this.#routes;
45542
45925
  if (!middleware || !routes) {
@@ -45551,18 +45934,18 @@ var RegExpRouter = class {
45551
45934
  });
45552
45935
  });
45553
45936
  }
45554
- if (path4 === "/*") {
45555
- path4 = "*";
45937
+ if (path7 === "/*") {
45938
+ path7 = "*";
45556
45939
  }
45557
- const paramCount = (path4.match(/\/:/g) || []).length;
45558
- if (/\*$/.test(path4)) {
45559
- const re = buildWildcardRegExp(path4);
45940
+ const paramCount = (path7.match(/\/:/g) || []).length;
45941
+ if (/\*$/.test(path7)) {
45942
+ const re = buildWildcardRegExp(path7);
45560
45943
  if (method === METHOD_NAME_ALL) {
45561
45944
  Object.keys(middleware).forEach((m2) => {
45562
- middleware[m2][path4] ||= findMiddleware(middleware[m2], path4) || findMiddleware(middleware[METHOD_NAME_ALL], path4) || [];
45945
+ middleware[m2][path7] ||= findMiddleware(middleware[m2], path7) || findMiddleware(middleware[METHOD_NAME_ALL], path7) || [];
45563
45946
  });
45564
45947
  } else {
45565
- middleware[method][path4] ||= findMiddleware(middleware[method], path4) || findMiddleware(middleware[METHOD_NAME_ALL], path4) || [];
45948
+ middleware[method][path7] ||= findMiddleware(middleware[method], path7) || findMiddleware(middleware[METHOD_NAME_ALL], path7) || [];
45566
45949
  }
45567
45950
  Object.keys(middleware).forEach((m2) => {
45568
45951
  if (method === METHOD_NAME_ALL || method === m2) {
@@ -45580,7 +45963,7 @@ var RegExpRouter = class {
45580
45963
  });
45581
45964
  return;
45582
45965
  }
45583
- const paths = checkOptionalParameter(path4) || [path4];
45966
+ const paths = checkOptionalParameter(path7) || [path7];
45584
45967
  for (let i = 0, len = paths.length; i < len; i++) {
45585
45968
  const path23 = paths[i];
45586
45969
  Object.keys(routes).forEach((m2) => {
@@ -45607,13 +45990,13 @@ var RegExpRouter = class {
45607
45990
  const routes = [];
45608
45991
  let hasOwnRoute = method === METHOD_NAME_ALL;
45609
45992
  [this.#middleware, this.#routes].forEach((r) => {
45610
- const ownRoute = r[method] ? Object.keys(r[method]).map((path4) => [path4, r[method][path4]]) : [];
45993
+ const ownRoute = r[method] ? Object.keys(r[method]).map((path7) => [path7, r[method][path7]]) : [];
45611
45994
  if (ownRoute.length !== 0) {
45612
45995
  hasOwnRoute ||= true;
45613
45996
  routes.push(...ownRoute);
45614
45997
  } else if (method !== METHOD_NAME_ALL) {
45615
45998
  routes.push(
45616
- ...Object.keys(r[METHOD_NAME_ALL]).map((path4) => [path4, r[METHOD_NAME_ALL][path4]])
45999
+ ...Object.keys(r[METHOD_NAME_ALL]).map((path7) => [path7, r[METHOD_NAME_ALL][path7]])
45617
46000
  );
45618
46001
  }
45619
46002
  });
@@ -45640,13 +46023,13 @@ var SmartRouter = class {
45640
46023
  constructor(init) {
45641
46024
  this.#routers = init.routers;
45642
46025
  }
45643
- add(method, path4, handler) {
46026
+ add(method, path7, handler) {
45644
46027
  if (!this.#routes) {
45645
46028
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
45646
46029
  }
45647
- this.#routes.push([method, path4, handler]);
46030
+ this.#routes.push([method, path7, handler]);
45648
46031
  }
45649
- match(method, path4) {
46032
+ match(method, path7) {
45650
46033
  if (!this.#routes) {
45651
46034
  throw new Error("Fatal error");
45652
46035
  }
@@ -45661,7 +46044,7 @@ var SmartRouter = class {
45661
46044
  for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
45662
46045
  router.add(...routes[i2]);
45663
46046
  }
45664
- res = router.match(method, path4);
46047
+ res = router.match(method, path7);
45665
46048
  } catch (e) {
45666
46049
  if (e instanceof UnsupportedPathError) {
45667
46050
  continue;
@@ -45718,10 +46101,10 @@ var Node2 = class _Node2 {
45718
46101
  }
45719
46102
  this.#patterns = [];
45720
46103
  }
45721
- insert(method, path4, handler) {
46104
+ insert(method, path7, handler) {
45722
46105
  this.#order = ++this.#order;
45723
46106
  let curNode = this;
45724
- const parts = splitRoutingPath(path4);
46107
+ const parts = splitRoutingPath(path7);
45725
46108
  const possibleKeys = [];
45726
46109
  for (let i = 0, len = parts.length; i < len; i++) {
45727
46110
  const p2 = parts[i];
@@ -45770,12 +46153,12 @@ var Node2 = class _Node2 {
45770
46153
  }
45771
46154
  }
45772
46155
  }
45773
- search(method, path4) {
46156
+ search(method, path7) {
45774
46157
  const handlerSets = [];
45775
46158
  this.#params = emptyParams;
45776
46159
  const curNode = this;
45777
46160
  let curNodes = [curNode];
45778
- const parts = splitPath(path4);
46161
+ const parts = splitPath(path7);
45779
46162
  const curNodesQueue = [];
45780
46163
  const len = parts.length;
45781
46164
  let partOffsets = null;
@@ -45817,13 +46200,13 @@ var Node2 = class _Node2 {
45817
46200
  if (matcher instanceof RegExp) {
45818
46201
  if (partOffsets === null) {
45819
46202
  partOffsets = new Array(len);
45820
- let offset = path4[0] === "/" ? 1 : 0;
46203
+ let offset = path7[0] === "/" ? 1 : 0;
45821
46204
  for (let p2 = 0; p2 < len; p2++) {
45822
46205
  partOffsets[p2] = offset;
45823
46206
  offset += parts[p2].length + 1;
45824
46207
  }
45825
46208
  }
45826
- const restPathString = path4.substring(partOffsets[i]);
46209
+ const restPathString = path7.substring(partOffsets[i]);
45827
46210
  const m2 = matcher.exec(restPathString);
45828
46211
  if (m2) {
45829
46212
  params[name] = m2[0];
@@ -45876,18 +46259,18 @@ var TrieRouter = class {
45876
46259
  constructor() {
45877
46260
  this.#node = new Node2();
45878
46261
  }
45879
- add(method, path4, handler) {
45880
- const results = checkOptionalParameter(path4);
46262
+ add(method, path7, handler) {
46263
+ const results = checkOptionalParameter(path7);
45881
46264
  if (results) {
45882
46265
  for (let i = 0, len = results.length; i < len; i++) {
45883
46266
  this.#node.insert(method, results[i], handler);
45884
46267
  }
45885
46268
  return;
45886
46269
  }
45887
- this.#node.insert(method, path4, handler);
46270
+ this.#node.insert(method, path7, handler);
45888
46271
  }
45889
- match(method, path4) {
45890
- return this.#node.search(method, path4);
46272
+ match(method, path7) {
46273
+ return this.#node.search(method, path7);
45891
46274
  }
45892
46275
  };
45893
46276
 
@@ -46671,15 +47054,20 @@ function normalizePrefix(prefix) {
46671
47054
  }
46672
47055
  function createPublicPaths(prefix) {
46673
47056
  return {
46674
- collection: (basePath) => `${prefix}/${basePath}`,
46675
- item: (basePath) => `${prefix}/${basePath}/:slug`,
46676
- taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,
46677
- taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,
46678
47057
  mediaCollection: () => `${prefix}/media`,
46679
47058
  mediaItem: () => `${prefix}/media/:id`,
46680
47059
  openapi: () => `${prefix}/openapi.json`
46681
47060
  };
46682
47061
  }
47062
+ function createVersionedPaths(prefix, version3) {
47063
+ const root = version3 === null ? prefix : `${prefix}/${version3}`;
47064
+ return {
47065
+ collection: (basePath) => `${root}/${basePath}`,
47066
+ item: (basePath) => `${root}/${basePath}/:slug`,
47067
+ taxonomyCollection: (typeName) => `${root}/taxonomy/${typeName}`,
47068
+ taxonomyItem: (typeName) => `${root}/taxonomy/${typeName}/:id`
47069
+ };
47070
+ }
46683
47071
  function createCorsMiddleware(corsConfig) {
46684
47072
  const allowList = Array.isArray(corsConfig.origin) ? corsConfig.origin : [corsConfig.origin];
46685
47073
  const wildcard = allowList.includes("*");
@@ -46817,8 +47205,8 @@ function setAuthCookie(c, name, token, options) {
46817
47205
  sameSite: "Strict"
46818
47206
  });
46819
47207
  }
46820
- function clearAuthCookie(c, name, path4) {
46821
- deleteCookie(c, name, path4 !== void 0 ? { path: path4 } : void 0);
47208
+ function clearAuthCookie(c, name, path7) {
47209
+ deleteCookie(c, name, path7 !== void 0 ? { path: path7 } : void 0);
46822
47210
  }
46823
47211
  function createAuthMiddleware(db) {
46824
47212
  return async (c, next) => {
@@ -47241,7 +47629,7 @@ function nestedTargets(fields) {
47241
47629
  }
47242
47630
  return out;
47243
47631
  }
47244
- function buildProjectors(registry2, fieldKeyMaps) {
47632
+ function buildProjectors(registry2, fieldKeyMaps, fallbacks) {
47245
47633
  const projectors = {};
47246
47634
  const sources = [
47247
47635
  registry2.content_types,
@@ -47252,7 +47640,14 @@ function buildProjectors(registry2, fieldKeyMaps) {
47252
47640
  for (const [typeName, type] of Object.entries(source)) {
47253
47641
  const map2 = fieldKeyMaps[typeName];
47254
47642
  if (!map2) continue;
47255
- projectors[typeName] = { map: map2, nested: nestedTargets(type.fields) };
47643
+ const typeFallbacks = fallbacks?.[typeName];
47644
+ projectors[typeName] = {
47645
+ map: map2,
47646
+ nested: nestedTargets(type.fields),
47647
+ // Spread rather than assign: with exactOptionalPropertyTypes, an optional
47648
+ // property may be ABSENT but not present-and-undefined.
47649
+ ...typeFallbacks !== void 0 && { fallbacks: typeFallbacks }
47650
+ };
47256
47651
  }
47257
47652
  }
47258
47653
  return projectors;
@@ -47261,6 +47656,11 @@ function projectRow(row, typeName, projectors) {
47261
47656
  const p2 = projectors[typeName];
47262
47657
  if (!p2) return row;
47263
47658
  const out = p2.map.toLabels(row);
47659
+ if (p2.fallbacks !== void 0) {
47660
+ for (const [label, value] of Object.entries(p2.fallbacks)) {
47661
+ if (out[label] === null || out[label] === void 0) out[label] = value;
47662
+ }
47663
+ }
47264
47664
  for (const { label, target } of p2.nested) {
47265
47665
  const v2 = out[label];
47266
47666
  if (v2 === null || v2 === void 0) continue;
@@ -47324,11 +47724,11 @@ function isPublished(item) {
47324
47724
  return item["published"] === true;
47325
47725
  }
47326
47726
  function registerPublicContentRoutes(app, registry2, repos, projectors, paths, listRateLimit, resolver) {
47327
- function registerListRoute(path4, handler) {
47727
+ function registerListRoute(path7, handler) {
47328
47728
  if (listRateLimit) {
47329
- app.get(path4, listRateLimit, handler);
47729
+ app.get(path7, listRateLimit, handler);
47330
47730
  } else {
47331
- app.get(path4, handler);
47731
+ app.get(path7, handler);
47332
47732
  }
47333
47733
  }
47334
47734
  registerListRoute(paths.collection("content"), (c) => {
@@ -47350,13 +47750,24 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47350
47750
  const basePath = contentType.default_base_path;
47351
47751
  const repo = repos[typeName];
47352
47752
  if (!repo) continue;
47353
- const relationFieldNames = new Set(
47354
- contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
47355
- );
47753
+ const fieldKeys = projectors[typeName].map;
47754
+ const relationFieldNames = /* @__PURE__ */ new Set();
47755
+ for (const f of contentType.fields) {
47756
+ if (!RELATION_FIELD_TYPES.has(f.field_type)) continue;
47757
+ if (isColumnBacked(f)) {
47758
+ const label = fieldKeys.labelFor(f.db_column.column_name);
47759
+ if (label !== void 0) relationFieldNames.add(label);
47760
+ } else {
47761
+ relationFieldNames.add(f.name);
47762
+ }
47763
+ }
47356
47764
  const filterableFieldNames = /* @__PURE__ */ new Set([
47357
- ...contentType.fields.filter((f) => f.field_type !== "programmatic").map((f) => f.name),
47765
+ ...fieldKeys.labels,
47358
47766
  ...contentType.system_fields.map((f) => f.name)
47359
47767
  ]);
47768
+ const sortableFieldNames = new Set(
47769
+ [...SORTABLE_FIELDS].filter((f) => filterableFieldNames.has(f))
47770
+ );
47360
47771
  if (contentType.only_one) {
47361
47772
  app.get(paths.collection(basePath), async (c) => {
47362
47773
  const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
@@ -47372,8 +47783,6 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47372
47783
  });
47373
47784
  } else {
47374
47785
  registerListRoute(paths.collection(basePath), async (c) => {
47375
- const projector = projectors[typeName];
47376
- const fieldKeys = projector.map;
47377
47786
  const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
47378
47787
  if (!pagination.ok) {
47379
47788
  return c.json(
@@ -47388,13 +47797,13 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47388
47797
  );
47389
47798
  }
47390
47799
  const sortBy = c.req.query("sort_by") ?? "created_at";
47391
- if (!SORTABLE_FIELDS.has(sortBy)) {
47800
+ if (!sortableFieldNames.has(sortBy)) {
47392
47801
  return c.json(
47393
47802
  {
47394
47803
  ok: false,
47395
47804
  error: {
47396
47805
  code: "INVALID_SORT_FIELD",
47397
- message: `'${sortBy}' is not sortable. Allowed: title, created_at, updated_at`
47806
+ message: `'${sortBy}' is not sortable. Allowed: ${[...sortableFieldNames].join(", ")}`
47398
47807
  }
47399
47808
  },
47400
47809
  400
@@ -47441,7 +47850,7 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47441
47850
  );
47442
47851
  }
47443
47852
  }
47444
- const sortColumn = fieldKeys.columnFor(sortBy) ?? sortBy;
47853
+ const sortColumn = fieldKeys.labels.includes(sortBy) ? fieldKeys.columnFor(sortBy) : sortBy;
47445
47854
  const result = await repo.findMany({
47446
47855
  published_only: true,
47447
47856
  page: pagination.page,
@@ -49293,7 +49702,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
49293
49702
  }
49294
49703
  var API_VERSION = (() => {
49295
49704
  try {
49296
- return "0.4.2";
49705
+ return "0.5.0";
49297
49706
  } catch {
49298
49707
  return "0.0.0";
49299
49708
  }
@@ -49682,6 +50091,90 @@ function createMediaRepository(db) {
49682
50091
  }
49683
50092
  };
49684
50093
  }
50094
+ var VERSION_SEGMENT = /^v\d+$/;
50095
+ function versionNumber2(version3) {
50096
+ return Number.parseInt(version3.slice(1), 10);
50097
+ }
50098
+ function classifyVersion(segment, model) {
50099
+ if (!VERSION_SEGMENT.test(segment)) return "not-a-version";
50100
+ if (model.live.includes(segment)) return "live";
50101
+ return versionNumber2(segment) < versionNumber2(model.current) ? "retired" : "unknown";
50102
+ }
50103
+ function fallbacksFor(projection) {
50104
+ const out = {};
50105
+ for (const [typeName, type] of Object.entries(projection.types)) {
50106
+ const perLabel = {};
50107
+ for (const f of type.fields) {
50108
+ if (f.fallback !== void 0) perLabel[f.exposed_as] = f.fallback;
50109
+ }
50110
+ if (Object.keys(perLabel).length > 0) out[typeName] = perLabel;
50111
+ }
50112
+ return out;
50113
+ }
50114
+ function buildVersionSurface(input2) {
50115
+ const { version: version3, projectionVersion, prefix, registry: registry2, model, makeRepo } = input2;
50116
+ const paths = createVersionedPaths(prefix, version3);
50117
+ const projection = model.projections[projectionVersion];
50118
+ const fieldKeyMaps = {};
50119
+ const sources = [
50120
+ registry2.content_types,
50121
+ registry2.taxonomy_types,
50122
+ registry2.paragraph_types
50123
+ ];
50124
+ for (const source of sources) {
50125
+ for (const [typeName, type] of Object.entries(source)) {
50126
+ const projectionType = projection?.types[typeName];
50127
+ fieldKeyMaps[typeName] = projectionType ? createFieldKeyMapFromProjection(projectionType, type.fields) : createFieldKeyMap(type.fields);
50128
+ }
50129
+ }
50130
+ const fallbacks = projection ? fallbacksFor(projection) : void 0;
50131
+ const projectors = buildProjectors(registry2, fieldKeyMaps, fallbacks);
50132
+ const sortableColumnsFor = (typeName) => {
50133
+ const map2 = fieldKeyMaps[typeName];
50134
+ const type = registry2.content_types[typeName] ?? registry2.taxonomy_types[typeName];
50135
+ const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name));
50136
+ const out = /* @__PURE__ */ new Set();
50137
+ for (const label of SORTABLE_FIELDS) {
50138
+ const column = map2.columnFor(label);
50139
+ if (column !== void 0) out.add(column);
50140
+ else if (systemFieldNames.has(label)) out.add(label);
50141
+ }
50142
+ return out;
50143
+ };
50144
+ const repos = {};
50145
+ for (const [typeName, ct2] of Object.entries(registry2.content_types)) {
50146
+ repos[typeName] = makeRepo(typeName, ct2.db.table_name, sortableColumnsFor(typeName));
50147
+ }
50148
+ for (const [typeName, tt2] of Object.entries(registry2.taxonomy_types)) {
50149
+ repos[typeName] = makeRepo(typeName, tt2.db.table_name, sortableColumnsFor(typeName));
50150
+ }
50151
+ return { paths, fieldKeyMaps, projectors, repos };
50152
+ }
50153
+ function deprecationHeaders(input2) {
50154
+ const { requested, model, successor } = input2;
50155
+ const link = `<${successor}>; rel="successor-version"`;
50156
+ if (requested === null) {
50157
+ if (model.live.length <= 1) return null;
50158
+ return {
50159
+ Deprecation: "true",
50160
+ Link: link,
50161
+ Warning: `299 - "Unversioned path resolves to the latest version (${model.current}) and will change when a new version is cut. Pin a version."`
50162
+ };
50163
+ }
50164
+ if (requested === model.current) return null;
50165
+ return { Deprecation: "true", Link: link };
50166
+ }
50167
+ function versionRoutePaths(registry2, paths) {
50168
+ const out = [paths.collection("content"), paths.collection("taxonomy")];
50169
+ for (const ct2 of Object.values(registry2.content_types)) {
50170
+ out.push(paths.collection(ct2.default_base_path));
50171
+ if (!ct2.only_one) out.push(paths.item(ct2.default_base_path));
50172
+ }
50173
+ for (const tt2 of Object.values(registry2.taxonomy_types)) {
50174
+ out.push(paths.taxonomyCollection(tt2.name), paths.taxonomyItem(tt2.name));
50175
+ }
50176
+ return out;
50177
+ }
49685
50178
  var MISSING_STORAGE_ERROR = `\u2717 api.storage is required but not configured.
49686
50179
  Add a storage adapter to your manguito.config.ts:
49687
50180
 
@@ -49697,6 +50190,7 @@ function createCmsApp(options) {
49697
50190
  }
49698
50191
  const prefix = normalizePrefix(options.prefix);
49699
50192
  const publicPaths = createPublicPaths(prefix);
50193
+ const versionedPaths = createVersionedPaths(prefix, null);
49700
50194
  const { storage, registry: registry2, db, rateLimit, media, cors } = options;
49701
50195
  const cmsName = options.name ?? "Manguito CMS";
49702
50196
  const maxFileSize = media?.max_file_size;
@@ -49745,9 +50239,18 @@ function createCmsApp(options) {
49745
50239
  ])
49746
50240
  ]);
49747
50241
  const projectors = buildProjectors(registry2, fieldKeyMaps);
49748
- const sortableColumnsFor = (typeName) => new Set(
49749
- [...SORTABLE_FIELDS].map((label) => fieldKeyMaps[typeName].columnFor(label) ?? label)
49750
- );
50242
+ const sortableColumnsFor = (typeName) => {
50243
+ const map2 = fieldKeyMaps[typeName];
50244
+ const type = registry2.content_types[typeName] ?? registry2.taxonomy_types[typeName];
50245
+ const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name));
50246
+ const out = /* @__PURE__ */ new Set();
50247
+ for (const label of SORTABLE_FIELDS) {
50248
+ const column = map2.columnFor(label);
50249
+ if (column !== void 0) out.add(column);
50250
+ else if (systemFieldNames.has(label)) out.add(label);
50251
+ }
50252
+ return out;
50253
+ };
49751
50254
  const contentRepos = Object.fromEntries(
49752
50255
  Object.entries(registry2.content_types).map(([typeName, ct2]) => [
49753
50256
  typeName,
@@ -49766,27 +50269,14 @@ function createCmsApp(options) {
49766
50269
  );
49767
50270
  const repos = { ...contentRepos, ...taxonomyRepos };
49768
50271
  const mediaRepo = createMediaRepository(db);
49769
- const publicContentRepos = Object.fromEntries(
49770
- Object.entries(registry2.content_types).map(([typeName, ct2]) => [
49771
- typeName,
49772
- createDrizzleContentRepository(db, ct2.db.table_name, {
49773
- relations: buildRelationsMap(ct2.fields, registry2),
49774
- publishedRelations: true,
49775
- sortableColumns: sortableColumnsFor(typeName)
49776
- })
49777
- ])
49778
- );
49779
- const publicTaxonomyRepos = Object.fromEntries(
49780
- Object.entries(registry2.taxonomy_types).map(([typeName, tt2]) => [
49781
- typeName,
49782
- createDrizzleContentRepository(db, tt2.db.table_name, {
49783
- relations: buildRelationsMap(tt2.fields, registry2),
49784
- publishedRelations: true,
49785
- sortableColumns: sortableColumnsFor(typeName)
49786
- })
49787
- ])
49788
- );
49789
- const publicRepos = { ...publicContentRepos, ...publicTaxonomyRepos };
50272
+ const makeRepo = (typeName, tableName, sortableColumns) => {
50273
+ const type = registry2.content_types[typeName] ?? registry2.taxonomy_types[typeName];
50274
+ return createDrizzleContentRepository(db, tableName, {
50275
+ relations: buildRelationsMap(type.fields, registry2),
50276
+ publishedRelations: true,
50277
+ sortableColumns
50278
+ });
50279
+ };
49790
50280
  const graphqlRepos = Object.fromEntries([
49791
50281
  ...Object.entries(registry2.content_types).map(([typeName, ct2]) => [
49792
50282
  typeName,
@@ -49809,17 +50299,17 @@ function createCmsApp(options) {
49809
50299
  for (const ct2 of Object.values(registry2.content_types)) {
49810
50300
  const base = ct2.default_base_path;
49811
50301
  if (ct2.only_one) {
49812
- paths[publicPaths.collection(base)] = { get: { summary: `Get ${ct2.label}`, tags: [ct2.label] } };
50302
+ paths[versionedPaths.collection(base)] = { get: { summary: `Get ${ct2.label}`, tags: [ct2.label] } };
49813
50303
  } else {
49814
- paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct2.label}`, tags: [ct2.label] } };
49815
- paths[publicPaths.item(base).replace(":slug", "{slug}")] = {
50304
+ paths[versionedPaths.collection(base)] = { get: { summary: `List published ${ct2.label}`, tags: [ct2.label] } };
50305
+ paths[versionedPaths.item(base).replace(":slug", "{slug}")] = {
49816
50306
  get: { summary: `Get ${ct2.label} by slug`, tags: [ct2.label] }
49817
50307
  };
49818
50308
  }
49819
50309
  }
49820
50310
  for (const tt2 of Object.values(registry2.taxonomy_types)) {
49821
- paths[publicPaths.taxonomyCollection(tt2.name)] = { get: { summary: `List published ${tt2.label}`, tags: [tt2.label] } };
49822
- paths[publicPaths.taxonomyItem(tt2.name).replace(":id", "{id}")] = {
50311
+ paths[versionedPaths.taxonomyCollection(tt2.name)] = { get: { summary: `List published ${tt2.label}`, tags: [tt2.label] } };
50312
+ paths[versionedPaths.taxonomyItem(tt2.name).replace(":id", "{id}")] = {
49823
50313
  get: { summary: `Get ${tt2.label} by id`, tags: [tt2.label] }
49824
50314
  };
49825
50315
  }
@@ -49839,13 +50329,68 @@ function createCmsApp(options) {
49839
50329
  paths: {}
49840
50330
  })
49841
50331
  );
49842
- registerPublicContentRoutes(app, registry2, publicRepos, projectors, publicPaths, listRateLimit, programmaticResolver);
50332
+ const model = options.versions ?? {
50333
+ current: "v1",
50334
+ live: ["v1"],
50335
+ projections: {}
50336
+ };
50337
+ const passes = [
50338
+ ...options.versions !== void 0 ? model.live.map((v2) => ({ version: v2, projectionVersion: v2 })) : [],
50339
+ { version: null, projectionVersion: model.current }
50340
+ ];
50341
+ for (const pass of passes) {
50342
+ const surface = buildVersionSurface({ ...pass, prefix, registry: registry2, model, makeRepo });
50343
+ const headers = deprecationHeaders({
50344
+ requested: pass.version,
50345
+ model,
50346
+ // The version root, with no trailing slash — never
50347
+ // createVersionedPaths(prefix, model.current).collection(''), which
50348
+ // would yield `${prefix}/${model.current}/` in every Link header.
50349
+ successor: `${prefix}/${model.current}`
50350
+ });
50351
+ if (headers !== null) {
50352
+ const attachDeprecationHeaders = async (c, next) => {
50353
+ await next();
50354
+ for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value);
50355
+ };
50356
+ for (const path7 of versionRoutePaths(registry2, surface.paths)) {
50357
+ app.use(path7, attachDeprecationHeaders);
50358
+ }
50359
+ }
50360
+ registerPublicContentRoutes(app, registry2, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver);
50361
+ }
50362
+ if (options.versions !== void 0) {
50363
+ app.all(`${prefix}/:version/*`, async (c, next) => {
50364
+ const kind = classifyVersion(c.req.param("version"), model);
50365
+ if (kind === "not-a-version" || kind === "live") return next();
50366
+ const live = model.live.join(", ");
50367
+ return kind === "retired" ? c.json(
50368
+ {
50369
+ ok: false,
50370
+ error: {
50371
+ code: "VERSION_RETIRED",
50372
+ message: `Version ${c.req.param("version")} is no longer served. Live versions: ${live}.`
50373
+ }
50374
+ },
50375
+ 410
50376
+ ) : c.json(
50377
+ {
50378
+ ok: false,
50379
+ error: {
50380
+ code: "VERSION_NOT_FOUND",
50381
+ message: `No version ${c.req.param("version")} is served. Live versions: ${live}.`
50382
+ }
50383
+ },
50384
+ 404
50385
+ );
50386
+ });
50387
+ }
49843
50388
  registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
49844
50389
  if (options.graphql?.enabled) {
49845
50390
  const gqlOptions = options.graphql;
49846
50391
  let gqlHandler = null;
49847
50392
  let initError = null;
49848
- const ready = import("./handler-45O5OX2C-OIEJ52PN.js").then(({ createGraphQLHandler }) => {
50393
+ const ready = import("./handler-X7JDHJAL-JEAY3P6W.js").then(({ createGraphQLHandler }) => {
49849
50394
  gqlHandler = createGraphQLHandler(registry2, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
49850
50395
  }).catch((err) => {
49851
50396
  initError = err;
@@ -49893,12 +50438,12 @@ function createCmsApp(options) {
49893
50438
 
49894
50439
  // src/codegen/drizzle-config.ts
49895
50440
  init_esm_shims();
49896
- import { writeFile as writeFile6 } from "fs/promises";
49897
- import { join as join8, relative as relative2, resolve as resolve4 } from "path";
50441
+ import { writeFile as writeFile7 } from "fs/promises";
50442
+ import { join as join10, relative as relative2, resolve as resolve5 } from "path";
49898
50443
  async function generateDrizzleConfig(config3, targetDir) {
49899
50444
  const migrationsFolder = config3.migrations?.folder ?? "./migrations";
49900
50445
  const migrationsTable = config3.migrations?.table ?? "__manguito_migrations";
49901
- const outPath = relative2(resolve4(targetDir), resolve4(migrationsFolder)).replace(/\\/g, "/");
50446
+ const outPath = relative2(resolve5(targetDir), resolve5(migrationsFolder)).replace(/\\/g, "/");
49902
50447
  const content = [
49903
50448
  `import { defineConfig } from 'drizzle-kit'`,
49904
50449
  ``,
@@ -49916,13 +50461,13 @@ async function generateDrizzleConfig(config3, targetDir) {
49916
50461
  ` tablesFilter: ['!${migrationsTable}'],`,
49917
50462
  `})`
49918
50463
  ].join("\n");
49919
- await writeFile6(join8(targetDir, "drizzle.config.ts"), content, "utf8");
50464
+ await writeFile7(join10(targetDir, "drizzle.config.ts"), content, "utf8");
49920
50465
  }
49921
50466
 
49922
50467
  // src/codegen/nav.ts
49923
50468
  init_esm_shims();
49924
- import { writeFile as writeFile7 } from "fs/promises";
49925
- import { join as join9 } from "path";
50469
+ import { writeFile as writeFile8 } from "fs/promises";
50470
+ import { join as join11 } from "path";
49926
50471
  function schemaBasePath(name) {
49927
50472
  const segment = name.includes("--") ? name.slice(name.indexOf("--") + 2) : name;
49928
50473
  return segment.replace(/_/g, "-");
@@ -49940,17 +50485,17 @@ async function generateNav(registry2, targetDir) {
49940
50485
  })).sort((a2, b2) => a2.label.localeCompare(b2.label));
49941
50486
  const content = `export const nav = ${JSON.stringify({ contentTypes, taxonomyTypes }, null, 2)}
49942
50487
  `;
49943
- await writeFile7(join9(targetDir, "nav.ts"), content, "utf8");
50488
+ await writeFile8(join11(targetDir, "nav.ts"), content, "utf8");
49944
50489
  }
49945
50490
 
49946
50491
  // src/commands/dev-routing.ts
49947
50492
  init_esm_shims();
49948
50493
  var ABSOLUTE_BACKEND_PATHS = ["/graphql"];
49949
50494
  function shouldBridgeToHono(url2, apiPrefix) {
49950
- const path4 = url2.split("?")[0] ?? "";
49951
- if (path4.startsWith(apiPrefix)) return true;
49952
- if (path4.startsWith("/admin/api")) return true;
49953
- return ABSOLUTE_BACKEND_PATHS.includes(path4);
50495
+ const path7 = url2.split("?")[0] ?? "";
50496
+ if (path7.startsWith(apiPrefix)) return true;
50497
+ if (path7.startsWith("/admin/api")) return true;
50498
+ return ABSOLUTE_BACKEND_PATHS.includes(path7);
49954
50499
  }
49955
50500
 
49956
50501
  // src/utils/db.ts
@@ -49985,7 +50530,7 @@ function createPromptAdapter() {
49985
50530
  init_esm_shims();
49986
50531
  import { existsSync as existsSync4 } from "fs";
49987
50532
  import { readdir } from "fs/promises";
49988
- import { resolve as resolve5, join as join10, extname as extname3 } from "path";
50533
+ import { resolve as resolve6, join as join12, extname as extname3 } from "path";
49989
50534
  import { pathToFileURL as pathToFileURL2 } from "url";
49990
50535
  var RESOLVER_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mjs", ".js"]);
49991
50536
  function isProgrammaticFieldDefinition(v2) {
@@ -49994,7 +50539,7 @@ function isProgrammaticFieldDefinition(v2) {
49994
50539
  async function walkTsFiles(dir) {
49995
50540
  const out = [];
49996
50541
  for (const entry of await readdir(dir, { withFileTypes: true })) {
49997
- const full = join10(dir, entry.name);
50542
+ const full = join12(dir, entry.name);
49998
50543
  if (entry.isDirectory()) {
49999
50544
  out.push(...await walkTsFiles(full));
50000
50545
  } else if (RESOLVER_EXTENSIONS.has(extname3(entry.name))) {
@@ -50004,7 +50549,7 @@ async function walkTsFiles(dir) {
50004
50549
  return out;
50005
50550
  }
50006
50551
  async function loadProgrammaticResolvers(cwd, dir) {
50007
- const abs = resolve5(cwd, dir);
50552
+ const abs = resolve6(cwd, dir);
50008
50553
  const map2 = /* @__PURE__ */ new Map();
50009
50554
  if (!existsSync4(abs)) return map2;
50010
50555
  for (const file2 of (await walkTsFiles(abs)).sort()) {
@@ -50051,13 +50596,13 @@ async function runDev(options, deps) {
50051
50596
  printSuccess("Config loaded");
50052
50597
  const db = await connectDb(config3);
50053
50598
  printSuccess("Database connected");
50054
- const manguitoDir = resolve6(cwd, ".manguito");
50599
+ const manguitoDir = resolve7(cwd, ".manguito");
50055
50600
  await mkdir2(manguitoDir, { recursive: true });
50056
- const drizzleConfigPath = resolve6(manguitoDir, "drizzle.config.ts");
50601
+ const drizzleConfigPath = resolve7(manguitoDir, "drizzle.config.ts");
50057
50602
  const needsSeed = !await db.tableExists("users");
50058
50603
  const bootstrapRegistry = await parseAllSchemas(cwd, config3);
50059
50604
  await generateDrizzleConfig(config3, manguitoDir);
50060
- await writeFile8(join11(manguitoDir, "schema.ts"), generateSchemaFile(bootstrapRegistry), "utf8");
50605
+ await writeFile9(join13(manguitoDir, "schema.ts"), generateSchemaFile(bootstrapRegistry), "utf8");
50061
50606
  await runDevMigration(drizzleConfigPath);
50062
50607
  printSuccess(needsSeed ? "Schema tables created" : "Schema tables up to date");
50063
50608
  if (needsSeed) {
@@ -50103,8 +50648,20 @@ async function runDev(options, deps) {
50103
50648
  );
50104
50649
  await generateSchemaRegistry(registry2, manguitoDir);
50105
50650
  await generateRoutes2(registry2, manguitoDir);
50106
- await generateForms(registry2, join11(manguitoDir, "forms"));
50651
+ await generateForms(registry2, join13(manguitoDir, "forms"));
50107
50652
  await generateNav(registry2, manguitoDir);
50653
+ const schemaConfig = resolveSchemaConfig(cwd, config3);
50654
+ const snapshotsResult = loadVersionSnapshots(schemaConfig, registry2);
50655
+ if (!snapshotsResult.ok) {
50656
+ printValidationErrors(snapshotsResult.errors, "Version snapshot errors", "manguito version:diff");
50657
+ process.exit(1);
50658
+ }
50659
+ const versionModelResult = computeVersionModel({ current: registry2, snapshots: snapshotsResult.value });
50660
+ if (!versionModelResult.ok) {
50661
+ printValidationErrors(versionModelResult.errors, "Version model errors", "manguito version:diff");
50662
+ process.exit(1);
50663
+ }
50664
+ const versionModel = reduceVersionModel(versionModelResult.value);
50108
50665
  const resolverMap = await loadProgrammaticResolvers(cwd, config3.programmatic.dir);
50109
50666
  const adapter = createCmsApp({
50110
50667
  name: config3.name,
@@ -50112,6 +50669,7 @@ async function runDev(options, deps) {
50112
50669
  db: db.getDb(),
50113
50670
  storage: config3.storage,
50114
50671
  resolvers: resolverMap,
50672
+ versions: versionModel,
50115
50673
  ...config3.api.prefix ? { prefix: config3.api.prefix } : {},
50116
50674
  ...config3.api.media?.max_file_size ? { media: { max_file_size: config3.api.media.max_file_size } } : {},
50117
50675
  ...config3.api.rateLimit ? { rateLimit: config3.api.rateLimit } : {},
@@ -50144,8 +50702,8 @@ async function runDev(options, deps) {
50144
50702
  }
50145
50703
  if (url2.startsWith("/uploads/")) {
50146
50704
  const relativePath = (url2.slice("/uploads/".length).split("?")[0] ?? "").replace(/\.\./g, "");
50147
- const uploadsDir = resolve6(cwd, "uploads");
50148
- const filePath = resolve6(uploadsDir, relativePath);
50705
+ const uploadsDir = resolve7(cwd, "uploads");
50706
+ const filePath = resolve7(uploadsDir, relativePath);
50149
50707
  if (!filePath.startsWith(uploadsDir + "/") && filePath !== uploadsDir) {
50150
50708
  res.statusCode = 403;
50151
50709
  res.end("Forbidden");
@@ -50186,7 +50744,7 @@ async function runDev(options, deps) {
50186
50744
  printSuccess(`Dev server running at http://localhost:${port}`);
50187
50745
  printSuccess(`Admin panel at http://localhost:${port}${config3.admin.prefix}`);
50188
50746
  });
50189
- const schemasDir = resolve6(cwd, config3.schema.base_path);
50747
+ const schemasDir = resolve7(cwd, config3.schema.base_path);
50190
50748
  const watcher = watch(schemasDir, { recursive: true });
50191
50749
  void (async () => {
50192
50750
  for await (const event of watcher) {
@@ -50205,7 +50763,7 @@ async function runDev(options, deps) {
50205
50763
  }
50206
50764
  }
50207
50765
  })();
50208
- const programmaticDir = resolve6(cwd, config3.programmatic.dir);
50766
+ const programmaticDir = resolve7(cwd, config3.programmatic.dir);
50209
50767
  if (existsSync5(programmaticDir)) {
50210
50768
  const progWatcher = watch(programmaticDir, { recursive: true });
50211
50769
  void (async () => {
@@ -50237,11 +50795,25 @@ async function onSchemaFileChange(args) {
50237
50795
  return;
50238
50796
  }
50239
50797
  await generateDrizzleConfig(config3, manguitoDir);
50240
- await writeFile8(join11(manguitoDir, "schema.ts"), generateSchemaFile(registry2), "utf8");
50798
+ await writeFile9(join13(manguitoDir, "schema.ts"), generateSchemaFile(registry2), "utf8");
50241
50799
  await generateSchemaRegistry(registry2, manguitoDir);
50242
50800
  await generateRoutes2(registry2, manguitoDir);
50243
- await generateForms(registry2, join11(manguitoDir, "forms"));
50801
+ await generateForms(registry2, join13(manguitoDir, "forms"));
50244
50802
  await generateNav(registry2, manguitoDir);
50803
+ const schemaConfig = resolveSchemaConfig(cwd, config3);
50804
+ const snapshotsResult = loadVersionSnapshots(schemaConfig, registry2);
50805
+ if (!snapshotsResult.ok) {
50806
+ printValidationErrors(snapshotsResult.errors, "Version snapshot errors", "manguito version:diff");
50807
+ process.stderr.write("\u26A0 Changes not applied.\n");
50808
+ return;
50809
+ }
50810
+ const versionModelResult = computeVersionModel({ current: registry2, snapshots: snapshotsResult.value });
50811
+ if (!versionModelResult.ok) {
50812
+ printValidationErrors(versionModelResult.errors, "Version model errors", "manguito version:diff");
50813
+ process.stderr.write("\u26A0 Changes not applied.\n");
50814
+ return;
50815
+ }
50816
+ const versionModel = reduceVersionModel(versionModelResult.value);
50245
50817
  try {
50246
50818
  await runDevMigration(drizzleConfigPath);
50247
50819
  } catch (err) {
@@ -50256,6 +50828,7 @@ async function onSchemaFileChange(args) {
50256
50828
  db: db.getDb(),
50257
50829
  storage: config3.storage,
50258
50830
  resolvers: resolverMap,
50831
+ versions: versionModel,
50259
50832
  ...config3.api.prefix ? { prefix: config3.api.prefix } : {},
50260
50833
  ...config3.api.media?.max_file_size ? { media: { max_file_size: config3.api.media.max_file_size } } : {},
50261
50834
  ...config3.api.rateLimit ? { rateLimit: config3.api.rateLimit } : {},
@@ -50280,7 +50853,7 @@ async function parseAllSchemas(cwd, config3) {
50280
50853
  }
50281
50854
  parsedSchemas.push(parseResult.schema);
50282
50855
  }
50283
- const rolesPath = resolve6(cwd, config3.schema.base_path, "roles.json");
50856
+ const rolesPath = resolve7(cwd, config3.schema.base_path, "roles.json");
50284
50857
  const rolesLoad = loadSchemaFile(rolesPath);
50285
50858
  if (!rolesLoad.ok) {
50286
50859
  printGuidedError("Could not read roles.json.");
@@ -50291,7 +50864,7 @@ async function parseAllSchemas(cwd, config3) {
50291
50864
  printGuidedError("roles.json parse errors \u2014 run `manguito validate` for details.");
50292
50865
  process.exit(1);
50293
50866
  }
50294
- const routesPath = resolve6(cwd, config3.schema.base_path, "routes.json");
50867
+ const routesPath = resolve7(cwd, config3.schema.base_path, "routes.json");
50295
50868
  const routesLoad = loadSchemaFile(routesPath);
50296
50869
  if (!routesLoad.ok) {
50297
50870
  printGuidedError("Could not read routes.json.");
@@ -50305,7 +50878,7 @@ async function parseAllSchemas(cwd, config3) {
50305
50878
  return buildSchemaRegistry(parsedSchemas, routesResult.value, rolesResult.value);
50306
50879
  }
50307
50880
  function resolveAdminRoot2(cwd) {
50308
- const cwdRequire = createRequire2(join11(cwd, "package.json"));
50881
+ const cwdRequire = createRequire2(join13(cwd, "package.json"));
50309
50882
  const adminPkgJson = cwdRequire.resolve("@bobbykim/manguito-cms-admin/package.json");
50310
50883
  return dirname2(adminPkgJson);
50311
50884
  }
@@ -50351,7 +50924,7 @@ async function bridgeToHono(req, res, honoFetch) {
50351
50924
 
50352
50925
  // src/commands/start.ts
50353
50926
  init_esm_shims();
50354
- import { resolve as resolve7 } from "path";
50927
+ import { resolve as resolve8 } from "path";
50355
50928
  import { pathToFileURL as pathToFileURL3 } from "url";
50356
50929
  function registerStart(program2) {
50357
50930
  program2.command("start").description("Start the production server from dist/").option("--env <path>", "path to .env file to load").action(async (options) => {
@@ -50364,7 +50937,7 @@ async function runStart(options, deps) {
50364
50937
  const config3 = await resolveConfig(cwd);
50365
50938
  const db = await connectDb(config3);
50366
50939
  const migrationsTable = config3.migrations?.table ?? "__manguito_migrations";
50367
- const migrationsFolder = config3.migrations?.folder ? resolve7(cwd, config3.migrations.folder) : resolve7(cwd, "./migrations");
50940
+ const migrationsFolder = config3.migrations?.folder ? resolve8(cwd, config3.migrations.folder) : resolve8(cwd, "./migrations");
50368
50941
  const trackingTableExists = await db.tableExists(migrationsTable);
50369
50942
  if (!trackingTableExists) {
50370
50943
  printGuidedError(
@@ -50399,7 +50972,7 @@ async function runStart(options, deps) {
50399
50972
  } else {
50400
50973
  allErrors.push(...walkResult.errors);
50401
50974
  }
50402
- const rolesPath = resolve7(cwd, config3.schema.base_path, "roles.json");
50975
+ const rolesPath = resolve8(cwd, config3.schema.base_path, "roles.json");
50403
50976
  const rolesLoad = loadSchemaFile(rolesPath);
50404
50977
  let parsedRoles = null;
50405
50978
  if (rolesLoad.ok) {
@@ -50412,7 +50985,7 @@ async function runStart(options, deps) {
50412
50985
  } else {
50413
50986
  allErrors.push(...rolesLoad.errors);
50414
50987
  }
50415
- const routesPath = resolve7(cwd, config3.schema.base_path, "routes.json");
50988
+ const routesPath = resolve8(cwd, config3.schema.base_path, "routes.json");
50416
50989
  const routesLoad = loadSchemaFile(routesPath);
50417
50990
  let parsedRoutes = null;
50418
50991
  if (routesLoad.ok) {
@@ -50434,7 +51007,7 @@ async function runStart(options, deps) {
50434
51007
  }
50435
51008
  const registry2 = buildSchemaRegistry(parsedSchemas, parsedRoutes, parsedRoles);
50436
51009
  await seedSystemTables(db.getDb(), registry2);
50437
- const serverUrl = pathToFileURL3(resolve7(cwd, "dist/server.js")).href;
51010
+ const serverUrl = pathToFileURL3(resolve8(cwd, "dist/server.js")).href;
50438
51011
  await (deps.loadServer ? deps.loadServer(serverUrl) : import(serverUrl));
50439
51012
  const port = Number(process.env["PORT"] ?? 3e3);
50440
51013
  printSuccess(`Server running at http://localhost:${port}`);
@@ -50443,7 +51016,7 @@ async function runStart(options, deps) {
50443
51016
 
50444
51017
  // src/commands/migrate.ts
50445
51018
  init_esm_shims();
50446
- import { resolve as resolve8, join as join12 } from "path";
51019
+ import { resolve as resolve9, join as join14 } from "path";
50447
51020
  import { readFile as readFile2 } from "fs/promises";
50448
51021
  function registerMigrate(program2) {
50449
51022
  program2.command("migrate").description("Apply pending database migrations").option("--env <path>", "path to .env file to load").option("--status", "show migration state without applying").option("--dry-run", "preview migrations without writing").option("--force", "skip destructive-change confirmation prompt").action(async (options) => {
@@ -50461,8 +51034,8 @@ function buildDeps(options, cwd) {
50461
51034
  buildRunner: async () => runBuild(options.env ? { env: options.env } : {}, { cwd }),
50462
51035
  needsRebuild: () => needsRebuild(cwd),
50463
51036
  db: createPostgresAdapter(),
50464
- migrationsFolder: resolve8(cwd, "./migrations"),
50465
- configPath: resolve8(cwd, "dist/generated/drizzle.config.ts"),
51037
+ migrationsFolder: resolve9(cwd, "./migrations"),
51038
+ configPath: resolve9(cwd, "dist/generated/drizzle.config.ts"),
50466
51039
  prompt: createPromptAdapter()
50467
51040
  };
50468
51041
  }
@@ -50477,7 +51050,7 @@ async function runMigrate(options, deps) {
50477
51050
  }
50478
51051
  const config4 = await resolveConfig(process.cwd());
50479
51052
  const migrationsTable2 = config4.migrations?.table ?? "__manguito_migrations";
50480
- const migrationsFolder2 = config4.migrations?.folder ? resolve8(process.cwd(), config4.migrations.folder) : deps.migrationsFolder;
51053
+ const migrationsFolder2 = config4.migrations?.folder ? resolve9(process.cwd(), config4.migrations.folder) : deps.migrationsFolder;
50481
51054
  const status = await getMigrationStatus(deps.db.getDb(), { migrationsTable: migrationsTable2, migrationsFolder: migrationsFolder2 });
50482
51055
  if (status.applied.length === 0 && status.pending.length === 0) {
50483
51056
  process.stdout.write(
@@ -50509,7 +51082,7 @@ async function runMigrate(options, deps) {
50509
51082
  loadEnvFile(options.env);
50510
51083
  const cwd = process.cwd();
50511
51084
  const config3 = await resolveConfig(cwd);
50512
- const migrationsFolder = config3.migrations?.folder ? resolve8(cwd, config3.migrations.folder) : deps.migrationsFolder;
51085
+ const migrationsFolder = config3.migrations?.folder ? resolve9(cwd, config3.migrations.folder) : deps.migrationsFolder;
50513
51086
  const migrationsTable = config3.migrations?.table ?? "__manguito_migrations";
50514
51087
  try {
50515
51088
  await deps.db.connect();
@@ -50529,10 +51102,10 @@ async function runMigrate(options, deps) {
50529
51102
  }
50530
51103
  }
50531
51104
  printSuccess("Build complete");
50532
- const generatedDir = resolve8(cwd, "dist/generated");
51105
+ const generatedDir = resolve9(cwd, "dist/generated");
50533
51106
  await generateDrizzleConfig(config3, generatedDir);
50534
51107
  const newFileNames = await generateMigration(deps.configPath, migrationsFolder);
50535
- const newFilePaths = newFileNames.map((f) => join12(migrationsFolder, f));
51108
+ const newFilePaths = newFileNames.map((f) => join14(migrationsFolder, f));
50536
51109
  printSuccess(
50537
51110
  newFileNames.length === 0 ? "No new migration files generated" : `Generated ${newFileNames.length} migration file${newFileNames.length !== 1 ? "s" : ""}: ${newFileNames.join(", ")}`
50538
51111
  );
@@ -50602,7 +51175,7 @@ async function runMigrate(options, deps) {
50602
51175
  } else {
50603
51176
  allErrors.push(...walkResult.errors);
50604
51177
  }
50605
- const rolesPath = resolve8(cwd, config3.schema.base_path, "roles.json");
51178
+ const rolesPath = resolve9(cwd, config3.schema.base_path, "roles.json");
50606
51179
  const rolesLoad = loadSchemaFile(rolesPath);
50607
51180
  let parsedRoles = null;
50608
51181
  if (rolesLoad.ok) {
@@ -50615,7 +51188,7 @@ async function runMigrate(options, deps) {
50615
51188
  } else {
50616
51189
  allErrors.push(...rolesLoad.errors);
50617
51190
  }
50618
- const routesPath = resolve8(cwd, config3.schema.base_path, "routes.json");
51191
+ const routesPath = resolve9(cwd, config3.schema.base_path, "routes.json");
50619
51192
  const routesLoad = loadSchemaFile(routesPath);
50620
51193
  let parsedRoutes = null;
50621
51194
  if (routesLoad.ok) {
@@ -50647,7 +51220,7 @@ async function runMigrate(options, deps) {
50647
51220
 
50648
51221
  // src/commands/validate.ts
50649
51222
  init_esm_shims();
50650
- import { resolve as resolve9 } from "path";
51223
+ import { resolve as resolve10 } from "path";
50651
51224
  function registerValidate(program2) {
50652
51225
  program2.command("validate").description("Parse and validate all schemas, config, roles, and routes").option("--env <path>", "path to .env file to load").action(async (options) => {
50653
51226
  await runValidate(options, { cwd: process.cwd() });
@@ -50672,7 +51245,7 @@ async function runValidate(options, deps) {
50672
51245
  }
50673
51246
  }
50674
51247
  }
50675
- const rolesPath = resolve9(cwd, config3.schema.base_path, "roles.json");
51248
+ const rolesPath = resolve10(cwd, config3.schema.base_path, "roles.json");
50676
51249
  const rolesLoadResult = loadSchemaFile(rolesPath);
50677
51250
  let parsedRoles = null;
50678
51251
  if (!rolesLoadResult.ok) {
@@ -50685,7 +51258,7 @@ async function runValidate(options, deps) {
50685
51258
  parsedRoles = rolesParseResult.value;
50686
51259
  }
50687
51260
  }
50688
- const routesPath = resolve9(cwd, config3.schema.base_path, "routes.json");
51261
+ const routesPath = resolve10(cwd, config3.schema.base_path, "routes.json");
50689
51262
  const routesLoadResult = loadSchemaFile(routesPath);
50690
51263
  let parsedRoutesDef = null;
50691
51264
  if (!routesLoadResult.ok) {
@@ -50960,6 +51533,321 @@ async function runUsersDemote(options, deps) {
50960
51533
  printSuccess(`${email3} demoted to ${targetRole.name}.`);
50961
51534
  }
50962
51535
 
51536
+ // src/commands/version.ts
51537
+ init_esm_shims();
51538
+ import fs6 from "fs";
51539
+ import path6 from "path";
51540
+
51541
+ // src/utils/registry.ts
51542
+ init_esm_shims();
51543
+ import { resolve as resolve11 } from "path";
51544
+ function loadWorkingRegistry(cwd, config3, command) {
51545
+ const schema = resolveSchemaConfig(cwd, config3);
51546
+ const allErrors = [];
51547
+ const parsedSchemas = [];
51548
+ const walkResult = walkSchemaDirectory(schema);
51549
+ if (!walkResult.ok) {
51550
+ allErrors.push(...walkResult.errors);
51551
+ } else {
51552
+ for (const file2 of walkResult.value) {
51553
+ const parseResult = parseSchema(file2.raw, file2.schema_type, file2.path);
51554
+ if (!parseResult.ok) {
51555
+ allErrors.push(...parseResult.errors);
51556
+ } else {
51557
+ parsedSchemas.push(parseResult.schema);
51558
+ }
51559
+ }
51560
+ }
51561
+ const rolesPath = resolve11(schema.base_path, "roles.json");
51562
+ let parsedRoles = null;
51563
+ const rolesLoad = loadSchemaFile(rolesPath);
51564
+ if (!rolesLoad.ok) {
51565
+ allErrors.push(...rolesLoad.errors);
51566
+ } else {
51567
+ const rolesParse = parseRoles(rolesLoad.value, rolesPath);
51568
+ if (!rolesParse.ok) allErrors.push(...rolesParse.errors);
51569
+ else parsedRoles = rolesParse.value;
51570
+ }
51571
+ const routesPath = resolve11(schema.base_path, "routes.json");
51572
+ let parsedRoutes = null;
51573
+ const routesLoad = loadSchemaFile(routesPath);
51574
+ if (!routesLoad.ok) {
51575
+ allErrors.push(...routesLoad.errors);
51576
+ } else {
51577
+ const routesParse = parseRoutes(routesLoad.value, routesPath);
51578
+ if (!routesParse.ok) allErrors.push(...routesParse.errors);
51579
+ else parsedRoutes = routesParse.value;
51580
+ }
51581
+ if (allErrors.length > 0 || parsedRoles === null || parsedRoutes === null) {
51582
+ printValidationErrors(allErrors, "Schema parse errors", command);
51583
+ process.exit(1);
51584
+ }
51585
+ return buildSchemaRegistry(parsedSchemas, parsedRoutes, parsedRoles);
51586
+ }
51587
+
51588
+ // src/commands/version-report.ts
51589
+ init_esm_shims();
51590
+ function formatField(change) {
51591
+ switch (change.kind) {
51592
+ case "added":
51593
+ return ` + ${change.name} ${change.field_type} new`;
51594
+ case "renamed":
51595
+ return ` ~ ${change.to_name} was "${change.from_name}" \u2192 column ${change.column}`;
51596
+ case "tombstoned": {
51597
+ const fallback = change.fallback === void 0 ? "" : `, fallback ${JSON.stringify(change.fallback)}`;
51598
+ return ` \u2298 ${change.name} tombstoned \u2192 column ${change.column} retained${fallback}`;
51599
+ }
51600
+ case "restored":
51601
+ return ` \u21BA ${change.name} restored \u2192 column ${change.column} exposed again`;
51602
+ }
51603
+ }
51604
+ function formatSchemaChange(change) {
51605
+ const header = change.from === null ? `Working schema \u2014 nothing has been cut yet, so ${change.to} would be the first version` : `Working schema vs ${change.from} (highest snapshot) \u2014 would become ${change.to}`;
51606
+ if (change.identical) {
51607
+ return `${header}
51608
+
51609
+ No column added, renamed, tombstoned or restored.`;
51610
+ }
51611
+ const blocks = change.types.map((type) => {
51612
+ const label = type.status === "added" ? `${type.type} (new type)` : type.type;
51613
+ if (type.fields.length === 0) return `${label}
51614
+ (no changes)`;
51615
+ return [label, ...type.fields.map(formatField)].join("\n");
51616
+ });
51617
+ return [header, "", ...blocks].join("\n");
51618
+ }
51619
+
51620
+ // src/commands/version-fs.ts
51621
+ init_esm_shims();
51622
+ import fs5 from "fs";
51623
+ import path5 from "path";
51624
+ var SUPPORTED = /* @__PURE__ */ new Set([".json", ".yaml", ".yml"]);
51625
+ function copySnapshotFolders(input2) {
51626
+ const { fromRoot, toDir, folders } = input2;
51627
+ for (const folder of Object.values(folders)) {
51628
+ const src = path5.join(fromRoot, folder);
51629
+ if (!fs5.existsSync(src) || !fs5.statSync(src).isDirectory()) continue;
51630
+ const dest = path5.join(toDir, folder);
51631
+ fs5.mkdirSync(dest, { recursive: true });
51632
+ for (const entry of fs5.readdirSync(src)) {
51633
+ if (!SUPPORTED.has(path5.extname(entry).toLowerCase())) continue;
51634
+ fs5.copyFileSync(path5.join(src, entry), path5.join(dest, entry));
51635
+ }
51636
+ }
51637
+ }
51638
+ function retireSnapshotDir(versionsDir, version3) {
51639
+ const from = path5.join(versionsDir, version3);
51640
+ const staging = path5.join(versionsDir, `.${version3}.removing`);
51641
+ fs5.rmSync(staging, { recursive: true, force: true });
51642
+ fs5.renameSync(from, staging);
51643
+ fs5.rmSync(staging, { recursive: true, force: true });
51644
+ }
51645
+ function writeSnapshotAtomically(input2) {
51646
+ const { fromRoot, versionsDir, version: version3, folders } = input2;
51647
+ const target = path5.join(versionsDir, version3);
51648
+ const staging = path5.join(versionsDir, `.${version3}.tmp`);
51649
+ fs5.rmSync(staging, { recursive: true, force: true });
51650
+ try {
51651
+ copySnapshotFolders({ fromRoot, toDir: staging, folders });
51652
+ fs5.renameSync(staging, target);
51653
+ } catch (err) {
51654
+ fs5.rmSync(staging, { recursive: true, force: true });
51655
+ throw err;
51656
+ }
51657
+ }
51658
+
51659
+ // src/commands/version.ts
51660
+ function highestSnapshot(snapshots) {
51661
+ let best = null;
51662
+ let bestN = -1;
51663
+ for (const s of snapshots) {
51664
+ const n = Number.parseInt(s.version.replace(/^v/, ""), 10);
51665
+ if (Number.isNaN(n) || n <= bestN) continue;
51666
+ best = s;
51667
+ bestN = n;
51668
+ }
51669
+ return best;
51670
+ }
51671
+ async function loadVersionContext(options, deps, command) {
51672
+ loadEnvFile(options.env);
51673
+ const config3 = await resolveConfig(deps.cwd);
51674
+ const schema = resolveSchemaConfig(deps.cwd, config3);
51675
+ const registry2 = loadWorkingRegistry(deps.cwd, config3, command);
51676
+ const snapshots = loadVersionSnapshots(schema, registry2);
51677
+ if (!snapshots.ok) {
51678
+ printValidationErrors(snapshots.errors, "Snapshot errors", command);
51679
+ process.exit(1);
51680
+ }
51681
+ const model = computeVersionModel({ current: registry2, snapshots: snapshots.value });
51682
+ if (!model.ok) {
51683
+ printValidationErrors(model.errors, "Version model errors", command);
51684
+ process.exit(1);
51685
+ }
51686
+ return { schema, registry: registry2, snapshots: snapshots.value, model: model.value };
51687
+ }
51688
+ function orphanedTombstoneErrors(input2) {
51689
+ const remaining = input2.snapshots.filter((s) => s.version !== input2.retiring);
51690
+ const model = computeVersionModel({ current: input2.registry, snapshots: remaining });
51691
+ if (model.ok) return [];
51692
+ return model.errors.filter((e) => e.code === "ORPHANED_TOMBSTONE");
51693
+ }
51694
+ function registerVersion(program2) {
51695
+ program2.command("version:diff").description("Show what cutting a new version would freeze").option("--env <path>", "path to .env file to load").action(async (options) => {
51696
+ await runVersionDiff(options, { cwd: process.cwd() });
51697
+ });
51698
+ program2.command("version:cut").description("Freeze the working schema as a new version").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (options) => {
51699
+ await runVersionCut(options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51700
+ });
51701
+ program2.command("version:retire <version>").description("Stop serving a cut version and delete its snapshot").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (version3, options) => {
51702
+ await runVersionRetire(version3, options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51703
+ });
51704
+ }
51705
+ async function runVersionDiff(options, deps) {
51706
+ const ctx = await loadVersionContext(options, deps, "manguito version:diff");
51707
+ const from = highestSnapshot(ctx.snapshots);
51708
+ const change = describeSchemaChange({
51709
+ from,
51710
+ to: { version: ctx.model.current, registry: ctx.registry }
51711
+ });
51712
+ process.stdout.write(`${formatSchemaChange(change)}
51713
+ `);
51714
+ if (change.identical) {
51715
+ process.stdout.write(
51716
+ `
51717
+ Nothing to cut \u2014 no column was added, renamed, tombstoned or restored, so ${ctx.model.current} would expose the same contract.
51718
+ `
51719
+ );
51720
+ return;
51721
+ }
51722
+ printSuccess(`Cutting now would create ${ctx.schema.base_path}/versions/${ctx.model.current}/`);
51723
+ }
51724
+ async function runVersionCut(options, deps) {
51725
+ const ctx = await loadVersionContext(options, deps, "manguito version:cut");
51726
+ const from = highestSnapshot(ctx.snapshots);
51727
+ const version3 = ctx.model.current;
51728
+ const change = describeSchemaChange({
51729
+ from,
51730
+ to: { version: version3, registry: ctx.registry }
51731
+ });
51732
+ if (change.identical) {
51733
+ printGuidedError(
51734
+ `No column was added, renamed, tombstoned or restored since ${from?.version ?? "the last cut"} \u2014 cutting ${version3} would freeze an identical contract.`,
51735
+ "Changes to paragraph, programmatic, many-to-many and enum definitions are not versioned and need no new version. A live version commits you to retaining every column it exposes, so cut only when a column actually changed \u2014 or run `manguito version:retire <version>` if you meant to shrink the live set."
51736
+ );
51737
+ process.exit(1);
51738
+ }
51739
+ const versionsDir = path6.join(ctx.schema.base_path, "versions");
51740
+ const target = path6.join(versionsDir, version3);
51741
+ if (fs6.existsSync(target)) {
51742
+ printGuidedError(
51743
+ `${target} already exists.`,
51744
+ "A snapshot directory is never overwritten. Remove or rename it, then run version:cut again."
51745
+ );
51746
+ process.exit(1);
51747
+ }
51748
+ process.stdout.write(`${formatSchemaChange(change)}
51749
+
51750
+ `);
51751
+ const live = [...ctx.model.live.filter((v2) => v2 !== version3), version3].join(" ");
51752
+ process.stdout.write(
51753
+ `After cutting, ${live} are live. Every column those versions expose must stay in the
51754
+ schema \u2014 as a live field or a tombstone \u2014 until you retire them.
51755
+
51756
+ `
51757
+ );
51758
+ if (options.yes !== true) {
51759
+ const ok = await deps.prompt.confirm(`Freeze the working schema as ${version3}?`);
51760
+ if (!ok) {
51761
+ process.stdout.write("Cancelled. Nothing was written.\n");
51762
+ return;
51763
+ }
51764
+ }
51765
+ try {
51766
+ writeSnapshotAtomically({ fromRoot: ctx.schema.base_path, versionsDir, version: version3, folders: ctx.schema.folders });
51767
+ } catch (err) {
51768
+ printGuidedError(
51769
+ `Failed to write ${target}: ${err instanceof Error ? err.message : String(err)}`,
51770
+ "Nothing was left behind \u2014 the snapshot is written to a temporary directory and renamed into place only once it is complete."
51771
+ );
51772
+ process.exit(1);
51773
+ }
51774
+ printSuccess(`Froze the working schema as ${version3} at ${target}`);
51775
+ process.stdout.write(`Live: ${live}. Working schema is now v${Number.parseInt(version3.slice(1), 10) + 1}.
51776
+ `);
51777
+ }
51778
+ async function runVersionRetire(version3, options, deps) {
51779
+ if (!/^v\d+$/.test(version3)) {
51780
+ printGuidedError(`"${version3}" is not a version name.`, "Expected v<number>, for example v1.");
51781
+ process.exit(1);
51782
+ }
51783
+ const ctx = await loadVersionContext(options, deps, "manguito version:retire");
51784
+ if (!ctx.snapshots.some((s) => s.version === version3)) {
51785
+ const existing = ctx.snapshots.map((s) => s.version).join(", ");
51786
+ printGuidedError(
51787
+ `${version3} is not a cut version.`,
51788
+ existing === "" ? "No versions have been cut yet \u2014 run `manguito version:cut` first." : `Cut versions: ${existing}.`
51789
+ );
51790
+ process.exit(1);
51791
+ }
51792
+ const highest = highestSnapshot(ctx.snapshots);
51793
+ if (highest !== null && highest.version === version3) {
51794
+ const target = path6.join(ctx.schema.base_path, "versions", version3);
51795
+ printGuidedError(
51796
+ `${version3} is the newest cut version and cannot be retired.`,
51797
+ `The working schema is ${ctx.model.current} because ${version3} is the highest snapshot \u2014 retiring it would renumber the working schema back onto ${version3}, and anyone pinned to ${version3} would get a different contract. Cutting a version with a real contract change makes ${version3} retirable. If ${version3} was cut in error, delete ${target} by hand \u2014 safe only while nothing yet consumes ${version3}, since deleting the newest snapshot renumbers the working schema back onto it.`
51798
+ );
51799
+ process.exit(1);
51800
+ }
51801
+ const orphans = orphanedTombstoneErrors({
51802
+ registry: ctx.registry,
51803
+ snapshots: ctx.snapshots,
51804
+ retiring: version3
51805
+ });
51806
+ process.stdout.write(`Retiring ${version3} will delete ${path6.join(ctx.schema.base_path, "versions", version3)}
51807
+
51808
+ `);
51809
+ if (orphans.length > 0) {
51810
+ process.stdout.write(
51811
+ `${orphans.length} tombstone${orphans.length === 1 ? "" : "s"} will be orphaned \u2014 their columns are no
51812
+ longer exposed by any live version. You must then delete these fields:
51813
+
51814
+ `
51815
+ );
51816
+ for (const o of orphans) {
51817
+ process.stdout.write(` ${o.file}
51818
+ ${o.message}
51819
+
51820
+ `);
51821
+ }
51822
+ process.stdout.write(
51823
+ "Until you do, `manguito validate` will report ORPHANED_TOMBSTONE. Deleting them\nshrinks the union and lets the next migration DROP those columns.\n\n"
51824
+ );
51825
+ }
51826
+ if (options.yes !== true) {
51827
+ const ok = await deps.prompt.confirm(`Retire ${version3}?`);
51828
+ if (!ok) {
51829
+ process.stdout.write("Cancelled. Nothing was deleted.\n");
51830
+ return;
51831
+ }
51832
+ }
51833
+ const versionsDir = path6.join(ctx.schema.base_path, "versions");
51834
+ try {
51835
+ retireSnapshotDir(versionsDir, version3);
51836
+ } catch (err) {
51837
+ printGuidedError(
51838
+ `Failed to retire ${version3}: ${err instanceof Error ? err.message : String(err)}`,
51839
+ "The snapshot is renamed out of the way before it is deleted, so the version is either fully retired or untouched."
51840
+ );
51841
+ process.exit(1);
51842
+ }
51843
+ printSuccess(`Retired ${version3}`);
51844
+ if (orphans.length > 0) {
51845
+ process.stdout.write(`
51846
+ Delete the ${orphans.length} orphaned tombstone field(s) listed above.
51847
+ `);
51848
+ }
51849
+ }
51850
+
50963
51851
  // src/index.ts
50964
51852
  var require2 = createRequire3(import.meta.url);
50965
51853
  var { version: version2 } = require2("../package.json");
@@ -50971,6 +51859,7 @@ registerMigrate(program);
50971
51859
  registerValidate(program);
50972
51860
  registerCreateSuperuser(program);
50973
51861
  registerUsers(program);
51862
+ registerVersion(program);
50974
51863
  program.parse();
50975
51864
  /*! Bundled license information:
50976
51865