canopycms-cdk 0.0.65 → 0.0.66-int.82

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.
Files changed (35) hide show
  1. package/lambda/asset-transform/dist/handler.js +3 -0
  2. package/lambda/asset-transform/dist/node_modules/.package-lock.json +39 -39
  3. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +1 -1
  4. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/{libvips-cpp.so.8.18.3 → libvips-cpp.so.8.18.6} +0 -0
  5. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/package.json +2 -2
  6. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/versions.json +13 -13
  7. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/index.cjs +1 -1
  8. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/lib/{sharp-linux-arm64-0.35.3.node → sharp-linux-arm64-0.35.4.node} +0 -0
  9. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/package.json +2 -2
  10. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/index.cjs +1 -1
  11. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.4.node.js +1 -0
  12. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/{sharp-wasm32-0.35.3.node.wasm → sharp-wasm32-0.35.4.node.wasm} +0 -0
  13. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/package.json +2 -2
  14. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/versions.json +9 -9
  15. package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.cjs +6 -6
  16. package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.mjs +6 -6
  17. package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.cjs +1 -1
  18. package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.mjs +1 -1
  19. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.cts +2 -2
  20. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.mts +2 -2
  21. package/lambda/asset-transform/dist/node_modules/sharp/dist/input.cjs +20 -15
  22. package/lambda/asset-transform/dist/node_modules/sharp/dist/input.mjs +20 -15
  23. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.cjs +4 -0
  24. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.mjs +4 -0
  25. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.cjs +4 -7
  26. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.mjs +4 -7
  27. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.cjs +8 -8
  28. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.mjs +8 -8
  29. package/lambda/asset-transform/dist/node_modules/sharp/lib/index.d.ts +2 -2
  30. package/lambda/asset-transform/dist/node_modules/sharp/package.json +38 -38
  31. package/lambda/asset-transform/dist/node_modules/sharp/src/common.cc +7 -4
  32. package/lambda/asset-transform/dist/node_modules/sharp/src/common.h +2 -2
  33. package/package.json +4 -4
  34. package/worker/dist/index.js +1684 -1764
  35. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.3.node.js +0 -1
@@ -814,10 +814,10 @@ var require_src2 = __commonJS({
814
814
  var fs_1 = __require("fs");
815
815
  var debug_1 = __importDefault2(require_src());
816
816
  var log7 = debug_1.default("@kwsites/file-exists");
817
- function check(path16, isFile, isDirectory) {
818
- log7(`checking %s`, path16);
817
+ function check(path20, isFile, isDirectory) {
818
+ log7(`checking %s`, path20);
819
819
  try {
820
- const stat = fs_1.statSync(path16);
820
+ const stat = fs_1.statSync(path20);
821
821
  if (stat.isFile() && isFile) {
822
822
  log7(`[OK] path represents a file`);
823
823
  return true;
@@ -837,8 +837,8 @@ var require_src2 = __commonJS({
837
837
  throw e5;
838
838
  }
839
839
  }
840
- function exists2(path16, type = exports.READABLE) {
841
- return check(path16, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
840
+ function exists2(path20, type = exports.READABLE) {
841
+ return check(path20, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
842
842
  }
843
843
  exports.exists = exists2;
844
844
  exports.FILE = 1;
@@ -927,54 +927,54 @@ var require_polyfills = __commonJS({
927
927
  }
928
928
  var chdir;
929
929
  module.exports = patch;
930
- function patch(fs11) {
930
+ function patch(fs15) {
931
931
  if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
932
- patchLchmod(fs11);
933
- }
934
- if (!fs11.lutimes) {
935
- patchLutimes(fs11);
936
- }
937
- fs11.chown = chownFix(fs11.chown);
938
- fs11.fchown = chownFix(fs11.fchown);
939
- fs11.lchown = chownFix(fs11.lchown);
940
- fs11.chmod = chmodFix(fs11.chmod);
941
- fs11.fchmod = chmodFix(fs11.fchmod);
942
- fs11.lchmod = chmodFix(fs11.lchmod);
943
- fs11.chownSync = chownFixSync(fs11.chownSync);
944
- fs11.fchownSync = chownFixSync(fs11.fchownSync);
945
- fs11.lchownSync = chownFixSync(fs11.lchownSync);
946
- fs11.chmodSync = chmodFixSync(fs11.chmodSync);
947
- fs11.fchmodSync = chmodFixSync(fs11.fchmodSync);
948
- fs11.lchmodSync = chmodFixSync(fs11.lchmodSync);
949
- fs11.stat = statFix(fs11.stat);
950
- fs11.fstat = statFix(fs11.fstat);
951
- fs11.lstat = statFix(fs11.lstat);
952
- fs11.statSync = statFixSync(fs11.statSync);
953
- fs11.fstatSync = statFixSync(fs11.fstatSync);
954
- fs11.lstatSync = statFixSync(fs11.lstatSync);
955
- if (fs11.chmod && !fs11.lchmod) {
956
- fs11.lchmod = function(path16, mode, cb) {
932
+ patchLchmod(fs15);
933
+ }
934
+ if (!fs15.lutimes) {
935
+ patchLutimes(fs15);
936
+ }
937
+ fs15.chown = chownFix(fs15.chown);
938
+ fs15.fchown = chownFix(fs15.fchown);
939
+ fs15.lchown = chownFix(fs15.lchown);
940
+ fs15.chmod = chmodFix(fs15.chmod);
941
+ fs15.fchmod = chmodFix(fs15.fchmod);
942
+ fs15.lchmod = chmodFix(fs15.lchmod);
943
+ fs15.chownSync = chownFixSync(fs15.chownSync);
944
+ fs15.fchownSync = chownFixSync(fs15.fchownSync);
945
+ fs15.lchownSync = chownFixSync(fs15.lchownSync);
946
+ fs15.chmodSync = chmodFixSync(fs15.chmodSync);
947
+ fs15.fchmodSync = chmodFixSync(fs15.fchmodSync);
948
+ fs15.lchmodSync = chmodFixSync(fs15.lchmodSync);
949
+ fs15.stat = statFix(fs15.stat);
950
+ fs15.fstat = statFix(fs15.fstat);
951
+ fs15.lstat = statFix(fs15.lstat);
952
+ fs15.statSync = statFixSync(fs15.statSync);
953
+ fs15.fstatSync = statFixSync(fs15.fstatSync);
954
+ fs15.lstatSync = statFixSync(fs15.lstatSync);
955
+ if (fs15.chmod && !fs15.lchmod) {
956
+ fs15.lchmod = function(path20, mode, cb) {
957
957
  if (cb) process.nextTick(cb);
958
958
  };
959
- fs11.lchmodSync = function() {
959
+ fs15.lchmodSync = function() {
960
960
  };
961
961
  }
962
- if (fs11.chown && !fs11.lchown) {
963
- fs11.lchown = function(path16, uid, gid, cb) {
962
+ if (fs15.chown && !fs15.lchown) {
963
+ fs15.lchown = function(path20, uid, gid, cb) {
964
964
  if (cb) process.nextTick(cb);
965
965
  };
966
- fs11.lchownSync = function() {
966
+ fs15.lchownSync = function() {
967
967
  };
968
968
  }
969
969
  if (platform === "win32") {
970
- fs11.rename = typeof fs11.rename !== "function" ? fs11.rename : (function(fs$rename) {
970
+ fs15.rename = typeof fs15.rename !== "function" ? fs15.rename : (function(fs$rename) {
971
971
  function rename(from, to, cb) {
972
972
  var start = Date.now();
973
973
  var backoff = 0;
974
974
  fs$rename(from, to, function CB(er) {
975
975
  if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start < 6e4) {
976
976
  setTimeout(function() {
977
- fs11.stat(to, function(stater, st) {
977
+ fs15.stat(to, function(stater, st) {
978
978
  if (stater && stater.code === "ENOENT")
979
979
  fs$rename(from, to, CB);
980
980
  else
@@ -990,9 +990,9 @@ var require_polyfills = __commonJS({
990
990
  }
991
991
  if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
992
992
  return rename;
993
- })(fs11.rename);
993
+ })(fs15.rename);
994
994
  }
995
- fs11.read = typeof fs11.read !== "function" ? fs11.read : (function(fs$read) {
995
+ fs15.read = typeof fs15.read !== "function" ? fs15.read : (function(fs$read) {
996
996
  function read(fd, buffer, offset, length, position, callback_) {
997
997
  var callback;
998
998
  if (callback_ && typeof callback_ === "function") {
@@ -1000,22 +1000,22 @@ var require_polyfills = __commonJS({
1000
1000
  callback = function(er, _3, __) {
1001
1001
  if (er && er.code === "EAGAIN" && eagCounter < 10) {
1002
1002
  eagCounter++;
1003
- return fs$read.call(fs11, fd, buffer, offset, length, position, callback);
1003
+ return fs$read.call(fs15, fd, buffer, offset, length, position, callback);
1004
1004
  }
1005
1005
  callback_.apply(this, arguments);
1006
1006
  };
1007
1007
  }
1008
- return fs$read.call(fs11, fd, buffer, offset, length, position, callback);
1008
+ return fs$read.call(fs15, fd, buffer, offset, length, position, callback);
1009
1009
  }
1010
1010
  if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read);
1011
1011
  return read;
1012
- })(fs11.read);
1013
- fs11.readSync = typeof fs11.readSync !== "function" ? fs11.readSync : /* @__PURE__ */ (function(fs$readSync) {
1012
+ })(fs15.read);
1013
+ fs15.readSync = typeof fs15.readSync !== "function" ? fs15.readSync : /* @__PURE__ */ (function(fs$readSync) {
1014
1014
  return function(fd, buffer, offset, length, position) {
1015
1015
  var eagCounter = 0;
1016
1016
  while (true) {
1017
1017
  try {
1018
- return fs$readSync.call(fs11, fd, buffer, offset, length, position);
1018
+ return fs$readSync.call(fs15, fd, buffer, offset, length, position);
1019
1019
  } catch (er) {
1020
1020
  if (er.code === "EAGAIN" && eagCounter < 10) {
1021
1021
  eagCounter++;
@@ -1025,11 +1025,11 @@ var require_polyfills = __commonJS({
1025
1025
  }
1026
1026
  }
1027
1027
  };
1028
- })(fs11.readSync);
1029
- function patchLchmod(fs12) {
1030
- fs12.lchmod = function(path16, mode, callback) {
1031
- fs12.open(
1032
- path16,
1028
+ })(fs15.readSync);
1029
+ function patchLchmod(fs16) {
1030
+ fs16.lchmod = function(path20, mode, callback) {
1031
+ fs16.open(
1032
+ path20,
1033
1033
  constants2.O_WRONLY | constants2.O_SYMLINK,
1034
1034
  mode,
1035
1035
  function(err, fd) {
@@ -1037,80 +1037,80 @@ var require_polyfills = __commonJS({
1037
1037
  if (callback) callback(err);
1038
1038
  return;
1039
1039
  }
1040
- fs12.fchmod(fd, mode, function(err2) {
1041
- fs12.close(fd, function(err22) {
1040
+ fs16.fchmod(fd, mode, function(err2) {
1041
+ fs16.close(fd, function(err22) {
1042
1042
  if (callback) callback(err2 || err22);
1043
1043
  });
1044
1044
  });
1045
1045
  }
1046
1046
  );
1047
1047
  };
1048
- fs12.lchmodSync = function(path16, mode) {
1049
- var fd = fs12.openSync(path16, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
1048
+ fs16.lchmodSync = function(path20, mode) {
1049
+ var fd = fs16.openSync(path20, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
1050
1050
  var threw = true;
1051
1051
  var ret;
1052
1052
  try {
1053
- ret = fs12.fchmodSync(fd, mode);
1053
+ ret = fs16.fchmodSync(fd, mode);
1054
1054
  threw = false;
1055
1055
  } finally {
1056
1056
  if (threw) {
1057
1057
  try {
1058
- fs12.closeSync(fd);
1058
+ fs16.closeSync(fd);
1059
1059
  } catch (er) {
1060
1060
  }
1061
1061
  } else {
1062
- fs12.closeSync(fd);
1062
+ fs16.closeSync(fd);
1063
1063
  }
1064
1064
  }
1065
1065
  return ret;
1066
1066
  };
1067
1067
  }
1068
- function patchLutimes(fs12) {
1069
- if (constants2.hasOwnProperty("O_SYMLINK") && fs12.futimes) {
1070
- fs12.lutimes = function(path16, at, mt, cb) {
1071
- fs12.open(path16, constants2.O_SYMLINK, function(er, fd) {
1068
+ function patchLutimes(fs16) {
1069
+ if (constants2.hasOwnProperty("O_SYMLINK") && fs16.futimes) {
1070
+ fs16.lutimes = function(path20, at, mt, cb) {
1071
+ fs16.open(path20, constants2.O_SYMLINK, function(er, fd) {
1072
1072
  if (er) {
1073
1073
  if (cb) cb(er);
1074
1074
  return;
1075
1075
  }
1076
- fs12.futimes(fd, at, mt, function(er2) {
1077
- fs12.close(fd, function(er22) {
1076
+ fs16.futimes(fd, at, mt, function(er2) {
1077
+ fs16.close(fd, function(er22) {
1078
1078
  if (cb) cb(er2 || er22);
1079
1079
  });
1080
1080
  });
1081
1081
  });
1082
1082
  };
1083
- fs12.lutimesSync = function(path16, at, mt) {
1084
- var fd = fs12.openSync(path16, constants2.O_SYMLINK);
1083
+ fs16.lutimesSync = function(path20, at, mt) {
1084
+ var fd = fs16.openSync(path20, constants2.O_SYMLINK);
1085
1085
  var ret;
1086
1086
  var threw = true;
1087
1087
  try {
1088
- ret = fs12.futimesSync(fd, at, mt);
1088
+ ret = fs16.futimesSync(fd, at, mt);
1089
1089
  threw = false;
1090
1090
  } finally {
1091
1091
  if (threw) {
1092
1092
  try {
1093
- fs12.closeSync(fd);
1093
+ fs16.closeSync(fd);
1094
1094
  } catch (er) {
1095
1095
  }
1096
1096
  } else {
1097
- fs12.closeSync(fd);
1097
+ fs16.closeSync(fd);
1098
1098
  }
1099
1099
  }
1100
1100
  return ret;
1101
1101
  };
1102
- } else if (fs12.futimes) {
1103
- fs12.lutimes = function(_a3, _b, _c5, cb) {
1102
+ } else if (fs16.futimes) {
1103
+ fs16.lutimes = function(_a3, _b, _c5, cb) {
1104
1104
  if (cb) process.nextTick(cb);
1105
1105
  };
1106
- fs12.lutimesSync = function() {
1106
+ fs16.lutimesSync = function() {
1107
1107
  };
1108
1108
  }
1109
1109
  }
1110
1110
  function chmodFix(orig) {
1111
1111
  if (!orig) return orig;
1112
1112
  return function(target, mode, cb) {
1113
- return orig.call(fs11, target, mode, function(er) {
1113
+ return orig.call(fs15, target, mode, function(er) {
1114
1114
  if (chownErOk(er)) er = null;
1115
1115
  if (cb) cb.apply(this, arguments);
1116
1116
  });
@@ -1120,7 +1120,7 @@ var require_polyfills = __commonJS({
1120
1120
  if (!orig) return orig;
1121
1121
  return function(target, mode) {
1122
1122
  try {
1123
- return orig.call(fs11, target, mode);
1123
+ return orig.call(fs15, target, mode);
1124
1124
  } catch (er) {
1125
1125
  if (!chownErOk(er)) throw er;
1126
1126
  }
@@ -1129,7 +1129,7 @@ var require_polyfills = __commonJS({
1129
1129
  function chownFix(orig) {
1130
1130
  if (!orig) return orig;
1131
1131
  return function(target, uid, gid, cb) {
1132
- return orig.call(fs11, target, uid, gid, function(er) {
1132
+ return orig.call(fs15, target, uid, gid, function(er) {
1133
1133
  if (chownErOk(er)) er = null;
1134
1134
  if (cb) cb.apply(this, arguments);
1135
1135
  });
@@ -1139,7 +1139,7 @@ var require_polyfills = __commonJS({
1139
1139
  if (!orig) return orig;
1140
1140
  return function(target, uid, gid) {
1141
1141
  try {
1142
- return orig.call(fs11, target, uid, gid);
1142
+ return orig.call(fs15, target, uid, gid);
1143
1143
  } catch (er) {
1144
1144
  if (!chownErOk(er)) throw er;
1145
1145
  }
@@ -1159,13 +1159,13 @@ var require_polyfills = __commonJS({
1159
1159
  }
1160
1160
  if (cb) cb.apply(this, arguments);
1161
1161
  }
1162
- return options ? orig.call(fs11, target, options, callback) : orig.call(fs11, target, callback);
1162
+ return options ? orig.call(fs15, target, options, callback) : orig.call(fs15, target, callback);
1163
1163
  };
1164
1164
  }
1165
1165
  function statFixSync(orig) {
1166
1166
  if (!orig) return orig;
1167
1167
  return function(target, options) {
1168
- var stats = options ? orig.call(fs11, target, options) : orig.call(fs11, target);
1168
+ var stats = options ? orig.call(fs15, target, options) : orig.call(fs15, target);
1169
1169
  if (stats) {
1170
1170
  if (stats.uid < 0) stats.uid += 4294967296;
1171
1171
  if (stats.gid < 0) stats.gid += 4294967296;
@@ -1194,16 +1194,16 @@ var require_legacy_streams = __commonJS({
1194
1194
  "../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/legacy-streams.js"(exports, module) {
1195
1195
  var Stream = __require("stream").Stream;
1196
1196
  module.exports = legacy;
1197
- function legacy(fs11) {
1197
+ function legacy(fs15) {
1198
1198
  return {
1199
1199
  ReadStream,
1200
1200
  WriteStream
1201
1201
  };
1202
- function ReadStream(path16, options) {
1203
- if (!(this instanceof ReadStream)) return new ReadStream(path16, options);
1202
+ function ReadStream(path20, options) {
1203
+ if (!(this instanceof ReadStream)) return new ReadStream(path20, options);
1204
1204
  Stream.call(this);
1205
1205
  var self2 = this;
1206
- this.path = path16;
1206
+ this.path = path20;
1207
1207
  this.fd = null;
1208
1208
  this.readable = true;
1209
1209
  this.paused = false;
@@ -1237,7 +1237,7 @@ var require_legacy_streams = __commonJS({
1237
1237
  });
1238
1238
  return;
1239
1239
  }
1240
- fs11.open(this.path, this.flags, this.mode, function(err, fd) {
1240
+ fs15.open(this.path, this.flags, this.mode, function(err, fd) {
1241
1241
  if (err) {
1242
1242
  self2.emit("error", err);
1243
1243
  self2.readable = false;
@@ -1248,10 +1248,10 @@ var require_legacy_streams = __commonJS({
1248
1248
  self2._read();
1249
1249
  });
1250
1250
  }
1251
- function WriteStream(path16, options) {
1252
- if (!(this instanceof WriteStream)) return new WriteStream(path16, options);
1251
+ function WriteStream(path20, options) {
1252
+ if (!(this instanceof WriteStream)) return new WriteStream(path20, options);
1253
1253
  Stream.call(this);
1254
- this.path = path16;
1254
+ this.path = path20;
1255
1255
  this.fd = null;
1256
1256
  this.writable = true;
1257
1257
  this.flags = "w";
@@ -1276,7 +1276,7 @@ var require_legacy_streams = __commonJS({
1276
1276
  this.busy = false;
1277
1277
  this._queue = [];
1278
1278
  if (this.fd === null) {
1279
- this._open = fs11.open;
1279
+ this._open = fs15.open;
1280
1280
  this._queue.push([this._open, this.path, this.flags, this.mode, void 0]);
1281
1281
  this.flush();
1282
1282
  }
@@ -1311,7 +1311,7 @@ var require_clone = __commonJS({
1311
1311
  // ../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js
1312
1312
  var require_graceful_fs = __commonJS({
1313
1313
  "../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js"(exports, module) {
1314
- var fs11 = __require("fs");
1314
+ var fs15 = __require("fs");
1315
1315
  var polyfills = require_polyfills();
1316
1316
  var legacy = require_legacy_streams();
1317
1317
  var clone = require_clone();
@@ -1343,12 +1343,12 @@ var require_graceful_fs = __commonJS({
1343
1343
  m5 = "GFS4: " + m5.split(/\n/).join("\nGFS4: ");
1344
1344
  console.error(m5);
1345
1345
  };
1346
- if (!fs11[gracefulQueue]) {
1346
+ if (!fs15[gracefulQueue]) {
1347
1347
  queue = global[gracefulQueue] || [];
1348
- publishQueue(fs11, queue);
1349
- fs11.close = (function(fs$close) {
1348
+ publishQueue(fs15, queue);
1349
+ fs15.close = (function(fs$close) {
1350
1350
  function close(fd, cb) {
1351
- return fs$close.call(fs11, fd, function(err) {
1351
+ return fs$close.call(fs15, fd, function(err) {
1352
1352
  if (!err) {
1353
1353
  resetQueue();
1354
1354
  }
@@ -1360,48 +1360,48 @@ var require_graceful_fs = __commonJS({
1360
1360
  value: fs$close
1361
1361
  });
1362
1362
  return close;
1363
- })(fs11.close);
1364
- fs11.closeSync = (function(fs$closeSync) {
1363
+ })(fs15.close);
1364
+ fs15.closeSync = (function(fs$closeSync) {
1365
1365
  function closeSync(fd) {
1366
- fs$closeSync.apply(fs11, arguments);
1366
+ fs$closeSync.apply(fs15, arguments);
1367
1367
  resetQueue();
1368
1368
  }
1369
1369
  Object.defineProperty(closeSync, previousSymbol, {
1370
1370
  value: fs$closeSync
1371
1371
  });
1372
1372
  return closeSync;
1373
- })(fs11.closeSync);
1373
+ })(fs15.closeSync);
1374
1374
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
1375
1375
  process.on("exit", function() {
1376
- debug2(fs11[gracefulQueue]);
1377
- __require("assert").equal(fs11[gracefulQueue].length, 0);
1376
+ debug2(fs15[gracefulQueue]);
1377
+ __require("assert").equal(fs15[gracefulQueue].length, 0);
1378
1378
  });
1379
1379
  }
1380
1380
  }
1381
1381
  var queue;
1382
1382
  if (!global[gracefulQueue]) {
1383
- publishQueue(global, fs11[gracefulQueue]);
1384
- }
1385
- module.exports = patch(clone(fs11));
1386
- if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs11.__patched) {
1387
- module.exports = patch(fs11);
1388
- fs11.__patched = true;
1389
- }
1390
- function patch(fs12) {
1391
- polyfills(fs12);
1392
- fs12.gracefulify = patch;
1393
- fs12.createReadStream = createReadStream;
1394
- fs12.createWriteStream = createWriteStream;
1395
- var fs$readFile = fs12.readFile;
1396
- fs12.readFile = readFile;
1397
- function readFile(path16, options, cb) {
1383
+ publishQueue(global, fs15[gracefulQueue]);
1384
+ }
1385
+ module.exports = patch(clone(fs15));
1386
+ if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs15.__patched) {
1387
+ module.exports = patch(fs15);
1388
+ fs15.__patched = true;
1389
+ }
1390
+ function patch(fs16) {
1391
+ polyfills(fs16);
1392
+ fs16.gracefulify = patch;
1393
+ fs16.createReadStream = createReadStream;
1394
+ fs16.createWriteStream = createWriteStream;
1395
+ var fs$readFile = fs16.readFile;
1396
+ fs16.readFile = readFile;
1397
+ function readFile(path20, options, cb) {
1398
1398
  if (typeof options === "function")
1399
1399
  cb = options, options = null;
1400
- return go$readFile(path16, options, cb);
1401
- function go$readFile(path17, options2, cb2, startTime) {
1402
- return fs$readFile(path17, options2, function(err) {
1400
+ return go$readFile(path20, options, cb);
1401
+ function go$readFile(path21, options2, cb2, startTime) {
1402
+ return fs$readFile(path21, options2, function(err) {
1403
1403
  if (err && (err.code === "EMFILE" || err.code === "ENFILE"))
1404
- enqueue([go$readFile, [path17, options2, cb2], err, startTime || Date.now(), Date.now()]);
1404
+ enqueue([go$readFile, [path21, options2, cb2], err, startTime || Date.now(), Date.now()]);
1405
1405
  else {
1406
1406
  if (typeof cb2 === "function")
1407
1407
  cb2.apply(this, arguments);
@@ -1409,16 +1409,16 @@ var require_graceful_fs = __commonJS({
1409
1409
  });
1410
1410
  }
1411
1411
  }
1412
- var fs$writeFile = fs12.writeFile;
1413
- fs12.writeFile = writeFile;
1414
- function writeFile(path16, data2, options, cb) {
1412
+ var fs$writeFile = fs16.writeFile;
1413
+ fs16.writeFile = writeFile;
1414
+ function writeFile(path20, data2, options, cb) {
1415
1415
  if (typeof options === "function")
1416
1416
  cb = options, options = null;
1417
- return go$writeFile(path16, data2, options, cb);
1418
- function go$writeFile(path17, data3, options2, cb2, startTime) {
1419
- return fs$writeFile(path17, data3, options2, function(err) {
1417
+ return go$writeFile(path20, data2, options, cb);
1418
+ function go$writeFile(path21, data3, options2, cb2, startTime) {
1419
+ return fs$writeFile(path21, data3, options2, function(err) {
1420
1420
  if (err && (err.code === "EMFILE" || err.code === "ENFILE"))
1421
- enqueue([go$writeFile, [path17, data3, options2, cb2], err, startTime || Date.now(), Date.now()]);
1421
+ enqueue([go$writeFile, [path21, data3, options2, cb2], err, startTime || Date.now(), Date.now()]);
1422
1422
  else {
1423
1423
  if (typeof cb2 === "function")
1424
1424
  cb2.apply(this, arguments);
@@ -1426,17 +1426,17 @@ var require_graceful_fs = __commonJS({
1426
1426
  });
1427
1427
  }
1428
1428
  }
1429
- var fs$appendFile = fs12.appendFile;
1429
+ var fs$appendFile = fs16.appendFile;
1430
1430
  if (fs$appendFile)
1431
- fs12.appendFile = appendFile;
1432
- function appendFile(path16, data2, options, cb) {
1431
+ fs16.appendFile = appendFile;
1432
+ function appendFile(path20, data2, options, cb) {
1433
1433
  if (typeof options === "function")
1434
1434
  cb = options, options = null;
1435
- return go$appendFile(path16, data2, options, cb);
1436
- function go$appendFile(path17, data3, options2, cb2, startTime) {
1437
- return fs$appendFile(path17, data3, options2, function(err) {
1435
+ return go$appendFile(path20, data2, options, cb);
1436
+ function go$appendFile(path21, data3, options2, cb2, startTime) {
1437
+ return fs$appendFile(path21, data3, options2, function(err) {
1438
1438
  if (err && (err.code === "EMFILE" || err.code === "ENFILE"))
1439
- enqueue([go$appendFile, [path17, data3, options2, cb2], err, startTime || Date.now(), Date.now()]);
1439
+ enqueue([go$appendFile, [path21, data3, options2, cb2], err, startTime || Date.now(), Date.now()]);
1440
1440
  else {
1441
1441
  if (typeof cb2 === "function")
1442
1442
  cb2.apply(this, arguments);
@@ -1444,9 +1444,9 @@ var require_graceful_fs = __commonJS({
1444
1444
  });
1445
1445
  }
1446
1446
  }
1447
- var fs$copyFile = fs12.copyFile;
1447
+ var fs$copyFile = fs16.copyFile;
1448
1448
  if (fs$copyFile)
1449
- fs12.copyFile = copyFile;
1449
+ fs16.copyFile = copyFile;
1450
1450
  function copyFile(src, dest, flags, cb) {
1451
1451
  if (typeof flags === "function") {
1452
1452
  cb = flags;
@@ -1464,34 +1464,34 @@ var require_graceful_fs = __commonJS({
1464
1464
  });
1465
1465
  }
1466
1466
  }
1467
- var fs$readdir = fs12.readdir;
1468
- fs12.readdir = readdir;
1467
+ var fs$readdir = fs16.readdir;
1468
+ fs16.readdir = readdir;
1469
1469
  var noReaddirOptionVersions = /^v[0-5]\./;
1470
- function readdir(path16, options, cb) {
1470
+ function readdir(path20, options, cb) {
1471
1471
  if (typeof options === "function")
1472
1472
  cb = options, options = null;
1473
- var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path17, options2, cb2, startTime) {
1474
- return fs$readdir(path17, fs$readdirCallback(
1475
- path17,
1473
+ var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path21, options2, cb2, startTime) {
1474
+ return fs$readdir(path21, fs$readdirCallback(
1475
+ path21,
1476
1476
  options2,
1477
1477
  cb2,
1478
1478
  startTime
1479
1479
  ));
1480
- } : function go$readdir2(path17, options2, cb2, startTime) {
1481
- return fs$readdir(path17, options2, fs$readdirCallback(
1482
- path17,
1480
+ } : function go$readdir2(path21, options2, cb2, startTime) {
1481
+ return fs$readdir(path21, options2, fs$readdirCallback(
1482
+ path21,
1483
1483
  options2,
1484
1484
  cb2,
1485
1485
  startTime
1486
1486
  ));
1487
1487
  };
1488
- return go$readdir(path16, options, cb);
1489
- function fs$readdirCallback(path17, options2, cb2, startTime) {
1488
+ return go$readdir(path20, options, cb);
1489
+ function fs$readdirCallback(path21, options2, cb2, startTime) {
1490
1490
  return function(err, files) {
1491
1491
  if (err && (err.code === "EMFILE" || err.code === "ENFILE"))
1492
1492
  enqueue([
1493
1493
  go$readdir,
1494
- [path17, options2, cb2],
1494
+ [path21, options2, cb2],
1495
1495
  err,
1496
1496
  startTime || Date.now(),
1497
1497
  Date.now()
@@ -1506,21 +1506,21 @@ var require_graceful_fs = __commonJS({
1506
1506
  }
1507
1507
  }
1508
1508
  if (process.version.substr(0, 4) === "v0.8") {
1509
- var legStreams = legacy(fs12);
1509
+ var legStreams = legacy(fs16);
1510
1510
  ReadStream = legStreams.ReadStream;
1511
1511
  WriteStream = legStreams.WriteStream;
1512
1512
  }
1513
- var fs$ReadStream = fs12.ReadStream;
1513
+ var fs$ReadStream = fs16.ReadStream;
1514
1514
  if (fs$ReadStream) {
1515
1515
  ReadStream.prototype = Object.create(fs$ReadStream.prototype);
1516
1516
  ReadStream.prototype.open = ReadStream$open;
1517
1517
  }
1518
- var fs$WriteStream = fs12.WriteStream;
1518
+ var fs$WriteStream = fs16.WriteStream;
1519
1519
  if (fs$WriteStream) {
1520
1520
  WriteStream.prototype = Object.create(fs$WriteStream.prototype);
1521
1521
  WriteStream.prototype.open = WriteStream$open;
1522
1522
  }
1523
- Object.defineProperty(fs12, "ReadStream", {
1523
+ Object.defineProperty(fs16, "ReadStream", {
1524
1524
  get: function() {
1525
1525
  return ReadStream;
1526
1526
  },
@@ -1530,7 +1530,7 @@ var require_graceful_fs = __commonJS({
1530
1530
  enumerable: true,
1531
1531
  configurable: true
1532
1532
  });
1533
- Object.defineProperty(fs12, "WriteStream", {
1533
+ Object.defineProperty(fs16, "WriteStream", {
1534
1534
  get: function() {
1535
1535
  return WriteStream;
1536
1536
  },
@@ -1541,7 +1541,7 @@ var require_graceful_fs = __commonJS({
1541
1541
  configurable: true
1542
1542
  });
1543
1543
  var FileReadStream = ReadStream;
1544
- Object.defineProperty(fs12, "FileReadStream", {
1544
+ Object.defineProperty(fs16, "FileReadStream", {
1545
1545
  get: function() {
1546
1546
  return FileReadStream;
1547
1547
  },
@@ -1552,7 +1552,7 @@ var require_graceful_fs = __commonJS({
1552
1552
  configurable: true
1553
1553
  });
1554
1554
  var FileWriteStream = WriteStream;
1555
- Object.defineProperty(fs12, "FileWriteStream", {
1555
+ Object.defineProperty(fs16, "FileWriteStream", {
1556
1556
  get: function() {
1557
1557
  return FileWriteStream;
1558
1558
  },
@@ -1562,7 +1562,7 @@ var require_graceful_fs = __commonJS({
1562
1562
  enumerable: true,
1563
1563
  configurable: true
1564
1564
  });
1565
- function ReadStream(path16, options) {
1565
+ function ReadStream(path20, options) {
1566
1566
  if (this instanceof ReadStream)
1567
1567
  return fs$ReadStream.apply(this, arguments), this;
1568
1568
  else
@@ -1582,7 +1582,7 @@ var require_graceful_fs = __commonJS({
1582
1582
  }
1583
1583
  });
1584
1584
  }
1585
- function WriteStream(path16, options) {
1585
+ function WriteStream(path20, options) {
1586
1586
  if (this instanceof WriteStream)
1587
1587
  return fs$WriteStream.apply(this, arguments), this;
1588
1588
  else
@@ -1600,22 +1600,22 @@ var require_graceful_fs = __commonJS({
1600
1600
  }
1601
1601
  });
1602
1602
  }
1603
- function createReadStream(path16, options) {
1604
- return new fs12.ReadStream(path16, options);
1603
+ function createReadStream(path20, options) {
1604
+ return new fs16.ReadStream(path20, options);
1605
1605
  }
1606
- function createWriteStream(path16, options) {
1607
- return new fs12.WriteStream(path16, options);
1606
+ function createWriteStream(path20, options) {
1607
+ return new fs16.WriteStream(path20, options);
1608
1608
  }
1609
- var fs$open = fs12.open;
1610
- fs12.open = open;
1611
- function open(path16, flags, mode, cb) {
1609
+ var fs$open = fs16.open;
1610
+ fs16.open = open;
1611
+ function open(path20, flags, mode, cb) {
1612
1612
  if (typeof mode === "function")
1613
1613
  cb = mode, mode = null;
1614
- return go$open(path16, flags, mode, cb);
1615
- function go$open(path17, flags2, mode2, cb2, startTime) {
1616
- return fs$open(path17, flags2, mode2, function(err, fd) {
1614
+ return go$open(path20, flags, mode, cb);
1615
+ function go$open(path21, flags2, mode2, cb2, startTime) {
1616
+ return fs$open(path21, flags2, mode2, function(err, fd) {
1617
1617
  if (err && (err.code === "EMFILE" || err.code === "ENFILE"))
1618
- enqueue([go$open, [path17, flags2, mode2, cb2], err, startTime || Date.now(), Date.now()]);
1618
+ enqueue([go$open, [path21, flags2, mode2, cb2], err, startTime || Date.now(), Date.now()]);
1619
1619
  else {
1620
1620
  if (typeof cb2 === "function")
1621
1621
  cb2.apply(this, arguments);
@@ -1623,20 +1623,20 @@ var require_graceful_fs = __commonJS({
1623
1623
  });
1624
1624
  }
1625
1625
  }
1626
- return fs12;
1626
+ return fs16;
1627
1627
  }
1628
1628
  function enqueue(elem) {
1629
1629
  debug2("ENQUEUE", elem[0].name, elem[1]);
1630
- fs11[gracefulQueue].push(elem);
1630
+ fs15[gracefulQueue].push(elem);
1631
1631
  retry2();
1632
1632
  }
1633
1633
  var retryTimer;
1634
1634
  function resetQueue() {
1635
1635
  var now = Date.now();
1636
- for (var i6 = 0; i6 < fs11[gracefulQueue].length; ++i6) {
1637
- if (fs11[gracefulQueue][i6].length > 2) {
1638
- fs11[gracefulQueue][i6][3] = now;
1639
- fs11[gracefulQueue][i6][4] = now;
1636
+ for (var i6 = 0; i6 < fs15[gracefulQueue].length; ++i6) {
1637
+ if (fs15[gracefulQueue][i6].length > 2) {
1638
+ fs15[gracefulQueue][i6][3] = now;
1639
+ fs15[gracefulQueue][i6][4] = now;
1640
1640
  }
1641
1641
  }
1642
1642
  retry2();
@@ -1644,9 +1644,9 @@ var require_graceful_fs = __commonJS({
1644
1644
  function retry2() {
1645
1645
  clearTimeout(retryTimer);
1646
1646
  retryTimer = void 0;
1647
- if (fs11[gracefulQueue].length === 0)
1647
+ if (fs15[gracefulQueue].length === 0)
1648
1648
  return;
1649
- var elem = fs11[gracefulQueue].shift();
1649
+ var elem = fs15[gracefulQueue].shift();
1650
1650
  var fn = elem[0];
1651
1651
  var args = elem[1];
1652
1652
  var err = elem[2];
@@ -1668,7 +1668,7 @@ var require_graceful_fs = __commonJS({
1668
1668
  debug2("RETRY", fn.name, args);
1669
1669
  fn.apply(null, args.concat([startTime]));
1670
1670
  } else {
1671
- fs11[gracefulQueue].push(elem);
1671
+ fs15[gracefulQueue].push(elem);
1672
1672
  }
1673
1673
  }
1674
1674
  if (retryTimer === void 0) {
@@ -2103,10 +2103,10 @@ var require_mtime_precision = __commonJS({
2103
2103
  "../../node_modules/.pnpm/proper-lockfile@4.1.2/node_modules/proper-lockfile/lib/mtime-precision.js"(exports, module) {
2104
2104
  "use strict";
2105
2105
  var cacheSymbol = Symbol();
2106
- function probe(file, fs11, callback) {
2107
- const cachedPrecision = fs11[cacheSymbol];
2106
+ function probe(file, fs15, callback) {
2107
+ const cachedPrecision = fs15[cacheSymbol];
2108
2108
  if (cachedPrecision) {
2109
- return fs11.stat(file, (err, stat) => {
2109
+ return fs15.stat(file, (err, stat) => {
2110
2110
  if (err) {
2111
2111
  return callback(err);
2112
2112
  }
@@ -2114,16 +2114,16 @@ var require_mtime_precision = __commonJS({
2114
2114
  });
2115
2115
  }
2116
2116
  const mtime = new Date(Math.ceil(Date.now() / 1e3) * 1e3 + 5);
2117
- fs11.utimes(file, mtime, mtime, (err) => {
2117
+ fs15.utimes(file, mtime, mtime, (err) => {
2118
2118
  if (err) {
2119
2119
  return callback(err);
2120
2120
  }
2121
- fs11.stat(file, (err2, stat) => {
2121
+ fs15.stat(file, (err2, stat) => {
2122
2122
  if (err2) {
2123
2123
  return callback(err2);
2124
2124
  }
2125
2125
  const precision = stat.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
2126
- Object.defineProperty(fs11, cacheSymbol, { value: precision });
2126
+ Object.defineProperty(fs15, cacheSymbol, { value: precision });
2127
2127
  callback(null, stat.mtime, precision);
2128
2128
  });
2129
2129
  });
@@ -2144,8 +2144,8 @@ var require_mtime_precision = __commonJS({
2144
2144
  var require_lockfile = __commonJS({
2145
2145
  "../../node_modules/.pnpm/proper-lockfile@4.1.2/node_modules/proper-lockfile/lib/lockfile.js"(exports, module) {
2146
2146
  "use strict";
2147
- var path16 = __require("path");
2148
- var fs11 = require_graceful_fs();
2147
+ var path20 = __require("path");
2148
+ var fs15 = require_graceful_fs();
2149
2149
  var retry2 = require_retry2();
2150
2150
  var onExit = require_signal_exit();
2151
2151
  var mtimePrecision = require_mtime_precision();
@@ -2155,7 +2155,7 @@ var require_lockfile = __commonJS({
2155
2155
  }
2156
2156
  function resolveCanonicalPath(file, options, callback) {
2157
2157
  if (!options.realpath) {
2158
- return callback(null, path16.resolve(file));
2158
+ return callback(null, path20.resolve(file));
2159
2159
  }
2160
2160
  options.fs.realpath(file, callback);
2161
2161
  }
@@ -2276,7 +2276,7 @@ var require_lockfile = __commonJS({
2276
2276
  update: null,
2277
2277
  realpath: true,
2278
2278
  retries: 0,
2279
- fs: fs11,
2279
+ fs: fs15,
2280
2280
  onCompromised: (err) => {
2281
2281
  throw err;
2282
2282
  },
@@ -2320,7 +2320,7 @@ var require_lockfile = __commonJS({
2320
2320
  }
2321
2321
  function unlock(file, options, callback) {
2322
2322
  options = {
2323
- fs: fs11,
2323
+ fs: fs15,
2324
2324
  realpath: true,
2325
2325
  ...options
2326
2326
  };
@@ -2342,7 +2342,7 @@ var require_lockfile = __commonJS({
2342
2342
  options = {
2343
2343
  stale: 1e4,
2344
2344
  realpath: true,
2345
- fs: fs11,
2345
+ fs: fs15,
2346
2346
  ...options
2347
2347
  };
2348
2348
  options.stale = Math.max(options.stale || 0, 2e3);
@@ -2381,16 +2381,16 @@ var require_lockfile = __commonJS({
2381
2381
  var require_adapter = __commonJS({
2382
2382
  "../../node_modules/.pnpm/proper-lockfile@4.1.2/node_modules/proper-lockfile/lib/adapter.js"(exports, module) {
2383
2383
  "use strict";
2384
- var fs11 = require_graceful_fs();
2385
- function createSyncFs(fs12) {
2384
+ var fs15 = require_graceful_fs();
2385
+ function createSyncFs(fs16) {
2386
2386
  const methods = ["mkdir", "realpath", "stat", "rmdir", "utimes"];
2387
- const newFs = { ...fs12 };
2387
+ const newFs = { ...fs16 };
2388
2388
  methods.forEach((method) => {
2389
2389
  newFs[method] = (...args) => {
2390
2390
  const callback = args.pop();
2391
2391
  let ret;
2392
2392
  try {
2393
- ret = fs12[`${method}Sync`](...args);
2393
+ ret = fs16[`${method}Sync`](...args);
2394
2394
  } catch (err) {
2395
2395
  return callback(err);
2396
2396
  }
@@ -2428,7 +2428,7 @@ var require_adapter = __commonJS({
2428
2428
  }
2429
2429
  function toSyncOptions(options) {
2430
2430
  options = { ...options };
2431
- options.fs = createSyncFs(options.fs || fs11);
2431
+ options.fs = createSyncFs(options.fs || fs15);
2432
2432
  if (typeof options.retries === "number" && options.retries > 0 || options.retries && typeof options.retries.retries === "number" && options.retries.retries > 0) {
2433
2433
  throw Object.assign(new Error("Cannot use retries with the sync api"), { code: "ESYNC" });
2434
2434
  }
@@ -3785,15 +3785,15 @@ var require_dist_node9 = __commonJS({
3785
3785
  octokit.log.debug("request", options);
3786
3786
  const start = Date.now();
3787
3787
  const requestOptions = octokit.request.endpoint.parse(options);
3788
- const path16 = requestOptions.url.replace(options.baseUrl, "");
3788
+ const path20 = requestOptions.url.replace(options.baseUrl, "");
3789
3789
  return request(options).then((response) => {
3790
3790
  octokit.log.info(
3791
- `${requestOptions.method} ${path16} - ${response.status} in ${Date.now() - start}ms`
3791
+ `${requestOptions.method} ${path20} - ${response.status} in ${Date.now() - start}ms`
3792
3792
  );
3793
3793
  return response;
3794
3794
  }).catch((error2) => {
3795
3795
  octokit.log.info(
3796
- `${requestOptions.method} ${path16} - ${error2.status} in ${Date.now() - start}ms`
3796
+ `${requestOptions.method} ${path20} - ${error2.status} in ${Date.now() - start}ms`
3797
3797
  );
3798
3798
  throw error2;
3799
3799
  });
@@ -7887,7 +7887,7 @@ var require_dist_node13 = __commonJS({
7887
7887
  ];
7888
7888
  function routeMatcher(paths) {
7889
7889
  const regexes = paths.map(
7890
- (path16) => path16.split("/").map((c7) => c7.startsWith("{") ? "(?:.+?)" : c7).join("/")
7890
+ (path20) => path20.split("/").map((c7) => c7.startsWith("{") ? "(?:.+?)" : c7).join("/")
7891
7891
  );
7892
7892
  const regex2 = `^(?:${regexes.map((r6) => `(?:${r6})`).join("|")})[^/]*$`;
7893
7893
  return new RegExp(regex2, "i");
@@ -8517,8 +8517,8 @@ var init_parseUtil = __esm({
8517
8517
  init_errors();
8518
8518
  init_en();
8519
8519
  makeIssue = (params) => {
8520
- const { data: data2, path: path16, errorMaps, issueData } = params;
8521
- const fullPath = [...path16, ...issueData.path || []];
8520
+ const { data: data2, path: path20, errorMaps, issueData } = params;
8521
+ const fullPath = [...path20, ...issueData.path || []];
8522
8522
  const fullIssue = {
8523
8523
  ...issueData,
8524
8524
  path: fullPath
@@ -8826,11 +8826,11 @@ var init_types2 = __esm({
8826
8826
  init_parseUtil();
8827
8827
  init_util2();
8828
8828
  ParseInputLazyPath = class {
8829
- constructor(parent, value, path16, key) {
8829
+ constructor(parent, value, path20, key) {
8830
8830
  this._cachedPath = [];
8831
8831
  this.parent = parent;
8832
8832
  this.data = value;
8833
- this._path = path16;
8833
+ this._path = path20;
8834
8834
  this._key = key;
8835
8835
  }
8836
8836
  get path() {
@@ -12517,8 +12517,8 @@ var init_permissions = __esm({
12517
12517
  });
12518
12518
 
12519
12519
  // ../canopycms/src/paths/normalize.ts
12520
- function normalizeFilesystemPath(path16) {
12521
- return path16.split(/[\\/]+/).filter(Boolean).join("/");
12520
+ function normalizeFilesystemPath(path20) {
12521
+ return path20.split(/[\\/]+/).filter(Boolean).join("/");
12522
12522
  }
12523
12523
  var init_normalize = __esm({
12524
12524
  "../canopycms/src/paths/normalize.ts"() {
@@ -13668,9 +13668,9 @@ var init_createPaginator = __esm({
13668
13668
  command = withCommand(command) ?? command;
13669
13669
  return await client.send(command, ...args);
13670
13670
  };
13671
- get = (fromObject, path16) => {
13671
+ get = (fromObject, path20) => {
13672
13672
  let cursor2 = fromObject;
13673
- const pathComponents = path16.split(".");
13673
+ const pathComponents = path20.split(".");
13674
13674
  for (const step of pathComponents) {
13675
13675
  if (!cursor2 || typeof cursor2 !== "object") {
13676
13676
  return void 0;
@@ -14688,12 +14688,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
14688
14688
  const password = request.password ?? "";
14689
14689
  auth = `${username}:${password}`;
14690
14690
  }
14691
- let path16 = request.path;
14691
+ let path20 = request.path;
14692
14692
  if (queryString) {
14693
- path16 += `?${queryString}`;
14693
+ path20 += `?${queryString}`;
14694
14694
  }
14695
14695
  if (request.fragment) {
14696
- path16 += `#${request.fragment}`;
14696
+ path20 += `#${request.fragment}`;
14697
14697
  }
14698
14698
  let hostname = request.hostname ?? "";
14699
14699
  if (hostname[0] === "[" && hostname.endsWith("]")) {
@@ -14705,7 +14705,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
14705
14705
  headers: request.headers,
14706
14706
  host: hostname,
14707
14707
  method: request.method,
14708
- path: path16,
14708
+ path: path20,
14709
14709
  port: request.port,
14710
14710
  agent,
14711
14711
  auth
@@ -14988,16 +14988,16 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
14988
14988
  reject(err);
14989
14989
  };
14990
14990
  const queryString = querystringBuilder.buildQueryString(query || {});
14991
- let path16 = request.path;
14991
+ let path20 = request.path;
14992
14992
  if (queryString) {
14993
- path16 += `?${queryString}`;
14993
+ path20 += `?${queryString}`;
14994
14994
  }
14995
14995
  if (request.fragment) {
14996
- path16 += `#${request.fragment}`;
14996
+ path20 += `#${request.fragment}`;
14997
14997
  }
14998
14998
  const req = session.request({
14999
14999
  ...request.headers,
15000
- [http2.constants.HTTP2_HEADER_PATH]: path16,
15000
+ [http2.constants.HTTP2_HEADER_PATH]: path20,
15001
15001
  [http2.constants.HTTP2_HEADER_METHOD]: method
15002
15002
  });
15003
15003
  session.ref();
@@ -15184,13 +15184,13 @@ var require_dist_cjs14 = __commonJS({
15184
15184
  const abortError = buildAbortError(abortSignal);
15185
15185
  return Promise.reject(abortError);
15186
15186
  }
15187
- let path16 = request.path;
15187
+ let path20 = request.path;
15188
15188
  const queryString = querystringBuilder.buildQueryString(request.query || {});
15189
15189
  if (queryString) {
15190
- path16 += `?${queryString}`;
15190
+ path20 += `?${queryString}`;
15191
15191
  }
15192
15192
  if (request.fragment) {
15193
- path16 += `#${request.fragment}`;
15193
+ path20 += `#${request.fragment}`;
15194
15194
  }
15195
15195
  let auth = "";
15196
15196
  if (request.username != null || request.password != null) {
@@ -15199,7 +15199,7 @@ var require_dist_cjs14 = __commonJS({
15199
15199
  auth = `${username}:${password}@`;
15200
15200
  }
15201
15201
  const { port, method } = request;
15202
- const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path16}`;
15202
+ const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path20}`;
15203
15203
  const body = method === "GET" || method === "HEAD" ? void 0 : request.body;
15204
15204
  const requestOptions = {
15205
15205
  body,
@@ -17431,13 +17431,13 @@ function __disposeResources(env) {
17431
17431
  }
17432
17432
  return next();
17433
17433
  }
17434
- function __rewriteRelativeImportExtension(path16, preserveJsx) {
17435
- if (typeof path16 === "string" && /^\.\.?\//.test(path16)) {
17436
- return path16.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m5, tsx, d5, ext, cm) {
17434
+ function __rewriteRelativeImportExtension(path20, preserveJsx) {
17435
+ if (typeof path20 === "string" && /^\.\.?\//.test(path20)) {
17436
+ return path20.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m5, tsx, d5, ext, cm) {
17437
17437
  return tsx ? preserveJsx ? ".jsx" : ".js" : d5 && (!ext || !cm) ? m5 : d5 + ext + "." + cm.toLowerCase() + "js";
17438
17438
  });
17439
17439
  }
17440
- return path16;
17440
+ return path20;
17441
17441
  }
17442
17442
  var extendStatics, __assign, __createBinding, __setModuleDefault, ownKeys, _SuppressedError, tslib_es6_default;
17443
17443
  var init_tslib_es6 = __esm({
@@ -18335,11 +18335,11 @@ var init_HttpBindingProtocol = __esm({
18335
18335
  const opTraits = translateTraits(operationSchema.traits);
18336
18336
  if (opTraits.http) {
18337
18337
  request.method = opTraits.http[0];
18338
- const [path16, search] = opTraits.http[1].split("?");
18338
+ const [path20, search] = opTraits.http[1].split("?");
18339
18339
  if (request.path == "/") {
18340
- request.path = path16;
18340
+ request.path = path20;
18341
18341
  } else {
18342
- request.path += path16;
18342
+ request.path += path20;
18343
18343
  }
18344
18344
  const traitSearchParams = new URLSearchParams(search ?? "");
18345
18345
  Object.assign(query, Object.fromEntries(traitSearchParams));
@@ -18742,8 +18742,8 @@ var init_requestBuilder = __esm({
18742
18742
  return this;
18743
18743
  }
18744
18744
  p(memberName, labelValueProvider, uriLabel, isGreedyLabel) {
18745
- this.resolvePathStack.push((path16) => {
18746
- this.path = resolvedPath(path16, this.input, memberName, labelValueProvider, uriLabel, isGreedyLabel);
18745
+ this.resolvePathStack.push((path20) => {
18746
+ this.path = resolvedPath(path20, this.input, memberName, labelValueProvider, uriLabel, isGreedyLabel);
18747
18747
  });
18748
18748
  return this;
18749
18749
  }
@@ -19393,18 +19393,18 @@ var require_dist_cjs20 = __commonJS({
19393
19393
  }
19394
19394
  };
19395
19395
  var booleanEquals = (value1, value2) => value1 === value2;
19396
- var getAttrPathList = (path16) => {
19397
- const parts = path16.split(".");
19396
+ var getAttrPathList = (path20) => {
19397
+ const parts = path20.split(".");
19398
19398
  const pathList = [];
19399
19399
  for (const part of parts) {
19400
19400
  const squareBracketIndex = part.indexOf("[");
19401
19401
  if (squareBracketIndex !== -1) {
19402
19402
  if (part.indexOf("]") !== part.length - 1) {
19403
- throw new EndpointError(`Path: '${path16}' does not end with ']'`);
19403
+ throw new EndpointError(`Path: '${path20}' does not end with ']'`);
19404
19404
  }
19405
19405
  const arrayIndex = part.slice(squareBracketIndex + 1, -1);
19406
19406
  if (Number.isNaN(parseInt(arrayIndex))) {
19407
- throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path16}'`);
19407
+ throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path20}'`);
19408
19408
  }
19409
19409
  if (squareBracketIndex !== 0) {
19410
19410
  pathList.push(part.slice(0, squareBracketIndex));
@@ -19416,9 +19416,9 @@ var require_dist_cjs20 = __commonJS({
19416
19416
  }
19417
19417
  return pathList;
19418
19418
  };
19419
- var getAttr = (value, path16) => getAttrPathList(path16).reduce((acc, index) => {
19419
+ var getAttr = (value, path20) => getAttrPathList(path20).reduce((acc, index) => {
19420
19420
  if (typeof acc !== "object") {
19421
- throw new EndpointError(`Index '${index}' in '${path16}' not found in '${JSON.stringify(value)}'`);
19421
+ throw new EndpointError(`Index '${index}' in '${path20}' not found in '${JSON.stringify(value)}'`);
19422
19422
  } else if (Array.isArray(acc)) {
19423
19423
  return acc[parseInt(index)];
19424
19424
  }
@@ -19437,8 +19437,8 @@ var require_dist_cjs20 = __commonJS({
19437
19437
  return value;
19438
19438
  }
19439
19439
  if (typeof value === "object" && "hostname" in value) {
19440
- const { hostname: hostname2, port, protocol: protocol2 = "", path: path16 = "", query = {} } = value;
19441
- const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path16}`);
19440
+ const { hostname: hostname2, port, protocol: protocol2 = "", path: path20 = "", query = {} } = value;
19441
+ const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path20}`);
19442
19442
  url.search = Object.entries(query).map(([k6, v5]) => `${k6}=${v5}`).join("&");
19443
19443
  return url;
19444
19444
  }
@@ -20883,10 +20883,10 @@ ${longDate}
20883
20883
  ${credentialScope}
20884
20884
  ${utilHexEncoding.toHex(hashedRequest)}`;
20885
20885
  }
20886
- getCanonicalPath({ path: path16 }) {
20886
+ getCanonicalPath({ path: path20 }) {
20887
20887
  if (this.uriEscapePath) {
20888
20888
  const normalizedPathSegments = [];
20889
- for (const pathSegment of path16.split("/")) {
20889
+ for (const pathSegment of path20.split("/")) {
20890
20890
  if (pathSegment?.length === 0)
20891
20891
  continue;
20892
20892
  if (pathSegment === ".")
@@ -20897,11 +20897,11 @@ ${utilHexEncoding.toHex(hashedRequest)}`;
20897
20897
  normalizedPathSegments.push(pathSegment);
20898
20898
  }
20899
20899
  }
20900
- const normalizedPath = `${path16?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path16?.endsWith("/") ? "/" : ""}`;
20900
+ const normalizedPath = `${path20?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path20?.endsWith("/") ? "/" : ""}`;
20901
20901
  const doubleEncoded = utilUriEscape.escapeUri(normalizedPath);
20902
20902
  return doubleEncoded.replace(/%2F/g, "/");
20903
20903
  }
20904
- return path16;
20904
+ return path20;
20905
20905
  }
20906
20906
  validateResolvedCredentials(credentials) {
20907
20907
  if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") {
@@ -22205,11 +22205,11 @@ var init_SmithyRpcV2CborProtocol = __esm({
22205
22205
  }
22206
22206
  }
22207
22207
  const { service, operation: operation2 } = (0, import_util_middleware5.getSmithyContext)(context);
22208
- const path16 = `/service/${service}/operation/${operation2}`;
22208
+ const path20 = `/service/${service}/operation/${operation2}`;
22209
22209
  if (request.path.endsWith("/")) {
22210
- request.path += path16.slice(1);
22210
+ request.path += path20.slice(1);
22211
22211
  } else {
22212
- request.path += path16;
22212
+ request.path += path20;
22213
22213
  }
22214
22214
  return request;
22215
22215
  }
@@ -27854,14 +27854,14 @@ var require_readFile = __commonJS({
27854
27854
  var promises_1 = __require("node:fs/promises");
27855
27855
  exports.filePromises = {};
27856
27856
  exports.fileIntercept = {};
27857
- var readFile = (path16, options) => {
27858
- if (exports.fileIntercept[path16] !== void 0) {
27859
- return exports.fileIntercept[path16];
27857
+ var readFile = (path20, options) => {
27858
+ if (exports.fileIntercept[path20] !== void 0) {
27859
+ return exports.fileIntercept[path20];
27860
27860
  }
27861
- if (!exports.filePromises[path16] || options?.ignoreCache) {
27862
- exports.filePromises[path16] = (0, promises_1.readFile)(path16, "utf8");
27861
+ if (!exports.filePromises[path20] || options?.ignoreCache) {
27862
+ exports.filePromises[path20] = (0, promises_1.readFile)(path20, "utf8");
27863
27863
  }
27864
- return exports.filePromises[path16];
27864
+ return exports.filePromises[path20];
27865
27865
  };
27866
27866
  exports.readFile = readFile;
27867
27867
  }
@@ -27874,7 +27874,7 @@ var require_dist_cjs33 = __commonJS({
27874
27874
  var getHomeDir = require_getHomeDir();
27875
27875
  var getSSOTokenFilepath = require_getSSOTokenFilepath();
27876
27876
  var getSSOTokenFromFile = require_getSSOTokenFromFile();
27877
- var path16 = __require("path");
27877
+ var path20 = __require("path");
27878
27878
  var types = require_dist_cjs();
27879
27879
  var readFile = require_readFile();
27880
27880
  var ENV_PROFILE = "AWS_PROFILE";
@@ -27896,9 +27896,9 @@ var require_dist_cjs33 = __commonJS({
27896
27896
  ...data2.default && { default: data2.default }
27897
27897
  });
27898
27898
  var ENV_CONFIG_PATH = "AWS_CONFIG_FILE";
27899
- var getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path16.join(getHomeDir.getHomeDir(), ".aws", "config");
27899
+ var getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path20.join(getHomeDir.getHomeDir(), ".aws", "config");
27900
27900
  var ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE";
27901
- var getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path16.join(getHomeDir.getHomeDir(), ".aws", "credentials");
27901
+ var getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path20.join(getHomeDir.getHomeDir(), ".aws", "credentials");
27902
27902
  var prefixKeyRegex = /^([\w-]+)\s(["'])?([\w-@\+\.%:/]+)\2$/;
27903
27903
  var profileNameBlockList = ["__proto__", "profile __proto__"];
27904
27904
  var parseIni = (iniData) => {
@@ -27953,11 +27953,11 @@ var require_dist_cjs33 = __commonJS({
27953
27953
  const relativeHomeDirPrefix = "~/";
27954
27954
  let resolvedFilepath = filepath;
27955
27955
  if (filepath.startsWith(relativeHomeDirPrefix)) {
27956
- resolvedFilepath = path16.join(homeDir, filepath.slice(2));
27956
+ resolvedFilepath = path20.join(homeDir, filepath.slice(2));
27957
27957
  }
27958
27958
  let resolvedConfigFilepath = configFilepath;
27959
27959
  if (configFilepath.startsWith(relativeHomeDirPrefix)) {
27960
- resolvedConfigFilepath = path16.join(homeDir, configFilepath.slice(2));
27960
+ resolvedConfigFilepath = path20.join(homeDir, configFilepath.slice(2));
27961
27961
  }
27962
27962
  const parsedFiles = await Promise.all([
27963
27963
  readFile.readFile(resolvedConfigFilepath, {
@@ -27996,8 +27996,8 @@ var require_dist_cjs33 = __commonJS({
27996
27996
  getFileRecord() {
27997
27997
  return readFile.fileIntercept;
27998
27998
  },
27999
- interceptFile(path17, contents) {
28000
- readFile.fileIntercept[path17] = Promise.resolve(contents);
27999
+ interceptFile(path21, contents) {
28000
+ readFile.fileIntercept[path21] = Promise.resolve(contents);
28001
28001
  },
28002
28002
  getTokenRecord() {
28003
28003
  return getSSOTokenFromFile.tokenIntercept;
@@ -28322,8 +28322,8 @@ var require_dist_cjs36 = __commonJS({
28322
28322
  return endpoint.url.href;
28323
28323
  }
28324
28324
  if ("hostname" in endpoint) {
28325
- const { protocol, hostname, port, path: path16 } = endpoint;
28326
- return `${protocol}//${hostname}${port ? ":" + port : ""}${path16}`;
28325
+ const { protocol, hostname, port, path: path20 } = endpoint;
28326
+ return `${protocol}//${hostname}${port ? ":" + port : ""}${path20}`;
28327
28327
  }
28328
28328
  }
28329
28329
  return endpoint;
@@ -36520,8 +36520,8 @@ var require_dist_cjs53 = __commonJS({
36520
36520
  });
36521
36521
 
36522
36522
  // ../canopycms/src/worker/cms-worker.ts
36523
- import fs9 from "node:fs/promises";
36524
- import path13 from "node:path";
36523
+ import fs13 from "node:fs/promises";
36524
+ import path17 from "node:path";
36525
36525
 
36526
36526
  // ../../node_modules/.pnpm/simple-git@3.36.0/node_modules/simple-git/dist/esm/index.js
36527
36527
  var import_file_exists = __toESM(require_dist(), 1);
@@ -37112,8 +37112,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
37112
37112
  function forEachLineWithContent(input, callback) {
37113
37113
  return toLinesWithContent(input, true).map((line) => callback(line));
37114
37114
  }
37115
- function folderExists(path16) {
37116
- return (0, import_file_exists.exists)(path16, import_file_exists.FOLDER);
37115
+ function folderExists(path20) {
37116
+ return (0, import_file_exists.exists)(path20, import_file_exists.FOLDER);
37117
37117
  }
37118
37118
  function append(target, item) {
37119
37119
  if (Array.isArray(target)) {
@@ -37515,8 +37515,8 @@ function checkIsRepoRootTask() {
37515
37515
  commands: commands5,
37516
37516
  format: "utf-8",
37517
37517
  onError,
37518
- parser(path16) {
37519
- return /^\.(git)?$/.test(path16.trim());
37518
+ parser(path20) {
37519
+ return /^\.(git)?$/.test(path20.trim());
37520
37520
  }
37521
37521
  };
37522
37522
  }
@@ -37950,11 +37950,11 @@ function parseGrep(grep) {
37950
37950
  const paths = /* @__PURE__ */ new Set();
37951
37951
  const results = {};
37952
37952
  forEachLineWithContent(grep, (input) => {
37953
- const [path16, line, preview] = input.split(NULL);
37954
- paths.add(path16);
37955
- (results[path16] = results[path16] || []).push({
37953
+ const [path20, line, preview] = input.split(NULL);
37954
+ paths.add(path20);
37955
+ (results[path20] = results[path20] || []).push({
37956
37956
  line: asNumber(line),
37957
- path: path16,
37957
+ path: path20,
37958
37958
  preview
37959
37959
  });
37960
37960
  });
@@ -38718,14 +38718,14 @@ var init_hash_object = __esm2({
38718
38718
  init_task();
38719
38719
  }
38720
38720
  });
38721
- function parseInit(bare, path16, text) {
38721
+ function parseInit(bare, path20, text) {
38722
38722
  const response = String(text).trim();
38723
38723
  let result;
38724
38724
  if (result = initResponseRegex.exec(response)) {
38725
- return new InitSummary(bare, path16, false, result[1]);
38725
+ return new InitSummary(bare, path20, false, result[1]);
38726
38726
  }
38727
38727
  if (result = reInitResponseRegex.exec(response)) {
38728
- return new InitSummary(bare, path16, true, result[1]);
38728
+ return new InitSummary(bare, path20, true, result[1]);
38729
38729
  }
38730
38730
  let gitDir = "";
38731
38731
  const tokens = response.split(" ");
@@ -38736,7 +38736,7 @@ function parseInit(bare, path16, text) {
38736
38736
  break;
38737
38737
  }
38738
38738
  }
38739
- return new InitSummary(bare, path16, /^re/i.test(response), gitDir);
38739
+ return new InitSummary(bare, path20, /^re/i.test(response), gitDir);
38740
38740
  }
38741
38741
  var InitSummary;
38742
38742
  var initResponseRegex;
@@ -38745,9 +38745,9 @@ var init_InitSummary = __esm2({
38745
38745
  "src/lib/responses/InitSummary.ts"() {
38746
38746
  "use strict";
38747
38747
  InitSummary = class {
38748
- constructor(bare, path16, existing, gitDir) {
38748
+ constructor(bare, path20, existing, gitDir) {
38749
38749
  this.bare = bare;
38750
- this.path = path16;
38750
+ this.path = path20;
38751
38751
  this.existing = existing;
38752
38752
  this.gitDir = gitDir;
38753
38753
  }
@@ -38759,7 +38759,7 @@ var init_InitSummary = __esm2({
38759
38759
  function hasBareCommand(command) {
38760
38760
  return command.includes(bareCommand);
38761
38761
  }
38762
- function initTask(bare = false, path16, customArgs) {
38762
+ function initTask(bare = false, path20, customArgs) {
38763
38763
  const commands5 = ["init", ...customArgs];
38764
38764
  if (bare && !hasBareCommand(commands5)) {
38765
38765
  commands5.splice(1, 0, bareCommand);
@@ -38768,7 +38768,7 @@ function initTask(bare = false, path16, customArgs) {
38768
38768
  commands: commands5,
38769
38769
  format: "utf-8",
38770
38770
  parser(text) {
38771
- return parseInit(commands5.includes("--bare"), path16, text);
38771
+ return parseInit(commands5.includes("--bare"), path20, text);
38772
38772
  }
38773
38773
  };
38774
38774
  }
@@ -39583,12 +39583,12 @@ var init_FileStatusSummary = __esm2({
39583
39583
  "use strict";
39584
39584
  fromPathRegex = /^(.+)\0(.+)$/;
39585
39585
  FileStatusSummary = class {
39586
- constructor(path16, index, working_dir) {
39587
- this.path = path16;
39586
+ constructor(path20, index, working_dir) {
39587
+ this.path = path20;
39588
39588
  this.index = index;
39589
39589
  this.working_dir = working_dir;
39590
39590
  if (index === "R" || working_dir === "R") {
39591
- const detail = fromPathRegex.exec(path16) || [null, path16, path16];
39591
+ const detail = fromPathRegex.exec(path20) || [null, path20, path20];
39592
39592
  this.from = detail[2] || "";
39593
39593
  this.path = detail[1] || "";
39594
39594
  }
@@ -39619,14 +39619,14 @@ function splitLine(result, lineStr) {
39619
39619
  default:
39620
39620
  return;
39621
39621
  }
39622
- function data2(index, workingDir, path16) {
39622
+ function data2(index, workingDir, path20) {
39623
39623
  const raw = `${index}${workingDir}`;
39624
39624
  const handler = parsers6.get(raw);
39625
39625
  if (handler) {
39626
- handler(result, path16);
39626
+ handler(result, path20);
39627
39627
  }
39628
39628
  if (raw !== "##" && raw !== "!!") {
39629
- result.files.push(new FileStatusSummary(path16, index, workingDir));
39629
+ result.files.push(new FileStatusSummary(path20, index, workingDir));
39630
39630
  }
39631
39631
  }
39632
39632
  }
@@ -39977,9 +39977,9 @@ var init_simple_git_api = __esm2({
39977
39977
  next
39978
39978
  );
39979
39979
  }
39980
- hashObject(path16, write) {
39980
+ hashObject(path20, write) {
39981
39981
  return this._runTask(
39982
- hashObjectTask(path16, write === true),
39982
+ hashObjectTask(path20, write === true),
39983
39983
  trailingFunctionArgument(arguments)
39984
39984
  );
39985
39985
  }
@@ -40333,8 +40333,8 @@ var init_branch = __esm2({
40333
40333
  }
40334
40334
  });
40335
40335
  function toPath(input) {
40336
- const path16 = input.trim().replace(/^["']|["']$/g, "");
40337
- return path16 && normalize(path16);
40336
+ const path20 = input.trim().replace(/^["']|["']$/g, "");
40337
+ return path20 && normalize(path20);
40338
40338
  }
40339
40339
  var parseCheckIgnore;
40340
40340
  var init_CheckIgnore = __esm2({
@@ -40619,8 +40619,8 @@ __export2(sub_module_exports, {
40619
40619
  subModuleTask: () => subModuleTask,
40620
40620
  updateSubModuleTask: () => updateSubModuleTask
40621
40621
  });
40622
- function addSubModuleTask(repo, path16) {
40623
- return subModuleTask(["add", repo, path16]);
40622
+ function addSubModuleTask(repo, path20) {
40623
+ return subModuleTask(["add", repo, path20]);
40624
40624
  }
40625
40625
  function initSubModuleTask(customArgs) {
40626
40626
  return subModuleTask(["init", ...customArgs]);
@@ -40934,8 +40934,8 @@ var require_git = __commonJS2({
40934
40934
  }
40935
40935
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
40936
40936
  };
40937
- Git2.prototype.submoduleAdd = function(repo, path16, then) {
40938
- return this._runTask(addSubModuleTask2(repo, path16), trailingFunctionArgument2(arguments));
40937
+ Git2.prototype.submoduleAdd = function(repo, path20, then) {
40938
+ return this._runTask(addSubModuleTask2(repo, path20), trailingFunctionArgument2(arguments));
40939
40939
  };
40940
40940
  Git2.prototype.submoduleUpdate = function(args, then) {
40941
40941
  return this._runTask(
@@ -42040,23 +42040,97 @@ async function createOrUpdatePullRequest(params) {
42040
42040
  return { number: pr.data.number, url: pr.data.html_url, created: true };
42041
42041
  }
42042
42042
 
42043
+ // ../canopycms/src/paths/branch-name.ts
42044
+ function sanitizeBranchName(branchName) {
42045
+ const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
42046
+ const squashed = replaced.replace(/-+/g, "-");
42047
+ const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
42048
+ return trimmedDots || "branch";
42049
+ }
42050
+ var RESERVED_SETTINGS_BRANCH_PREFIX = "canopycms-settings-";
42051
+
42052
+ // ../canopycms/src/worker/cms-worker.ts
42053
+ init_deployment_name();
42054
+
42055
+ // ../canopycms/src/worker/worker-status.ts
42056
+ import path4 from "node:path";
42057
+ var WORKER_STATUS_FILE = "worker-status.json";
42058
+ async function writeWorkerStatus(taskDir, report) {
42059
+ const filePath = path4.join(taskDir, WORKER_STATUS_FILE);
42060
+ const stamped = { ...report, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
42061
+ await atomicWriteFile(filePath, JSON.stringify(stamped, null, 2));
42062
+ }
42063
+
42064
+ // ../canopycms/src/worker/log.ts
42065
+ init_logger();
42066
+ function timestamp() {
42067
+ return (/* @__PURE__ */ new Date()).toISOString();
42068
+ }
42069
+ function workerLog(...args) {
42070
+ console.log(timestamp(), "INFO", ...args);
42071
+ }
42072
+ function workerLogWarn(...args) {
42073
+ console.warn(timestamp(), "WARN", ...args);
42074
+ }
42075
+ function workerLogError(...args) {
42076
+ console.error(timestamp(), "ERROR", ...args);
42077
+ }
42078
+ function installWorkerLogger() {
42079
+ setCanopyLogger({ log: workerLog, warn: workerLogWarn, error: workerLogError });
42080
+ }
42081
+
42082
+ // ../canopycms/src/worker/task-runner.ts
42083
+ import fs10 from "node:fs/promises";
42084
+
42043
42085
  // ../canopycms/src/branch-metadata.ts
42044
- import fs6 from "node:fs/promises";
42045
- import path7 from "node:path";
42086
+ import fs7 from "node:fs/promises";
42087
+ import path9 from "node:path";
42046
42088
 
42047
42089
  // ../canopycms/src/branch-registry.ts
42048
- import fs4 from "node:fs/promises";
42090
+ import fs5 from "node:fs/promises";
42091
+ import path7 from "node:path";
42092
+
42093
+ // ../canopycms/src/branch-metadata-file.ts
42094
+ import fs3 from "node:fs/promises";
42049
42095
  import path5 from "node:path";
42096
+ var BRANCH_META_DIR = ".canopy-meta";
42097
+ var BRANCH_META_FILE = "branch.json";
42098
+ var BranchMetadataCorruptError = class extends Error {
42099
+ constructor(branchRoot, cause) {
42100
+ super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
42101
+ this.name = "BranchMetadataCorruptError";
42102
+ this.branchRoot = branchRoot;
42103
+ this.parseCause = cause;
42104
+ }
42105
+ };
42106
+ var branchMetadataFilePath = (branchRoot) => path5.join(path5.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
42107
+ async function readBranchMetadataFile(branchRoot) {
42108
+ const resolvedRoot = path5.resolve(branchRoot);
42109
+ try {
42110
+ const raw = await fs3.readFile(branchMetadataFilePath(resolvedRoot), "utf8");
42111
+ return JSON.parse(raw);
42112
+ } catch (err) {
42113
+ if (isNotFoundError2(err)) {
42114
+ return null;
42115
+ }
42116
+ if (err instanceof SyntaxError) {
42117
+ throw new BranchMetadataCorruptError(resolvedRoot, err.message);
42118
+ }
42119
+ throw err;
42120
+ }
42121
+ }
42122
+
42123
+ // ../canopycms/src/branch-registry.ts
42050
42124
  init_logger();
42051
42125
 
42052
42126
  // ../canopycms/src/resource-generation.ts
42053
- import fs3 from "node:fs/promises";
42054
- import path4 from "node:path";
42127
+ import fs4 from "node:fs/promises";
42128
+ import path6 from "node:path";
42055
42129
  import { randomUUID } from "node:crypto";
42056
42130
  var log = createDebugLogger({ prefix: "ResourceGeneration" });
42057
42131
  var META_DIR = ".canopy-meta";
42058
42132
  function resourceGenerationPath(root, resource) {
42059
- return path4.resolve(root, META_DIR, `${resource}.generation`);
42133
+ return path6.resolve(root, META_DIR, `${resource}.generation`);
42060
42134
  }
42061
42135
  async function bumpResourceGeneration(root, resource, options) {
42062
42136
  const token = randomUUID();
@@ -42073,7 +42147,7 @@ async function bumpResourceGeneration(root, resource, options) {
42073
42147
  }
42074
42148
  async function readResourceGeneration(root, resource) {
42075
42149
  try {
42076
- const token = await fs3.readFile(resourceGenerationPath(root, resource), "utf-8");
42150
+ const token = await fs4.readFile(resourceGenerationPath(root, resource), "utf-8");
42077
42151
  return { ok: true, token };
42078
42152
  } catch (err) {
42079
42153
  if (isNodeError(err) && err.code === "ENOENT") return { ok: true, token: null };
@@ -42101,10 +42175,10 @@ var BranchRegistry = class {
42101
42175
  this.regenInFlight = null;
42102
42176
  /** Throttle clock for the get() suspicious-miss backstop. */
42103
42177
  this.lastForcedRefreshMs = 0;
42104
- this.root = path5.resolve(root);
42105
- this.registryPath = path5.join(this.root, REGISTRY_FILE);
42106
- this.stalePath = path5.join(this.root, REGISTRY_STALE_FILE);
42107
- this.tempPath = path5.join(this.root, REGISTRY_TEMP_FILE);
42178
+ this.root = path7.resolve(root);
42179
+ this.registryPath = path7.join(this.root, REGISTRY_FILE);
42180
+ this.stalePath = path7.join(this.root, REGISTRY_STALE_FILE);
42181
+ this.tempPath = path7.join(this.root, REGISTRY_TEMP_FILE);
42108
42182
  }
42109
42183
  /**
42110
42184
  * Returns all branches. Uses the cached snapshot if its embedded generation
@@ -42113,7 +42187,7 @@ var BranchRegistry = class {
42113
42187
  async list() {
42114
42188
  let parsed;
42115
42189
  try {
42116
- const raw = await fs4.readFile(this.registryPath, "utf8");
42190
+ const raw = await fs5.readFile(this.registryPath, "utf8");
42117
42191
  parsed = JSON.parse(raw);
42118
42192
  } catch (err) {
42119
42193
  if (isNotFoundError2(err)) {
@@ -42197,7 +42271,7 @@ var BranchRegistry = class {
42197
42271
  async performRegenerate() {
42198
42272
  const read = await readResourceGeneration(this.root, RESOURCE);
42199
42273
  const branches = await this.scanBranchDirectories();
42200
- await fs4.unlink(this.stalePath).catch(() => {
42274
+ await fs5.unlink(this.stalePath).catch(() => {
42201
42275
  });
42202
42276
  if (!read.ok) {
42203
42277
  return branches;
@@ -42208,12 +42282,12 @@ var BranchRegistry = class {
42208
42282
  generation: read.token
42209
42283
  };
42210
42284
  const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
42211
- await fs4.mkdir(this.root, { recursive: true });
42212
- await fs4.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
42285
+ await fs5.mkdir(this.root, { recursive: true });
42286
+ await fs5.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
42213
42287
  try {
42214
- await fs4.rename(uniqueTempPath, this.registryPath);
42288
+ await fs5.rename(uniqueTempPath, this.registryPath);
42215
42289
  } catch (err) {
42216
- await fs4.unlink(uniqueTempPath).catch(() => {
42290
+ await fs5.unlink(uniqueTempPath).catch(() => {
42217
42291
  });
42218
42292
  throw err;
42219
42293
  }
@@ -42227,15 +42301,15 @@ var BranchRegistry = class {
42227
42301
  async scanBranchDirectories() {
42228
42302
  const branches = [];
42229
42303
  try {
42230
- const entries = await fs4.readdir(this.root, { withFileTypes: true });
42304
+ const entries = await fs5.readdir(this.root, { withFileTypes: true });
42231
42305
  for (const entry of entries) {
42232
42306
  if (!entry.isDirectory() || entry.name.startsWith(".")) {
42233
42307
  continue;
42234
42308
  }
42235
- const branchRoot = path5.join(this.root, entry.name);
42309
+ const branchRoot = path7.join(this.root, entry.name);
42236
42310
  let meta;
42237
42311
  try {
42238
- meta = await BranchMetadataFileManager.loadOnly(branchRoot);
42312
+ meta = await readBranchMetadataFile(branchRoot);
42239
42313
  } catch (err) {
42240
42314
  canopyLogWarn(
42241
42315
  `CanopyCMS: Skipping branch directory '${entry.name}' during registry scan: ${getErrorMessage2(err)}`
@@ -42275,15 +42349,6 @@ function isValidContentId(id) {
42275
42349
  // ../canopycms/src/paths/branch.ts
42276
42350
  init_operating_mode();
42277
42351
 
42278
- // ../canopycms/src/paths/branch-name.ts
42279
- function sanitizeBranchName(branchName) {
42280
- const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
42281
- const squashed = replaced.replace(/-+/g, "-");
42282
- const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
42283
- return trimmedDots || "branch";
42284
- }
42285
- var RESERVED_SETTINGS_BRANCH_PREFIX = "canopycms-settings-";
42286
-
42287
42352
  // ../canopycms/src/utils/async-mutex.ts
42288
42353
  var locks = /* @__PURE__ */ new Map();
42289
42354
  async function withLock(key, fn) {
@@ -42304,8 +42369,8 @@ async function withLock(key, fn) {
42304
42369
 
42305
42370
  // ../canopycms/src/utils/occ-json-write.ts
42306
42371
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
42307
- import fs5 from "node:fs/promises";
42308
- import path6 from "node:path";
42372
+ import fs6 from "node:fs/promises";
42373
+ import path8 from "node:path";
42309
42374
  import { randomUUID as randomUUID2 } from "node:crypto";
42310
42375
  var log3 = createDebugLogger({ prefix: "OccJsonWrite" });
42311
42376
  var OccWriteConflictError = class extends Error {
@@ -42320,29 +42385,29 @@ async function writeOccJsonFile(filePath, payload2, options) {
42320
42385
  const writeId = randomUUID2();
42321
42386
  const data2 = { ...payload2, version: newVersion, writeId };
42322
42387
  const content = JSON.stringify(data2, null, 2) + (trailingNewline ? "\n" : "");
42323
- const dir = path6.dirname(filePath);
42324
- await fs5.mkdir(dir, { recursive: true });
42388
+ const dir = path8.dirname(filePath);
42389
+ await fs6.mkdir(dir, { recursive: true });
42325
42390
  const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
42326
- await fs5.writeFile(tempPath, content, "utf-8");
42391
+ await fs6.writeFile(tempPath, content, "utf-8");
42327
42392
  if (expectedVersion === null) {
42328
42393
  try {
42329
- await fs5.link(tempPath, filePath);
42394
+ await fs6.link(tempPath, filePath);
42330
42395
  } catch (err) {
42331
- await fs5.unlink(tempPath).catch(() => {
42396
+ await fs6.unlink(tempPath).catch(() => {
42332
42397
  });
42333
42398
  if (isFileExistsError(err)) {
42334
42399
  throw new OccWriteConflictError();
42335
42400
  }
42336
42401
  throw err;
42337
42402
  }
42338
- await fs5.unlink(tempPath).catch(() => {
42403
+ await fs6.unlink(tempPath).catch(() => {
42339
42404
  });
42340
42405
  return { version: newVersion, writeId };
42341
42406
  }
42342
42407
  try {
42343
42408
  let currentVersion = null;
42344
42409
  try {
42345
- const current = JSON.parse(await fs5.readFile(filePath, "utf-8"));
42410
+ const current = JSON.parse(await fs6.readFile(filePath, "utf-8"));
42346
42411
  currentVersion = current.version ?? 0;
42347
42412
  } catch {
42348
42413
  currentVersion = null;
@@ -42350,16 +42415,16 @@ async function writeOccJsonFile(filePath, payload2, options) {
42350
42415
  if (currentVersion !== expectedVersion) {
42351
42416
  throw new OccWriteConflictError();
42352
42417
  }
42353
- await fs5.rename(tempPath, filePath);
42418
+ await fs6.rename(tempPath, filePath);
42354
42419
  if (settleMs > 0) {
42355
42420
  await new Promise((resolve2) => setTimeout(resolve2, settleMs));
42356
42421
  }
42357
- const afterWrite = JSON.parse(await fs5.readFile(filePath, "utf-8"));
42422
+ const afterWrite = JSON.parse(await fs6.readFile(filePath, "utf-8"));
42358
42423
  if (afterWrite.writeId !== writeId) {
42359
42424
  throw new OccWriteConflictError();
42360
42425
  }
42361
42426
  } catch (err) {
42362
- await fs5.unlink(tempPath).catch(() => {
42427
+ await fs6.unlink(tempPath).catch(() => {
42363
42428
  });
42364
42429
  throw err;
42365
42430
  }
@@ -42385,8 +42450,8 @@ async function withOccRetry(operation2, options) {
42385
42450
  throw new Error("Unreachable");
42386
42451
  }
42387
42452
  async function withOccFileLock(filePath, fn) {
42388
- const dir = path6.dirname(filePath);
42389
- await fs5.mkdir(dir, { recursive: true });
42453
+ const dir = path8.dirname(filePath);
42454
+ await fs6.mkdir(dir, { recursive: true });
42390
42455
  const MAX_ATTEMPTS = 14;
42391
42456
  let release = null;
42392
42457
  for (let attempt = 1; release === null; attempt++) {
@@ -42429,8 +42494,6 @@ async function withOccFileLock(filePath, fn) {
42429
42494
  }
42430
42495
 
42431
42496
  // ../canopycms/src/branch-metadata.ts
42432
- var BRANCH_META_DIR = ".canopy-meta";
42433
- var BRANCH_META_FILE = "branch.json";
42434
42497
  var CURRENT_SCHEMA_VERSION = 1;
42435
42498
  var BranchMetadataConflictError = class extends Error {
42436
42499
  constructor(message = "Concurrent modification detected in branch metadata") {
@@ -42438,18 +42501,10 @@ var BranchMetadataConflictError = class extends Error {
42438
42501
  this.name = "BranchMetadataConflictError";
42439
42502
  }
42440
42503
  };
42441
- var BranchMetadataCorruptError = class extends Error {
42442
- constructor(branchRoot, cause) {
42443
- super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
42444
- this.name = "BranchMetadataCorruptError";
42445
- this.branchRoot = branchRoot;
42446
- this.parseCause = cause;
42447
- }
42448
- };
42449
42504
  var BranchMetadataFileManager = class _BranchMetadataFileManager {
42450
42505
  constructor(branchRoot, baseRoot, options) {
42451
- this.branchRoot = path7.resolve(branchRoot);
42452
- this.filePath = path7.join(this.branchRoot, BRANCH_META_DIR, BRANCH_META_FILE);
42506
+ this.branchRoot = path9.resolve(branchRoot);
42507
+ this.filePath = path9.join(this.branchRoot, BRANCH_META_DIR, BRANCH_META_FILE);
42453
42508
  this.baseRoot = baseRoot;
42454
42509
  this.settleMs = options?.settleMs;
42455
42510
  }
@@ -42458,20 +42513,7 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
42458
42513
  * Use this for read-only access (e.g., in registry scanning or loadBranchContext).
42459
42514
  */
42460
42515
  static async loadOnly(branchRoot) {
42461
- const resolvedRoot = path7.resolve(branchRoot);
42462
- const filePath = path7.join(resolvedRoot, BRANCH_META_DIR, BRANCH_META_FILE);
42463
- try {
42464
- const raw = await fs6.readFile(filePath, "utf8");
42465
- return JSON.parse(raw);
42466
- } catch (err) {
42467
- if (isNotFoundError2(err)) {
42468
- return null;
42469
- }
42470
- if (err instanceof SyntaxError) {
42471
- throw new BranchMetadataCorruptError(resolvedRoot, err.message);
42472
- }
42473
- throw err;
42474
- }
42516
+ return readBranchMetadataFile(branchRoot);
42475
42517
  }
42476
42518
  /**
42477
42519
  * Get a BranchMetadataFileManager instance configured for registry invalidation.
@@ -42482,7 +42524,7 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
42482
42524
  }
42483
42525
  async load() {
42484
42526
  try {
42485
- const raw = await fs6.readFile(this.filePath, "utf8");
42527
+ const raw = await fs7.readFile(this.filePath, "utf8");
42486
42528
  const parsed = JSON.parse(raw);
42487
42529
  const version = parsed.version ?? 0;
42488
42530
  return { meta: parsed, version };
@@ -42544,7 +42586,7 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
42544
42586
  */
42545
42587
  async save(incoming) {
42546
42588
  try {
42547
- await fs6.stat(this.branchRoot);
42589
+ await fs7.stat(this.branchRoot);
42548
42590
  } catch (err) {
42549
42591
  if (isNotFoundError2(err)) {
42550
42592
  throw new BranchMetadataConflictError("Branch no longer exists");
@@ -42626,30 +42668,8 @@ var getBranchMetadataFileManager = (branchRoot, baseRoot, options) => {
42626
42668
  return BranchMetadataFileManager.get(branchRoot, baseRoot, options);
42627
42669
  };
42628
42670
 
42629
- // ../canopycms/src/id.ts
42630
- var import_short_uuid = __toESM(require_dist3(), 1);
42631
- var isValidId = isValidContentId;
42632
-
42633
- // ../canopycms/src/content-id-index.ts
42634
- init_logger();
42635
- function extractIdFromFilename(filename) {
42636
- if (filename.startsWith(".")) {
42637
- return null;
42638
- }
42639
- const parts = filename.split(".");
42640
- if (parts.length >= 3) {
42641
- const candidate = parts[parts.length - 2];
42642
- if (isValidId(candidate)) return candidate;
42643
- }
42644
- if (parts.length === 2) {
42645
- const candidate = parts[parts.length - 1];
42646
- if (isValidId(candidate)) return candidate;
42647
- }
42648
- return null;
42649
- }
42650
-
42651
42671
  // ../canopycms/src/content-index-registry.ts
42652
- import path8 from "node:path";
42672
+ import path10 from "node:path";
42653
42673
  var registry = /* @__PURE__ */ new Map();
42654
42674
  var finalization = new FinalizationRegistry(({ rootKey, ref }) => {
42655
42675
  const refs = registry.get(rootKey);
@@ -42658,8 +42678,8 @@ var finalization = new FinalizationRegistry(({ rootKey, ref }) => {
42658
42678
  if (refs.size === 0) registry.delete(rootKey);
42659
42679
  });
42660
42680
  function invalidateContentIndexesForRoot(root) {
42661
- const rootKey = path8.resolve(root);
42662
- const prefix = rootKey + path8.sep;
42681
+ const rootKey = path10.resolve(root);
42682
+ const prefix = rootKey + path10.sep;
42663
42683
  for (const [storeRoot, refs] of registry) {
42664
42684
  if (storeRoot !== rootKey && !storeRoot.startsWith(prefix)) continue;
42665
42685
  for (const ref of refs) {
@@ -42677,6 +42697,30 @@ function invalidateContentIndexesForRoot(root) {
42677
42697
  // ../canopycms/src/schema/meta-loader.ts
42678
42698
  init_zod();
42679
42699
  init_logger();
42700
+
42701
+ // ../canopycms/src/id.ts
42702
+ var import_short_uuid = __toESM(require_dist3(), 1);
42703
+ var isValidId = isValidContentId;
42704
+
42705
+ // ../canopycms/src/content-id-index.ts
42706
+ init_logger();
42707
+ function extractIdFromFilename(filename) {
42708
+ if (filename.startsWith(".")) {
42709
+ return null;
42710
+ }
42711
+ const parts = filename.split(".");
42712
+ if (parts.length >= 3) {
42713
+ const candidate = parts[parts.length - 2];
42714
+ if (isValidId(candidate)) return candidate;
42715
+ }
42716
+ if (parts.length === 2) {
42717
+ const candidate = parts[parts.length - 1];
42718
+ if (isValidId(candidate)) return candidate;
42719
+ }
42720
+ return null;
42721
+ }
42722
+
42723
+ // ../canopycms/src/schema/meta-loader.ts
42680
42724
  var entryTypeMetaSchema = external_exports.object({
42681
42725
  name: external_exports.string().min(1),
42682
42726
  format: external_exports.enum(["md", "mdx", "json", "yaml"]),
@@ -42733,13 +42777,9 @@ async function invalidateBranchContentCaches(root) {
42733
42777
  invalidateContentIndexesForRoot(root);
42734
42778
  }
42735
42779
 
42736
- // ../canopycms/src/worker/cms-worker.ts
42737
- init_types3();
42738
- init_normalize();
42739
-
42740
42780
  // ../canopycms/src/utils/git.ts
42741
- import fs7 from "node:fs/promises";
42742
- import path9 from "node:path";
42781
+ import fs8 from "node:fs/promises";
42782
+ import path11 from "node:path";
42743
42783
  var REJECTED_MARKER = "[rejected]";
42744
42784
  var NON_FAST_FORWARD_REASONS = ["non-fast-forward", "fetch first"];
42745
42785
  var REJECTION_HINT = "Updates were rejected because";
@@ -42752,20 +42792,20 @@ function isStaleLeaseRejection(message) {
42752
42792
  return message.includes(REJECTED_MARKER) && message.includes(STALE_LEASE_REASON);
42753
42793
  }
42754
42794
  async function resolveGitDir(repoPath) {
42755
- const dotGit = path9.join(repoPath, ".git");
42795
+ const dotGit = path11.join(repoPath, ".git");
42756
42796
  let stat;
42757
42797
  try {
42758
- stat = await fs7.stat(dotGit);
42798
+ stat = await fs8.stat(dotGit);
42759
42799
  } catch {
42760
42800
  return null;
42761
42801
  }
42762
42802
  if (stat.isDirectory()) return dotGit;
42763
42803
  try {
42764
- const pointer = await fs7.readFile(dotGit, "utf-8");
42804
+ const pointer = await fs8.readFile(dotGit, "utf-8");
42765
42805
  const match2 = /^gitdir:\s*(.+)$/m.exec(pointer);
42766
42806
  if (!match2) return null;
42767
42807
  const target = match2[1].trim();
42768
- return path9.isAbsolute(target) ? target : path9.resolve(repoPath, target);
42808
+ return path11.isAbsolute(target) ? target : path11.resolve(repoPath, target);
42769
42809
  } catch {
42770
42810
  return null;
42771
42811
  }
@@ -42775,7 +42815,7 @@ async function isRebaseInProgress(repoPath) {
42775
42815
  if (!gitDir) return false;
42776
42816
  const results = await Promise.all(
42777
42817
  ["rebase-merge", "rebase-apply"].map(
42778
- (dir) => fs7.stat(path9.join(gitDir, dir)).then(() => true).catch(() => false)
42818
+ (dir) => fs8.stat(path11.join(gitDir, dir)).then(() => true).catch(() => false)
42779
42819
  )
42780
42820
  );
42781
42821
  return results.some(Boolean);
@@ -42783,8 +42823,8 @@ async function isRebaseInProgress(repoPath) {
42783
42823
 
42784
42824
  // ../canopycms/src/utils/provisioning-lock.ts
42785
42825
  var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
42786
- import fs8 from "node:fs/promises";
42787
- import path10 from "node:path";
42826
+ import fs9 from "node:fs/promises";
42827
+ import path12 from "node:path";
42788
42828
  init_logger();
42789
42829
  function provisioningLockOptions(lockPath, retries, onCompromised) {
42790
42830
  return {
@@ -42836,8 +42876,8 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
42836
42876
  };
42837
42877
  }
42838
42878
  async function tryAcquireProvisioningLock(lockTargetDir2, lockName, onCompromised) {
42839
- await fs8.mkdir(lockTargetDir2, { recursive: true });
42840
- const lockPath = path10.join(lockTargetDir2, lockName);
42879
+ await fs9.mkdir(lockTargetDir2, { recursive: true });
42880
+ const lockPath = path12.join(lockTargetDir2, lockName);
42841
42881
  const release = await import_proper_lockfile2.default.lock(lockPath, provisioningLockOptions(lockPath, 0, onCompromised));
42842
42882
  return releaseIgnoringAlreadyReleased(release, lockPath);
42843
42883
  }
@@ -42864,65 +42904,110 @@ function gitNetworkChildEnv() {
42864
42904
  }
42865
42905
  var GITHUB_TRACKING_REF_PREFIX = "refs/remotes/github/";
42866
42906
 
42867
- // ../canopycms/src/worker/cms-worker.ts
42868
- init_deployment_name();
42869
-
42870
- // ../canopycms/src/utils/content-write-lock.ts
42871
- import path11 from "node:path";
42872
- init_logger();
42873
- var META_DIR2 = ".canopy-meta";
42874
- var CONTENT_WRITE_LOCK_NAME = "content-write.lock";
42875
- function lockTargetDir(branchRoot) {
42876
- return path11.join(path11.resolve(branchRoot), META_DIR2);
42877
- }
42878
- function tryAcquireContentWriteLock(branchRoot, onCompromised) {
42879
- return tryAcquireProvisioningLock(
42880
- lockTargetDir(branchRoot),
42881
- CONTENT_WRITE_LOCK_NAME,
42882
- onCompromised
42883
- );
42907
+ // ../canopycms/src/worker/history-rewrite.ts
42908
+ import path13 from "node:path";
42909
+ async function readPublishedSha(ctx, branchRef) {
42910
+ try {
42911
+ const out = await simpleGit().raw([
42912
+ "--git-dir",
42913
+ ctx.remoteGitPath,
42914
+ "rev-parse",
42915
+ "--verify",
42916
+ `refs/heads/${branchRef}`
42917
+ ]);
42918
+ const sha = out.trim();
42919
+ return sha.length > 0 ? sha : null;
42920
+ } catch {
42921
+ return null;
42922
+ }
42884
42923
  }
42885
-
42886
- // ../canopycms/src/worker/worker-status.ts
42887
- import path12 from "node:path";
42888
- var WORKER_STATUS_FILE = "worker-status.json";
42889
- async function writeWorkerStatus(taskDir, report) {
42890
- const filePath = path12.join(taskDir, WORKER_STATUS_FILE);
42891
- const stamped = { ...report, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
42892
- await atomicWriteFile(filePath, JSON.stringify(stamped, null, 2));
42924
+ async function markHistoryRewritten(ctx, branchPath, branchDir, expectedSha) {
42925
+ const current = await BranchMetadataFileManager.loadOnly(branchPath);
42926
+ if (typeof current?.branch.historyRewrittenFrom === "string") return;
42927
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
42928
+ await meta.save({ branch: { name: branchDir, historyRewrittenFrom: expectedSha } });
42893
42929
  }
42894
-
42895
- // ../canopycms/src/worker/log.ts
42896
- init_logger();
42897
- function timestamp() {
42898
- return (/* @__PURE__ */ new Date()).toISOString();
42930
+ async function clearHistoryRewrittenMarker(ctx, branchPath, branchDir) {
42931
+ try {
42932
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
42933
+ await meta.save({ branch: { name: branchDir, historyRewrittenFrom: void 0 } });
42934
+ } catch (err) {
42935
+ workerLogWarn(
42936
+ ` Failed to clear history-rewrite marker for ${branchDir}: ${getErrorMessage2(err)}`
42937
+ );
42938
+ }
42899
42939
  }
42900
- function workerLog(...args) {
42901
- console.log(timestamp(), "INFO", ...args);
42940
+ async function forcePublishToLocalRemote(ctx, branchPath, branchRef, expectedSha) {
42941
+ const pushGit = simpleGit({ baseDir: branchPath, timeout: { block: ctx.taskTimeoutMs } });
42942
+ pushGit.env(gitChildEnv({}));
42943
+ try {
42944
+ await pushGit.raw([
42945
+ "push",
42946
+ `--force-with-lease=${branchRef}:${expectedSha}`,
42947
+ // Real flags must precede --end-of-options; everything after it is
42948
+ // positional (see GitManager.push() for the same guard).
42949
+ "--end-of-options",
42950
+ "origin",
42951
+ `${branchRef}:${branchRef}`
42952
+ ]);
42953
+ workerLog(` Published rebased ${branchRef} into remote.git`);
42954
+ return true;
42955
+ } catch (err) {
42956
+ const message = redactCredentials(getErrorMessage2(err));
42957
+ workerLogWarn(
42958
+ isStaleLeaseRejection(message) ? ` Did not publish rebased ${branchRef} into remote.git: it moved since this cycle read it (concurrent submit?) -- retrying next cycle` : ` Failed to publish rebased ${branchRef} into remote.git: ${message}`
42959
+ );
42960
+ return false;
42961
+ }
42902
42962
  }
42903
- function workerLogWarn(...args) {
42904
- console.warn(timestamp(), "WARN", ...args);
42963
+ async function enqueueGitHubPush(ctx, branchRef) {
42964
+ try {
42965
+ for (const status of ["pending", "processing"]) {
42966
+ const inFlight = await listTasks(ctx.taskDir, status);
42967
+ if (inFlight.some((t6) => t6.action === "push-branch" && t6.payload.branch === branchRef)) {
42968
+ return;
42969
+ }
42970
+ }
42971
+ await enqueueTask(ctx.taskDir, { action: "push-branch", payload: { branch: branchRef } });
42972
+ workerLog(` Queued GitHub push for ${branchRef}`);
42973
+ } catch (err) {
42974
+ workerLogWarn(` Failed to queue GitHub push for ${branchRef}: ${getErrorMessage2(err)}`);
42975
+ }
42905
42976
  }
42906
- function workerLogError(...args) {
42907
- console.error(timestamp(), "ERROR", ...args);
42977
+ async function reconcilePendingRewrite(ctx, options) {
42978
+ const { branchPath, branchDir, branchRef, headSha, marker } = options;
42979
+ const publishedSha = await readPublishedSha(ctx, branchRef);
42980
+ if (publishedSha === null) {
42981
+ workerLogWarn(
42982
+ ` ${branchDir}: a rewritten history is recorded but ${branchRef} is gone from remote.git -- nothing to publish`
42983
+ );
42984
+ return;
42985
+ }
42986
+ if (publishedSha === headSha) {
42987
+ await enqueueGitHubPush(ctx, branchRef);
42988
+ return;
42989
+ }
42990
+ if (publishedSha === marker) {
42991
+ if (await forcePublishToLocalRemote(ctx, branchPath, branchRef, marker)) {
42992
+ await enqueueGitHubPush(ctx, branchRef);
42993
+ }
42994
+ return;
42995
+ }
42996
+ workerLogWarn(
42997
+ ` ${branchDir}: remote.git is at ${publishedSha} for ${branchRef}, which is neither the rebased tip nor the commit the rewrite replaced -- left untouched`
42998
+ );
42908
42999
  }
42909
- function installWorkerLogger() {
42910
- setCanopyLogger({ log: workerLog, warn: workerLogWarn, error: workerLogError });
43000
+ async function hasPendingHistoryRewrite(ctx, branchName) {
43001
+ try {
43002
+ const branchPath = path13.join(ctx.contentBranchesPath, sanitizeBranchName(branchName));
43003
+ const metaFile = await BranchMetadataFileManager.loadOnly(branchPath);
43004
+ return typeof metaFile?.branch.historyRewrittenFrom === "string";
43005
+ } catch {
43006
+ return false;
43007
+ }
42911
43008
  }
42912
43009
 
42913
- // ../canopycms/src/worker/cms-worker.ts
42914
- var DEFAULT_TASK_TIMEOUT = 6e4;
42915
- var DEFAULT_MAX_RETRIES2 = 3;
42916
- var DEFAULT_LOCK_STALE_MS = 6e4;
42917
- var TRASH_RETENTION_MS = 30 * 24 * 60 * 6e4;
42918
- var TRASH_DIR_STAMP_RE = /-(\d{8}T\d{6}Z)$/;
42919
- function parseTrashStamp(stamp) {
42920
- const match2 = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(stamp);
42921
- if (!match2) return null;
42922
- const [, year2, month, day, hour, minute, second] = match2;
42923
- const date2 = /* @__PURE__ */ new Date(`${year2}-${month}-${day}T${hour}:${minute}:${second}.000Z`);
42924
- return Number.isNaN(date2.getTime()) ? null : date2;
42925
- }
43010
+ // ../canopycms/src/worker/task-runner.ts
42926
43011
  var PermanentTaskError = class extends Error {
42927
43012
  };
42928
43013
  function isPermanentTaskFailure(err) {
@@ -42973,6 +43058,1019 @@ function optionalString(payload2, key, fallback) {
42973
43058
  const val = payload2[key];
42974
43059
  return typeof val === "string" ? val : fallback;
42975
43060
  }
43061
+ function orphanRecoveryMaxAgeMs(ctx) {
43062
+ return Math.max(5 * 6e4, ctx.taskTimeoutMs * 2);
43063
+ }
43064
+ async function processTaskQueue(ctx) {
43065
+ if (!ctx.isRunning()) return;
43066
+ const recovered = await recoverOrphanedTasks(ctx.taskDir, orphanRecoveryMaxAgeMs(ctx), ctx.log);
43067
+ if (recovered > 0) {
43068
+ workerLog(`Recovered ${recovered} orphaned task(s)`);
43069
+ }
43070
+ let processed = 0;
43071
+ let task;
43072
+ while (processed < ctx.maxTasksPerCycle && (task = await dequeueTask(ctx.taskDir, ctx.log)) !== null) {
43073
+ try {
43074
+ const result = await executeTaskWithTimeout(ctx, task);
43075
+ await completeTask(ctx.taskDir, task.id, result, ctx.log);
43076
+ await updateBranchMetadata(ctx, task, result);
43077
+ } catch (err) {
43078
+ const message = getErrorMessage2(err);
43079
+ workerLogError(`Task ${task.id} (${task.action}) failed:`, message);
43080
+ const persistedMessage = redactCredentials(message);
43081
+ const permanent = isPermanentTaskFailure(err);
43082
+ const retryCount = task.retryCount ?? 0;
43083
+ const maxRetries = task.maxRetries ?? ctx.maxRetries;
43084
+ if (!permanent && retryCount < maxRetries) {
43085
+ await retryTask(ctx.taskDir, task.id, persistedMessage, ctx.log);
43086
+ workerLog(` Will retry (attempt ${retryCount + 1}/${maxRetries})`);
43087
+ } else {
43088
+ await failTask(ctx.taskDir, task.id, persistedMessage, ctx.log);
43089
+ await updateBranchMetadataOnFailure(ctx, task, persistedMessage);
43090
+ workerLogError(
43091
+ permanent ? " Permanently failed (non-retryable error)" : ` Permanently failed after ${maxRetries} retries`
43092
+ );
43093
+ }
43094
+ }
43095
+ processed++;
43096
+ }
43097
+ if (processed > 0) {
43098
+ const report = ctx.ensureStatusReport();
43099
+ report.lastTaskCycleAt = (/* @__PURE__ */ new Date()).toISOString();
43100
+ await writeWorkerStatus(ctx.taskDir, report).catch(
43101
+ (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
43102
+ );
43103
+ }
43104
+ }
43105
+ async function executeTaskWithTimeout(ctx, task) {
43106
+ const controller = new AbortController();
43107
+ const timer = setTimeout(() => controller.abort(), ctx.taskTimeoutMs);
43108
+ try {
43109
+ const work = ctx.executeTask(task, controller.signal);
43110
+ work.catch(() => {
43111
+ });
43112
+ const timedOut = new Promise((_3, reject) => {
43113
+ controller.signal.addEventListener(
43114
+ "abort",
43115
+ () => reject(new Error(`Task timed out after ${ctx.taskTimeoutMs}ms`)),
43116
+ { once: true }
43117
+ );
43118
+ });
43119
+ return await Promise.race([work, timedOut]);
43120
+ } finally {
43121
+ clearTimeout(timer);
43122
+ }
43123
+ }
43124
+ async function executeTask(ctx, task, signal) {
43125
+ const { action, payload: payload2 } = task;
43126
+ switch (action) {
43127
+ case "push-branch": {
43128
+ const branch = requireString(payload2, "branch");
43129
+ await ctx.pushBranchToGitHub(branch);
43130
+ return { pushed: true };
43131
+ }
43132
+ case "push-and-create-pr": {
43133
+ const branch = requireString(payload2, "branch");
43134
+ await ctx.pushBranchToGitHub(branch);
43135
+ const pr = await ctx.octokit().pulls.create({
43136
+ owner: ctx.githubOwner,
43137
+ repo: ctx.githubRepo,
43138
+ head: branch,
43139
+ base: optionalString(payload2, "baseBranch", ctx.baseBranch),
43140
+ title: optionalString(payload2, "title", `Submit ${branch}`),
43141
+ body: optionalString(payload2, "body", ""),
43142
+ request: { signal }
43143
+ });
43144
+ workerLog(`Created PR #${pr.data.number} for ${branch}`);
43145
+ return { prUrl: pr.data.html_url, prNumber: pr.data.number };
43146
+ }
43147
+ case "push-and-update-pr": {
43148
+ const branch = requireString(payload2, "branch");
43149
+ const prNumber = requireNumber(payload2, "pullRequestNumber");
43150
+ await ctx.pushBranchToGitHub(branch);
43151
+ await ctx.octokit().pulls.update({
43152
+ owner: ctx.githubOwner,
43153
+ repo: ctx.githubRepo,
43154
+ pull_number: prNumber,
43155
+ title: optionalString(payload2, "title", `Submit ${branch}`),
43156
+ body: optionalString(payload2, "body", ""),
43157
+ request: { signal }
43158
+ });
43159
+ workerLog(`Updated PR #${prNumber} for ${branch}`);
43160
+ return { prNumber };
43161
+ }
43162
+ case "push-and-create-or-update-pr": {
43163
+ const branch = requireString(payload2, "branch");
43164
+ const base = optionalString(payload2, "baseBranch", ctx.baseBranch);
43165
+ if (sanitizeBranchName(branch) === ctx.sanitizedBaseBranch) {
43166
+ throw new PermanentTaskError(
43167
+ `Refusing to push-and-create-or-update-pr for "${branch}": it is the base branch -- submitting the base branch is never valid`
43168
+ );
43169
+ }
43170
+ await ctx.pushBranchToGitHub(branch);
43171
+ const result = await createOrUpdatePullRequest({
43172
+ octokit: ctx.octokit(),
43173
+ owner: ctx.githubOwner,
43174
+ repo: ctx.githubRepo,
43175
+ head: branch,
43176
+ base,
43177
+ title: optionalString(payload2, "title", `Submit ${branch}`),
43178
+ body: optionalString(payload2, "body", ""),
43179
+ // Content submits (api/github-sync.ts) set this; settings-branch
43180
+ // syncs (services.ts) deliberately don't.
43181
+ markReadyIfDraft: payload2.markReadyIfDraft === true,
43182
+ signal
43183
+ });
43184
+ workerLog(
43185
+ result.created ? `Created PR #${result.number} for ${branch}` : `Updated existing PR #${result.number} for ${branch}`
43186
+ );
43187
+ return { prUrl: result.url, prNumber: result.number };
43188
+ }
43189
+ case "convert-to-draft": {
43190
+ const draftPrNumber = requireNumber(payload2, "pullRequestNumber");
43191
+ const { data: pr } = await ctx.octokit().pulls.get({
43192
+ owner: ctx.githubOwner,
43193
+ repo: ctx.githubRepo,
43194
+ pull_number: draftPrNumber,
43195
+ request: { signal }
43196
+ });
43197
+ await ctx.octokit().graphql(
43198
+ `mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`,
43199
+ { id: pr.node_id, request: { signal } }
43200
+ );
43201
+ workerLog(`Converted PR #${draftPrNumber} to draft`);
43202
+ return { prNumber: draftPrNumber, draft: true };
43203
+ }
43204
+ case "close-pr": {
43205
+ const closePrNumber = requireNumber(payload2, "pullRequestNumber");
43206
+ await ctx.octokit().pulls.update({
43207
+ owner: ctx.githubOwner,
43208
+ repo: ctx.githubRepo,
43209
+ pull_number: closePrNumber,
43210
+ state: "closed",
43211
+ request: { signal }
43212
+ });
43213
+ return { closed: true };
43214
+ }
43215
+ case "delete-remote-branch": {
43216
+ const branch = requireString(payload2, "branch");
43217
+ await ctx.octokit().git.deleteRef({
43218
+ owner: ctx.githubOwner,
43219
+ repo: ctx.githubRepo,
43220
+ ref: `heads/${branch}`,
43221
+ request: { signal }
43222
+ });
43223
+ return { deleted: true };
43224
+ }
43225
+ default:
43226
+ throw new PermanentTaskError(`Unknown task action: ${action}`);
43227
+ }
43228
+ }
43229
+ async function updateBranchMetadata(ctx, task, result) {
43230
+ const branch = typeof task.payload.branch === "string" ? task.payload.branch : null;
43231
+ if (!branch) return;
43232
+ const branchPath = ctx.branchWorkspacePath(branch);
43233
+ try {
43234
+ await fs10.stat(branchPath);
43235
+ } catch {
43236
+ return;
43237
+ }
43238
+ try {
43239
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43240
+ const updates = {
43241
+ name: branch,
43242
+ syncStatus: "synced",
43243
+ // save()'s merge only overwrites keys present in this update (see
43244
+ // BranchMetadataFileManager.save()'s spread order) -- a prior
43245
+ // syncFailureReason from an earlier failed attempt would otherwise
43246
+ // survive forever past this successful sync. Explicitly clear it,
43247
+ // same pattern as rebaseFailure's PR-W2 M2 clearing elsewhere.
43248
+ syncFailureReason: void 0
43249
+ };
43250
+ if (result.prUrl) updates.pullRequestUrl = result.prUrl;
43251
+ if (result.prNumber) {
43252
+ updates.pullRequestNumber = result.prNumber;
43253
+ const current = await BranchMetadataFileManager.loadOnly(branchPath);
43254
+ const terminal = current?.branch.status === "archived" || current?.branch.pullRequestState === "merged" || current?.branch.pullRequestState === "closed";
43255
+ if (!terminal) {
43256
+ updates.pullRequestState = "open";
43257
+ }
43258
+ }
43259
+ await meta.save({ branch: updates });
43260
+ } catch (err) {
43261
+ workerLogError(
43262
+ `Failed to update metadata for ${branch}:`,
43263
+ err instanceof Error ? err.message : err
43264
+ );
43265
+ }
43266
+ }
43267
+ async function updateBranchMetadataOnFailure(ctx, task, error2) {
43268
+ const branch = typeof task.payload.branch === "string" ? task.payload.branch : null;
43269
+ if (!branch) return;
43270
+ const branchPath = ctx.branchWorkspacePath(branch);
43271
+ try {
43272
+ await fs10.stat(branchPath);
43273
+ } catch {
43274
+ return;
43275
+ }
43276
+ try {
43277
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43278
+ await meta.save({
43279
+ branch: { name: branch, syncStatus: "sync-failed", syncFailureReason: error2 }
43280
+ });
43281
+ } catch (err) {
43282
+ workerLogError(
43283
+ `Failed to update failure metadata for ${branch}:`,
43284
+ err instanceof Error ? err.message : err
43285
+ );
43286
+ }
43287
+ }
43288
+ async function pushBranchToGitHub(ctx, branch) {
43289
+ const git = simpleGit({
43290
+ baseDir: ctx.remoteGitPath,
43291
+ // DEP-H1: kill the git process if it produces no output for
43292
+ // taskTimeoutMs (network stall, credential prompt) instead of letting
43293
+ // it hang past the task timeout.
43294
+ timeout: { block: ctx.taskTimeoutMs }
43295
+ });
43296
+ git.env(gitNetworkChildEnv());
43297
+ const branchPath = ctx.branchWorkspacePath(branch);
43298
+ const metaFile = await BranchMetadataFileManager.loadOnly(branchPath).catch(() => null);
43299
+ const marker = metaFile?.branch.historyRewrittenFrom;
43300
+ const outgoingSha = await readPublishedSha(ctx, branch);
43301
+ try {
43302
+ if (marker) {
43303
+ await git.raw([
43304
+ "push",
43305
+ `--force-with-lease=${branch}:${marker}`,
43306
+ "--end-of-options",
43307
+ ctx.buildGitHubUrl(),
43308
+ `${branch}:${branch}`
43309
+ ]);
43310
+ } else {
43311
+ await git.push(ctx.buildGitHubUrl(), branch);
43312
+ }
43313
+ } catch (err) {
43314
+ const message = getErrorMessage2(err);
43315
+ if (marker && isStaleLeaseRejection(message)) {
43316
+ try {
43317
+ await git.push(ctx.buildGitHubUrl(), branch);
43318
+ } catch (retryErr) {
43319
+ const retryMessage = getErrorMessage2(retryErr);
43320
+ if (isNonFastForwardRejection(retryMessage)) {
43321
+ throw new PermanentTaskError(
43322
+ `Push rejected for branch "${branch}": GitHub's tip is neither the commit this deployment last published nor an ancestor of what it is pushing, so the branch has genuinely diverged and nothing was overwritten. Something else moved it on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`
43323
+ );
43324
+ }
43325
+ throw retryErr;
43326
+ }
43327
+ await clearHistoryRewrittenMarker(ctx, branchPath, branch);
43328
+ workerLog(`Pushed ${branch} to GitHub (GitHub had already moved past the rewritten commit)`);
43329
+ return;
43330
+ }
43331
+ if (isNonFastForwardRejection(message)) {
43332
+ throw new PermanentTaskError(
43333
+ `Push rejected for branch "${branch}": GitHub's tip is not what this deployment last published, so the branch has diverged and needs reconciling. Something else moved it on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`
43334
+ );
43335
+ }
43336
+ throw err;
43337
+ }
43338
+ if (marker && outgoingSha && outgoingSha !== marker) {
43339
+ await clearHistoryRewrittenMarker(ctx, branchPath, branch);
43340
+ }
43341
+ workerLog(`Pushed ${branch} to GitHub`);
43342
+ }
43343
+
43344
+ // ../canopycms/src/worker/rebase.ts
43345
+ import fs11 from "node:fs/promises";
43346
+ import path15 from "node:path";
43347
+ init_normalize();
43348
+ init_types3();
43349
+
43350
+ // ../canopycms/src/utils/content-write-lock.ts
43351
+ import path14 from "node:path";
43352
+ init_logger();
43353
+ var META_DIR2 = ".canopy-meta";
43354
+ var CONTENT_WRITE_LOCK_NAME = "content-write.lock";
43355
+ function lockTargetDir(branchRoot) {
43356
+ return path14.join(path14.resolve(branchRoot), META_DIR2);
43357
+ }
43358
+ function tryAcquireContentWriteLock(branchRoot, onCompromised) {
43359
+ return tryAcquireProvisioningLock(
43360
+ lockTargetDir(branchRoot),
43361
+ CONTENT_WRITE_LOCK_NAME,
43362
+ onCompromised
43363
+ );
43364
+ }
43365
+
43366
+ // ../canopycms/src/worker/rebase.ts
43367
+ async function pollMergeState(ctx, branchDir, branchPath, metaFile) {
43368
+ const prNumber = metaFile?.branch.pullRequestNumber;
43369
+ if (!prNumber) return;
43370
+ const controller = new AbortController();
43371
+ const timer = setTimeout(() => controller.abort(), ctx.taskTimeoutMs);
43372
+ try {
43373
+ const { data: data2 } = await ctx.octokit().pulls.get({
43374
+ owner: ctx.githubOwner,
43375
+ repo: ctx.githubRepo,
43376
+ pull_number: prNumber,
43377
+ request: { signal: controller.signal }
43378
+ });
43379
+ if (data2.merged) {
43380
+ const meta2 = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43381
+ await meta2.save({
43382
+ branch: buildMergedBranchUpdate(
43383
+ branchDir,
43384
+ data2.merged_at ? new Date(data2.merged_at) : void 0
43385
+ )
43386
+ });
43387
+ workerLog(` PR #${prNumber} for ${branchDir} is merged -> archived`);
43388
+ return;
43389
+ }
43390
+ const newState = data2.state === "closed" ? "closed" : "open";
43391
+ const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
43392
+ if (currentMeta?.branch.pullRequestState === newState) return;
43393
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43394
+ await meta.save({ branch: { name: branchDir, pullRequestState: newState } });
43395
+ workerLog(` PR #${prNumber} for ${branchDir}: pullRequestState -> ${newState}`);
43396
+ } catch (err) {
43397
+ workerLogWarn(` Failed to poll PR #${prNumber} for ${branchDir}: ${getErrorMessage2(err)}`);
43398
+ } finally {
43399
+ clearTimeout(timer);
43400
+ }
43401
+ }
43402
+ async function recordRebaseFailure(ctx, branchPath, branchDir, message) {
43403
+ const RECORD_REFRESH_MS = 60 * 60 * 1e3;
43404
+ const redactedMessage = redactCredentials(message);
43405
+ try {
43406
+ const existing = await BranchMetadataFileManager.loadOnly(branchPath);
43407
+ const prior = existing?.branch.rebaseFailure;
43408
+ const sameMessage = prior?.message === redactedMessage;
43409
+ const now = /* @__PURE__ */ new Date();
43410
+ if (sameMessage) {
43411
+ const lastAtMs = Date.parse(prior.lastAt);
43412
+ if (!Number.isNaN(lastAtMs) && now.getTime() - lastAtMs < RECORD_REFRESH_MS) {
43413
+ return;
43414
+ }
43415
+ }
43416
+ const nowIso = now.toISOString();
43417
+ const firstAt = sameMessage ? prior.firstAt : nowIso;
43418
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43419
+ await meta.save({
43420
+ branch: {
43421
+ name: branchDir,
43422
+ rebaseFailure: { message: redactedMessage, firstAt, lastAt: nowIso }
43423
+ }
43424
+ });
43425
+ } catch (err) {
43426
+ workerLogWarn(` Failed to record rebase failure for ${branchDir}: ${getErrorMessage2(err)}`);
43427
+ }
43428
+ }
43429
+ var MAX_REBASE_ROUNDS = 50;
43430
+ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLockCompromised) {
43431
+ const conflictedFiles = [];
43432
+ let nextAction = "start";
43433
+ let completed = false;
43434
+ let failureReason;
43435
+ for (let round = 0; round < MAX_REBASE_ROUNDS && !completed; round++) {
43436
+ if (isLockCompromised()) break;
43437
+ try {
43438
+ if (nextAction === "start") {
43439
+ await branchGit.rebase([fetchedBaseTip]);
43440
+ } else if (nextAction === "continue") {
43441
+ await branchGit.rebase(["--continue"]);
43442
+ } else {
43443
+ await branchGit.rebase(["--skip"]);
43444
+ }
43445
+ completed = true;
43446
+ } catch (rebaseErr) {
43447
+ nextAction = "continue";
43448
+ const st = await branchGit.status();
43449
+ if (st.conflicted.length > 0) {
43450
+ await ctx.afterConflictDetectedForTesting();
43451
+ const conflictKind = new Map(st.files.map((f5) => [f5.path, `${f5.index}${f5.working_dir}`]));
43452
+ let resolutionFailure;
43453
+ for (const file of st.conflicted) {
43454
+ const kind = conflictKind.get(file);
43455
+ try {
43456
+ if (kind === "UD") {
43457
+ await branchGit.raw(["rm", "-f", "--", file]);
43458
+ } else if (kind === "DU") {
43459
+ await branchGit.add(file);
43460
+ } else {
43461
+ await branchGit.raw(["checkout", "--theirs", file]);
43462
+ await branchGit.add(file);
43463
+ }
43464
+ } catch (resolveErr) {
43465
+ resolutionFailure = `failed to resolve conflicted file '${file}' (status ${kind ?? "??"}): ` + getErrorMessage2(resolveErr);
43466
+ break;
43467
+ }
43468
+ conflictedFiles.push(file);
43469
+ }
43470
+ if (resolutionFailure !== void 0) {
43471
+ failureReason = resolutionFailure;
43472
+ break;
43473
+ }
43474
+ } else {
43475
+ const msg = rebaseErr instanceof Error ? rebaseErr.message : "";
43476
+ if (msg.toLowerCase().includes("nothing to commit") || msg.toLowerCase().includes("apply --skip")) {
43477
+ nextAction = "skip";
43478
+ } else {
43479
+ workerLogWarn(` Unexpected rebase error in ${branchDir}: ${msg || "Unknown error"}`);
43480
+ failureReason = msg || "Unknown error";
43481
+ await branchGit.rebase(["--abort"]).catch(() => {
43482
+ });
43483
+ break;
43484
+ }
43485
+ }
43486
+ }
43487
+ }
43488
+ return { completed, conflictedFiles, failureReason };
43489
+ }
43490
+ function conflictFilesToContentIds(conflictedFiles, contentRoot) {
43491
+ const normalizedContentRoot = normalizeFilesystemPath(contentRoot);
43492
+ const conflictIds = [...new Set(conflictedFiles)].map((f5) => {
43493
+ const fileId = extractIdFromFilename(path15.basename(f5));
43494
+ if (fileId) return fileId;
43495
+ const parentDir = path15.basename(path15.dirname(f5));
43496
+ const dirId = extractIdFromFilename(parentDir);
43497
+ if (dirId) return dirId;
43498
+ const parentPath = normalizeFilesystemPath(path15.dirname(f5));
43499
+ if (path15.basename(f5) === ".collection.json" && parentPath === normalizedContentRoot) {
43500
+ return ROOT_COLLECTION_ID;
43501
+ }
43502
+ return null;
43503
+ }).filter((id) => id !== null);
43504
+ return [...new Set(conflictIds)];
43505
+ }
43506
+ async function carryForwardRewrittenHistory(ctx, args) {
43507
+ const { branchPath, branchDir, branchRef, publishedSha, preRebaseHead } = args;
43508
+ if (publishedSha !== null) {
43509
+ if (publishedSha === preRebaseHead) {
43510
+ await markHistoryRewritten(ctx, branchPath, branchDir, publishedSha);
43511
+ if (await forcePublishToLocalRemote(ctx, branchPath, branchRef, publishedSha)) {
43512
+ await enqueueGitHubPush(ctx, branchRef);
43513
+ }
43514
+ } else {
43515
+ const divergence = `rebased locally, but remote.git holds ${publishedSha} for ${branchRef}, which this clone never had (a direct push to the branch?). Left untouched -- reconcile it before submitting again.`;
43516
+ workerLogWarn(` ${branchDir}: ${divergence}`);
43517
+ await recordRebaseFailure(ctx, branchPath, branchDir, divergence);
43518
+ }
43519
+ }
43520
+ }
43521
+ async function runRebaseCycle(ctx) {
43522
+ const rebased = [];
43523
+ const skippedDirty = [];
43524
+ const skippedLocked = [];
43525
+ const failed = [];
43526
+ let branchDirs;
43527
+ try {
43528
+ branchDirs = await fs11.readdir(ctx.contentBranchesPath);
43529
+ } catch {
43530
+ return { rebased, skippedDirty, skippedLocked, failed };
43531
+ }
43532
+ for (const branchDir of branchDirs) {
43533
+ if (branchDir.startsWith(".") || branchDir === "branches.json") {
43534
+ continue;
43535
+ }
43536
+ const branchPath = path15.join(ctx.contentBranchesPath, branchDir);
43537
+ const gitDir = path15.join(branchPath, ".git");
43538
+ try {
43539
+ const stat = await fs11.stat(gitDir);
43540
+ if (!stat.isDirectory()) {
43541
+ workerLog(` Skipping ${branchDir}: .git is not a directory`);
43542
+ continue;
43543
+ }
43544
+ } catch {
43545
+ workerLog(` Skipping ${branchDir}: no .git directory (not a branch workspace)`);
43546
+ continue;
43547
+ }
43548
+ if (branchDir === ctx.sanitizedBaseBranch) {
43549
+ workerLog(` Skipping ${branchDir}: base branch (refreshed separately)`);
43550
+ continue;
43551
+ }
43552
+ const outcome = await rebaseOneBranch(ctx, branchDir, branchPath);
43553
+ if (outcome.kind === "rebased") rebased.push(branchDir);
43554
+ else if (outcome.kind === "skippedDirty") skippedDirty.push(branchDir);
43555
+ else if (outcome.kind === "skippedLocked") skippedLocked.push(branchDir);
43556
+ else if (outcome.kind === "failed") {
43557
+ if (outcome.rebased) rebased.push(branchDir);
43558
+ failed.push({ branch: branchDir, error: outcome.error });
43559
+ }
43560
+ }
43561
+ return { rebased, skippedDirty, skippedLocked, failed };
43562
+ }
43563
+ async function rebaseOneBranch(ctx, branchDir, branchPath) {
43564
+ let didRebase = false;
43565
+ try {
43566
+ const metaFile = await BranchMetadataFileManager.loadOnly(branchPath);
43567
+ const branchStatus2 = metaFile?.branch.status;
43568
+ if (branchStatus2 === "submitted" || branchStatus2 === "approved") {
43569
+ workerLog(` Skipping ${branchDir} (${branchStatus2})`);
43570
+ await pollMergeState(ctx, branchDir, branchPath, metaFile);
43571
+ return { kind: "none" };
43572
+ }
43573
+ if (branchStatus2 === "archived") {
43574
+ workerLog(` Skipping ${branchDir} (${branchStatus2})`);
43575
+ return { kind: "none" };
43576
+ }
43577
+ const branchGit = simpleGit({
43578
+ baseDir: branchPath,
43579
+ // Keep git non-interactive during rebase/merge so it never blocks on an editor.
43580
+ // simple-git >=3.32 blocks setting core.editor unless explicitly opted in; the
43581
+ // value here is a hardcoded literal ("true", the shell no-op), not user input,
43582
+ // so enabling allowUnsafeEditor carries no injection risk.
43583
+ config: ["core.editor=true"],
43584
+ unsafe: { allowUnsafeEditor: true }
43585
+ });
43586
+ let releaseContentLock;
43587
+ let contentLockCompromised = false;
43588
+ try {
43589
+ releaseContentLock = await tryAcquireContentWriteLock(branchPath, (lockErr) => {
43590
+ contentLockCompromised = true;
43591
+ workerLogWarn(
43592
+ ` Content-write lock compromised mid-rebase for ${branchDir}: ${getErrorMessage2(lockErr)}`
43593
+ );
43594
+ });
43595
+ } catch (lockErr) {
43596
+ if (isNodeError(lockErr) && lockErr.code === "ELOCKED") {
43597
+ workerLog(` Skipping ${branchDir}: content write in progress (retrying next cycle)`);
43598
+ return { kind: "skippedLocked" };
43599
+ }
43600
+ throw lockErr;
43601
+ }
43602
+ try {
43603
+ if (await isRebaseInProgress(branchPath)) {
43604
+ const preAbort = await branchGit.status().catch(() => null);
43605
+ const collateral = (preAbort?.files ?? []).filter((f5) => !preAbort?.conflicted.includes(f5.path)).filter((f5) => f5.working_dir !== " " && f5.working_dir !== "?").map((f5) => f5.path);
43606
+ if (collateral.length > 0) {
43607
+ workerLogWarn(
43608
+ ` ${branchDir}: aborting the interrupted rebase will DISCARD working-tree changes to ${collateral.length} file(s) saved while the worker was down: ${collateral.join(", ")}`
43609
+ );
43610
+ }
43611
+ workerLogWarn(
43612
+ ` ${branchDir}: found an interrupted rebase (this worker's own abandoned work) -- aborting it to recover the branch`
43613
+ );
43614
+ try {
43615
+ await branchGit.rebase(["--abort"]);
43616
+ } catch (abortErr) {
43617
+ const reason = redactCredentials(
43618
+ `could not abort interrupted rebase: ${getErrorMessage2(abortErr)}`
43619
+ );
43620
+ workerLogWarn(` Skipping ${branchDir}: ${reason}`);
43621
+ await recordRebaseFailure(ctx, branchPath, branchDir, reason);
43622
+ return { kind: "failed", error: reason };
43623
+ }
43624
+ }
43625
+ const dirtyCheck = await branchGit.status();
43626
+ if (dirtyCheck.files.length > 0) {
43627
+ workerLog(` Skipping ${branchDir}: has uncommitted changes`);
43628
+ return { kind: "skippedDirty" };
43629
+ }
43630
+ const branchRef = (await branchGit.revparse(["--abbrev-ref", "HEAD"])).trim();
43631
+ const canPublish = branchRef.length > 0 && branchRef !== "HEAD";
43632
+ if (canPublish && metaFile?.branch.historyRewrittenFrom) {
43633
+ await reconcilePendingRewrite(ctx, {
43634
+ branchPath,
43635
+ branchDir,
43636
+ branchRef,
43637
+ headSha: (await branchGit.revparse(["HEAD"])).trim(),
43638
+ marker: metaFile.branch.historyRewrittenFrom
43639
+ });
43640
+ }
43641
+ await branchGit.fetch("origin", ctx.baseBranch);
43642
+ const fetchedBaseTip = (await branchGit.revparse(["FETCH_HEAD"])).trim();
43643
+ const behindCount = parseInt(
43644
+ (await branchGit.raw(["rev-list", "--count", `HEAD..${fetchedBaseTip}`])).trim(),
43645
+ 10
43646
+ );
43647
+ const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43648
+ if (behindCount === 0) {
43649
+ const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
43650
+ const conflictStatus = currentMeta?.branch.conflictStatus;
43651
+ const conflictFiles = currentMeta?.branch.conflictFiles;
43652
+ const conflictAlreadyClean = (conflictStatus === void 0 || conflictStatus === "clean") && (conflictFiles === void 0 || conflictFiles.length === 0);
43653
+ const alreadyClean = conflictAlreadyClean && currentMeta?.branch.rebaseFailure === void 0;
43654
+ if (alreadyClean) {
43655
+ return { kind: "none" };
43656
+ }
43657
+ await meta.save({
43658
+ branch: {
43659
+ name: branchDir,
43660
+ conflictStatus: "clean",
43661
+ conflictFiles: [],
43662
+ rebaseFailure: void 0
43663
+ }
43664
+ });
43665
+ return { kind: "none" };
43666
+ }
43667
+ workerLog(`Rebasing ${branchDir} (${behindCount} commits behind)...`);
43668
+ const preRebaseHead = (await branchGit.revparse(["HEAD"])).trim();
43669
+ const publishedSha = canPublish ? await readPublishedSha(ctx, branchRef) : null;
43670
+ const { completed, conflictedFiles, failureReason } = await runRebaseRounds(
43671
+ ctx,
43672
+ branchGit,
43673
+ branchDir,
43674
+ fetchedBaseTip,
43675
+ () => contentLockCompromised
43676
+ );
43677
+ if (completed) await ctx.afterRebaseCompletedForTesting();
43678
+ if (contentLockCompromised && !completed) {
43679
+ workerLogWarn(
43680
+ ` Skipping ${branchDir}: content-write lock was compromised mid-rebase (retrying next cycle)`
43681
+ );
43682
+ await branchGit.rebase(["--abort"]).catch(() => {
43683
+ });
43684
+ return { kind: "skippedLocked" };
43685
+ }
43686
+ if (contentLockCompromised) {
43687
+ workerLogWarn(
43688
+ ` Content-write lock for ${branchDir} was compromised, but its rebase had already completed -- finishing the sync (history is rewritten; skipping now would strand the history-rewrite marker and wedge the branch)`
43689
+ );
43690
+ }
43691
+ if (!completed) {
43692
+ workerLogWarn(
43693
+ failureReason !== void 0 ? ` Rebase of ${branchDir} aborted due to unexpected error: ${failureReason}` : ` Rebase of ${branchDir} did not complete within ${MAX_REBASE_ROUNDS} rounds, aborting`
43694
+ );
43695
+ await branchGit.rebase(["--abort"]).catch(() => {
43696
+ });
43697
+ const rebaseFailureMessage = failureReason ?? `did not complete within ${MAX_REBASE_ROUNDS} rounds`;
43698
+ const redactedRebaseFailureMessage = redactCredentials(rebaseFailureMessage);
43699
+ await recordRebaseFailure(ctx, branchPath, branchDir, redactedRebaseFailureMessage);
43700
+ return { kind: "failed", error: redactedRebaseFailureMessage };
43701
+ }
43702
+ await invalidateBranchContentCaches(branchPath);
43703
+ const conflictIdsDeduped = conflictFilesToContentIds(conflictedFiles, ctx.contentRoot);
43704
+ const hadConflicts = conflictIdsDeduped.length > 0;
43705
+ workerLog(
43706
+ hadConflicts ? ` Rebased ${branchDir} (kept branch version for ${conflictIdsDeduped.length} conflicting file(s))` : ` Rebased ${branchDir} successfully`
43707
+ );
43708
+ await meta.save({
43709
+ branch: {
43710
+ name: branchDir,
43711
+ conflictStatus: hadConflicts ? "conflicts-detected" : "clean",
43712
+ conflictFiles: conflictIdsDeduped,
43713
+ // PR-W2: the cycle completed successfully -- clear any prior
43714
+ // failure record regardless of conflict outcome.
43715
+ rebaseFailure: void 0
43716
+ }
43717
+ });
43718
+ didRebase = true;
43719
+ await carryForwardRewrittenHistory(ctx, {
43720
+ branchPath,
43721
+ branchDir,
43722
+ branchRef,
43723
+ publishedSha,
43724
+ preRebaseHead
43725
+ });
43726
+ return { kind: "rebased" };
43727
+ } finally {
43728
+ try {
43729
+ if (await isRebaseInProgress(branchPath)) {
43730
+ workerLogWarn(
43731
+ ` ${branchDir}: rebase still in progress on exit -- aborting so the clone is not left wedged`
43732
+ );
43733
+ await branchGit.rebase(["--abort"]);
43734
+ }
43735
+ } catch (abortErr) {
43736
+ workerLogWarn(
43737
+ ` Failed to abort in-progress rebase for ${branchDir}: ${getErrorMessage2(abortErr)}`
43738
+ );
43739
+ }
43740
+ await releaseContentLock?.().catch((releaseErr) => {
43741
+ workerLogWarn(
43742
+ ` Failed to release content-write lock for ${branchDir}: ${getErrorMessage2(releaseErr)}`
43743
+ );
43744
+ });
43745
+ }
43746
+ } catch (err) {
43747
+ const message = err instanceof Error ? err.message : "Unknown error";
43748
+ workerLogWarn(` Failed to sync ${branchDir}: ${message}`);
43749
+ const redactedMessage = redactCredentials(message);
43750
+ await recordRebaseFailure(ctx, branchPath, branchDir, redactedMessage);
43751
+ return { kind: "failed", error: redactedMessage, rebased: didRebase };
43752
+ }
43753
+ }
43754
+
43755
+ // ../canopycms/src/worker/git-sync.ts
43756
+ import fs12 from "node:fs/promises";
43757
+ import path16 from "node:path";
43758
+ var TRASH_RETENTION_MS = 30 * 24 * 60 * 6e4;
43759
+ var TRASH_DIR_STAMP_RE = /-(\d{8}T\d{6}Z)$/;
43760
+ function parseTrashStamp(stamp) {
43761
+ const match2 = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(stamp);
43762
+ if (!match2) return null;
43763
+ const [, year2, month, day, hour, minute, second] = match2;
43764
+ const date2 = /* @__PURE__ */ new Date(`${year2}-${month}-${day}T${hour}:${minute}:${second}.000Z`);
43765
+ return Number.isNaN(date2.getTime()) ? null : date2;
43766
+ }
43767
+ async function pushSettingsBranches(ctx, git, trackedNames) {
43768
+ try {
43769
+ const settingsBranch = ctx.ensureSettingsBranch();
43770
+ const branches = await git.branch();
43771
+ const settingsBranches = branches.all.filter(
43772
+ (b6) => b6.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX)
43773
+ );
43774
+ const foreign = settingsBranches.filter((b6) => b6 !== settingsBranch);
43775
+ if (foreign.length > 0) {
43776
+ workerLogWarn(
43777
+ `Found settings branch(es) not owned by this deployment (${settingsBranch}): ${foreign.join(", ")}. Another CanopyCMS deployment may share this GitHub repo. Not pushing them.`
43778
+ );
43779
+ }
43780
+ const ownBranchMissing = !branches.all.includes(settingsBranch);
43781
+ const strandedLocal = foreign.filter((b6) => !trackedNames.has(b6));
43782
+ if (strandedLocal.length > 0) {
43783
+ workerLogWarn(
43784
+ ownBranchMissing ? `Settings branch mismatch: this worker owns "${settingsBranch}", which does not exist in remote.git, while ${strandedLocal.join(", ")} exist(s) here and has never been pushed to GitHub. The API and this worker disagree about deploymentName, so settings changes are NOT reaching GitHub. Set CANOPYCMS_DEPLOYMENT_NAME (or settingsBranch) on this worker to match what the API resolves.` : `Settings branch(es) ${strandedLocal.join(", ")} exist in remote.git but not on GitHub, and are not owned by this deployment (${settingsBranch}) -- nothing will ever push them onward. Check that deploymentName matches across this deployment's API and worker.`
43785
+ );
43786
+ }
43787
+ if (ownBranchMissing) {
43788
+ return;
43789
+ }
43790
+ try {
43791
+ await git.push(ctx.buildGitHubUrl(), settingsBranch);
43792
+ workerLog(`Pushed settings branch ${settingsBranch} to GitHub`);
43793
+ } catch (err) {
43794
+ const message = getErrorMessage2(err);
43795
+ if (isNonFastForwardRejection(message)) {
43796
+ workerLogWarn(
43797
+ `Settings push for ${settingsBranch} was rejected (non-fast-forward): another CanopyCMS deployment appears to own this settings branch on GitHub. Settings from that deployment will NOT be overwritten; this deployment's local settings changes were not pushed. Rename this deployment's settings branch (config.settingsBranch or deploymentName) to resolve the collision.`
43798
+ );
43799
+ } else {
43800
+ workerLogWarn(`Settings push for ${settingsBranch}:`, message);
43801
+ }
43802
+ }
43803
+ } catch (err) {
43804
+ workerLogWarn(
43805
+ "Failed to list branches for settings push:",
43806
+ err instanceof Error ? err.message : err
43807
+ );
43808
+ }
43809
+ }
43810
+ async function reconcileTrackedBranches(ctx, git) {
43811
+ const GIT_ZERO_OID = "0000000000000000000000000000000000000000";
43812
+ const created = [];
43813
+ const fastForwarded = [];
43814
+ const ahead = [];
43815
+ const diverged = [];
43816
+ const rewritten = [];
43817
+ const raw = await git.raw([
43818
+ "for-each-ref",
43819
+ "--format=%(refname) %(objectname)",
43820
+ "refs/heads/",
43821
+ GITHUB_TRACKING_REF_PREFIX
43822
+ ]);
43823
+ const heads = /* @__PURE__ */ new Map();
43824
+ const tracked = /* @__PURE__ */ new Map();
43825
+ for (const line of raw.split("\n")) {
43826
+ const trimmed2 = line.trim();
43827
+ if (!trimmed2) continue;
43828
+ const [refname, sha] = trimmed2.split(" ");
43829
+ if (refname.startsWith("refs/heads/")) {
43830
+ heads.set(refname.slice("refs/heads/".length), sha);
43831
+ } else if (refname.startsWith(GITHUB_TRACKING_REF_PREFIX)) {
43832
+ tracked.set(refname.slice(GITHUB_TRACKING_REF_PREFIX.length), sha);
43833
+ }
43834
+ }
43835
+ for (const [name, trackedSha] of tracked) {
43836
+ const localSha = heads.get(name);
43837
+ const localRef = `refs/heads/${name}`;
43838
+ if (!localSha) {
43839
+ try {
43840
+ await git.raw(["update-ref", localRef, trackedSha, GIT_ZERO_OID]);
43841
+ created.push(name);
43842
+ } catch (err) {
43843
+ workerLogWarn(
43844
+ ` Tracked-branch reconcile: failed to create local ref for ${name} (concurrent update?): ${getErrorMessage2(err)}`
43845
+ );
43846
+ }
43847
+ continue;
43848
+ }
43849
+ if (localSha === trackedSha) continue;
43850
+ try {
43851
+ const counts = (await git.raw(["rev-list", "--left-right", "--count", `${localSha}...${trackedSha}`])).trim();
43852
+ const [leftStr, rightStr] = counts.split(/\s+/);
43853
+ const localAheadCount = parseInt(leftStr, 10);
43854
+ const localBehindCount = parseInt(rightStr, 10);
43855
+ if (!Number.isInteger(localAheadCount) || !Number.isInteger(localBehindCount)) {
43856
+ workerLogWarn(
43857
+ ` Tracked-branch reconcile: skipping ${name}: unparseable rev-list output ${JSON.stringify(counts)}`
43858
+ );
43859
+ continue;
43860
+ }
43861
+ if (localAheadCount === 0 && localBehindCount > 0) {
43862
+ try {
43863
+ await git.raw(["update-ref", localRef, trackedSha, localSha]);
43864
+ fastForwarded.push(name);
43865
+ } catch (err) {
43866
+ workerLogWarn(
43867
+ ` Tracked-branch reconcile: failed to fast-forward ${name} (concurrent update?): ${getErrorMessage2(err)}`
43868
+ );
43869
+ }
43870
+ } else if (localBehindCount === 0 && localAheadCount > 0) {
43871
+ ahead.push(name);
43872
+ } else if (await hasPendingHistoryRewrite(ctx, name)) {
43873
+ rewritten.push(name);
43874
+ } else {
43875
+ diverged.push(name);
43876
+ }
43877
+ } catch (err) {
43878
+ workerLogWarn(
43879
+ ` Tracked-branch reconcile: skipping ${name} (unreadable ref or object?): ${getErrorMessage2(err)}`
43880
+ );
43881
+ continue;
43882
+ }
43883
+ }
43884
+ if (diverged.length > 0) {
43885
+ workerLogWarn(
43886
+ ` Tracked-branch reconcile: ${diverged.length} branch(es) diverged from GitHub and were left untouched: ${diverged.join(", ")}`
43887
+ );
43888
+ }
43889
+ if (rewritten.length > 0) {
43890
+ workerLog(
43891
+ ` Tracked-branch reconcile: ${rewritten.length} branch(es) rebased locally with the GitHub push still pending: ${rewritten.join(", ")}`
43892
+ );
43893
+ }
43894
+ return {
43895
+ summary: { created, fastForwarded, ahead, diverged, rewritten },
43896
+ trackedNames: new Set(tracked.keys())
43897
+ };
43898
+ }
43899
+ async function syncGit(ctx) {
43900
+ if (!ctx.isRunning()) return;
43901
+ workerLog("Syncing git...");
43902
+ const cycleStartedAt = Date.now();
43903
+ const git = simpleGit({
43904
+ baseDir: ctx.remoteGitPath,
43905
+ // DEP-H1: a hung fetch/push would stall the sync loop forever
43906
+ // (scheduleLoop only reschedules after completion). The block timeout
43907
+ // is inactivity-based, so a slow-but-flowing transfer is unaffected.
43908
+ timeout: { block: ctx.taskTimeoutMs }
43909
+ });
43910
+ git.env(gitNetworkChildEnv());
43911
+ try {
43912
+ await git.raw([
43913
+ "fetch",
43914
+ ctx.buildGitHubUrl(),
43915
+ "--prune",
43916
+ `+refs/heads/*:${GITHUB_TRACKING_REF_PREFIX}*`
43917
+ ]);
43918
+ workerLog("Fetched from GitHub");
43919
+ const { summary: trackedSummary, trackedNames } = await reconcileTrackedBranches(ctx, git);
43920
+ await pushSettingsBranches(ctx, git, trackedNames);
43921
+ await refreshBaseBranchWorkspace(ctx);
43922
+ const rebaseSummary = await runRebaseCycle(ctx);
43923
+ await cleanupOldTasks(ctx.taskDir, void 0, ctx.log);
43924
+ const trashRemoved = await cleanupTrashedBranchDirs(ctx);
43925
+ if (trashRemoved > 0) {
43926
+ workerLog(`Removed ${trashRemoved} expired trashed branch dir(s)`);
43927
+ }
43928
+ const report = ctx.ensureStatusReport();
43929
+ report.lastGitSyncAt = (/* @__PURE__ */ new Date()).toISOString();
43930
+ delete report.lastGitSyncError;
43931
+ report.lastGitSync = {
43932
+ durationMs: Date.now() - cycleStartedAt,
43933
+ rebased: rebaseSummary.rebased,
43934
+ skippedDirty: rebaseSummary.skippedDirty,
43935
+ skippedLocked: rebaseSummary.skippedLocked,
43936
+ failed: rebaseSummary.failed,
43937
+ tracked: trackedSummary
43938
+ };
43939
+ await writeWorkerStatus(ctx.taskDir, report).catch(
43940
+ (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
43941
+ );
43942
+ } catch (err) {
43943
+ const report = ctx.ensureStatusReport();
43944
+ report.lastGitSyncError = {
43945
+ message: redactCredentials(getErrorMessage2(err)),
43946
+ at: (/* @__PURE__ */ new Date()).toISOString()
43947
+ };
43948
+ await writeWorkerStatus(ctx.taskDir, report).catch(
43949
+ (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
43950
+ );
43951
+ throw err;
43952
+ }
43953
+ }
43954
+ async function cleanupTrashedBranchDirs(ctx) {
43955
+ let entries;
43956
+ try {
43957
+ entries = await fs12.readdir(ctx.contentBranchesPath);
43958
+ } catch (err) {
43959
+ if (isNodeError(err) && err.code === "ENOENT") return 0;
43960
+ throw err;
43961
+ }
43962
+ const now = Date.now();
43963
+ let removed = 0;
43964
+ let loggedUnparseable = false;
43965
+ for (const name of entries) {
43966
+ if (!name.startsWith(".trash-")) continue;
43967
+ const stampMatch = TRASH_DIR_STAMP_RE.exec(name);
43968
+ const stampDate = stampMatch ? parseTrashStamp(stampMatch[1]) : null;
43969
+ if (!stampDate) {
43970
+ if (!loggedUnparseable) {
43971
+ workerLog(`CanopyCMS: Skipping trash dir with unparseable stamp: ${name}`);
43972
+ loggedUnparseable = true;
43973
+ }
43974
+ continue;
43975
+ }
43976
+ if (now - stampDate.getTime() < TRASH_RETENTION_MS) continue;
43977
+ try {
43978
+ await fs12.rm(path16.join(ctx.contentBranchesPath, name), {
43979
+ recursive: true,
43980
+ force: true,
43981
+ maxRetries: 3,
43982
+ retryDelay: 100
43983
+ });
43984
+ removed++;
43985
+ } catch (err) {
43986
+ workerLogError(
43987
+ `CanopyCMS: Failed to remove trashed branch dir ${name}:`,
43988
+ getErrorMessage2(err)
43989
+ );
43990
+ }
43991
+ }
43992
+ return removed;
43993
+ }
43994
+ async function refreshBaseBranchWorkspace(ctx) {
43995
+ const basePath32 = path16.join(ctx.contentBranchesPath, ctx.sanitizedBaseBranch);
43996
+ const gitDir = path16.join(basePath32, ".git");
43997
+ try {
43998
+ let gitDirStat;
43999
+ try {
44000
+ gitDirStat = await fs12.stat(gitDir);
44001
+ } catch {
44002
+ gitDirStat = null;
44003
+ }
44004
+ if (!gitDirStat || !gitDirStat.isDirectory()) {
44005
+ workerLog(`Base branch workspace (${ctx.baseBranch}): not yet provisioned, skipping refresh`);
44006
+ return;
44007
+ }
44008
+ const baseGit = simpleGit({
44009
+ baseDir: basePath32,
44010
+ // Keep git non-interactive during the merge so it never blocks on an
44011
+ // editor. simple-git >=3.32 blocks setting core.editor unless
44012
+ // explicitly opted in; the value here is a hardcoded literal
44013
+ // ("true", the shell no-op), not user input, so enabling
44014
+ // allowUnsafeEditor carries no injection risk (same as the rebase
44015
+ // loop's git config below).
44016
+ config: ["core.editor=true"],
44017
+ unsafe: { allowUnsafeEditor: true },
44018
+ // DEP-H1: a hung fetch/merge against this EFS-backed clone would
44019
+ // stall the sync loop forever (scheduleLoop only reschedules after
44020
+ // completion). The block timeout is inactivity-based, so a
44021
+ // slow-but-flowing transfer is unaffected -- same as syncGit()'s
44022
+ // remote handle.
44023
+ timeout: { block: ctx.taskTimeoutMs }
44024
+ });
44025
+ const status = await baseGit.status();
44026
+ const trackedDirty = status.files.filter((f5) => f5.index !== "?" || f5.working_dir !== "?");
44027
+ if (trackedDirty.length > 0) {
44028
+ workerLogError(
44029
+ `Base branch workspace (${ctx.baseBranch}) has uncommitted changes -- skipping refresh. Dirty files: ${trackedDirty.map((f5) => f5.path).join(", ")}`
44030
+ );
44031
+ return;
44032
+ }
44033
+ await baseGit.fetch("origin", ctx.baseBranch);
44034
+ const fetchedTip = (await baseGit.revparse(["FETCH_HEAD"])).trim();
44035
+ const behindCount = parseInt(
44036
+ (await baseGit.raw(["rev-list", "--count", `HEAD..${fetchedTip}`])).trim(),
44037
+ 10
44038
+ );
44039
+ if (behindCount > 0) {
44040
+ try {
44041
+ await baseGit.merge(["--ff-only", fetchedTip]);
44042
+ } catch (err) {
44043
+ workerLogError(
44044
+ `Base branch workspace (${ctx.baseBranch}) failed to fast-forward (diverged local history?): ${getErrorMessage2(err)}`
44045
+ );
44046
+ return;
44047
+ }
44048
+ await invalidateBranchContentCaches(basePath32);
44049
+ }
44050
+ const currentMeta = await BranchMetadataFileManager.loadOnly(basePath32);
44051
+ const conflictStatus = currentMeta?.branch.conflictStatus;
44052
+ const conflictFiles = currentMeta?.branch.conflictFiles;
44053
+ const alreadyClean = (conflictStatus === void 0 || conflictStatus === "clean") && (conflictFiles === void 0 || conflictFiles.length === 0);
44054
+ if (!alreadyClean) {
44055
+ const meta = getBranchMetadataFileManager(basePath32, ctx.contentBranchesPath);
44056
+ await meta.save({
44057
+ branch: { name: ctx.baseBranch, conflictStatus: "clean", conflictFiles: [] }
44058
+ });
44059
+ }
44060
+ workerLog(
44061
+ behindCount > 0 ? `Base branch workspace (${ctx.baseBranch}): fast-forwarded ${behindCount} commit(s)` : `Base branch workspace (${ctx.baseBranch}): up to date`
44062
+ );
44063
+ } catch (err) {
44064
+ workerLogError(
44065
+ `Base branch workspace (${ctx.baseBranch}) refresh failed: ${getErrorMessage2(err)}`
44066
+ );
44067
+ }
44068
+ }
44069
+
44070
+ // ../canopycms/src/worker/cms-worker.ts
44071
+ var DEFAULT_TASK_TIMEOUT = 6e4;
44072
+ var DEFAULT_MAX_RETRIES2 = 3;
44073
+ var DEFAULT_LOCK_STALE_MS = 6e4;
42976
44074
  var CmsWorker = class {
42977
44075
  constructor(config) {
42978
44076
  this.config = config;
@@ -42982,15 +44080,15 @@ var CmsWorker = class {
42982
44080
  this.releaseLockFn = null;
42983
44081
  this.log = cmsTaskQueueLogger;
42984
44082
  this.octokit = createCanopyOctokit({ auth: config.githubToken });
42985
- this.taskDir = path13.join(config.workspacePath, ".tasks");
42986
- this.remoteGitPath = path13.join(config.workspacePath, "remote.git");
42987
- this.contentBranchesPath = path13.join(config.workspacePath, "content-branches");
44083
+ this.taskDir = path17.join(config.workspacePath, ".tasks");
44084
+ this.remoteGitPath = path17.join(config.workspacePath, "remote.git");
44085
+ this.contentBranchesPath = path17.join(config.workspacePath, "content-branches");
42988
44086
  this.baseBranch = config.baseBranch ?? "main";
42989
44087
  this.sanitizedBaseBranch = sanitizeBranchName(this.baseBranch);
42990
44088
  this.maxTasksPerCycle = config.maxTasksPerCycle ?? 10;
42991
44089
  this.taskTimeoutMs = config.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT;
42992
44090
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES2;
42993
- this.lockFilePath = path13.join(config.workspacePath, ".tasks", ".worker-lock");
44091
+ this.lockFilePath = path17.join(config.workspacePath, ".tasks", ".worker-lock");
42994
44092
  this.lockStaleMs = config.lockStaleMs ?? DEFAULT_LOCK_STALE_MS;
42995
44093
  this.contentRoot = config.contentRoot ?? "content";
42996
44094
  }
@@ -43043,6 +44141,46 @@ var CmsWorker = class {
43043
44141
  }
43044
44142
  return this.settingsBranchResolved;
43045
44143
  }
44144
+ /**
44145
+ * Build the {@link WorkerContext} handed to the extracted clusters
44146
+ * (task-runner.ts, git-sync.ts, rebase.ts, history-rewrite.ts).
44147
+ *
44148
+ * Built FRESH on every call rather than once in the constructor, and the
44149
+ * instance-backed members are functions rather than copied values. Both
44150
+ * choices exist for the same reason: the test suite drives this class by
44151
+ * replacing `octokit` and `buildGitHubUrl` ON THE INSTANCE, by setting
44152
+ * `running` directly, and by subclassing to override the two rebase test
44153
+ * hooks. A context that captured any of those at construction time would hand
44154
+ * the extracted code the pre-test value -- which for `buildGitHubUrl` means a
44155
+ * test's push going to github.com for real instead of its local fixture repo.
44156
+ * See WorkerContext's doc comment for the full list.
44157
+ */
44158
+ ctx() {
44159
+ return {
44160
+ githubOwner: this.config.githubOwner,
44161
+ githubRepo: this.config.githubRepo,
44162
+ baseBranch: this.baseBranch,
44163
+ sanitizedBaseBranch: this.sanitizedBaseBranch,
44164
+ taskDir: this.taskDir,
44165
+ remoteGitPath: this.remoteGitPath,
44166
+ contentBranchesPath: this.contentBranchesPath,
44167
+ contentRoot: this.contentRoot,
44168
+ taskTimeoutMs: this.taskTimeoutMs,
44169
+ maxTasksPerCycle: this.maxTasksPerCycle,
44170
+ maxRetries: this.maxRetries,
44171
+ log: this.log,
44172
+ octokit: () => this.octokit,
44173
+ buildGitHubUrl: () => this.buildGitHubUrl(),
44174
+ branchWorkspacePath: (branchRefName) => this.branchWorkspacePath(branchRefName),
44175
+ executeTask: (task, signal) => this.executeTask(task, signal),
44176
+ pushBranchToGitHub: (branch) => this.pushBranchToGitHub(branch),
44177
+ isRunning: () => this.running,
44178
+ ensureStatusReport: () => this.ensureStatusReport(),
44179
+ ensureSettingsBranch: () => this.ensureSettingsBranch(),
44180
+ afterConflictDetectedForTesting: () => this.afterConflictDetectedForTesting(),
44181
+ afterRebaseCompletedForTesting: () => this.afterRebaseCompletedForTesting()
44182
+ };
44183
+ }
43046
44184
  async start() {
43047
44185
  this.running = true;
43048
44186
  workerLog("CMS Worker starting...");
@@ -43053,7 +44191,7 @@ var CmsWorker = class {
43053
44191
  await this.ensureRemoteGit();
43054
44192
  const recovered = await recoverOrphanedTasks(
43055
44193
  this.taskDir,
43056
- this.orphanRecoveryMaxAgeMs(),
44194
+ orphanRecoveryMaxAgeMs(this.ctx()),
43057
44195
  this.log
43058
44196
  );
43059
44197
  if (recovered > 0) {
@@ -43067,7 +44205,7 @@ var CmsWorker = class {
43067
44205
  } catch (err) {
43068
44206
  const report = this.ensureStatusReport();
43069
44207
  report.lastFatalError = {
43070
- // [HIGH-1] Persisted to worker-status.json and served to the
44208
+ // [REDACT] Persisted to worker-status.json and served to the
43071
44209
  // browser by the admin panel -- must never carry the bot token
43072
44210
  // that a poisoned/failed git URL (buildGitHubUrl()) can embed.
43073
44211
  message: redactCredentials(getErrorMessage2(err)),
@@ -43141,7 +44279,7 @@ var CmsWorker = class {
43141
44279
  * negligible, but a host with a badly wrong clock could misjudge liveness.
43142
44280
  */
43143
44281
  async acquireLock() {
43144
- await fs9.mkdir(this.taskDir, { recursive: true });
44282
+ await fs13.mkdir(this.taskDir, { recursive: true });
43145
44283
  try {
43146
44284
  this.releaseLockFn = await import_proper_lockfile3.default.lock(this.taskDir, {
43147
44285
  lockfilePath: this.lockFilePath,
@@ -43287,7 +44425,7 @@ var CmsWorker = class {
43287
44425
  async ensureRemoteGit() {
43288
44426
  let exists2;
43289
44427
  try {
43290
- await fs9.stat(this.remoteGitPath);
44428
+ await fs13.stat(this.remoteGitPath);
43291
44429
  exists2 = true;
43292
44430
  } catch {
43293
44431
  exists2 = false;
@@ -43307,286 +44445,52 @@ var CmsWorker = class {
43307
44445
  workerLog("Initializing remote.git from GitHub...");
43308
44446
  const git = simpleGit();
43309
44447
  const stagingPath = `${this.remoteGitPath}.cloning`;
43310
- await fs9.rm(stagingPath, { recursive: true, force: true });
44448
+ await fs13.rm(stagingPath, { recursive: true, force: true });
43311
44449
  try {
43312
44450
  await git.clone(this.buildGitHubUrl(), stagingPath, ["--bare"]);
43313
44451
  await this.scrubPersistedRemote(stagingPath);
43314
44452
  await this.verifyBaseBranchExists(stagingPath);
43315
44453
  } catch (err) {
43316
44454
  workerLogError(`remote.git clone failed: ${redactCredentials(getErrorMessage2(err))}`);
43317
- await fs9.rm(stagingPath, { recursive: true, force: true });
44455
+ await fs13.rm(stagingPath, { recursive: true, force: true });
43318
44456
  throw new Error(
43319
44457
  `remote.git clone of ${this.config.githubOwner}/${this.config.githubRepo} failed or has no branch '${this.baseBranch}' - the GitHub repository may be empty, or the base branch may not exist. Push an initial commit to '${this.baseBranch}' and restart the worker (systemd will retry automatically).`
43320
44458
  );
43321
44459
  }
43322
- await fs9.rename(stagingPath, this.remoteGitPath);
44460
+ await fs13.rename(stagingPath, this.remoteGitPath);
43323
44461
  workerLog("remote.git initialized");
43324
44462
  }
43325
- /**
43326
- * Staleness threshold for recoverOrphanedTasks, derived from the
43327
- * configured task timeout rather than fixed: the safety argument for
43328
- * running recovery on every poll cycle is "no legitimately in-flight task
43329
- * can be this old, because executeTaskWithTimeout bounds every attempt by
43330
- * taskTimeoutMs" -- which is only true if this threshold scales with
43331
- * taskTimeoutMs. 2x leaves the same comfortable margin the defaults have
43332
- * (60s timeout vs 5min threshold); the 5-minute floor preserves the
43333
- * long-standing default for a replacement instance's boot window.
43334
- */
43335
- orphanRecoveryMaxAgeMs() {
43336
- return Math.max(5 * 6e4, this.taskTimeoutMs * 2);
43337
- }
43338
- /**
43339
- * Process queued tasks from Lambda.
43340
- * Polls .tasks/pending/ directory and executes each task.
43341
- * Processes up to maxTasksPerCycle tasks per invocation.
43342
- * Retries transient failures with exponential backoff.
43343
- */
44463
+ // --- Task-queue cluster (worker/task-runner.ts) ------------------------
44464
+ //
44465
+ // READ THIS BEFORE STUBBING ANY DELEGATOR BELOW. They are not all the same,
44466
+ // and the difference is invisible from here.
44467
+ //
44468
+ // `processTaskQueue` is the public loop entry `scheduleLoop` drives, so it IS
44469
+ // on the production path. The other three exist ONLY so the existing test
44470
+ // files can reach the implementations through the instance; production never
44471
+ // dispatches through them, because the extracted modules call each other
44472
+ // directly at module level.
44473
+ //
44474
+ // The consequence: replacing one of these on an instance only affects
44475
+ // production behaviour if the context also routes it. Two do --
44476
+ // `executeTask` and `pushBranchToGitHub` are on `WorkerContext` precisely
44477
+ // because cms-worker.test.ts assigns over them and then asserts on the
44478
+ // replacement. `updateBranchMetadata` is NOT: the test calls it directly and
44479
+ // never stubs it, so a stub installed there today would be a silent no-op.
44480
+ // If you need to stub a method that isn't on the context, add it to
44481
+ // WorkerContext and route the internal caller through `ctx` -- do not assume
44482
+ // the delegator's existence means the stub takes effect.
43344
44483
  async processTaskQueue() {
43345
- if (!this.running) return;
43346
- const recovered = await recoverOrphanedTasks(
43347
- this.taskDir,
43348
- this.orphanRecoveryMaxAgeMs(),
43349
- this.log
43350
- );
43351
- if (recovered > 0) {
43352
- workerLog(`Recovered ${recovered} orphaned task(s)`);
43353
- }
43354
- let processed = 0;
43355
- let task;
43356
- while (processed < this.maxTasksPerCycle && (task = await dequeueTask(this.taskDir, this.log)) !== null) {
43357
- try {
43358
- const result = await this.executeTaskWithTimeout(task);
43359
- await completeTask(this.taskDir, task.id, result, this.log);
43360
- await this.updateBranchMetadata(task, result);
43361
- } catch (err) {
43362
- const message = getErrorMessage2(err);
43363
- workerLogError(`Task ${task.id} (${task.action}) failed:`, message);
43364
- const persistedMessage = redactCredentials(message);
43365
- const permanent = isPermanentTaskFailure(err);
43366
- const retryCount = task.retryCount ?? 0;
43367
- const maxRetries = task.maxRetries ?? this.maxRetries;
43368
- if (!permanent && retryCount < maxRetries) {
43369
- await retryTask(this.taskDir, task.id, persistedMessage, this.log);
43370
- workerLog(` Will retry (attempt ${retryCount + 1}/${maxRetries})`);
43371
- } else {
43372
- await failTask(this.taskDir, task.id, persistedMessage, this.log);
43373
- await this.updateBranchMetadataOnFailure(task, persistedMessage);
43374
- workerLogError(
43375
- permanent ? " Permanently failed (non-retryable error)" : ` Permanently failed after ${maxRetries} retries`
43376
- );
43377
- }
43378
- }
43379
- processed++;
43380
- }
43381
- if (processed > 0) {
43382
- const report = this.ensureStatusReport();
43383
- report.lastTaskCycleAt = (/* @__PURE__ */ new Date()).toISOString();
43384
- await writeWorkerStatus(this.taskDir, report).catch(
43385
- (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
43386
- );
43387
- }
43388
- }
43389
- /**
43390
- * Execute a task bounded by taskTimeoutMs (DEP-H1). Two layers:
43391
- * - An AbortSignal cancels Octokit HTTP calls promptly.
43392
- * - A Promise.race rejects when the timeout fires, so work that cannot
43393
- * observe the signal (git subprocesses via simple-git) still fails the
43394
- * attempt and the worker moves on instead of stalling forever.
43395
- * pushBranchToGitHub additionally kills stalled git processes via
43396
- * simple-git's block timeout, so a hung push doesn't leak a process.
43397
- */
43398
- async executeTaskWithTimeout(task) {
43399
- const controller = new AbortController();
43400
- const timer = setTimeout(() => controller.abort(), this.taskTimeoutMs);
43401
- try {
43402
- const work = this.executeTask(task, controller.signal);
43403
- work.catch(() => {
43404
- });
43405
- const timedOut = new Promise((_3, reject) => {
43406
- controller.signal.addEventListener(
43407
- "abort",
43408
- () => reject(new Error(`Task timed out after ${this.taskTimeoutMs}ms`)),
43409
- { once: true }
43410
- );
43411
- });
43412
- return await Promise.race([work, timedOut]);
43413
- } finally {
43414
- clearTimeout(timer);
43415
- }
44484
+ return processTaskQueue(this.ctx());
43416
44485
  }
43417
44486
  async executeTask(task, signal) {
43418
- const { action, payload: payload2 } = task;
43419
- switch (action) {
43420
- case "push-branch": {
43421
- const branch = requireString(payload2, "branch");
43422
- await this.pushBranchToGitHub(branch);
43423
- return { pushed: true };
43424
- }
43425
- case "push-and-create-pr": {
43426
- const branch = requireString(payload2, "branch");
43427
- await this.pushBranchToGitHub(branch);
43428
- const pr = await this.octokit.pulls.create({
43429
- owner: this.config.githubOwner,
43430
- repo: this.config.githubRepo,
43431
- head: branch,
43432
- base: optionalString(payload2, "baseBranch", this.baseBranch),
43433
- title: optionalString(payload2, "title", `Submit ${branch}`),
43434
- body: optionalString(payload2, "body", ""),
43435
- request: { signal }
43436
- });
43437
- workerLog(`Created PR #${pr.data.number} for ${branch}`);
43438
- return { prUrl: pr.data.html_url, prNumber: pr.data.number };
43439
- }
43440
- case "push-and-update-pr": {
43441
- const branch = requireString(payload2, "branch");
43442
- const prNumber = requireNumber(payload2, "pullRequestNumber");
43443
- await this.pushBranchToGitHub(branch);
43444
- await this.octokit.pulls.update({
43445
- owner: this.config.githubOwner,
43446
- repo: this.config.githubRepo,
43447
- pull_number: prNumber,
43448
- title: optionalString(payload2, "title", `Submit ${branch}`),
43449
- body: optionalString(payload2, "body", ""),
43450
- request: { signal }
43451
- });
43452
- workerLog(`Updated PR #${prNumber} for ${branch}`);
43453
- return { prNumber };
43454
- }
43455
- case "push-and-create-or-update-pr": {
43456
- const branch = requireString(payload2, "branch");
43457
- const base = optionalString(payload2, "baseBranch", this.baseBranch);
43458
- if (sanitizeBranchName(branch) === this.sanitizedBaseBranch) {
43459
- throw new PermanentTaskError(
43460
- `Refusing to push-and-create-or-update-pr for "${branch}": it is the base branch -- submitting the base branch is never valid`
43461
- );
43462
- }
43463
- await this.pushBranchToGitHub(branch);
43464
- const result = await createOrUpdatePullRequest({
43465
- octokit: this.octokit,
43466
- owner: this.config.githubOwner,
43467
- repo: this.config.githubRepo,
43468
- head: branch,
43469
- base,
43470
- title: optionalString(payload2, "title", `Submit ${branch}`),
43471
- body: optionalString(payload2, "body", ""),
43472
- // Content submits (api/github-sync.ts) set this; settings-branch
43473
- // syncs (services.ts) deliberately don't.
43474
- markReadyIfDraft: payload2.markReadyIfDraft === true,
43475
- signal
43476
- });
43477
- workerLog(
43478
- result.created ? `Created PR #${result.number} for ${branch}` : `Updated existing PR #${result.number} for ${branch}`
43479
- );
43480
- return { prUrl: result.url, prNumber: result.number };
43481
- }
43482
- case "convert-to-draft": {
43483
- const draftPrNumber = requireNumber(payload2, "pullRequestNumber");
43484
- const { data: pr } = await this.octokit.pulls.get({
43485
- owner: this.config.githubOwner,
43486
- repo: this.config.githubRepo,
43487
- pull_number: draftPrNumber,
43488
- request: { signal }
43489
- });
43490
- await this.octokit.graphql(
43491
- `mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`,
43492
- { id: pr.node_id, request: { signal } }
43493
- );
43494
- workerLog(`Converted PR #${draftPrNumber} to draft`);
43495
- return { prNumber: draftPrNumber, draft: true };
43496
- }
43497
- case "close-pr": {
43498
- const closePrNumber = requireNumber(payload2, "pullRequestNumber");
43499
- await this.octokit.pulls.update({
43500
- owner: this.config.githubOwner,
43501
- repo: this.config.githubRepo,
43502
- pull_number: closePrNumber,
43503
- state: "closed",
43504
- request: { signal }
43505
- });
43506
- return { closed: true };
43507
- }
43508
- case "delete-remote-branch": {
43509
- const branch = requireString(payload2, "branch");
43510
- await this.octokit.git.deleteRef({
43511
- owner: this.config.githubOwner,
43512
- repo: this.config.githubRepo,
43513
- ref: `heads/${branch}`,
43514
- request: { signal }
43515
- });
43516
- return { deleted: true };
43517
- }
43518
- default:
43519
- throw new PermanentTaskError(`Unknown task action: ${action}`);
43520
- }
44487
+ return executeTask(this.ctx(), task, signal);
43521
44488
  }
43522
- /**
43523
- * Update branch metadata after successful task completion.
43524
- * Writes PR URL/number and sets syncStatus to 'synced'.
43525
- */
43526
44489
  async updateBranchMetadata(task, result) {
43527
- const branch = typeof task.payload.branch === "string" ? task.payload.branch : null;
43528
- if (!branch) return;
43529
- const branchPath = this.branchWorkspacePath(branch);
43530
- try {
43531
- await fs9.stat(branchPath);
43532
- } catch {
43533
- return;
43534
- }
43535
- try {
43536
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
43537
- const updates = {
43538
- name: branch,
43539
- syncStatus: "synced",
43540
- // save()'s merge only overwrites keys present in this update (see
43541
- // BranchMetadataFileManager.save()'s spread order) -- a prior
43542
- // syncFailureReason from an earlier failed attempt would otherwise
43543
- // survive forever past this successful sync. Explicitly clear it,
43544
- // same pattern as rebaseFailure's PR-W2 M2 clearing elsewhere.
43545
- syncFailureReason: void 0
43546
- };
43547
- if (result.prUrl) updates.pullRequestUrl = result.prUrl;
43548
- if (result.prNumber) {
43549
- updates.pullRequestNumber = result.prNumber;
43550
- const current = await BranchMetadataFileManager.loadOnly(branchPath);
43551
- const terminal = current?.branch.status === "archived" || current?.branch.pullRequestState === "merged" || current?.branch.pullRequestState === "closed";
43552
- if (!terminal) {
43553
- updates.pullRequestState = "open";
43554
- }
43555
- }
43556
- await meta.save({ branch: updates });
43557
- } catch (err) {
43558
- workerLogError(
43559
- `Failed to update metadata for ${branch}:`,
43560
- err instanceof Error ? err.message : err
43561
- );
43562
- }
44490
+ return updateBranchMetadata(this.ctx(), task, result);
43563
44491
  }
43564
- /**
43565
- * Update branch metadata after permanent task failure.
43566
- * Sets syncStatus to 'sync-failed' and records `error` (already redacted
43567
- * by the caller, processTaskQueue -- see [HIGH-1] there) as
43568
- * syncFailureReason, so the editor can show WHY, not just that it failed.
43569
- */
43570
- async updateBranchMetadataOnFailure(task, error2) {
43571
- const branch = typeof task.payload.branch === "string" ? task.payload.branch : null;
43572
- if (!branch) return;
43573
- const branchPath = this.branchWorkspacePath(branch);
43574
- try {
43575
- await fs9.stat(branchPath);
43576
- } catch {
43577
- return;
43578
- }
43579
- try {
43580
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
43581
- await meta.save({
43582
- branch: { name: branch, syncStatus: "sync-failed", syncFailureReason: error2 }
43583
- });
43584
- } catch (err) {
43585
- workerLogError(
43586
- `Failed to update failure metadata for ${branch}:`,
43587
- err instanceof Error ? err.message : err
43588
- );
43589
- }
44492
+ async pushBranchToGitHub(branch) {
44493
+ return pushBranchToGitHub(this.ctx(), branch);
43590
44494
  }
43591
44495
  buildGitHubUrl() {
43592
44496
  return `https://x-access-token:${this.config.githubToken}@github.com/${this.config.githubOwner}/${this.config.githubRepo}.git`;
@@ -43608,732 +44512,7 @@ var CmsWorker = class {
43608
44512
  * is sanitized.
43609
44513
  */
43610
44514
  branchWorkspacePath(branchRefName) {
43611
- return path13.join(this.contentBranchesPath, sanitizeBranchName(branchRefName));
43612
- }
43613
- /**
43614
- * What `remote.git` currently holds for `branchRef`, or null when this
43615
- * branch was never published there (never submitted) or the ref is
43616
- * unreadable.
43617
- *
43618
- * Uses the same explicit `--git-dir` shape as verifyBaseBranchExists()
43619
- * above: reading a bare repo that way does not depend on
43620
- * `safe.bareRepository` being permissive, which is why prod code takes
43621
- * this route rather than the config override the test-only `openBareRepo`
43622
- * helper uses.
43623
- */
43624
- async readPublishedSha(branchRef) {
43625
- try {
43626
- const out = await simpleGit().raw([
43627
- "--git-dir",
43628
- this.remoteGitPath,
43629
- "rev-parse",
43630
- "--verify",
43631
- `refs/heads/${branchRef}`
43632
- ]);
43633
- const sha = out.trim();
43634
- return sha.length > 0 ? sha : null;
43635
- } catch {
43636
- return null;
43637
- }
43638
- }
43639
- /**
43640
- * Record that this worker rewrote `expectedSha` out of a branch's already
43641
- * published history (see BranchMetadata.historyRewrittenFrom).
43642
- *
43643
- * Set-once: if a marker is already present it is LEFT ALONE. Across two
43644
- * rebases before any GitHub push lands, GitHub still holds the commit the
43645
- * FIRST rebase replaced, so advancing the marker would aim the lease at a
43646
- * commit GitHub never had and permanently wedge the branch.
43647
- *
43648
- * Re-reads metadata rather than trusting the caller's loop-top snapshot:
43649
- * the task loop runs concurrently with syncGit() (see scheduleLoop) and may
43650
- * have cleared the marker while this branch was rebasing.
43651
- */
43652
- async markHistoryRewritten(branchPath, branchDir, expectedSha) {
43653
- const current = await BranchMetadataFileManager.loadOnly(branchPath);
43654
- if (typeof current?.branch.historyRewrittenFrom === "string") return;
43655
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
43656
- await meta.save({ branch: { name: branchDir, historyRewrittenFrom: expectedSha } });
43657
- }
43658
- /**
43659
- * Clear the marker once GitHub is confirmed to hold the rewritten history.
43660
- *
43661
- * Best-effort only in the sense that a failure here destroys nothing: the
43662
- * lease still refuses anything unexpected, and the plain-push fallback only
43663
- * ever fast-forwards. It is NOT harmless. A marker that outlives its
43664
- * episode can wedge the NEXT one -- if the base advances before the stale
43665
- * marker is revisited, the queued push leases a commit GitHub has already
43666
- * moved off, falls back to a plain push of a rebased (non-ancestor)
43667
- * history, and fails permanently with a "something else moved it on
43668
- * GitHub" diagnosis that is false: we did.
43669
- *
43670
- * Tracked, with the concurrent-clear race that can drop a marker
43671
- * mid-arming, in
43672
- * .claude/future-tasks/worker-history-rewrite-marker-races.md.
43673
- */
43674
- async clearHistoryRewrittenMarker(branchPath, branchDir) {
43675
- try {
43676
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
43677
- await meta.save({ branch: { name: branchDir, historyRewrittenFrom: void 0 } });
43678
- } catch (err) {
43679
- workerLogWarn(
43680
- ` Failed to clear history-rewrite marker for ${branchDir}: ${getErrorMessage2(err)}`
43681
- );
43682
- }
43683
- }
43684
- /**
43685
- * Publish a branch clone's rebased history into `remote.git`, replacing
43686
- * EXACTLY `expectedSha` and nothing else.
43687
- *
43688
- * The lease is the entire safety argument. `--force-with-lease=<ref>:<sha>`
43689
- * refuses unless `remote.git` still stands at `<sha>`, so this can only
43690
- * ever undo the commit our own rebase rewrote away. Callers must never pass
43691
- * "whatever remote.git currently holds" -- see the arming guard in
43692
- * rebaseActiveBranches() for the interleaving where that would silently
43693
- * delete a reviewer's direct push.
43694
- *
43695
- * Returns whether the push landed. A refused lease means a concurrent
43696
- * Lambda push moved the ref; that is logged and retried by the self-heal
43697
- * pass on a later cycle, never thrown.
43698
- */
43699
- async forcePublishToLocalRemote(branchPath, branchRef, expectedSha) {
43700
- const pushGit = simpleGit({ baseDir: branchPath, timeout: { block: this.taskTimeoutMs } });
43701
- pushGit.env(gitChildEnv({}));
43702
- try {
43703
- await pushGit.raw([
43704
- "push",
43705
- `--force-with-lease=${branchRef}:${expectedSha}`,
43706
- // Real flags must precede --end-of-options; everything after it is
43707
- // positional (see GitManager.push() for the same guard).
43708
- "--end-of-options",
43709
- "origin",
43710
- `${branchRef}:${branchRef}`
43711
- ]);
43712
- workerLog(` Published rebased ${branchRef} into remote.git`);
43713
- return true;
43714
- } catch (err) {
43715
- const message = redactCredentials(getErrorMessage2(err));
43716
- workerLogWarn(
43717
- isStaleLeaseRejection(message) ? ` Did not publish rebased ${branchRef} into remote.git: it moved since this cycle read it (concurrent submit?) -- retrying next cycle` : ` Failed to publish rebased ${branchRef} into remote.git: ${message}`
43718
- );
43719
- return false;
43720
- }
43721
- }
43722
- /**
43723
- * Queue the GitHub hop for a branch whose rewritten history now sits in
43724
- * `remote.git`, so an open PR's head follows the rebase within a cycle
43725
- * instead of waiting for the editor's next submit.
43726
- *
43727
- * Deliberately NOT skipped when a marker is already set: inferring "a task
43728
- * must already be queued" from the marker starves this hop whenever a task
43729
- * was lost, failed permanently, or was never written. Duplicate push tasks
43730
- * are bounded by base-branch advances and are idempotent (a repeat push is
43731
- * a no-op once GitHub holds the tip).
43732
- */
43733
- async enqueueGitHubPush(branchRef) {
43734
- try {
43735
- for (const status of ["pending", "processing"]) {
43736
- const inFlight = await listTasks(this.taskDir, status);
43737
- if (inFlight.some((t6) => t6.action === "push-branch" && t6.payload.branch === branchRef)) {
43738
- return;
43739
- }
43740
- }
43741
- await enqueueTask(this.taskDir, { action: "push-branch", payload: { branch: branchRef } });
43742
- workerLog(` Queued GitHub push for ${branchRef}`);
43743
- } catch (err) {
43744
- workerLogWarn(` Failed to queue GitHub push for ${branchRef}: ${getErrorMessage2(err)}`);
43745
- }
43746
- }
43747
- /**
43748
- * Complete a rewrite this worker started but did not finish: get the
43749
- * rebased history into `remote.git` and queued for GitHub.
43750
- *
43751
- * Runs from the rebase loop for any branch carrying a marker, whether or
43752
- * not it is behind base this cycle, so an interrupted publish converges
43753
- * without waiting for the next base-branch advance.
43754
- *
43755
- * Always leases on the MARKER, never on remote.git's current tip -- the
43756
- * marker is the one commit we know our own rebase replaced.
43757
- */
43758
- async reconcilePendingRewrite(options) {
43759
- const { branchPath, branchDir, branchRef, headSha, marker } = options;
43760
- const publishedSha = await this.readPublishedSha(branchRef);
43761
- if (publishedSha === null) {
43762
- workerLogWarn(
43763
- ` ${branchDir}: a rewritten history is recorded but ${branchRef} is gone from remote.git -- nothing to publish`
43764
- );
43765
- return;
43766
- }
43767
- if (publishedSha === headSha) {
43768
- await this.enqueueGitHubPush(branchRef);
43769
- return;
43770
- }
43771
- if (publishedSha === marker) {
43772
- if (await this.forcePublishToLocalRemote(branchPath, branchRef, marker)) {
43773
- await this.enqueueGitHubPush(branchRef);
43774
- }
43775
- return;
43776
- }
43777
- workerLogWarn(
43778
- ` ${branchDir}: remote.git is at ${publishedSha} for ${branchRef}, which is neither the rebased tip nor the commit the rewrite replaced -- left untouched`
43779
- );
43780
- }
43781
- async pushBranchToGitHub(branch) {
43782
- const git = simpleGit({
43783
- baseDir: this.remoteGitPath,
43784
- // DEP-H1: kill the git process if it produces no output for
43785
- // taskTimeoutMs (network stall, credential prompt) instead of letting
43786
- // it hang past the task timeout.
43787
- timeout: { block: this.taskTimeoutMs }
43788
- });
43789
- git.env(gitNetworkChildEnv());
43790
- const branchPath = this.branchWorkspacePath(branch);
43791
- const metaFile = await BranchMetadataFileManager.loadOnly(branchPath).catch(() => null);
43792
- const marker = metaFile?.branch.historyRewrittenFrom;
43793
- const outgoingSha = await this.readPublishedSha(branch);
43794
- try {
43795
- if (marker) {
43796
- await git.raw([
43797
- "push",
43798
- `--force-with-lease=${branch}:${marker}`,
43799
- "--end-of-options",
43800
- this.buildGitHubUrl(),
43801
- `${branch}:${branch}`
43802
- ]);
43803
- } else {
43804
- await git.push(this.buildGitHubUrl(), branch);
43805
- }
43806
- } catch (err) {
43807
- const message = getErrorMessage2(err);
43808
- if (marker && isStaleLeaseRejection(message)) {
43809
- try {
43810
- await git.push(this.buildGitHubUrl(), branch);
43811
- } catch (retryErr) {
43812
- const retryMessage = getErrorMessage2(retryErr);
43813
- if (isNonFastForwardRejection(retryMessage)) {
43814
- throw new PermanentTaskError(
43815
- `Push rejected for branch "${branch}": GitHub's tip is neither the commit this deployment last published nor an ancestor of what it is pushing, so the branch has genuinely diverged and nothing was overwritten. Something else moved it on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`
43816
- );
43817
- }
43818
- throw retryErr;
43819
- }
43820
- await this.clearHistoryRewrittenMarker(branchPath, branch);
43821
- workerLog(`Pushed ${branch} to GitHub (GitHub had already moved past the rewritten commit)`);
43822
- return;
43823
- }
43824
- if (isNonFastForwardRejection(message)) {
43825
- throw new PermanentTaskError(
43826
- `Push rejected for branch "${branch}": GitHub's tip is not what this deployment last published, so the branch has diverged and needs reconciling. Something else moved it on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`
43827
- );
43828
- }
43829
- throw err;
43830
- }
43831
- if (marker && outgoingSha && outgoingSha !== marker) {
43832
- await this.clearHistoryRewrittenMarker(branchPath, branch);
43833
- }
43834
- workerLog(`Pushed ${branch} to GitHub`);
43835
- }
43836
- /**
43837
- * Push THIS deployment's own settings branch (`ensureSettingsBranch()`) from
43838
- * remote.git to GitHub. Non-fatal: a no-op push for an up-to-date branch
43839
- * just succeeds quietly.
43840
- *
43841
- * Deliberately narrowed to one branch — this used to push EVERY local
43842
- * branch matching `canopycms-settings-*`. With the tracking-namespace fetch
43843
- * fix (GITHUB_TRACKING_REF_PREFIX), `reconcileTrackedBranches` creates local
43844
- * heads for branches that exist on GitHub, so ANOTHER deployment's settings
43845
- * branch (sharing this same GitHub repo) can legitimately show up as a
43846
- * local head here too. Pushing it would be this deployment shipping
43847
- * settings state it doesn't own.
43848
- */
43849
- async pushSettingsBranches(git, trackedNames) {
43850
- try {
43851
- const settingsBranch = this.ensureSettingsBranch();
43852
- const branches = await git.branch();
43853
- const settingsBranches = branches.all.filter(
43854
- (b6) => b6.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX)
43855
- );
43856
- const foreign = settingsBranches.filter((b6) => b6 !== settingsBranch);
43857
- if (foreign.length > 0) {
43858
- workerLogWarn(
43859
- `Found settings branch(es) not owned by this deployment (${settingsBranch}): ${foreign.join(", ")}. Another CanopyCMS deployment may share this GitHub repo. Not pushing them.`
43860
- );
43861
- }
43862
- const ownBranchMissing = !branches.all.includes(settingsBranch);
43863
- const strandedLocal = foreign.filter((b6) => !trackedNames.has(b6));
43864
- if (strandedLocal.length > 0) {
43865
- workerLogWarn(
43866
- ownBranchMissing ? `Settings branch mismatch: this worker owns "${settingsBranch}", which does not exist in remote.git, while ${strandedLocal.join(", ")} exist(s) here and has never been pushed to GitHub. The API and this worker disagree about deploymentName, so settings changes are NOT reaching GitHub. Set CANOPYCMS_DEPLOYMENT_NAME (or settingsBranch) on this worker to match what the API resolves.` : `Settings branch(es) ${strandedLocal.join(", ")} exist in remote.git but not on GitHub, and are not owned by this deployment (${settingsBranch}) -- nothing will ever push them onward. Check that deploymentName matches across this deployment's API and worker.`
43867
- );
43868
- }
43869
- if (ownBranchMissing) {
43870
- return;
43871
- }
43872
- try {
43873
- await git.push(this.buildGitHubUrl(), settingsBranch);
43874
- workerLog(`Pushed settings branch ${settingsBranch} to GitHub`);
43875
- } catch (err) {
43876
- const message = getErrorMessage2(err);
43877
- if (isNonFastForwardRejection(message)) {
43878
- workerLogWarn(
43879
- `Settings push for ${settingsBranch} was rejected (non-fast-forward): another CanopyCMS deployment appears to own this settings branch on GitHub. Settings from that deployment will NOT be overwritten; this deployment's local settings changes were not pushed. Rename this deployment's settings branch (config.settingsBranch or deploymentName) to resolve the collision.`
43880
- );
43881
- } else {
43882
- workerLogWarn(`Settings push for ${settingsBranch}:`, message);
43883
- }
43884
- }
43885
- } catch (err) {
43886
- workerLogWarn(
43887
- "Failed to list branches for settings push:",
43888
- err instanceof Error ? err.message : err
43889
- );
43890
- }
43891
- }
43892
- /**
43893
- * Bring `refs/heads/*` in `remote.git` toward what was just fetched into
43894
- * `GITHUB_TRACKING_REF_PREFIX` -- WITHOUT ever force-rewinding or deleting
43895
- * a local head. This is the non-destructive replacement for what the old
43896
- * `+refs/heads/*:refs/heads/*` fetch refspec used to do implicitly (and
43897
- * destructively) as part of the fetch itself; see
43898
- * `GITHUB_TRACKING_REF_PREFIX`'s doc comment for the two failure modes
43899
- * that refspec caused.
43900
- *
43901
- * Per tracked branch:
43902
- * - no local `refs/heads/<name>` yet -> create it at the tracked commit
43903
- * (a branch created on GitHub, or by another deployment sharing this
43904
- * GitHub repo, becomes visible locally).
43905
- * - local is a strict ancestor of tracked (behind) -> fast-forward it.
43906
- * - local === tracked -> nothing to do.
43907
- * - tracked is a strict ancestor of local (ahead) -> LEAVE IT ALONE. This
43908
- * is unpushed editor/settings work; the queued push task (or
43909
- * pushSettingsBranches) ships it. This is exactly the branch state the
43910
- * old refspec used to destroy.
43911
- * - neither is an ancestor of the other (diverged) -> LEAVE IT ALONE and
43912
- * count/log it. A real collision (e.g. another deployment moved the
43913
- * same branch name on GitHub); the next push attempt will be rejected
43914
- * non-fast-forward, which is the correct, visible outcome -- this
43915
- * method must never silently pick a winner. The one expected, benign
43916
- * form of this -- our own rebase loop having published a rewrite into
43917
- * remote.git with the GitHub push still queued -- is split out into the
43918
- * `rewritten` bucket so the collision warning stays meaningful.
43919
- *
43920
- * Never deletes a local head: a branch removed on GitHub simply stops
43921
- * being tracked here; the local ref persists until removed through its
43922
- * own explicit path (the sync loop must not be one of them).
43923
- *
43924
- * `remote.git` is bare, so there is no worktree to invalidate by moving
43925
- * these refs -- unlike a non-bare repo, updating the ref that happens to
43926
- * be "checked out" is a non-issue here.
43927
- *
43928
- * Concurrency: `remote.git` is bare and on EFS, and the Lambda pushes into
43929
- * it concurrently (`GitManager.push()`'s `target:target` refspec) while
43930
- * this runs. Every `update-ref` below passes the expected old value (the
43931
- * all-zeros OID for "must not exist yet" on creation, the previously-read
43932
- * SHA for the fast-forward case) so a concurrent Lambda write landing in
43933
- * the gap between the read and the write loses the ref update instead of
43934
- * being silently clobbered -- the branch is simply revisited next cycle.
43935
- */
43936
- async reconcileTrackedBranches(git) {
43937
- const GIT_ZERO_OID = "0000000000000000000000000000000000000000";
43938
- const created = [];
43939
- const fastForwarded = [];
43940
- const ahead = [];
43941
- const diverged = [];
43942
- const rewritten = [];
43943
- const raw = await git.raw([
43944
- "for-each-ref",
43945
- "--format=%(refname) %(objectname)",
43946
- "refs/heads/",
43947
- GITHUB_TRACKING_REF_PREFIX
43948
- ]);
43949
- const heads = /* @__PURE__ */ new Map();
43950
- const tracked = /* @__PURE__ */ new Map();
43951
- for (const line of raw.split("\n")) {
43952
- const trimmed2 = line.trim();
43953
- if (!trimmed2) continue;
43954
- const [refname, sha] = trimmed2.split(" ");
43955
- if (refname.startsWith("refs/heads/")) {
43956
- heads.set(refname.slice("refs/heads/".length), sha);
43957
- } else if (refname.startsWith(GITHUB_TRACKING_REF_PREFIX)) {
43958
- tracked.set(refname.slice(GITHUB_TRACKING_REF_PREFIX.length), sha);
43959
- }
43960
- }
43961
- for (const [name, trackedSha] of tracked) {
43962
- const localSha = heads.get(name);
43963
- const localRef = `refs/heads/${name}`;
43964
- if (!localSha) {
43965
- try {
43966
- await git.raw(["update-ref", localRef, trackedSha, GIT_ZERO_OID]);
43967
- created.push(name);
43968
- } catch (err) {
43969
- workerLogWarn(
43970
- ` Tracked-branch reconcile: failed to create local ref for ${name} (concurrent update?): ${getErrorMessage2(err)}`
43971
- );
43972
- }
43973
- continue;
43974
- }
43975
- if (localSha === trackedSha) continue;
43976
- try {
43977
- const counts = (await git.raw(["rev-list", "--left-right", "--count", `${localSha}...${trackedSha}`])).trim();
43978
- const [leftStr, rightStr] = counts.split(/\s+/);
43979
- const localAheadCount = parseInt(leftStr, 10);
43980
- const localBehindCount = parseInt(rightStr, 10);
43981
- if (!Number.isInteger(localAheadCount) || !Number.isInteger(localBehindCount)) {
43982
- workerLogWarn(
43983
- ` Tracked-branch reconcile: skipping ${name}: unparseable rev-list output ${JSON.stringify(counts)}`
43984
- );
43985
- continue;
43986
- }
43987
- if (localAheadCount === 0 && localBehindCount > 0) {
43988
- try {
43989
- await git.raw(["update-ref", localRef, trackedSha, localSha]);
43990
- fastForwarded.push(name);
43991
- } catch (err) {
43992
- workerLogWarn(
43993
- ` Tracked-branch reconcile: failed to fast-forward ${name} (concurrent update?): ${getErrorMessage2(err)}`
43994
- );
43995
- }
43996
- } else if (localBehindCount === 0 && localAheadCount > 0) {
43997
- ahead.push(name);
43998
- } else if (await this.hasPendingHistoryRewrite(name)) {
43999
- rewritten.push(name);
44000
- } else {
44001
- diverged.push(name);
44002
- }
44003
- } catch (err) {
44004
- workerLogWarn(
44005
- ` Tracked-branch reconcile: skipping ${name} (unreadable ref or object?): ${getErrorMessage2(err)}`
44006
- );
44007
- continue;
44008
- }
44009
- }
44010
- if (diverged.length > 0) {
44011
- workerLogWarn(
44012
- ` Tracked-branch reconcile: ${diverged.length} branch(es) diverged from GitHub and were left untouched: ${diverged.join(", ")}`
44013
- );
44014
- }
44015
- if (rewritten.length > 0) {
44016
- workerLog(
44017
- ` Tracked-branch reconcile: ${rewritten.length} branch(es) rebased locally with the GitHub push still pending: ${rewritten.join(", ")}`
44018
- );
44019
- }
44020
- return {
44021
- summary: { created, fastForwarded, ahead, diverged, rewritten },
44022
- trackedNames: new Set(tracked.keys())
44023
- };
44024
- }
44025
- /**
44026
- * Whether this branch carries a pending history rewrite -- the rebase loop
44027
- * rewrote already-published history and the GitHub push has not landed yet
44028
- * (see BranchMetadata.historyRewrittenFrom).
44029
- *
44030
- * `branchName` is a git ref name; branch workspaces are directories named
44031
- * with the sanitized form, hence the conversion. Best-effort: a settings
44032
- * branch (no workspace at all), a missing directory or an unreadable
44033
- * branch.json all mean "no known rewrite", which is the conservative
44034
- * answer -- it keeps the branch in the louder `diverged` bucket.
44035
- */
44036
- async hasPendingHistoryRewrite(branchName) {
44037
- try {
44038
- const branchPath = path13.join(this.contentBranchesPath, sanitizeBranchName(branchName));
44039
- const metaFile = await BranchMetadataFileManager.loadOnly(branchPath);
44040
- return typeof metaFile?.branch.historyRewrittenFrom === "string";
44041
- } catch {
44042
- return false;
44043
- }
44044
- }
44045
- async syncGit() {
44046
- if (!this.running) return;
44047
- workerLog("Syncing git...");
44048
- const cycleStartedAt = Date.now();
44049
- const git = simpleGit({
44050
- baseDir: this.remoteGitPath,
44051
- // DEP-H1: a hung fetch/push would stall the sync loop forever
44052
- // (scheduleLoop only reschedules after completion). The block timeout
44053
- // is inactivity-based, so a slow-but-flowing transfer is unaffected.
44054
- timeout: { block: this.taskTimeoutMs }
44055
- });
44056
- git.env(gitNetworkChildEnv());
44057
- try {
44058
- await git.raw([
44059
- "fetch",
44060
- this.buildGitHubUrl(),
44061
- "--prune",
44062
- `+refs/heads/*:${GITHUB_TRACKING_REF_PREFIX}*`
44063
- ]);
44064
- workerLog("Fetched from GitHub");
44065
- const { summary: trackedSummary, trackedNames } = await this.reconcileTrackedBranches(git);
44066
- await this.pushSettingsBranches(git, trackedNames);
44067
- await this.refreshBaseBranchWorkspace();
44068
- const rebaseSummary = await this.rebaseActiveBranches();
44069
- await cleanupOldTasks(this.taskDir, void 0, this.log);
44070
- const trashRemoved = await this.cleanupTrashedBranchDirs();
44071
- if (trashRemoved > 0) {
44072
- workerLog(`Removed ${trashRemoved} expired trashed branch dir(s)`);
44073
- }
44074
- const report = this.ensureStatusReport();
44075
- report.lastGitSyncAt = (/* @__PURE__ */ new Date()).toISOString();
44076
- delete report.lastGitSyncError;
44077
- report.lastGitSync = {
44078
- durationMs: Date.now() - cycleStartedAt,
44079
- rebased: rebaseSummary.rebased,
44080
- skippedDirty: rebaseSummary.skippedDirty,
44081
- skippedLocked: rebaseSummary.skippedLocked,
44082
- failed: rebaseSummary.failed,
44083
- tracked: trackedSummary
44084
- };
44085
- await writeWorkerStatus(this.taskDir, report).catch(
44086
- (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
44087
- );
44088
- } catch (err) {
44089
- const report = this.ensureStatusReport();
44090
- report.lastGitSyncError = {
44091
- message: redactCredentials(getErrorMessage2(err)),
44092
- at: (/* @__PURE__ */ new Date()).toISOString()
44093
- };
44094
- await writeWorkerStatus(this.taskDir, report).catch(
44095
- (writeErr) => workerLogError("Failed to write worker status:", getErrorMessage2(writeErr))
44096
- );
44097
- throw err;
44098
- }
44099
- }
44100
- /**
44101
- * [C1] Remove `.trash-*` branch directories (created by the admin purge
44102
- * action, api/admin-branch-health.ts) whose name-embedded stamp is older
44103
- * than {@link TRASH_RETENTION_MS}. Names that don't match the expected
44104
- * `.trash-{dirName}-{STAMP}` shape, or whose stamp fails to parse, are
44105
- * left alone (logged once per cycle, not per file, to avoid flooding logs
44106
- * if something odd accumulates) -- purge is the only writer of this
44107
- * naming scheme, so an unparseable name is unexpected and worth a human
44108
- * looking rather than a silent skip.
44109
- */
44110
- async cleanupTrashedBranchDirs() {
44111
- let entries;
44112
- try {
44113
- entries = await fs9.readdir(this.contentBranchesPath);
44114
- } catch (err) {
44115
- if (isNodeError(err) && err.code === "ENOENT") return 0;
44116
- throw err;
44117
- }
44118
- const now = Date.now();
44119
- let removed = 0;
44120
- let loggedUnparseable = false;
44121
- for (const name of entries) {
44122
- if (!name.startsWith(".trash-")) continue;
44123
- const stampMatch = TRASH_DIR_STAMP_RE.exec(name);
44124
- const stampDate = stampMatch ? parseTrashStamp(stampMatch[1]) : null;
44125
- if (!stampDate) {
44126
- if (!loggedUnparseable) {
44127
- workerLog(`CanopyCMS: Skipping trash dir with unparseable stamp: ${name}`);
44128
- loggedUnparseable = true;
44129
- }
44130
- continue;
44131
- }
44132
- if (now - stampDate.getTime() < TRASH_RETENTION_MS) continue;
44133
- try {
44134
- await fs9.rm(path13.join(this.contentBranchesPath, name), {
44135
- recursive: true,
44136
- force: true,
44137
- maxRetries: 3,
44138
- retryDelay: 100
44139
- });
44140
- removed++;
44141
- } catch (err) {
44142
- workerLogError(
44143
- `CanopyCMS: Failed to remove trashed branch dir ${name}:`,
44144
- getErrorMessage2(err)
44145
- );
44146
- }
44147
- }
44148
- return removed;
44149
- }
44150
- /**
44151
- * Fast-forward the base branch's own working-tree clone
44152
- * (content-branches/<baseBranch>) to match origin/<baseBranch>.
44153
- *
44154
- * Previously this clone was refreshed only incidentally, by the generic
44155
- * rebase loop below (rebaseActiveBranches): for a branch with status
44156
- * 'editing', rebasing onto origin/<baseBranch> degenerates to a
44157
- * fast-forward when the clone IS the base branch. But that loop's skip
44158
- * paths -- a dirty tree, a missing .git -- are silent, which is the
44159
- * suspected live failure mode: a wedged base clone with no diagnosable
44160
- * signal in the logs. This dedicated step makes the refresh explicit,
44161
- * ff-only, and loud, so a stuck base view (an editor forking a new branch
44162
- * "from base" that's actually a stale snapshot) is diagnosable from logs.
44163
- * This runs every sync cycle so the drift window is bounded by
44164
- * gitSyncInterval.
44165
- *
44166
- * ff-only on purpose: this clone must stay a linear mirror of
44167
- * origin/<baseBranch>, so a merge that isn't a fast-forward (diverged
44168
- * local history) is treated as a should-never-happen condition and left
44169
- * untouched rather than force-resolved.
44170
- */
44171
- async refreshBaseBranchWorkspace() {
44172
- const basePath32 = path13.join(this.contentBranchesPath, this.sanitizedBaseBranch);
44173
- const gitDir = path13.join(basePath32, ".git");
44174
- try {
44175
- let gitDirStat;
44176
- try {
44177
- gitDirStat = await fs9.stat(gitDir);
44178
- } catch {
44179
- gitDirStat = null;
44180
- }
44181
- if (!gitDirStat || !gitDirStat.isDirectory()) {
44182
- workerLog(
44183
- `Base branch workspace (${this.baseBranch}): not yet provisioned, skipping refresh`
44184
- );
44185
- return;
44186
- }
44187
- const baseGit = simpleGit({
44188
- baseDir: basePath32,
44189
- // Keep git non-interactive during the merge so it never blocks on an
44190
- // editor. simple-git >=3.32 blocks setting core.editor unless
44191
- // explicitly opted in; the value here is a hardcoded literal
44192
- // ("true", the shell no-op), not user input, so enabling
44193
- // allowUnsafeEditor carries no injection risk (same as the rebase
44194
- // loop's git config below).
44195
- config: ["core.editor=true"],
44196
- unsafe: { allowUnsafeEditor: true },
44197
- // DEP-H1: a hung fetch/merge against this EFS-backed clone would
44198
- // stall the sync loop forever (scheduleLoop only reschedules after
44199
- // completion). The block timeout is inactivity-based, so a
44200
- // slow-but-flowing transfer is unaffected -- same as syncGit()'s
44201
- // remote handle.
44202
- timeout: { block: this.taskTimeoutMs }
44203
- });
44204
- const status = await baseGit.status();
44205
- const trackedDirty = status.files.filter((f5) => f5.index !== "?" || f5.working_dir !== "?");
44206
- if (trackedDirty.length > 0) {
44207
- workerLogError(
44208
- `Base branch workspace (${this.baseBranch}) has uncommitted changes -- skipping refresh. Dirty files: ${trackedDirty.map((f5) => f5.path).join(", ")}`
44209
- );
44210
- return;
44211
- }
44212
- await baseGit.fetch("origin", this.baseBranch);
44213
- const fetchedTip = (await baseGit.revparse(["FETCH_HEAD"])).trim();
44214
- const behindCount = parseInt(
44215
- (await baseGit.raw(["rev-list", "--count", `HEAD..${fetchedTip}`])).trim(),
44216
- 10
44217
- );
44218
- if (behindCount > 0) {
44219
- try {
44220
- await baseGit.merge(["--ff-only", fetchedTip]);
44221
- } catch (err) {
44222
- workerLogError(
44223
- `Base branch workspace (${this.baseBranch}) failed to fast-forward (diverged local history?): ${getErrorMessage2(err)}`
44224
- );
44225
- return;
44226
- }
44227
- await invalidateBranchContentCaches(basePath32);
44228
- }
44229
- const currentMeta = await BranchMetadataFileManager.loadOnly(basePath32);
44230
- const conflictStatus = currentMeta?.branch.conflictStatus;
44231
- const conflictFiles = currentMeta?.branch.conflictFiles;
44232
- const alreadyClean = (conflictStatus === void 0 || conflictStatus === "clean") && (conflictFiles === void 0 || conflictFiles.length === 0);
44233
- if (!alreadyClean) {
44234
- const meta = getBranchMetadataFileManager(basePath32, this.contentBranchesPath);
44235
- await meta.save({
44236
- branch: { name: this.baseBranch, conflictStatus: "clean", conflictFiles: [] }
44237
- });
44238
- }
44239
- workerLog(
44240
- behindCount > 0 ? `Base branch workspace (${this.baseBranch}): fast-forwarded ${behindCount} commit(s)` : `Base branch workspace (${this.baseBranch}): up to date`
44241
- );
44242
- } catch (err) {
44243
- workerLogError(
44244
- `Base branch workspace (${this.baseBranch}) refresh failed: ${getErrorMessage2(err)}`
44245
- );
44246
- }
44247
- }
44248
- /**
44249
- * Poll GitHub for a submitted/approved branch's PR resolution.
44250
- *
44251
- * submitted/approved branches sit outside the rebase loop and get no
44252
- * other signal that their PR resolved on GitHub -- nothing pushes a
44253
- * merge/close webhook back into the branch workspace. merged ->
44254
- * auto-archive via buildMergedBranchUpdate (shared with the manual
44255
- * markAsMerged API so both paths produce identical archived-branch
44256
- * metadata). closed-without-merge -> record pullRequestState only; an
44257
- * admin decides the workflow transition from there. Best-effort: any
44258
- * failure here is logged and swallowed, retried next sync cycle.
44259
- */
44260
- async pollMergeState(branchDir, branchPath, metaFile) {
44261
- const prNumber = metaFile?.branch.pullRequestNumber;
44262
- if (!prNumber) return;
44263
- const controller = new AbortController();
44264
- const timer = setTimeout(() => controller.abort(), this.taskTimeoutMs);
44265
- try {
44266
- const { data: data2 } = await this.octokit.pulls.get({
44267
- owner: this.config.githubOwner,
44268
- repo: this.config.githubRepo,
44269
- pull_number: prNumber,
44270
- request: { signal: controller.signal }
44271
- });
44272
- if (data2.merged) {
44273
- const meta2 = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
44274
- await meta2.save({
44275
- branch: buildMergedBranchUpdate(
44276
- branchDir,
44277
- data2.merged_at ? new Date(data2.merged_at) : void 0
44278
- )
44279
- });
44280
- workerLog(` PR #${prNumber} for ${branchDir} is merged -> archived`);
44281
- return;
44282
- }
44283
- const newState = data2.state === "closed" ? "closed" : "open";
44284
- const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
44285
- if (currentMeta?.branch.pullRequestState === newState) return;
44286
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
44287
- await meta.save({ branch: { name: branchDir, pullRequestState: newState } });
44288
- workerLog(` PR #${prNumber} for ${branchDir}: pullRequestState -> ${newState}`);
44289
- } catch (err) {
44290
- workerLogWarn(` Failed to poll PR #${prNumber} for ${branchDir}: ${getErrorMessage2(err)}`);
44291
- } finally {
44292
- clearTimeout(timer);
44293
- }
44294
- }
44295
- /**
44296
- * Persist a per-branch rebase failure to branch.json (PR-W2), bounded to
44297
- * roughly one save per failing branch per hour: a branch stuck failing
44298
- * every cycle must not turn into unbounded save-per-cycle x N-failing-
44299
- * branches write amplification -- save() eager-regenerates the branch
44300
- * registry (branch-metadata.ts's invalidateRegistry(), O(branch count) EFS
44301
- * reads), the same concern the `alreadyClean` no-op guard above exists
44302
- * for.
44303
- *
44304
- * Best-effort and non-fatal like every other metadata write in this
44305
- * loop's error paths: a corrupt branch.json, a lock-contention error, or
44306
- * any other save failure here must never abort the per-branch iteration.
44307
- * This matters doubly at the two call sites -- one is inside the outer
44308
- * per-branch catch, with no further catch of its own around this call --
44309
- * so the whole method is wrapped, not just the load.
44310
- */
44311
- async recordRebaseFailure(branchPath, branchDir, message) {
44312
- const RECORD_REFRESH_MS = 60 * 60 * 1e3;
44313
- const redactedMessage = redactCredentials(message);
44314
- try {
44315
- const existing = await BranchMetadataFileManager.loadOnly(branchPath);
44316
- const prior = existing?.branch.rebaseFailure;
44317
- const sameMessage = prior?.message === redactedMessage;
44318
- const now = /* @__PURE__ */ new Date();
44319
- if (sameMessage) {
44320
- const lastAtMs = Date.parse(prior.lastAt);
44321
- if (!Number.isNaN(lastAtMs) && now.getTime() - lastAtMs < RECORD_REFRESH_MS) {
44322
- return;
44323
- }
44324
- }
44325
- const nowIso = now.toISOString();
44326
- const firstAt = sameMessage ? prior.firstAt : nowIso;
44327
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
44328
- await meta.save({
44329
- branch: {
44330
- name: branchDir,
44331
- rebaseFailure: { message: redactedMessage, firstAt, lastAt: nowIso }
44332
- }
44333
- });
44334
- } catch (err) {
44335
- workerLogWarn(` Failed to record rebase failure for ${branchDir}: ${getErrorMessage2(err)}`);
44336
- }
44515
+ return path17.join(this.contentBranchesPath, sanitizeBranchName(branchRefName));
44337
44516
  }
44338
44517
  /**
44339
44518
  * Test-only seam: awaited inside `rebaseActiveBranches()`'s conflict round,
@@ -44358,307 +44537,48 @@ var CmsWorker = class {
44358
44537
  */
44359
44538
  async afterRebaseCompletedForTesting() {
44360
44539
  }
44540
+ // --- Rebase loop (worker/rebase.ts) ------------------------------------
44541
+ //
44542
+ // Both are TEST-ONLY entry points -- see the task-queue block above. Nothing
44543
+ // in production dispatches through either; `syncGit` calls `runRebaseCycle`
44544
+ // directly and that calls `pollMergeState` directly. Four test files call
44545
+ // `rebaseActiveBranches` (it is the single entry point the whole rebase suite
44546
+ // drives), and cms-worker-merge-poll.test.ts calls `pollMergeState` with a
44547
+ // mocked `octokit`. Neither is on WorkerContext, so a stub installed on
44548
+ // either is a no-op as far as a production code path is concerned.
44549
+ //
44550
+ // `rebaseActiveBranches` also has a consumer outside the test files:
44551
+ // apps/test-app/app/api/e2e-test/rebase/route.ts drives it from an e2e
44552
+ // fixture route. Do not delete it as unused.
44361
44553
  async rebaseActiveBranches() {
44362
- const rebased = [];
44363
- const skippedDirty = [];
44364
- const skippedLocked = [];
44365
- const failed = [];
44366
- let branchDirs;
44367
- try {
44368
- branchDirs = await fs9.readdir(this.contentBranchesPath);
44369
- } catch {
44370
- return { rebased, skippedDirty, skippedLocked, failed };
44371
- }
44372
- for (const branchDir of branchDirs) {
44373
- if (branchDir.startsWith(".") || branchDir === "branches.json") {
44374
- continue;
44375
- }
44376
- const branchPath = path13.join(this.contentBranchesPath, branchDir);
44377
- const gitDir = path13.join(branchPath, ".git");
44378
- try {
44379
- const stat = await fs9.stat(gitDir);
44380
- if (!stat.isDirectory()) {
44381
- workerLog(` Skipping ${branchDir}: .git is not a directory`);
44382
- continue;
44383
- }
44384
- } catch {
44385
- workerLog(` Skipping ${branchDir}: no .git directory (not a branch workspace)`);
44386
- continue;
44387
- }
44388
- if (branchDir === this.sanitizedBaseBranch) {
44389
- workerLog(` Skipping ${branchDir}: base branch (refreshed separately)`);
44390
- continue;
44391
- }
44392
- try {
44393
- const metaFile = await BranchMetadataFileManager.loadOnly(branchPath);
44394
- const branchStatus2 = metaFile?.branch.status;
44395
- if (branchStatus2 === "submitted" || branchStatus2 === "approved") {
44396
- workerLog(` Skipping ${branchDir} (${branchStatus2})`);
44397
- await this.pollMergeState(branchDir, branchPath, metaFile);
44398
- continue;
44399
- }
44400
- if (branchStatus2 === "archived") {
44401
- workerLog(` Skipping ${branchDir} (${branchStatus2})`);
44402
- continue;
44403
- }
44404
- const branchGit = simpleGit({
44405
- baseDir: branchPath,
44406
- // Keep git non-interactive during rebase/merge so it never blocks on an editor.
44407
- // simple-git >=3.32 blocks setting core.editor unless explicitly opted in; the
44408
- // value here is a hardcoded literal ("true", the shell no-op), not user input,
44409
- // so enabling allowUnsafeEditor carries no injection risk.
44410
- config: ["core.editor=true"],
44411
- unsafe: { allowUnsafeEditor: true }
44412
- });
44413
- let releaseContentLock;
44414
- let contentLockCompromised = false;
44415
- try {
44416
- releaseContentLock = await tryAcquireContentWriteLock(branchPath, (lockErr) => {
44417
- contentLockCompromised = true;
44418
- workerLogWarn(
44419
- ` Content-write lock compromised mid-rebase for ${branchDir}: ${getErrorMessage2(lockErr)}`
44420
- );
44421
- });
44422
- } catch (lockErr) {
44423
- if (isNodeError(lockErr) && lockErr.code === "ELOCKED") {
44424
- workerLog(` Skipping ${branchDir}: content write in progress (retrying next cycle)`);
44425
- skippedLocked.push(branchDir);
44426
- continue;
44427
- }
44428
- throw lockErr;
44429
- }
44430
- try {
44431
- if (await isRebaseInProgress(branchPath)) {
44432
- const preAbort = await branchGit.status().catch(() => null);
44433
- const collateral = (preAbort?.files ?? []).filter((f5) => !preAbort?.conflicted.includes(f5.path)).filter((f5) => f5.working_dir !== " " && f5.working_dir !== "?").map((f5) => f5.path);
44434
- if (collateral.length > 0) {
44435
- workerLogWarn(
44436
- ` ${branchDir}: aborting the interrupted rebase will DISCARD working-tree changes to ${collateral.length} file(s) saved while the worker was down: ${collateral.join(", ")}`
44437
- );
44438
- }
44439
- workerLogWarn(
44440
- ` ${branchDir}: found an interrupted rebase (this worker's own abandoned work) -- aborting it to recover the branch`
44441
- );
44442
- try {
44443
- await branchGit.rebase(["--abort"]);
44444
- } catch (abortErr) {
44445
- const reason = redactCredentials(
44446
- `could not abort interrupted rebase: ${getErrorMessage2(abortErr)}`
44447
- );
44448
- workerLogWarn(` Skipping ${branchDir}: ${reason}`);
44449
- failed.push({ branch: branchDir, error: reason });
44450
- await this.recordRebaseFailure(branchPath, branchDir, reason);
44451
- continue;
44452
- }
44453
- }
44454
- const dirtyCheck = await branchGit.status();
44455
- if (dirtyCheck.files.length > 0) {
44456
- workerLog(` Skipping ${branchDir}: has uncommitted changes`);
44457
- skippedDirty.push(branchDir);
44458
- continue;
44459
- }
44460
- const branchRef = (await branchGit.revparse(["--abbrev-ref", "HEAD"])).trim();
44461
- const canPublish = branchRef.length > 0 && branchRef !== "HEAD";
44462
- if (canPublish && metaFile?.branch.historyRewrittenFrom) {
44463
- await this.reconcilePendingRewrite({
44464
- branchPath,
44465
- branchDir,
44466
- branchRef,
44467
- headSha: (await branchGit.revparse(["HEAD"])).trim(),
44468
- marker: metaFile.branch.historyRewrittenFrom
44469
- });
44470
- }
44471
- await branchGit.fetch("origin", this.baseBranch);
44472
- const fetchedBaseTip = (await branchGit.revparse(["FETCH_HEAD"])).trim();
44473
- const behindCount = parseInt(
44474
- (await branchGit.raw(["rev-list", "--count", `HEAD..${fetchedBaseTip}`])).trim(),
44475
- 10
44476
- );
44477
- const meta = getBranchMetadataFileManager(branchPath, this.contentBranchesPath);
44478
- if (behindCount === 0) {
44479
- const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
44480
- const conflictStatus = currentMeta?.branch.conflictStatus;
44481
- const conflictFiles = currentMeta?.branch.conflictFiles;
44482
- const conflictAlreadyClean = (conflictStatus === void 0 || conflictStatus === "clean") && (conflictFiles === void 0 || conflictFiles.length === 0);
44483
- const alreadyClean = conflictAlreadyClean && currentMeta?.branch.rebaseFailure === void 0;
44484
- if (alreadyClean) {
44485
- continue;
44486
- }
44487
- await meta.save({
44488
- branch: {
44489
- name: branchDir,
44490
- conflictStatus: "clean",
44491
- conflictFiles: [],
44492
- rebaseFailure: void 0
44493
- }
44494
- });
44495
- continue;
44496
- }
44497
- workerLog(`Rebasing ${branchDir} (${behindCount} commits behind)...`);
44498
- const preRebaseHead = (await branchGit.revparse(["HEAD"])).trim();
44499
- const publishedSha = canPublish ? await this.readPublishedSha(branchRef) : null;
44500
- const conflictedFiles = [];
44501
- let nextAction = "start";
44502
- let completed = false;
44503
- let failureReason;
44504
- const MAX_ROUNDS = 50;
44505
- for (let round = 0; round < MAX_ROUNDS && !completed; round++) {
44506
- if (contentLockCompromised) break;
44507
- try {
44508
- if (nextAction === "start") {
44509
- await branchGit.rebase([fetchedBaseTip]);
44510
- } else if (nextAction === "continue") {
44511
- await branchGit.rebase(["--continue"]);
44512
- } else {
44513
- await branchGit.rebase(["--skip"]);
44514
- }
44515
- completed = true;
44516
- } catch (rebaseErr) {
44517
- nextAction = "continue";
44518
- const st = await branchGit.status();
44519
- if (st.conflicted.length > 0) {
44520
- await this.afterConflictDetectedForTesting();
44521
- const conflictKind = new Map(
44522
- st.files.map((f5) => [f5.path, `${f5.index}${f5.working_dir}`])
44523
- );
44524
- let resolutionFailure;
44525
- for (const file of st.conflicted) {
44526
- const kind = conflictKind.get(file);
44527
- try {
44528
- if (kind === "UD") {
44529
- await branchGit.raw(["rm", "-f", "--", file]);
44530
- } else if (kind === "DU") {
44531
- await branchGit.add(file);
44532
- } else {
44533
- await branchGit.raw(["checkout", "--theirs", file]);
44534
- await branchGit.add(file);
44535
- }
44536
- } catch (resolveErr) {
44537
- resolutionFailure = `failed to resolve conflicted file '${file}' (status ${kind ?? "??"}): ` + getErrorMessage2(resolveErr);
44538
- break;
44539
- }
44540
- conflictedFiles.push(file);
44541
- }
44542
- if (resolutionFailure !== void 0) {
44543
- failureReason = resolutionFailure;
44544
- break;
44545
- }
44546
- } else {
44547
- const msg = rebaseErr instanceof Error ? rebaseErr.message : "";
44548
- if (msg.toLowerCase().includes("nothing to commit") || msg.toLowerCase().includes("apply --skip")) {
44549
- nextAction = "skip";
44550
- } else {
44551
- workerLogWarn(
44552
- ` Unexpected rebase error in ${branchDir}: ${msg || "Unknown error"}`
44553
- );
44554
- failureReason = msg || "Unknown error";
44555
- await branchGit.rebase(["--abort"]).catch(() => {
44556
- });
44557
- break;
44558
- }
44559
- }
44560
- }
44561
- }
44562
- if (completed) await this.afterRebaseCompletedForTesting();
44563
- if (contentLockCompromised && !completed) {
44564
- workerLogWarn(
44565
- ` Skipping ${branchDir}: content-write lock was compromised mid-rebase (retrying next cycle)`
44566
- );
44567
- await branchGit.rebase(["--abort"]).catch(() => {
44568
- });
44569
- skippedLocked.push(branchDir);
44570
- continue;
44571
- }
44572
- if (contentLockCompromised) {
44573
- workerLogWarn(
44574
- ` Content-write lock for ${branchDir} was compromised, but its rebase had already completed -- finishing the sync (history is rewritten; skipping now would strand the history-rewrite marker and wedge the branch)`
44575
- );
44576
- }
44577
- if (!completed) {
44578
- workerLogWarn(
44579
- failureReason !== void 0 ? ` Rebase of ${branchDir} aborted due to unexpected error: ${failureReason}` : ` Rebase of ${branchDir} did not complete within ${MAX_ROUNDS} rounds, aborting`
44580
- );
44581
- await branchGit.rebase(["--abort"]).catch(() => {
44582
- });
44583
- const rebaseFailureMessage = failureReason ?? `did not complete within ${MAX_ROUNDS} rounds`;
44584
- const redactedRebaseFailureMessage = redactCredentials(rebaseFailureMessage);
44585
- failed.push({
44586
- branch: branchDir,
44587
- error: redactedRebaseFailureMessage
44588
- });
44589
- await this.recordRebaseFailure(branchPath, branchDir, redactedRebaseFailureMessage);
44590
- continue;
44591
- }
44592
- await invalidateBranchContentCaches(branchPath);
44593
- const normalizedContentRoot = normalizeFilesystemPath(this.contentRoot);
44594
- const conflictIds = [...new Set(conflictedFiles)].map((f5) => {
44595
- const fileId = extractIdFromFilename(path13.basename(f5));
44596
- if (fileId) return fileId;
44597
- const parentDir = path13.basename(path13.dirname(f5));
44598
- const dirId = extractIdFromFilename(parentDir);
44599
- if (dirId) return dirId;
44600
- const parentPath = normalizeFilesystemPath(path13.dirname(f5));
44601
- if (path13.basename(f5) === ".collection.json" && parentPath === normalizedContentRoot) {
44602
- return ROOT_COLLECTION_ID;
44603
- }
44604
- return null;
44605
- }).filter((id) => id !== null);
44606
- const conflictIdsDeduped = [...new Set(conflictIds)];
44607
- const hadConflicts = conflictIdsDeduped.length > 0;
44608
- workerLog(
44609
- hadConflicts ? ` Rebased ${branchDir} (kept branch version for ${conflictIdsDeduped.length} conflicting file(s))` : ` Rebased ${branchDir} successfully`
44610
- );
44611
- await meta.save({
44612
- branch: {
44613
- name: branchDir,
44614
- conflictStatus: hadConflicts ? "conflicts-detected" : "clean",
44615
- conflictFiles: conflictIdsDeduped,
44616
- // PR-W2: the cycle completed successfully -- clear any prior
44617
- // failure record regardless of conflict outcome.
44618
- rebaseFailure: void 0
44619
- }
44620
- });
44621
- rebased.push(branchDir);
44622
- if (publishedSha !== null) {
44623
- if (publishedSha === preRebaseHead) {
44624
- await this.markHistoryRewritten(branchPath, branchDir, publishedSha);
44625
- if (await this.forcePublishToLocalRemote(branchPath, branchRef, publishedSha)) {
44626
- await this.enqueueGitHubPush(branchRef);
44627
- }
44628
- } else {
44629
- const divergence = `rebased locally, but remote.git holds ${publishedSha} for ${branchRef}, which this clone never had (a direct push to the branch?). Left untouched -- reconcile it before submitting again.`;
44630
- workerLogWarn(` ${branchDir}: ${divergence}`);
44631
- await this.recordRebaseFailure(branchPath, branchDir, divergence);
44632
- }
44633
- }
44634
- } finally {
44635
- try {
44636
- if (await isRebaseInProgress(branchPath)) {
44637
- workerLogWarn(
44638
- ` ${branchDir}: rebase still in progress on exit -- aborting so the clone is not left wedged`
44639
- );
44640
- await branchGit.rebase(["--abort"]);
44641
- }
44642
- } catch (abortErr) {
44643
- workerLogWarn(
44644
- ` Failed to abort in-progress rebase for ${branchDir}: ${getErrorMessage2(abortErr)}`
44645
- );
44646
- }
44647
- await releaseContentLock?.().catch((releaseErr) => {
44648
- workerLogWarn(
44649
- ` Failed to release content-write lock for ${branchDir}: ${getErrorMessage2(releaseErr)}`
44650
- );
44651
- });
44652
- }
44653
- } catch (err) {
44654
- const message = err instanceof Error ? err.message : "Unknown error";
44655
- workerLogWarn(` Failed to sync ${branchDir}: ${message}`);
44656
- const redactedMessage = redactCredentials(message);
44657
- failed.push({ branch: branchDir, error: redactedMessage });
44658
- await this.recordRebaseFailure(branchPath, branchDir, redactedMessage);
44659
- }
44660
- }
44661
- return { rebased, skippedDirty, skippedLocked, failed };
44554
+ return runRebaseCycle(this.ctx());
44555
+ }
44556
+ // --- Git-sync cluster (worker/git-sync.ts) -----------------------------
44557
+ //
44558
+ // `syncGit` is the public loop entry `scheduleLoop` drives. The three private
44559
+ // ones below it are TEST-ONLY entry points -- see the task-queue block above
44560
+ // -- each called directly by one test file (cms-worker-sync-reconcile for the
44561
+ // settings push, cms-worker-base-refresh for the base workspace,
44562
+ // cms-worker.test.ts for the trash sweep). `syncGit` reaches all three as
44563
+ // module-level calls, so a stub installed on one of these is a no-op.
44564
+ //
44565
+ // `reconcileTrackedBranches` deliberately has NO delegator: no test reaches
44566
+ // it through the instance. Comments elsewhere that point at it as living in
44567
+ // this file are stale by definition -- it is in git-sync.ts.
44568
+ async syncGit() {
44569
+ return syncGit(this.ctx());
44570
+ }
44571
+ async pushSettingsBranches(git, trackedNames) {
44572
+ return pushSettingsBranches(this.ctx(), git, trackedNames);
44573
+ }
44574
+ async refreshBaseBranchWorkspace() {
44575
+ return refreshBaseBranchWorkspace(this.ctx());
44576
+ }
44577
+ async cleanupTrashedBranchDirs() {
44578
+ return cleanupTrashedBranchDirs(this.ctx());
44579
+ }
44580
+ async pollMergeState(branchDir, branchPath, metaFile) {
44581
+ return pollMergeState(this.ctx(), branchDir, branchPath, metaFile);
44662
44582
  }
44663
44583
  async refreshAuthCache() {
44664
44584
  if (!this.running || !this.config.refreshAuthCache) return;
@@ -47853,10 +47773,10 @@ var SessionAPI = class extends AbstractAPI {
47853
47773
  */
47854
47774
  async getToken(sessionId, template, expiresInSeconds) {
47855
47775
  this.requireId(sessionId);
47856
- const path16 = template ? joinPaths(basePath24, sessionId, "tokens", template) : joinPaths(basePath24, sessionId, "tokens");
47776
+ const path20 = template ? joinPaths(basePath24, sessionId, "tokens", template) : joinPaths(basePath24, sessionId, "tokens");
47857
47777
  const requestOptions = {
47858
47778
  method: "POST",
47859
- path: path16
47779
+ path: path20
47860
47780
  };
47861
47781
  if (expiresInSeconds !== void 0) {
47862
47782
  requestOptions.bodyParams = { expires_in_seconds: expiresInSeconds };
@@ -49978,12 +49898,12 @@ function buildRequest(options) {
49978
49898
  userAgent = USER_AGENT,
49979
49899
  skipApiVersionInUrl = false
49980
49900
  } = options;
49981
- const { path: path16, method, queryParams, headerParams, bodyParams, formData, options: opts } = requestOptions;
49901
+ const { path: path20, method, queryParams, headerParams, bodyParams, formData, options: opts } = requestOptions;
49982
49902
  const { deepSnakecaseBodyParamKeys = false } = opts || {};
49983
49903
  if (requireSecretKey) {
49984
49904
  assertValidSecretKey(secretKey);
49985
49905
  }
49986
- const url = skipApiVersionInUrl ? joinPaths(apiUrl, path16) : joinPaths(apiUrl, apiVersion, path16);
49906
+ const url = skipApiVersionInUrl ? joinPaths(apiUrl, path20) : joinPaths(apiUrl, apiVersion, path20);
49987
49907
  const finalUrl = new URL(url);
49988
49908
  if (queryParams) {
49989
49909
  const snakecasedQueryParams = snakecase_keys_default({ ...queryParams });
@@ -52045,38 +51965,38 @@ function createClerkClient(options) {
52045
51965
  }
52046
51966
 
52047
51967
  // ../canopycms/src/auth/file-based-auth-cache.ts
52048
- import fs10 from "node:fs/promises";
52049
- import path14 from "node:path";
51968
+ import fs14 from "node:fs/promises";
51969
+ import path18 from "node:path";
52050
51970
  var log5 = createDebugLogger({ prefix: "FileBasedAuthCache" });
52051
51971
  async function writeAuthCacheSnapshot(cachePath, files) {
52052
- await fs10.mkdir(cachePath, { recursive: true });
51972
+ await fs14.mkdir(cachePath, { recursive: true });
52053
51973
  const timestamp2 = Date.now();
52054
- const snapshotDir = path14.join(cachePath, `snapshot-${timestamp2}`);
52055
- await fs10.mkdir(snapshotDir, { recursive: true });
51974
+ const snapshotDir = path18.join(cachePath, `snapshot-${timestamp2}`);
51975
+ await fs14.mkdir(snapshotDir, { recursive: true });
52056
51976
  for (const [fileName, data2] of Object.entries(files)) {
52057
- const tmpPath = path14.join(snapshotDir, `${fileName}.tmp`);
52058
- const finalPath = path14.join(snapshotDir, fileName);
52059
- await fs10.writeFile(tmpPath, JSON.stringify(data2, null, 2), "utf-8");
52060
- await fs10.rename(tmpPath, finalPath);
52061
- }
52062
- const currentLink = path14.join(cachePath, "current");
52063
- const tmpLink = path14.join(cachePath, `current-${timestamp2}`);
52064
- await fs10.symlink(path14.basename(snapshotDir), tmpLink);
52065
- await fs10.rename(tmpLink, currentLink);
51977
+ const tmpPath = path18.join(snapshotDir, `${fileName}.tmp`);
51978
+ const finalPath = path18.join(snapshotDir, fileName);
51979
+ await fs14.writeFile(tmpPath, JSON.stringify(data2, null, 2), "utf-8");
51980
+ await fs14.rename(tmpPath, finalPath);
51981
+ }
51982
+ const currentLink = path18.join(cachePath, "current");
51983
+ const tmpLink = path18.join(cachePath, `current-${timestamp2}`);
51984
+ await fs14.symlink(path18.basename(snapshotDir), tmpLink);
51985
+ await fs14.rename(tmpLink, currentLink);
52066
51986
  await cleanupOldSnapshots(cachePath, 2);
52067
51987
  return snapshotDir;
52068
51988
  }
52069
51989
  async function cleanupOldSnapshots(cachePath, keepCount) {
52070
51990
  let entries;
52071
51991
  try {
52072
- entries = await fs10.readdir(cachePath);
51992
+ entries = await fs14.readdir(cachePath);
52073
51993
  } catch {
52074
51994
  return;
52075
51995
  }
52076
51996
  const snapshots = entries.filter((e5) => e5.startsWith("snapshot-")).sort().reverse();
52077
51997
  for (const snapshot of snapshots.slice(keepCount)) {
52078
51998
  try {
52079
- await fs10.rm(path14.join(cachePath, snapshot), {
51999
+ await fs14.rm(path18.join(cachePath, snapshot), {
52080
52000
  recursive: true,
52081
52001
  force: true
52082
52002
  });
@@ -52170,7 +52090,7 @@ async function refreshClerkCache(options) {
52170
52090
  }
52171
52091
 
52172
52092
  // worker/index.ts
52173
- import path15 from "node:path";
52093
+ import path19 from "node:path";
52174
52094
  async function getSecret(secretArn, retries = 3) {
52175
52095
  const { SecretsManagerClient, GetSecretValueCommand } = await Promise.resolve().then(() => __toESM(require_dist_cjs53(), 1));
52176
52096
  const client = new SecretsManagerClient({});
@@ -52209,7 +52129,7 @@ async function main() {
52209
52129
  if (!clerkSecretKey && process.env.CLERK_SECRET_KEY_SECRET_ARN) {
52210
52130
  clerkSecretKey = await getSecret(process.env.CLERK_SECRET_KEY_SECRET_ARN);
52211
52131
  }
52212
- const cachePath = path15.join(workspacePath, ".cache");
52132
+ const cachePath = path19.join(workspacePath, ".cache");
52213
52133
  const refreshAuthCache = clerkSecretKey ? async () => {
52214
52134
  const result = await refreshClerkCache({
52215
52135
  secretKey: clerkSecretKey,