percher 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/bin/percher.js +1903 -972
  2. package/dist/package.json +4 -4
  3. package/dist/src/cli-utils.d.ts +1 -0
  4. package/dist/src/cli-utils.d.ts.map +1 -1
  5. package/dist/src/cli-utils.js +9 -0
  6. package/dist/src/cli-utils.js.map +1 -1
  7. package/dist/src/commands/apps.d.ts.map +1 -1
  8. package/dist/src/commands/apps.js +7 -14
  9. package/dist/src/commands/apps.js.map +1 -1
  10. package/dist/src/commands/data-records.d.ts +20 -0
  11. package/dist/src/commands/data-records.d.ts.map +1 -0
  12. package/dist/src/commands/data-records.js +183 -0
  13. package/dist/src/commands/data-records.js.map +1 -0
  14. package/dist/src/commands/data.d.ts.map +1 -1
  15. package/dist/src/commands/data.js +18 -2
  16. package/dist/src/commands/data.js.map +1 -1
  17. package/dist/src/commands/deploys.d.ts.map +1 -1
  18. package/dist/src/commands/deploys.js +9 -1
  19. package/dist/src/commands/deploys.js.map +1 -1
  20. package/dist/src/commands/telemetry.d.ts +4 -0
  21. package/dist/src/commands/telemetry.d.ts.map +1 -0
  22. package/dist/src/commands/telemetry.js +27 -0
  23. package/dist/src/commands/telemetry.js.map +1 -0
  24. package/dist/src/index.d.ts +6 -1
  25. package/dist/src/index.d.ts.map +1 -1
  26. package/dist/src/index.js +9 -3
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/telemetry.d.ts +28 -3
  29. package/dist/src/telemetry.d.ts.map +1 -1
  30. package/dist/src/telemetry.js +107 -24
  31. package/dist/src/telemetry.js.map +1 -1
  32. package/dist/templates/astro/Dockerfile +10 -4
  33. package/dist/templates/capsule/Dockerfile +10 -4
  34. package/dist/templates/eleventy/Dockerfile +10 -4
  35. package/dist/templates/hugo/Dockerfile +10 -4
  36. package/dist/templates/preact/Dockerfile +10 -4
  37. package/dist/templates/solid/Dockerfile +10 -4
  38. package/dist/templates/vite-react/Dockerfile +10 -4
  39. package/dist/templates/vite-vue/Dockerfile +10 -4
  40. package/package.json +4 -4
@@ -4751,7 +4751,7 @@ var init_build_env_prefixes = __esm(() => {
4751
4751
  });
4752
4752
 
4753
4753
  // ../client/dist/config.js
4754
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
4754
+ import { chmodSync, closeSync, existsSync as existsSync4, fchmodSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
4755
4755
  import { homedir } from "node:os";
4756
4756
  import { dirname as dirname4, join as join6 } from "node:path";
4757
4757
  function home() {
@@ -4800,12 +4800,32 @@ function readAnonClaims() {
4800
4800
  }
4801
4801
  function saveConfig(partial2) {
4802
4802
  const p = configPath();
4803
- mkdirSync4(dirname4(p), { recursive: true });
4803
+ mkdirSync4(dirname4(p), { recursive: true, mode: 448 });
4804
+ if (process.platform !== "win32") {
4805
+ chmodSync(dirname4(p), 448);
4806
+ if (existsSync4(p))
4807
+ chmodSync(p, 384);
4808
+ }
4804
4809
  const current = readFile2();
4805
4810
  const merged = { ...current, ...partial2 };
4806
4811
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
4807
- writeFileSync4(tmp, JSON.stringify(merged, null, 2), "utf8");
4808
- renameSync(tmp, p);
4812
+ let fd;
4813
+ let created = false;
4814
+ try {
4815
+ fd = openSync(tmp, "wx", 384);
4816
+ created = true;
4817
+ if (process.platform !== "win32")
4818
+ fchmodSync(fd, 384);
4819
+ writeFileSync4(fd, JSON.stringify(merged, null, 2), "utf8");
4820
+ closeSync(fd);
4821
+ fd = undefined;
4822
+ renameSync(tmp, p);
4823
+ } finally {
4824
+ if (fd !== undefined)
4825
+ closeSync(fd);
4826
+ if (created)
4827
+ rmSync2(tmp, { force: true });
4828
+ }
4809
4829
  }
4810
4830
  function getAnonClaim(app) {
4811
4831
  return readAnonClaims()[app] ?? null;
@@ -4830,7 +4850,7 @@ var package_default;
4830
4850
  var init_package = __esm(() => {
4831
4851
  package_default = {
4832
4852
  name: "@percher/client",
4833
- version: "1.1.0",
4853
+ version: "1.3.0",
4834
4854
  description: "Official TypeScript SDK for Percher: deploy apps, manage env vars, stream logs, and automate hosting workflows.",
4835
4855
  keywords: [
4836
4856
  "percher",
@@ -4871,7 +4891,7 @@ var init_package = __esm(() => {
4871
4891
  typecheck: "tsc --noEmit"
4872
4892
  },
4873
4893
  dependencies: {
4874
- "@percher/toml": "^1.0.3"
4894
+ "@percher/toml": "^1.0.4"
4875
4895
  },
4876
4896
  devDependencies: {
4877
4897
  "bun-types": "^1.1.0",
@@ -5384,10 +5404,11 @@ class AppsResource {
5384
5404
  deleteIconImage(appRef) {
5385
5405
  return this.http.deleteJson(`/apps/${encodeURIComponent(appRef)}/icon/image`);
5386
5406
  }
5387
- uploadPreviewImage(appRef, bytes, mime) {
5407
+ uploadPreviewImage(appRef, bytes, mime, orientation) {
5388
5408
  const ab = new ArrayBuffer(bytes.byteLength);
5389
5409
  new Uint8Array(ab).set(bytes);
5390
- return this.http.postBinary(`/apps/${encodeURIComponent(appRef)}/preview/image`, new Blob([ab]), { "Content-Type": mime });
5410
+ const query = orientation ? `?orientation=${orientation}` : "";
5411
+ return this.http.postBinary(`/apps/${encodeURIComponent(appRef)}/preview/image${query}`, new Blob([ab]), { "Content-Type": mime });
5391
5412
  }
5392
5413
  deletePreviewImage(appRef) {
5393
5414
  return this.http.deleteJson(`/apps/${encodeURIComponent(appRef)}/preview/image`);
@@ -5642,6 +5663,58 @@ class BillingResource {
5642
5663
  }
5643
5664
  }
5644
5665
 
5666
+ // ../client/dist/resources/data-records.js
5667
+ function dataPathSegment(kind, value) {
5668
+ const re = kind === "collection" ? COLLECTION_RE : RECORD_ID_RE;
5669
+ if (!re.test(value))
5670
+ throw new TypeError(`invalid data ${kind}`);
5671
+ return encodeURIComponent(value);
5672
+ }
5673
+ function outcomeUnknown(cause, observed) {
5674
+ const answered = cause instanceof PercherApiError;
5675
+ return new PercherApiError(OUTCOME_UNKNOWN_MESSAGE, {
5676
+ status: answered ? cause.status : 0,
5677
+ code: "DATA_WRITE_OUTCOME_UNKNOWN",
5678
+ requestId: answered ? cause.requestId : undefined,
5679
+ cause,
5680
+ extra: { observed }
5681
+ });
5682
+ }
5683
+ function isRecordReply(reply) {
5684
+ return typeof reply === "object" && reply !== null && !Array.isArray(reply) && typeof reply.id === "string" && reply.id.length > 0;
5685
+ }
5686
+ function isDeleteReply(reply) {
5687
+ return reply === undefined || typeof reply === "object" && reply !== null && !Array.isArray(reply);
5688
+ }
5689
+ async function writeOnce(run, usable) {
5690
+ let reply;
5691
+ try {
5692
+ reply = await run();
5693
+ } catch (err) {
5694
+ if (err instanceof SunsetError)
5695
+ throw err;
5696
+ if (err instanceof PercherApiError) {
5697
+ if (err.code === "DATA_WRITE_OUTCOME_UNKNOWN")
5698
+ throw err;
5699
+ if (err.status >= 400 && err.status < 500)
5700
+ throw err;
5701
+ if (err.status >= 500 && WRITE_NOT_SENT.has(err.code))
5702
+ throw err;
5703
+ }
5704
+ throw outcomeUnknown(err, err instanceof PercherApiError ? "error_response" : "no_usable_response");
5705
+ }
5706
+ if (!usable(reply))
5707
+ throw outcomeUnknown(undefined, "unusable_reply");
5708
+ return reply;
5709
+ }
5710
+ var COLLECTION_RE, RECORD_ID_RE, WRITE_NOT_SENT, OUTCOME_UNKNOWN_MESSAGE = "No usable answer arrived for this write, so it may or may not have been saved.";
5711
+ var init_data_records = __esm(() => {
5712
+ init_errors();
5713
+ COLLECTION_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
5714
+ RECORD_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
5715
+ WRITE_NOT_SENT = new Set(["PB_NOT_RUNNING", "PB_AUTH_FAILED", "PB_VERSION_UNSUPPORTED"]);
5716
+ });
5717
+
5645
5718
  // ../client/dist/resources/data.js
5646
5719
  class DataResource {
5647
5720
  http;
@@ -5678,7 +5751,37 @@ class DataResource {
5678
5751
  removeOAuthProvider(appRef, provider) {
5679
5752
  return this.http.delete(`/apps/${encodeURIComponent(appRef)}/data/oauth/${encodeURIComponent(provider)}`);
5680
5753
  }
5754
+ getSchema(appRef) {
5755
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/data/schema`);
5756
+ }
5757
+ queryRecords(appRef, collection, query = {}) {
5758
+ return this.http.post(`${this.records(appRef, collection)}/query`, query);
5759
+ }
5760
+ getRecord(appRef, collection, id) {
5761
+ return this.http.get(this.record(appRef, collection, id));
5762
+ }
5763
+ createRecord(appRef, collection, input) {
5764
+ const path = this.records(appRef, collection);
5765
+ return writeOnce(() => this.http.post(path, input), isRecordReply);
5766
+ }
5767
+ updateRecord(appRef, collection, id, input) {
5768
+ const path = this.record(appRef, collection, id);
5769
+ return writeOnce(() => this.http.patch(path, input), isRecordReply);
5770
+ }
5771
+ async deleteRecord(appRef, collection, id) {
5772
+ const path = this.record(appRef, collection, id);
5773
+ await writeOnce(() => this.http.deleteJson(path), isDeleteReply);
5774
+ }
5775
+ records(appRef, collection) {
5776
+ return `/apps/${encodeURIComponent(appRef)}/data/collections/${dataPathSegment("collection", collection)}/records`;
5777
+ }
5778
+ record(appRef, collection, id) {
5779
+ return `${this.records(appRef, collection)}/${dataPathSegment("id", id)}`;
5780
+ }
5681
5781
  }
5782
+ var init_data = __esm(() => {
5783
+ init_data_records();
5784
+ });
5682
5785
 
5683
5786
  // ../client/dist/resources/env.js
5684
5787
  function scopeQuery(opts) {
@@ -6142,6 +6245,7 @@ var init_client = __esm(() => {
6142
6245
  init_http();
6143
6246
  init_apps();
6144
6247
  init_auth();
6248
+ init_data();
6145
6249
  init_logs();
6146
6250
  });
6147
6251
 
@@ -6262,6 +6366,55 @@ var init_env_file = __esm(() => {
6262
6366
  };
6263
6367
  });
6264
6368
 
6369
+ // ../client/dist/telemetry-consent.js
6370
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
6371
+ import { dirname as dirname5, join as join7 } from "node:path";
6372
+ function consentPath() {
6373
+ return join7(dirname5(configPath()), "telemetry.json");
6374
+ }
6375
+ function envOptOut() {
6376
+ if (process.env.PERCHER_NO_TELEMETRY === "1" || process.env.PERCHER_NO_TELEMETRY === "true") {
6377
+ return "PERCHER_NO_TELEMETRY";
6378
+ }
6379
+ if (process.env.DO_NOT_TRACK === "1")
6380
+ return "DO_NOT_TRACK";
6381
+ return null;
6382
+ }
6383
+ function readStored() {
6384
+ try {
6385
+ const parsed = JSON.parse(readFileSync5(consentPath(), "utf8"));
6386
+ if (typeof parsed?.enabled === "boolean" && typeof parsed.decidedAt === "string" && typeof parsed.version === "string") {
6387
+ return { enabled: parsed.enabled, decidedAt: parsed.decidedAt, version: parsed.version };
6388
+ }
6389
+ } catch {}
6390
+ return null;
6391
+ }
6392
+ function telemetryState() {
6393
+ const optOut = envOptOut();
6394
+ const stored = readStored();
6395
+ return { enabled: optOut === null && stored?.enabled === true, envOptOut: optOut, stored };
6396
+ }
6397
+ function saveTelemetryAnswer(enabled, version2) {
6398
+ const p = consentPath();
6399
+ mkdirSync5(dirname5(p), { recursive: true, mode: 448 });
6400
+ if (process.platform !== "win32")
6401
+ chmodSync2(dirname5(p), 448);
6402
+ const answer = { enabled, decidedAt: new Date().toISOString(), version: version2 };
6403
+ const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
6404
+ try {
6405
+ writeFileSync5(tmp, `${JSON.stringify(answer, null, 2)}
6406
+ `, { mode: 384, flag: "wx" });
6407
+ if (process.platform !== "win32")
6408
+ chmodSync2(tmp, 384);
6409
+ renameSync2(tmp, p);
6410
+ } finally {
6411
+ rmSync3(tmp, { force: true });
6412
+ }
6413
+ }
6414
+ var init_telemetry_consent = __esm(() => {
6415
+ init_config();
6416
+ });
6417
+
6265
6418
  // ../client/dist/index.js
6266
6419
  var init_dist = __esm(() => {
6267
6420
  init_build_env_prefixes();
@@ -6271,7 +6424,9 @@ var init_dist = __esm(() => {
6271
6424
  init_errors();
6272
6425
  init_apps();
6273
6426
  init_auth();
6427
+ init_data();
6274
6428
  init_logs();
6429
+ init_telemetry_consent();
6275
6430
  });
6276
6431
 
6277
6432
  // ../../node_modules/.bun/ignore@7.0.5/node_modules/ignore/index.js
@@ -6718,19 +6873,19 @@ var require_tslib = __commonJS((exports, module) => {
6718
6873
  }
6719
6874
  return t;
6720
6875
  };
6721
- __decorate = function(decorators, target, key, desc) {
6722
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6876
+ __decorate = function(decorators, target2, key, desc) {
6877
+ var c = arguments.length, r = c < 3 ? target2 : desc === null ? desc = Object.getOwnPropertyDescriptor(target2, key) : desc, d;
6723
6878
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
6724
- r = Reflect.decorate(decorators, target, key, desc);
6879
+ r = Reflect.decorate(decorators, target2, key, desc);
6725
6880
  else
6726
6881
  for (var i = decorators.length - 1;i >= 0; i--)
6727
6882
  if (d = decorators[i])
6728
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6729
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6883
+ r = (c < 3 ? d(r) : c > 3 ? d(target2, key, r) : d(target2, key)) || r;
6884
+ return c > 3 && r && Object.defineProperty(target2, key, r), r;
6730
6885
  };
6731
6886
  __param = function(paramIndex, decorator) {
6732
- return function(target, key) {
6733
- decorator(target, key, paramIndex);
6887
+ return function(target2, key) {
6888
+ decorator(target2, key, paramIndex);
6734
6889
  };
6735
6890
  };
6736
6891
  __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
@@ -6740,8 +6895,8 @@ var require_tslib = __commonJS((exports, module) => {
6740
6895
  return f;
6741
6896
  }
6742
6897
  var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
6743
- var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6744
- var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6898
+ var target2 = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6899
+ var descriptor = descriptorIn || (target2 ? Object.getOwnPropertyDescriptor(target2, contextIn.name) : {});
6745
6900
  var _, done = false;
6746
6901
  for (var i = decorators.length - 1;i >= 0; i--) {
6747
6902
  var context = {};
@@ -6773,8 +6928,8 @@ var require_tslib = __commonJS((exports, module) => {
6773
6928
  descriptor[key] = _;
6774
6929
  }
6775
6930
  }
6776
- if (target)
6777
- Object.defineProperty(target, contextIn.name, descriptor);
6931
+ if (target2)
6932
+ Object.defineProperty(target2, contextIn.name, descriptor);
6778
6933
  done = true;
6779
6934
  };
6780
6935
  __runInitializers = function(thisArg, initializers, value) {
@@ -8951,13 +9106,13 @@ var require_path_visitor = __commonJS((exports, module) => {
8951
9106
  isFunction.assert(Visitor.visit);
8952
9107
  return new Visitor;
8953
9108
  };
8954
- function extend2(target, source) {
9109
+ function extend2(target2, source) {
8955
9110
  for (var property in source) {
8956
9111
  if (hasOwn.call(source, property)) {
8957
- target[property] = source[property];
9112
+ target2[property] = source[property];
8958
9113
  }
8959
9114
  }
8960
- return target;
9115
+ return target2;
8961
9116
  }
8962
9117
  PathVisitor.visit = function visit(node, methods) {
8963
9118
  return PathVisitor.fromMethodsObject(methods).visit(node);
@@ -10582,7 +10737,7 @@ var require_util = __commonJS((exports) => {
10582
10737
  return path;
10583
10738
  }
10584
10739
  exports.normalize = normalize2;
10585
- function join18(aRoot, aPath) {
10740
+ function join19(aRoot, aPath) {
10586
10741
  if (aRoot === "") {
10587
10742
  aRoot = ".";
10588
10743
  }
@@ -10614,7 +10769,7 @@ var require_util = __commonJS((exports) => {
10614
10769
  }
10615
10770
  return joined;
10616
10771
  }
10617
- exports.join = join18;
10772
+ exports.join = join19;
10618
10773
  exports.isAbsolute = function(aPath) {
10619
10774
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
10620
10775
  };
@@ -10787,7 +10942,7 @@ var require_util = __commonJS((exports) => {
10787
10942
  parsed.path = parsed.path.substring(0, index + 1);
10788
10943
  }
10789
10944
  }
10790
- sourceURL = join18(urlGenerate(parsed), sourceURL);
10945
+ sourceURL = join19(urlGenerate(parsed), sourceURL);
10791
10946
  }
10792
10947
  return normalize2(sourceURL);
10793
10948
  }
@@ -12532,27 +12687,27 @@ var require_esprima2 = __commonJS((exports, module) => {
12532
12687
  };
12533
12688
  CommentHandler2.prototype.findLeadingComments = function(metadata) {
12534
12689
  var leadingComments = [];
12535
- var target;
12690
+ var target2;
12536
12691
  while (this.stack.length > 0) {
12537
12692
  var entry = this.stack[this.stack.length - 1];
12538
12693
  if (entry && entry.start >= metadata.start.offset) {
12539
- target = entry.node;
12694
+ target2 = entry.node;
12540
12695
  this.stack.pop();
12541
12696
  } else {
12542
12697
  break;
12543
12698
  }
12544
12699
  }
12545
- if (target) {
12546
- var count = target.leadingComments ? target.leadingComments.length : 0;
12700
+ if (target2) {
12701
+ var count = target2.leadingComments ? target2.leadingComments.length : 0;
12547
12702
  for (var i = count - 1;i >= 0; --i) {
12548
- var comment = target.leadingComments[i];
12703
+ var comment = target2.leadingComments[i];
12549
12704
  if (comment.range[1] <= metadata.start.offset) {
12550
12705
  leadingComments.unshift(comment);
12551
- target.leadingComments.splice(i, 1);
12706
+ target2.leadingComments.splice(i, 1);
12552
12707
  }
12553
12708
  }
12554
- if (target.leadingComments && target.leadingComments.length === 0) {
12555
- delete target.leadingComments;
12709
+ if (target2.leadingComments && target2.leadingComments.length === 0) {
12710
+ delete target2.leadingComments;
12556
12711
  }
12557
12712
  return leadingComments;
12558
12713
  }
@@ -13038,9 +13193,9 @@ var require_esprima2 = __commonJS((exports, module) => {
13038
13193
  if (this.match("}")) {
13039
13194
  this.tolerateError("JSX attributes must only be assigned a non-empty expression");
13040
13195
  }
13041
- var expression = this.parseAssignmentExpression();
13196
+ var expression2 = this.parseAssignmentExpression();
13042
13197
  this.reenterJSX();
13043
- return this.finalize(node, new JSXNode.JSXExpressionContainer(expression));
13198
+ return this.finalize(node, new JSXNode.JSXExpressionContainer(expression2));
13044
13199
  };
13045
13200
  JSXParser2.prototype.parseJSXAttributeValue = function() {
13046
13201
  return this.matchJSX("{") ? this.parseJSXExpressionAttribute() : this.matchJSX("<") ? this.parseJSXElement() : this.parseJSXStringLiteralAttribute();
@@ -13113,16 +13268,16 @@ var require_esprima2 = __commonJS((exports, module) => {
13113
13268
  JSXParser2.prototype.parseJSXExpressionContainer = function() {
13114
13269
  var node = this.createJSXNode();
13115
13270
  this.expectJSX("{");
13116
- var expression;
13271
+ var expression2;
13117
13272
  if (this.matchJSX("}")) {
13118
- expression = this.parseJSXEmptyExpression();
13273
+ expression2 = this.parseJSXEmptyExpression();
13119
13274
  this.expectJSX("}");
13120
13275
  } else {
13121
13276
  this.finishJSX();
13122
- expression = this.parseAssignmentExpression();
13277
+ expression2 = this.parseAssignmentExpression();
13123
13278
  this.reenterJSX();
13124
13279
  }
13125
- return this.finalize(node, new JSXNode.JSXExpressionContainer(expression));
13280
+ return this.finalize(node, new JSXNode.JSXExpressionContainer(expression2));
13126
13281
  };
13127
13282
  JSXParser2.prototype.parseJSXChildren = function() {
13128
13283
  var children = [];
@@ -13268,9 +13423,9 @@ var require_esprima2 = __commonJS((exports, module) => {
13268
13423
  }();
13269
13424
  exports2.JSXEmptyExpression = JSXEmptyExpression;
13270
13425
  var JSXExpressionContainer = function() {
13271
- function JSXExpressionContainer2(expression) {
13426
+ function JSXExpressionContainer2(expression2) {
13272
13427
  this.type = jsx_syntax_1.JSXSyntax.JSXExpressionContainer;
13273
- this.expression = expression;
13428
+ this.expression = expression2;
13274
13429
  }
13275
13430
  return JSXExpressionContainer2;
13276
13431
  }();
@@ -13374,13 +13529,13 @@ var require_esprima2 = __commonJS((exports, module) => {
13374
13529
  }();
13375
13530
  exports2.ArrayPattern = ArrayPattern;
13376
13531
  var ArrowFunctionExpression = function() {
13377
- function ArrowFunctionExpression2(params, body, expression) {
13532
+ function ArrowFunctionExpression2(params, body, expression2) {
13378
13533
  this.type = syntax_1.Syntax.ArrowFunctionExpression;
13379
13534
  this.id = null;
13380
13535
  this.params = params;
13381
13536
  this.body = body;
13382
13537
  this.generator = false;
13383
- this.expression = expression;
13538
+ this.expression = expression2;
13384
13539
  this.async = false;
13385
13540
  }
13386
13541
  return ArrowFunctionExpression2;
@@ -13406,13 +13561,13 @@ var require_esprima2 = __commonJS((exports, module) => {
13406
13561
  }();
13407
13562
  exports2.AssignmentPattern = AssignmentPattern;
13408
13563
  var AsyncArrowFunctionExpression = function() {
13409
- function AsyncArrowFunctionExpression2(params, body, expression) {
13564
+ function AsyncArrowFunctionExpression2(params, body, expression2) {
13410
13565
  this.type = syntax_1.Syntax.ArrowFunctionExpression;
13411
13566
  this.id = null;
13412
13567
  this.params = params;
13413
13568
  this.body = body;
13414
13569
  this.generator = false;
13415
- this.expression = expression;
13570
+ this.expression = expression2;
13416
13571
  this.async = true;
13417
13572
  }
13418
13573
  return AsyncArrowFunctionExpression2;
@@ -13561,9 +13716,9 @@ var require_esprima2 = __commonJS((exports, module) => {
13561
13716
  }();
13562
13717
  exports2.DebuggerStatement = DebuggerStatement;
13563
13718
  var Directive = function() {
13564
- function Directive2(expression, directive) {
13719
+ function Directive2(expression2, directive) {
13565
13720
  this.type = syntax_1.Syntax.ExpressionStatement;
13566
- this.expression = expression;
13721
+ this.expression = expression2;
13567
13722
  this.directive = directive;
13568
13723
  }
13569
13724
  return Directive2;
@@ -13621,9 +13776,9 @@ var require_esprima2 = __commonJS((exports, module) => {
13621
13776
  }();
13622
13777
  exports2.ExportSpecifier = ExportSpecifier;
13623
13778
  var ExpressionStatement = function() {
13624
- function ExpressionStatement2(expression) {
13779
+ function ExpressionStatement2(expression2) {
13625
13780
  this.type = syntax_1.Syntax.ExpressionStatement;
13626
- this.expression = expression;
13781
+ this.expression = expression2;
13627
13782
  }
13628
13783
  return ExpressionStatement2;
13629
13784
  }();
@@ -15357,14 +15512,14 @@ var require_esprima2 = __commonJS((exports, module) => {
15357
15512
  } else {
15358
15513
  body = this.isolateCoverGrammar(this.parseAssignmentExpression);
15359
15514
  }
15360
- var expression = body.type !== syntax_1.Syntax.BlockStatement;
15515
+ var expression2 = body.type !== syntax_1.Syntax.BlockStatement;
15361
15516
  if (this.context.strict && list.firstRestricted) {
15362
15517
  this.throwUnexpectedToken(list.firstRestricted, list.message);
15363
15518
  }
15364
15519
  if (this.context.strict && list.stricted) {
15365
15520
  this.tolerateUnexpectedToken(list.stricted, list.message);
15366
15521
  }
15367
- expr = isAsync ? this.finalize(node, new Node.AsyncArrowFunctionExpression(list.params, body, expression)) : this.finalize(node, new Node.ArrowFunctionExpression(list.params, body, expression));
15522
+ expr = isAsync ? this.finalize(node, new Node.AsyncArrowFunctionExpression(list.params, body, expression2)) : this.finalize(node, new Node.ArrowFunctionExpression(list.params, body, expression2));
15368
15523
  this.context.strict = previousStrict;
15369
15524
  this.context.allowStrictDirective = previousAllowStrictDirective;
15370
15525
  this.context.allowYield = previousAllowYield;
@@ -19914,7 +20069,7 @@ var require_fast_path = __commonJS((exports) => {
19914
20069
  }
19915
20070
  return s[0];
19916
20071
  };
19917
- FPp.call = function call(callback) {
20072
+ FPp.call = function call2(callback) {
19918
20073
  var s = this.stack;
19919
20074
  var origLen = s.length;
19920
20075
  var value = s[origLen - 1];
@@ -22157,8 +22312,8 @@ while`);
22157
22312
  return (0, lines_1.concat)([path.call(print, "left"), ".", path.call(print, "right")]);
22158
22313
  case "TSAsExpression":
22159
22314
  case "TSSatisfiesExpression": {
22160
- var expression = path.call(print, "expression");
22161
- parts.push(expression, n.type === "TSSatisfiesExpression" ? " satisfies " : " as ", path.call(print, "typeAnnotation"));
22315
+ var expression2 = path.call(print, "expression");
22316
+ parts.push(expression2, n.type === "TSSatisfiesExpression" ? " satisfies " : " as ", path.call(print, "typeAnnotation"));
22162
22317
  return (0, lines_1.concat)(parts);
22163
22318
  }
22164
22319
  case "TSTypeCastExpression":
@@ -23793,9 +23948,9 @@ var require_lib = __commonJS((exports) => {
23793
23948
  UnsupportedDefaultExport: "Only expressions, functions or classes are allowed as the `default` export.",
23794
23949
  UnsupportedImport: "`import` can only be used in `import()` or `import.meta`.",
23795
23950
  UnsupportedMetaProperty: ({
23796
- target,
23951
+ target: target2,
23797
23952
  onlyValidPropertyName
23798
- }) => `The only valid meta property for ${target} is ${target}.${onlyValidPropertyName}.`,
23953
+ }) => `The only valid meta property for ${target2} is ${target2}.${onlyValidPropertyName}.`,
23799
23954
  UnsupportedParameterDecorator: "Decorators cannot be used to decorate parameters.",
23800
23955
  UnsupportedPropertyDecorator: "Decorators cannot be used to decorate object literal properties.",
23801
23956
  UnsupportedSuper: "'super' can only be used with function calls (i.e. super()) or in property accesses (i.e. super.prop or super[prop]).",
@@ -24083,15 +24238,15 @@ var require_lib = __commonJS((exports) => {
24083
24238
  return this.finishNodeAt(chain, "ChainExpression", endLoc);
24084
24239
  }
24085
24240
  directiveToStmt(directive) {
24086
- const expression = directive.value;
24241
+ const expression2 = directive.value;
24087
24242
  delete directive.value;
24088
- this.castNodeTo(expression, "Literal");
24089
- expression.raw = expression.extra.raw;
24090
- expression.value = expression.extra.expressionValue;
24243
+ this.castNodeTo(expression2, "Literal");
24244
+ expression2.raw = expression2.extra.raw;
24245
+ expression2.value = expression2.extra.expressionValue;
24091
24246
  const stmt = this.castNodeTo(directive, "ExpressionStatement");
24092
- stmt.expression = expression;
24093
- stmt.directive = expression.extra.rawValue;
24094
- delete expression.extra;
24247
+ stmt.expression = expression2;
24248
+ stmt.directive = expression2.extra.rawValue;
24249
+ delete expression2.extra;
24095
24250
  return stmt;
24096
24251
  }
24097
24252
  fillOptionalPropertiesForTSESLint(node) {}
@@ -27042,14 +27197,14 @@ var require_lib = __commonJS((exports) => {
27042
27197
  this.resetEndLocation(decl.id);
27043
27198
  }
27044
27199
  }
27045
- parseAsyncArrowFromCallExpression(node, call) {
27200
+ parseAsyncArrowFromCallExpression(node, call2) {
27046
27201
  if (this.match(14)) {
27047
27202
  const oldNoAnonFunctionType = this.state.noAnonFunctionType;
27048
27203
  this.state.noAnonFunctionType = true;
27049
27204
  node.returnType = this.flowParseTypeAnnotation();
27050
27205
  this.state.noAnonFunctionType = oldNoAnonFunctionType;
27051
27206
  }
27052
- return super.parseAsyncArrowFromCallExpression(node, call);
27207
+ return super.parseAsyncArrowFromCallExpression(node, call2);
27053
27208
  }
27054
27209
  shouldParseAsyncArrow() {
27055
27210
  return this.match(14) || super.shouldParseAsyncArrow();
@@ -28203,8 +28358,8 @@ var require_lib = __commonJS((exports) => {
28203
28358
  if (this.match(8)) {
28204
28359
  node.expression = this.jsxParseEmptyExpression();
28205
28360
  } else {
28206
- const expression = this.parseExpression();
28207
- node.expression = expression;
28361
+ const expression2 = this.parseExpression();
28362
+ node.expression = expression2;
28208
28363
  }
28209
28364
  this.setContext(previousContext);
28210
28365
  this.state.canStartJSXElement = true;
@@ -31140,53 +31295,53 @@ var require_lib = __commonJS((exports) => {
31140
31295
  }
31141
31296
  return false;
31142
31297
  }
31143
- isOptionalMemberExpression(expression) {
31144
- return expression.type === "OptionalMemberExpression";
31298
+ isOptionalMemberExpression(expression2) {
31299
+ return expression2.type === "OptionalMemberExpression";
31145
31300
  }
31146
- checkLVal(expression, ancestor, binding = 64, checkClashes = false, strictModeChanged = false, hasParenthesizedAncestor = false, disallowCallExpression = false) {
31301
+ checkLVal(expression2, ancestor, binding = 64, checkClashes = false, strictModeChanged = false, hasParenthesizedAncestor = false, disallowCallExpression = false) {
31147
31302
  var _expression$extra;
31148
- const type = expression.type;
31149
- if (this.isObjectMethod(expression))
31303
+ const type = expression2.type;
31304
+ if (this.isObjectMethod(expression2))
31150
31305
  return;
31151
- const isOptionalMemberExpression = this.isOptionalMemberExpression(expression);
31306
+ const isOptionalMemberExpression = this.isOptionalMemberExpression(expression2);
31152
31307
  if (isOptionalMemberExpression || type === "MemberExpression") {
31153
31308
  if (isOptionalMemberExpression) {
31154
- this.expectPlugin("optionalChainingAssign", expression.loc.start);
31309
+ this.expectPlugin("optionalChainingAssign", expression2.loc.start);
31155
31310
  if (ancestor.type !== "AssignmentExpression") {
31156
- this.raise(Errors.InvalidLhsOptionalChaining, expression, {
31311
+ this.raise(Errors.InvalidLhsOptionalChaining, expression2, {
31157
31312
  ancestor
31158
31313
  });
31159
31314
  }
31160
31315
  }
31161
31316
  if (binding !== 64) {
31162
- this.raise(Errors.InvalidPropertyBindingPattern, expression);
31317
+ this.raise(Errors.InvalidPropertyBindingPattern, expression2);
31163
31318
  }
31164
31319
  return;
31165
31320
  }
31166
31321
  if (type === "Identifier") {
31167
- this.checkIdentifier(expression, binding, strictModeChanged);
31322
+ this.checkIdentifier(expression2, binding, strictModeChanged);
31168
31323
  const {
31169
31324
  name
31170
- } = expression;
31325
+ } = expression2;
31171
31326
  if (checkClashes) {
31172
31327
  if (checkClashes.has(name)) {
31173
- this.raise(Errors.ParamDupe, expression);
31328
+ this.raise(Errors.ParamDupe, expression2);
31174
31329
  } else {
31175
31330
  checkClashes.add(name);
31176
31331
  }
31177
31332
  }
31178
31333
  return;
31179
31334
  } else if (type === "VoidPattern" && ancestor.type === "CatchClause") {
31180
- this.raise(Errors.VoidPatternCatchClauseParam, expression);
31335
+ this.raise(Errors.VoidPatternCatchClauseParam, expression2);
31181
31336
  }
31182
- const unwrappedExpression = unwrapParenthesizedExpression(expression);
31337
+ const unwrappedExpression = unwrapParenthesizedExpression(expression2);
31183
31338
  disallowCallExpression || (disallowCallExpression = unwrappedExpression.type === "CallExpression" && (unwrappedExpression.callee.type === "Import" || unwrappedExpression.callee.type === "Super"));
31184
- const validity = this.isValidLVal(type, disallowCallExpression, !(hasParenthesizedAncestor || (_expression$extra = expression.extra) != null && _expression$extra.parenthesized) && ancestor.type === "AssignmentExpression", binding);
31339
+ const validity = this.isValidLVal(type, disallowCallExpression, !(hasParenthesizedAncestor || (_expression$extra = expression2.extra) != null && _expression$extra.parenthesized) && ancestor.type === "AssignmentExpression", binding);
31185
31340
  if (validity === true)
31186
31341
  return;
31187
31342
  if (validity === false) {
31188
31343
  const ParseErrorClass = binding === 64 ? Errors.InvalidLhs : Errors.InvalidLhsBinding;
31189
- this.raise(ParseErrorClass, expression, {
31344
+ this.raise(ParseErrorClass, expression2, {
31190
31345
  ancestor
31191
31346
  });
31192
31347
  return;
@@ -31201,7 +31356,7 @@ var require_lib = __commonJS((exports) => {
31201
31356
  const nextAncestor = type === "ArrayPattern" || type === "ObjectPattern" ? {
31202
31357
  type
31203
31358
  } : ancestor;
31204
- const val = expression[key];
31359
+ const val = expression2[key];
31205
31360
  if (Array.isArray(val)) {
31206
31361
  for (const child of val) {
31207
31362
  if (child) {
@@ -33459,11 +33614,11 @@ var require_lib = __commonJS((exports) => {
33459
33614
  this.resetEndLocation(decl.id);
33460
33615
  }
33461
33616
  }
33462
- parseAsyncArrowFromCallExpression(node, call) {
33617
+ parseAsyncArrowFromCallExpression(node, call2) {
33463
33618
  if (this.match(14)) {
33464
33619
  node.returnType = this.tsParseTypeAnnotation();
33465
33620
  }
33466
- return super.parseAsyncArrowFromCallExpression(node, call);
33621
+ return super.parseAsyncArrowFromCallExpression(node, call2);
33467
33622
  }
33468
33623
  parseMaybeAssign(refExpressionErrors, afterLeftParse) {
33469
33624
  var _jsx, _jsx2, _typeCast, _jsx3, _typeCast2;
@@ -33654,9 +33809,9 @@ var require_lib = __commonJS((exports) => {
33654
33809
  if (this.match(47) || this.match(51)) {
33655
33810
  const typeArguments = this.tsParseTypeArgumentsInExpression();
33656
33811
  if (this.match(10)) {
33657
- const call = super.parseMaybeDecoratorArguments(expr, startLoc);
33658
- call.typeParameters = typeArguments;
33659
- return call;
33812
+ const call2 = super.parseMaybeDecoratorArguments(expr, startLoc);
33813
+ call2.typeParameters = typeArguments;
33814
+ return call2;
33660
33815
  }
33661
33816
  this.unexpected(null, 10);
33662
33817
  }
@@ -34046,31 +34201,31 @@ var require_lib = __commonJS((exports) => {
34046
34201
  return this.chStartsBindingIdentifier(nextCh, next) || nextCh === 34 || nextCh === 39;
34047
34202
  }
34048
34203
  };
34049
- function isPossiblyLiteralEnum(expression) {
34050
- if (expression.type !== "MemberExpression")
34204
+ function isPossiblyLiteralEnum(expression2) {
34205
+ if (expression2.type !== "MemberExpression")
34051
34206
  return false;
34052
34207
  const {
34053
34208
  computed,
34054
34209
  property
34055
- } = expression;
34210
+ } = expression2;
34056
34211
  if (computed && property.type !== "StringLiteral" && (property.type !== "TemplateLiteral" || property.expressions.length > 0)) {
34057
34212
  return false;
34058
34213
  }
34059
- return isUncomputedMemberExpressionChain(expression.object);
34214
+ return isUncomputedMemberExpressionChain(expression2.object);
34060
34215
  }
34061
- function isValidAmbientConstInitializer(expression, estree2) {
34216
+ function isValidAmbientConstInitializer(expression2, estree2) {
34062
34217
  var _expression$extra;
34063
34218
  const {
34064
34219
  type
34065
- } = expression;
34066
- if ((_expression$extra = expression.extra) != null && _expression$extra.parenthesized) {
34220
+ } = expression2;
34221
+ if ((_expression$extra = expression2.extra) != null && _expression$extra.parenthesized) {
34067
34222
  return false;
34068
34223
  }
34069
34224
  if (estree2) {
34070
34225
  if (type === "Literal") {
34071
34226
  const {
34072
34227
  value
34073
- } = expression;
34228
+ } = expression2;
34074
34229
  if (typeof value === "string" || typeof value === "boolean") {
34075
34230
  return true;
34076
34231
  }
@@ -34080,42 +34235,42 @@ var require_lib = __commonJS((exports) => {
34080
34235
  return true;
34081
34236
  }
34082
34237
  }
34083
- if (isNumber(expression, estree2) || isNegativeNumber(expression, estree2)) {
34238
+ if (isNumber(expression2, estree2) || isNegativeNumber(expression2, estree2)) {
34084
34239
  return true;
34085
34240
  }
34086
- if (type === "TemplateLiteral" && expression.expressions.length === 0) {
34241
+ if (type === "TemplateLiteral" && expression2.expressions.length === 0) {
34087
34242
  return true;
34088
34243
  }
34089
- if (isPossiblyLiteralEnum(expression)) {
34244
+ if (isPossiblyLiteralEnum(expression2)) {
34090
34245
  return true;
34091
34246
  }
34092
34247
  return false;
34093
34248
  }
34094
- function isNumber(expression, estree2) {
34249
+ function isNumber(expression2, estree2) {
34095
34250
  if (estree2) {
34096
- return expression.type === "Literal" && (typeof expression.value === "number" || ("bigint" in expression));
34251
+ return expression2.type === "Literal" && (typeof expression2.value === "number" || ("bigint" in expression2));
34097
34252
  }
34098
- return expression.type === "NumericLiteral" || expression.type === "BigIntLiteral";
34253
+ return expression2.type === "NumericLiteral" || expression2.type === "BigIntLiteral";
34099
34254
  }
34100
- function isNegativeNumber(expression, estree2) {
34101
- if (expression.type === "UnaryExpression") {
34255
+ function isNegativeNumber(expression2, estree2) {
34256
+ if (expression2.type === "UnaryExpression") {
34102
34257
  const {
34103
34258
  operator,
34104
34259
  argument
34105
- } = expression;
34260
+ } = expression2;
34106
34261
  if (operator === "-" && isNumber(argument, estree2)) {
34107
34262
  return true;
34108
34263
  }
34109
34264
  }
34110
34265
  return false;
34111
34266
  }
34112
- function isUncomputedMemberExpressionChain(expression) {
34113
- if (expression.type === "Identifier")
34267
+ function isUncomputedMemberExpressionChain(expression2) {
34268
+ if (expression2.type === "Identifier")
34114
34269
  return true;
34115
- if (expression.type !== "MemberExpression" || expression.computed) {
34270
+ if (expression2.type !== "MemberExpression" || expression2.computed) {
34116
34271
  return false;
34117
34272
  }
34118
- return isUncomputedMemberExpressionChain(expression.object);
34273
+ return isUncomputedMemberExpressionChain(expression2.object);
34119
34274
  }
34120
34275
  var PlaceholderErrors = ParseErrorEnum`placeholders`({
34121
34276
  ClassNameIsRequired: "A class name is required.",
@@ -34977,16 +35132,16 @@ var require_lib = __commonJS((exports) => {
34977
35132
  shouldParseAsyncArrow() {
34978
35133
  return this.match(19) && !this.canInsertSemicolon();
34979
35134
  }
34980
- parseAsyncArrowFromCallExpression(node, call) {
35135
+ parseAsyncArrowFromCallExpression(node, call2) {
34981
35136
  var _call$extra;
34982
- this.resetPreviousNodeTrailingComments(call);
35137
+ this.resetPreviousNodeTrailingComments(call2);
34983
35138
  this.expect(19);
34984
- this.parseArrowExpression(node, call.arguments, true, (_call$extra = call.extra) == null ? undefined : _call$extra.trailingCommaLoc);
34985
- if (call.innerComments) {
34986
- setInnerComments(node, call.innerComments);
35139
+ this.parseArrowExpression(node, call2.arguments, true, (_call$extra = call2.extra) == null ? undefined : _call$extra.trailingCommaLoc);
35140
+ if (call2.innerComments) {
35141
+ setInnerComments(node, call2.innerComments);
34987
35142
  }
34988
- if (call.callee.trailingComments) {
34989
- setInnerComments(node, call.callee.trailingComments);
35143
+ if (call2.callee.trailingComments) {
35144
+ setInnerComments(node, call2.callee.trailingComments);
34990
35145
  }
34991
35146
  return node;
34992
35147
  }
@@ -35414,15 +35569,15 @@ var require_lib = __commonJS((exports) => {
35414
35569
  }
35415
35570
  return this.wrapParenthesis(startLoc, val);
35416
35571
  }
35417
- wrapParenthesis(startLoc, expression) {
35572
+ wrapParenthesis(startLoc, expression2) {
35418
35573
  if (!(this.optionFlags & 1024)) {
35419
- this.addExtra(expression, "parenthesized", true);
35420
- this.addExtra(expression, "parenStart", startLoc.index);
35421
- this.takeSurroundingComments(expression, startLoc.index, this.state.lastTokEndLoc.index);
35422
- return expression;
35574
+ this.addExtra(expression2, "parenthesized", true);
35575
+ this.addExtra(expression2, "parenStart", startLoc.index);
35576
+ this.takeSurroundingComments(expression2, startLoc.index, this.state.lastTokEndLoc.index);
35577
+ return expression2;
35423
35578
  }
35424
35579
  const parenExpression = this.startNodeAt(startLoc);
35425
- parenExpression.expression = expression;
35580
+ parenExpression.expression = expression2;
35426
35581
  return this.finishNode(parenExpression, "ParenthesizedExpression");
35427
35582
  }
35428
35583
  shouldParseArrow(params) {
@@ -36070,10 +36225,10 @@ var require_lib = __commonJS((exports) => {
36070
36225
  return this.finishNode(bodyNode, "PipelineTopicExpression");
36071
36226
  }
36072
36227
  }
36073
- isSimpleReference(expression) {
36074
- switch (expression.type) {
36228
+ isSimpleReference(expression2) {
36229
+ switch (expression2.type) {
36075
36230
  case "MemberExpression":
36076
- return !expression.computed && this.isSimpleReference(expression.object);
36231
+ return !expression2.computed && this.isSimpleReference(expression2.object);
36077
36232
  case "Identifier":
36078
36233
  return true;
36079
36234
  default:
@@ -86844,17 +86999,17 @@ var require_template = __commonJS((exports, module) => {
86844
86999
  function statement() {
86845
87000
  return statements.apply(null, arguments)[0];
86846
87001
  }
86847
- function expression(template) {
87002
+ function expression2(template) {
86848
87003
  template = Array.from(template);
86849
87004
  if (template.length > 0) {
86850
87005
  template[0] = "(" + template[0];
86851
87006
  template[template.length - 1] += ")";
86852
87007
  }
86853
- const expression2 = statement.apply(null, [template].concat(Array.from(arguments).slice(1))).expression;
86854
- if (expression2.extra) {
86855
- expression2.extra.parenthesized = false;
87008
+ const expression3 = statement.apply(null, [template].concat(Array.from(arguments).slice(1))).expression;
87009
+ if (expression3.extra) {
87010
+ expression3.extra.parenthesized = false;
86856
87011
  }
86857
- return expression2;
87012
+ return expression3;
86858
87013
  }
86859
87014
  function asyncExpression(template) {
86860
87015
  template = Array.from(template);
@@ -86862,13 +87017,13 @@ var require_template = __commonJS((exports, module) => {
86862
87017
  template[0] = "async () => (" + template[0];
86863
87018
  template[template.length - 1] += ")";
86864
87019
  }
86865
- const expression2 = statement.apply(null, [template].concat(Array.from(arguments).slice(1))).expression.body;
86866
- if (expression2.extra) {
86867
- expression2.extra.parenthesized = false;
87020
+ const expression3 = statement.apply(null, [template].concat(Array.from(arguments).slice(1))).expression.body;
87021
+ if (expression3.extra) {
87022
+ expression3.extra.parenthesized = false;
86868
87023
  }
86869
- return expression2;
87024
+ return expression3;
86870
87025
  }
86871
- return { statements, statement, expression, asyncExpression };
87026
+ return { statements, statement, expression: expression2, asyncExpression };
86872
87027
  };
86873
87028
  });
86874
87029
 
@@ -92665,15 +92820,161 @@ function iconColourHex(token) {
92665
92820
  return BRAND.green;
92666
92821
  return ICON_COLOURS[token] ?? BRAND.green;
92667
92822
  }
92823
+ // ../shared/dist/data-records.js
92824
+ var DATA_COLLECTION_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
92825
+ var DATA_RECORD_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
92826
+ var DATA_LIMITS = {
92827
+ bodyBytes: 262144,
92828
+ responseBytes: 1048576,
92829
+ maxPage: 1000,
92830
+ maxPerPage: 100,
92831
+ expressionChars: 1024,
92832
+ filterMaxDepth: 16,
92833
+ filterMaxComparisons: 64,
92834
+ accessMaxApps: 50,
92835
+ schemaPerPage: 200,
92836
+ schemaMaxPages: 10,
92837
+ sortMaxItems: 8,
92838
+ actAsTokenSeconds: 900,
92839
+ actAsCacheMaxSeconds: 600,
92840
+ identityCandidatesPerPage: 20
92841
+ };
92842
+ var DATA_IDENTITY_KINDS = ["user", "visitor", "superuser"];
92843
+ var DATA_ACTING_AS = [...DATA_IDENTITY_KINDS, "unset"];
92844
+ var DATA_RECORD_ERROR_STATUS = {
92845
+ PB_VERSION_UNSUPPORTED: 503,
92846
+ PB_RECORD_NOT_FOUND: 404,
92847
+ PB_VALIDATION: 400,
92848
+ PB_CONFLICT: 409,
92849
+ PB_CONFLICT_UNSUPPORTED: 400,
92850
+ DATA_COLLECTION_FORBIDDEN: 403,
92851
+ DATA_FIELD_UNSUPPORTED: 400,
92852
+ DATA_RATE_LIMITED: 429,
92853
+ DATA_RESPONSE_TOO_LARGE: 413,
92854
+ OAUTH_DATA_ACCESS_REQUIRED: 403,
92855
+ DATA_TOOLS_NOT_ENABLED: 403,
92856
+ PB_AUTH_FAILED: 503,
92857
+ PB_NOT_RUNNING: 503,
92858
+ PB_BRIDGE_UNAVAILABLE: 502,
92859
+ NOT_POCKETBASE: 400,
92860
+ DATA_QUERY_INVALID: 400,
92861
+ DATA_SCHEMA_TOO_LARGE: 413,
92862
+ DATA_QUERY_TIMEOUT: 504,
92863
+ DATA_WRITE_OUTCOME_UNKNOWN: 502,
92864
+ PB_RULE_DENIED: 403,
92865
+ DATA_ACT_AS_REQUIRED: 403,
92866
+ DATA_ACT_AS_UNAVAILABLE: 403
92867
+ };
92868
+
92869
+ // ../shared/dist/data-filter.js
92870
+ class ExpressionError extends Error {
92871
+ reason;
92872
+ at;
92873
+ constructor(reason, at) {
92874
+ super(reason);
92875
+ this.reason = reason;
92876
+ this.at = at;
92877
+ }
92878
+ }
92879
+ var RESERVED = new Set(["true", "false", "null"]);
92880
+ class SchemaIndex {
92881
+ visible = new Set;
92882
+ hidden = new Set;
92883
+ lowered = new Set;
92884
+ constructor(fields) {
92885
+ for (const f of fields) {
92886
+ (f.hidden ? this.hidden : this.visible).add(f.name);
92887
+ this.lowered.add(f.name.toLowerCase());
92888
+ }
92889
+ }
92890
+ checkField(name, at) {
92891
+ if (RESERVED.has(name.toLowerCase()))
92892
+ throw new ExpressionError("reserved_word", at);
92893
+ if (this.visible.has(name))
92894
+ return;
92895
+ throw new ExpressionError(this.hidden.has(name) ? "hidden_field" : "unknown_field", at);
92896
+ }
92897
+ checkKeywordLiteral(word, at) {
92898
+ if (this.lowered.has(word))
92899
+ throw new ExpressionError("shadowed_literal", at);
92900
+ }
92901
+ }
92902
+ // ../shared/dist/disposable-email-domains.js
92903
+ var DISPOSABLE_DOMAINS = new Set([
92904
+ "mailinator.com",
92905
+ "mailinator.net",
92906
+ "mailinator2.com",
92907
+ "binkmail.com",
92908
+ "bobmail.info",
92909
+ "chammy.info",
92910
+ "devnullmail.com",
92911
+ "letthemeatspam.com",
92912
+ "mailnesia.com",
92913
+ "mailtothis.com",
92914
+ "notmailinator.com",
92915
+ "reallymymail.com",
92916
+ "safetymail.info",
92917
+ "thisisnotmyrealemail.com",
92918
+ "tradermail.info",
92919
+ "veryrealemail.com",
92920
+ "zippymail.info",
92921
+ "guerrillamail.com",
92922
+ "guerrillamail.net",
92923
+ "guerrillamail.org",
92924
+ "guerrillamail.biz",
92925
+ "guerrillamail.de",
92926
+ "guerrillamailblock.com",
92927
+ "sharklasers.com",
92928
+ "grr.la",
92929
+ "spam4.me",
92930
+ "10minutemail.com",
92931
+ "10minutemail.net",
92932
+ "20minutemail.com",
92933
+ "30minutemail.com",
92934
+ "tempmail.com",
92935
+ "temp-mail.org",
92936
+ "temp-mail.io",
92937
+ "tempmailaddress.com",
92938
+ "tempmailo.com",
92939
+ "tempinbox.com",
92940
+ "throwawaymail.com",
92941
+ "yopmail.com",
92942
+ "yopmail.fr",
92943
+ "yopmail.net",
92944
+ "maildrop.cc",
92945
+ "trashmail.com",
92946
+ "trashmail.de",
92947
+ "trashmail.net",
92948
+ "trash-mail.com",
92949
+ "dispostable.com",
92950
+ "fakeinbox.com",
92951
+ "getnada.com",
92952
+ "mintemail.com",
92953
+ "mohmal.com",
92954
+ "moakt.cc",
92955
+ "formtests.info",
92956
+ "mailcatch.com",
92957
+ "mailnull.com",
92958
+ "spambog.com",
92959
+ "spambox.us",
92960
+ "spamgourmet.com",
92961
+ "emailondeck.com",
92962
+ "fakemailgenerator.com",
92963
+ "jetable.org",
92964
+ "mytrashmail.com",
92965
+ "owlpic.com",
92966
+ "rcpt.at",
92967
+ "trbvm.com"
92968
+ ]);
92668
92969
  // ../shared/dist/legal.js
92669
92970
  var LEGAL_DOCUMENT_VERSIONS = {
92670
- terms: "1.0.2",
92971
+ terms: "1.0.3",
92671
92972
  aup: "1.0.1",
92672
- privacy: "1.0.2",
92673
- cookies: "1.0.3",
92674
- dpa: "1.0",
92675
- subprocessors: "1.0.2",
92676
- dataPortability: "1.0"
92973
+ privacy: "1.0.4",
92974
+ cookies: "1.0.4",
92975
+ dpa: "1.1",
92976
+ subprocessors: "1.2",
92977
+ dataPortability: "1.0.4"
92677
92978
  };
92678
92979
  var CURRENT_TERMS_VERSION = LEGAL_DOCUMENT_VERSIONS.terms;
92679
92980
  // ../shared/dist/legal-changes.js
@@ -92688,6 +92989,12 @@ var UPLOAD_SESSION_TTL_MS = 6 * 60 * 60 * 1000;
92688
92989
  var UPLOAD_SESSION_MAX_LIFETIME_MS = 24 * 60 * 60 * 1000;
92689
92990
  // ../shared/dist/logs.js
92690
92991
  var MAX_LOG_SEARCH_LENGTH = 256;
92992
+ // ../shared/dist/oauth-scopes.js
92993
+ var DATA_ACCESS_SUFFIX = "records in your apps' databases, including data your app's users entered. Every record the assistant reads is sent to it and may be processed by its provider under the provider's terms. Revoke in Settings → Connections; copies already sent are not recalled.";
92994
+ var DATA_ACCESS_MODE_LABELS = {
92995
+ readwrite: `Read, create, change and delete ${DATA_ACCESS_SUFFIX}`,
92996
+ read: `Read ${DATA_ACCESS_SUFFIX}`
92997
+ };
92691
92998
  // ../shared/dist/platform-env.js
92692
92999
  var PERCHER_INJECTED_ENV_KEYS = new Set([
92693
93000
  "NODE_ENV",
@@ -92713,6 +93020,23 @@ var PROXY_LOWERCASE_ALIASES = new Set([
92713
93020
  // ../shared/dist/runtime.js
92714
93021
  var DEFAULT_NODE_VERSION_MAJOR = "24";
92715
93022
  var DEFAULT_NODE_VERSION_LABEL = `${DEFAULT_NODE_VERSION_MAJOR} LTS`;
93023
+ // ../shared/dist/signup-day.js
93024
+ var stockholmDate = new Intl.DateTimeFormat("sv-SE", {
93025
+ timeZone: "Europe/Stockholm",
93026
+ year: "numeric",
93027
+ month: "2-digit",
93028
+ day: "2-digit"
93029
+ });
93030
+ var stockholmClock = new Intl.DateTimeFormat("en-GB", {
93031
+ timeZone: "Europe/Stockholm",
93032
+ year: "numeric",
93033
+ month: "2-digit",
93034
+ day: "2-digit",
93035
+ hour: "2-digit",
93036
+ minute: "2-digit",
93037
+ second: "2-digit",
93038
+ hourCycle: "h23"
93039
+ });
92716
93040
  // ../shared/dist/switches.js
92717
93041
  var ONE = ["1"];
92718
93042
  var TRUE = ["true"];
@@ -92796,6 +93120,7 @@ var SWITCHES = [
92796
93120
  },
92797
93121
  bool("PERCHER_ALLOW_NO_CAPTCHA", ["dashboard"], "allow production without Turnstile", ONE),
92798
93122
  bool("PERCHER_ALLOW_UNVERIFIED_SIGNUP", ["dashboard"], "allow production without Resend", ONE),
93123
+ bool("PERCHER_DASHBOARD_SECRET_BUILD", ["dashboard"], "allow an ephemeral BetterAuth key during `next build` only, never at runtime", ONE),
92799
93124
  bool("PERCHER_ALLOW_NO_ALERTING", ["api"], "boot without an alert channel", ONE),
92800
93125
  {
92801
93126
  kind: "presence",
@@ -106437,7 +106762,7 @@ var ENV_NAME_SHAPE = "UPPER_SNAKE_CASE, at most 256 characters";
106437
106762
  var buildSchema = exports_external.object({
106438
106763
  command: exports_external.string().optional(),
106439
106764
  output: exports_external.string().refine((v) => !v.startsWith("/"), "must be a relative path (no leading slash)").optional(),
106440
- pass_env: exports_external.array(exports_external.string().regex(ENV_NAME_RE2, `must be a valid env var name (${ENV_NAME_SHAPE})`).refine((name) => !name.startsWith("BUILDKIT_"), "BUILDKIT_* names are reserved by Percher and cannot be passed to the build")).max(50, "at most 50 build env vars can be exposed").optional()
106765
+ pass_env: exports_external.array(exports_external.string().regex(ENV_NAME_RE2, `must be a valid env var name (${ENV_NAME_SHAPE})`).refine((name) => !name.startsWith("BUILDKIT_") && name !== "PERCHER_BUILD_SECRET_DIGEST", "BUILDKIT_* and PERCHER_BUILD_SECRET_DIGEST are reserved by Percher and cannot be passed to the build")).max(50, "at most 50 build env vars can be exposed").optional()
106441
106766
  }).strict();
106442
106767
  var webSchema = exports_external.object({
106443
106768
  port: exports_external.number().int().min(1024).max(65535),
@@ -107138,7 +107463,7 @@ var LEGACY_AUDIT_REQUIRED_CONJUNCTS = [
107138
107463
  "volumes_attributed",
107139
107464
  "scans_complete",
107140
107465
  "runtime_evidence_complete",
107141
- "clabbe_attestation_complete",
107466
+ "security_attestation_complete",
107142
107467
  "launch_gate_ready"
107143
107468
  ];
107144
107469
  function legacyAuditIsGreen(result) {
@@ -107774,20 +108099,32 @@ function manifestStarter({ name, themeColorHex }) {
107774
108099
  theme_color: themeColorHex
107775
108100
  }, null, 2);
107776
108101
  }
107777
- function serviceWorkerStarter() {
107778
- return `// sw.js — minimal offline shell. Drop in at your app root and serve from /sw.js.
107779
- const CACHE = "app-shell-v1";
107780
- const SHELL = ["/"];
108102
+ function serviceWorkerStarter(assets = []) {
108103
+ if (assets.some((path) => !path.startsWith("/") || path.startsWith("//") || path.includes("\\"))) {
108104
+ throw new Error("Offline assets must be same-origin absolute paths");
108105
+ }
108106
+ return `// sw.js — public offline shell. Serve from /sw.js.
108107
+ // Add public assets, e.g. ["/offline.html", "/app.js", "/app.css"]. Never add private pages or API data.
108108
+ // Include "/" only if the page is public and identical for signed-in and anonymous visitors.
108109
+ // Bump CACHE whenever the asset list or its content changes. Test an offline reload.
108110
+ const CACHE = "percher-public-shell-v1";
108111
+ const SHELL = ${JSON.stringify(assets)};
107781
108112
 
107782
108113
  self.addEventListener("install", (event) => {
107783
- event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(SHELL)));
108114
+ event.waitUntil(caches.open(CACHE).then((cache) => Promise.all(SHELL.map(async (path) => {
108115
+ const response = await fetch(path, { credentials: "omit", redirect: "error" });
108116
+ if (!response.ok || /private|no-store/i.test(response.headers.get("cache-control") || "")) {
108117
+ throw new Error("Offline asset is unavailable or private: " + path);
108118
+ }
108119
+ await cache.put(path, response);
108120
+ }))));
107784
108121
  self.skipWaiting();
107785
108122
  });
107786
108123
 
107787
108124
  self.addEventListener("activate", (event) => {
107788
108125
  event.waitUntil(
107789
108126
  caches.keys().then((keys) =>
107790
- Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))),
108127
+ Promise.all(keys.filter((k) => k.startsWith("percher-public-shell-") && k !== CACHE).map((k) => caches.delete(k))),
107791
108128
  ),
107792
108129
  );
107793
108130
  self.clients.claim();
@@ -107795,8 +108132,10 @@ self.addEventListener("activate", (event) => {
107795
108132
 
107796
108133
  self.addEventListener("fetch", (event) => {
107797
108134
  if (event.request.method !== "GET") return;
108135
+ const url = new URL(event.request.url);
108136
+ if (url.origin !== self.location.origin || !SHELL.includes(url.pathname + url.search)) return;
107798
108137
  event.respondWith(
107799
- caches.match(event.request).then((cached) => cached || fetch(event.request)),
108138
+ caches.open(CACHE).then((cache) => cache.match(event.request)).then((cached) => cached || fetch(event.request)),
107800
108139
  );
107801
108140
  });`;
107802
108141
  }
@@ -107965,17 +108304,17 @@ async function claim(ctx, input = {}) {
107965
108304
  };
107966
108305
  }
107967
108306
  // ../core/dist/commands/create.js
107968
- import { join as join8, resolve as resolve5 } from "node:path";
108307
+ import { join as join9, resolve as resolve5 } from "node:path";
107969
108308
 
107970
108309
  // ../core/dist/templates.js
107971
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync5 } from "node:fs";
107972
- import { basename as basename2, join as join7, normalize, relative as relative2, resolve as resolve4 } from "node:path";
107973
- var __dirname = "C:\\dev\\Percher\\packages\\core\\dist";
108310
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readdirSync, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync6 } from "node:fs";
108311
+ import { basename as basename2, join as join8, normalize, relative as relative2, resolve as resolve4 } from "node:path";
108312
+ var __dirname = "C:\\dev\\Percher-npm-final-release-20261018\\packages\\core\\dist";
107974
108313
  var TEMPLATES_DIR = resolve4(import.meta.dirname ?? __dirname, "../templates");
107975
108314
  var PLACEHOLDER = "__APP_NAME__";
107976
108315
  function listTemplates() {
107977
108316
  return readdirSync(TEMPLATES_DIR).filter((name) => {
107978
- const full = join7(TEMPLATES_DIR, name);
108317
+ const full = join8(TEMPLATES_DIR, name);
107979
108318
  return statSync(full).isDirectory();
107980
108319
  });
107981
108320
  }
@@ -107992,13 +108331,13 @@ function scaffoldTemplate(input) {
107992
108331
  throw new Error(`Target directory "${absTarget}" is not empty. Use --force to overwrite.`);
107993
108332
  }
107994
108333
  }
107995
- const srcDir = join7(TEMPLATES_DIR, template);
108334
+ const srcDir = join8(TEMPLATES_DIR, template);
107996
108335
  const files = [];
107997
108336
  function copyDir(src, dest) {
107998
- mkdirSync5(dest, { recursive: true });
108337
+ mkdirSync6(dest, { recursive: true });
107999
108338
  for (const entry of readdirSync(src)) {
108000
- const srcPath = join7(src, entry);
108001
- const destPath = join7(dest, entry);
108339
+ const srcPath = join8(src, entry);
108340
+ const destPath = join8(dest, entry);
108002
108341
  const rel = relative2(absTarget, normalize(destPath));
108003
108342
  if (rel.startsWith("..") || resolve4(destPath) === resolve4(absTarget, "..", basename2(absTarget))) {
108004
108343
  throw new Error(`Template contains path traversal: ${entry}`);
@@ -108009,11 +108348,11 @@ function scaffoldTemplate(input) {
108009
108348
  if (existsSync5(destPath) && !force) {
108010
108349
  continue;
108011
108350
  }
108012
- let content = readFileSync5(srcPath, "utf-8");
108351
+ let content = readFileSync6(srcPath, "utf-8");
108013
108352
  content = content.replaceAll(PLACEHOLDER, appName);
108014
108353
  content = content.replace(/\r\n/g, `
108015
108354
  `);
108016
- writeFileSync5(destPath, content, "utf-8");
108355
+ writeFileSync6(destPath, content, "utf-8");
108017
108356
  files.push(relative2(absTarget, destPath).replace(/\\/g, "/"));
108018
108357
  }
108019
108358
  }
@@ -108046,7 +108385,7 @@ async function create(ctx, input) {
108046
108385
  });
108047
108386
  }
108048
108387
  } else {
108049
- targetDir = join8(ctx.cwd, input.name);
108388
+ targetDir = join9(ctx.cwd, input.name);
108050
108389
  }
108051
108390
  try {
108052
108391
  const result = scaffoldTemplate({
@@ -108101,226 +108440,6 @@ async function dashboard(ctx, input = {}) {
108101
108440
  await ctx.openUrl(url2);
108102
108441
  return url2;
108103
108442
  }
108104
- // ../core/dist/commands/data.js
108105
- var dataInputSchema = exports_external.object({
108106
- app: exports_external.string().optional(),
108107
- json: exports_external.boolean().optional()
108108
- });
108109
- async function data(ctx, input = {}) {
108110
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
108111
- if (!name) {
108112
- throw new PercherCoreError("No app specified and no percher.toml found", {
108113
- code: "data.no-app",
108114
- hint: "Pass an app name or run from a directory with percher.toml."
108115
- });
108116
- }
108117
- const status = await ctx.client.data.getStatus(name);
108118
- const adminLink = await ctx.client.data.getAdminLink(name);
108119
- let collections = [];
108120
- let stats = { collections: 0, totalRecords: 0 };
108121
- let warning = null;
108122
- try {
108123
- const cols = await ctx.client.data.getCollections(name);
108124
- collections = cols.map((c) => ({ name: c.name, type: c.type }));
108125
- stats = await ctx.client.data.getStats(name);
108126
- } catch (err) {
108127
- const code = err.code;
108128
- if (code === "PB_AUTH_FAILED") {
108129
- warning = `Could not authenticate with PocketBase — collections + stats unavailable. Try \`percher data reset-superuser ${name}\` to rotate the credentials.`;
108130
- } else {
108131
- throw err;
108132
- }
108133
- }
108134
- return {
108135
- mode: status.mode,
108136
- status: status.status,
108137
- version: status.version ?? null,
108138
- collections,
108139
- stats,
108140
- adminUrl: adminLink.adminUrl ?? null,
108141
- warning
108142
- };
108143
- }
108144
- // ../core/dist/commands/data-export.js
108145
- import { resolve as resolve6 } from "node:path";
108146
- var dataExportInputSchema = exports_external.object({
108147
- app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
108148
- out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
108149
- });
108150
- async function dataExport(ctx, input) {
108151
- const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
108152
- if (!appName) {
108153
- throw new PercherCoreError("No app specified and no percher.toml found", {
108154
- code: "data-export.no-app",
108155
- hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
108156
- });
108157
- }
108158
- ctx.status?.(`Exporting ${appName} data...`);
108159
- const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
108160
- const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
108161
- const path = resolve6(ctx.cwd, fileName);
108162
- const res = await ctx.client.apps.streamDataExport(appName);
108163
- const bytes = await writeGzipResponseToFileAtomic(res, path);
108164
- return {
108165
- app: appName,
108166
- path,
108167
- bytes,
108168
- exportedAt: new Date().toISOString()
108169
- };
108170
- }
108171
- // ../core/dist/commands/data-oauth.js
108172
- var dataOauthInputSchema = exports_external.object({
108173
- app: exports_external.string().optional(),
108174
- provider: exports_external.string().default("google"),
108175
- clientId: exports_external.string().optional(),
108176
- clientSecret: exports_external.string().optional(),
108177
- remove: exports_external.boolean().optional(),
108178
- json: exports_external.boolean().optional()
108179
- });
108180
- async function dataOauth(ctx, input = { provider: "google" }) {
108181
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
108182
- if (!name) {
108183
- throw new PercherCoreError("No app specified and no percher.toml found", {
108184
- code: "data.no-app",
108185
- hint: "Pass an app name or run from a directory with percher.toml."
108186
- });
108187
- }
108188
- const provider = input.provider || "google";
108189
- if (input.remove) {
108190
- await ctx.client.data.removeOAuthProvider(name, provider);
108191
- return { action: "removed", provider };
108192
- }
108193
- if (input.clientId && input.clientSecret) {
108194
- const res = await ctx.client.data.setOAuthProvider(name, {
108195
- provider,
108196
- clientId: input.clientId,
108197
- clientSecret: input.clientSecret
108198
- });
108199
- return {
108200
- action: "set",
108201
- provider: res.provider,
108202
- redirectUri: res.redirectUri,
108203
- note: res.note
108204
- };
108205
- }
108206
- const status = await ctx.client.data.getOAuthStatus(name);
108207
- return {
108208
- action: "status",
108209
- provider,
108210
- providers: status.providers,
108211
- redirectUri: status.redirectUri
108212
- };
108213
- }
108214
- // ../core/dist/commands/delete.js
108215
- var deleteInputSchema = exports_external.object({
108216
- app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
108217
- confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
108218
- });
108219
- async function deleteApp(ctx, input) {
108220
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
108221
- if (!name) {
108222
- throw new PercherCoreError("No app specified and no percher.toml found", {
108223
- code: "env.no-app",
108224
- hint: "Pass an app name or run from a directory with percher.toml."
108225
- });
108226
- }
108227
- if (!input.confirm) {
108228
- throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
108229
- code: "delete.confirmation-required",
108230
- hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
108231
- });
108232
- }
108233
- await ctx.client.apps.delete(name);
108234
- }
108235
- // ../core/dist/commands/deploy-repo.js
108236
- init_dist();
108237
-
108238
- // ../core/dist/poll-deployment.js
108239
- init_dist();
108240
- class DeployPollLostContactError extends PercherCoreError {
108241
- deploymentId;
108242
- constructor(message, deploymentId, appName, options) {
108243
- super(message, {
108244
- code: "push.lost-contact",
108245
- hint: `The deploy may already be live. Check with: percher deploys ${appName}`,
108246
- cause: options?.cause
108247
- });
108248
- this.deploymentId = deploymentId;
108249
- this.name = "DeployPollLostContactError";
108250
- }
108251
- }
108252
- function isTransientPollError(err) {
108253
- if (isTransientNetworkError(err))
108254
- return true;
108255
- if (err instanceof PercherApiError)
108256
- return err.status === 502 || err.status === 503 || err.status === 504;
108257
- return err instanceof TypeError && /terminated/i.test(err.message);
108258
- }
108259
- async function pollDeployment(opts) {
108260
- const terminal = opts.terminal ?? ["live", "failed"];
108261
- const intervalMs = opts.intervalMs ?? 2000;
108262
- const timeoutMs = opts.timeoutMs ?? TIMEOUTS.clientPublishPoll;
108263
- const maxRetries = opts.maxRetries ?? Number.POSITIVE_INFINITY;
108264
- const retryBudgetMs = opts.retryBudgetMs ?? 5 * 60000;
108265
- let deployment = opts.initial;
108266
- const startedAt = Date.now();
108267
- const seenEventIds = new Set;
108268
- if (opts.onEvent) {
108269
- await drainNewEvents(opts, deployment.id, seenEventIds);
108270
- }
108271
- while (!terminal.includes(deployment.status)) {
108272
- if (Date.now() - startedAt > timeoutMs) {
108273
- opts.onTimeout(deployment);
108274
- }
108275
- opts.onTick?.(deployment);
108276
- await new Promise((r) => setTimeout(r, intervalMs));
108277
- let attempt = 0;
108278
- let streakStartedAt = 0;
108279
- let polled = false;
108280
- while (!polled) {
108281
- try {
108282
- deployment = await opts.ctx.client.apps.getDeployment(opts.appId, deployment.id);
108283
- polled = true;
108284
- } catch (err) {
108285
- attempt++;
108286
- if (!isTransientPollError(err))
108287
- throw err;
108288
- if (attempt === 1) {
108289
- streakStartedAt = Date.now();
108290
- opts.ctx.status("Lost contact with Percher; retrying while the deploy continues...");
108291
- }
108292
- if (Date.now() - startedAt > timeoutMs)
108293
- opts.onTimeout(deployment);
108294
- if (attempt >= maxRetries || Date.now() - streakStartedAt >= retryBudgetMs) {
108295
- const msg = err.message ?? String(err);
108296
- throw new DeployPollLostContactError(`${msg}
108297
-
108298
- The deploy may already be live — contact with Percher was lost mid-poll. Check with: percher deploys ${opts.appName}`, deployment.id, opts.appName, { cause: err });
108299
- }
108300
- const backoff = (opts.backoffMs ?? ((n) => Math.min(1000 * 2 ** (n - 1), 15000)))(attempt);
108301
- await new Promise((r) => setTimeout(r, backoff));
108302
- }
108303
- }
108304
- if (opts.onEvent) {
108305
- await drainNewEvents(opts, deployment.id, seenEventIds);
108306
- }
108307
- }
108308
- return deployment;
108309
- }
108310
- async function drainNewEvents(opts, deployId, seenEventIds) {
108311
- if (!opts.onEvent)
108312
- return;
108313
- try {
108314
- const { events } = await opts.ctx.client.apps.getDeployEvents(opts.appId, deployId);
108315
- for (const event of events) {
108316
- if (seenEventIds.has(event.id))
108317
- continue;
108318
- seenEventIds.add(event.id);
108319
- opts.onEvent(event);
108320
- }
108321
- } catch {}
108322
- }
108323
-
108324
108443
  // ../core/dist/untrusted.js
108325
108444
  import { relative as relative3 } from "node:path";
108326
108445
  function boundedSeconds(value, maxSeconds = 86400) {
@@ -108459,6 +108578,323 @@ function projectRelative(cwd, abs) {
108459
108578
  return isSafeRelativePath(rel) ? rel : null;
108460
108579
  }
108461
108580
 
108581
+ // ../core/dist/commands/data.js
108582
+ var dataInputSchema = exports_external.object({
108583
+ app: exports_external.string().optional(),
108584
+ json: exports_external.boolean().optional()
108585
+ });
108586
+ async function data(ctx, input = {}) {
108587
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
108588
+ if (!name) {
108589
+ throw new PercherCoreError("No app specified and no percher.toml found", {
108590
+ code: "data.no-app",
108591
+ hint: "Pass an app name or run from a directory with percher.toml."
108592
+ });
108593
+ }
108594
+ const status = await ctx.client.data.getStatus(name);
108595
+ const adminLink = await ctx.client.data.getAdminLink(name);
108596
+ let collections = [];
108597
+ let stats = { collections: 0, totalRecords: 0 };
108598
+ let warning = null;
108599
+ try {
108600
+ const cols = await ctx.client.data.getCollections(name);
108601
+ collections = cols.map((c) => ({ name: c.name, type: c.type }));
108602
+ stats = await ctx.client.data.getStats(name);
108603
+ } catch (err) {
108604
+ const code = err.code;
108605
+ if (code === "PB_AUTH_FAILED") {
108606
+ const ref = appRef(name) ?? "<app>";
108607
+ const verdict = err.extra?.verdict;
108608
+ warning = verdict === "rejected" ? `Percher has no working PocketBase admin credentials for this app; collections and counts are unavailable. The app owner can issue new ones with \`percher data reset-superuser ${ref}\`.` : `PocketBase authentication failed; collections and counts are unavailable. Retry \`percher data ${ref}\`; if this continues, contact support.`;
108609
+ } else {
108610
+ throw err;
108611
+ }
108612
+ }
108613
+ return {
108614
+ mode: status.mode,
108615
+ status: status.status,
108616
+ version: status.version ?? null,
108617
+ collections,
108618
+ stats,
108619
+ adminUrl: adminLink.adminUrl ?? null,
108620
+ warning
108621
+ };
108622
+ }
108623
+ // ../core/dist/commands/data-export.js
108624
+ import { resolve as resolve6 } from "node:path";
108625
+ var dataExportInputSchema = exports_external.object({
108626
+ app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
108627
+ out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
108628
+ });
108629
+ async function dataExport(ctx, input) {
108630
+ const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
108631
+ if (!appName) {
108632
+ throw new PercherCoreError("No app specified and no percher.toml found", {
108633
+ code: "data-export.no-app",
108634
+ hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
108635
+ });
108636
+ }
108637
+ ctx.status?.(`Exporting ${appName} data...`);
108638
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
108639
+ const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
108640
+ const path = resolve6(ctx.cwd, fileName);
108641
+ const res = await ctx.client.apps.streamDataExport(appName);
108642
+ const bytes = await writeGzipResponseToFileAtomic(res, path);
108643
+ return {
108644
+ app: appName,
108645
+ path,
108646
+ bytes,
108647
+ exportedAt: new Date().toISOString()
108648
+ };
108649
+ }
108650
+ // ../core/dist/commands/data-oauth.js
108651
+ var dataOauthInputSchema = exports_external.object({
108652
+ app: exports_external.string().optional(),
108653
+ provider: exports_external.string().default("google"),
108654
+ clientId: exports_external.string().optional(),
108655
+ clientSecret: exports_external.string().optional(),
108656
+ remove: exports_external.boolean().optional(),
108657
+ json: exports_external.boolean().optional()
108658
+ });
108659
+ async function dataOauth(ctx, input = { provider: "google" }) {
108660
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
108661
+ if (!name) {
108662
+ throw new PercherCoreError("No app specified and no percher.toml found", {
108663
+ code: "data.no-app",
108664
+ hint: "Pass an app name or run from a directory with percher.toml."
108665
+ });
108666
+ }
108667
+ const provider = input.provider || "google";
108668
+ if (input.remove) {
108669
+ await ctx.client.data.removeOAuthProvider(name, provider);
108670
+ return { action: "removed", provider };
108671
+ }
108672
+ if (input.clientId && input.clientSecret) {
108673
+ const res = await ctx.client.data.setOAuthProvider(name, {
108674
+ provider,
108675
+ clientId: input.clientId,
108676
+ clientSecret: input.clientSecret
108677
+ });
108678
+ return {
108679
+ action: "set",
108680
+ provider: res.provider,
108681
+ redirectUri: res.redirectUri,
108682
+ note: res.note
108683
+ };
108684
+ }
108685
+ const status = await ctx.client.data.getOAuthStatus(name);
108686
+ return {
108687
+ action: "status",
108688
+ provider,
108689
+ providers: status.providers,
108690
+ redirectUri: status.redirectUri
108691
+ };
108692
+ }
108693
+ // ../core/dist/commands/data-records.js
108694
+ init_dist();
108695
+
108696
+ // ../core/dist/data-recovery.js
108697
+ init_dist();
108698
+
108699
+ // ../core/dist/structured-error-codes.js
108700
+ var MAPPINGS = {
108701
+ PB_PROVISION_FAILED: {
108702
+ errorClass: "pb_provision_failed",
108703
+ phase: "infra",
108704
+ cause: "pb_provision_failed",
108705
+ title: "Database admin setup failed",
108706
+ explanation: () => "The platform could not set up the database admin account.",
108707
+ suggestion: "Publishing again after the platform failure is resolved is safe."
108708
+ },
108709
+ BUILD_SECRET_KEY_UNAVAILABLE: {
108710
+ errorClass: "build_secrets_unavailable",
108711
+ phase: "infra",
108712
+ cause: "build_secret_key_unavailable",
108713
+ title: "Percher could not protect build secrets",
108714
+ explanation: () => "Percher's key for build secrets is unavailable, so it refused the build rather than run it without them. This is a platform issue and does not count against your app.",
108715
+ suggestion: "Contact support. Removing the non-public keys from [build] pass_env lets the build run without them."
108716
+ },
108717
+ BUILD_SECRETS_UNSUPPORTED_NIXPACKS: {
108718
+ errorClass: "build_secrets_unavailable",
108719
+ phase: "infra",
108720
+ cause: "build_secrets_unsupported_nixpacks",
108721
+ title: "This builder cannot mount build secrets",
108722
+ explanation: () => "Percher is building this app with its fallback builder, which cannot keep build secrets out of the image, so it refused the build. It does not count against your app.",
108723
+ suggestion: "Remove the non-public keys from [build] pass_env, or deploy again later."
108724
+ },
108725
+ SHARED_NETWORK_FORBIDDEN: {
108726
+ errorClass: "infra_unavailable",
108727
+ phase: "infra",
108728
+ cause: "shared_network_forbidden",
108729
+ title: "Percher refused unsafe network placement",
108730
+ explanation: () => "This app is still on Percher's shared bridge. Percher refused to start another container there. This is a platform issue and does not count against your app.",
108731
+ suggestion: "Contact support. A Percher operator must migrate the app to its own network before you retry."
108732
+ },
108733
+ PLATFORM_DISK_LOW: {
108734
+ errorClass: "platform_at_capacity",
108735
+ phase: "infra",
108736
+ cause: "disk_headroom_exhausted",
108737
+ title: "Platform storage is too low",
108738
+ explanation: () => "Percher paused new deploys: the platform is nearly out of disk. Not your app — nothing to change. Deployed apps keep running; only new deploys are paused.",
108739
+ suggestion: "The operator is alerted automatically. Run `percher publish` again in ~15 min; this failure does not count against your app. Still failing after an hour: https://percher.app/status"
108740
+ },
108741
+ PLATFORM_DISK_UNKNOWN: {
108742
+ errorClass: "platform_at_capacity",
108743
+ phase: "infra",
108744
+ cause: "disk_state_unreadable",
108745
+ title: "Platform storage state is unknown",
108746
+ explanation: () => "Percher paused new deploys: it can't read how much disk is left, and won't assume there's room. Not your app. Deployed apps have not been stopped by this.",
108747
+ suggestion: "Run `percher publish` again in ~5 min; this failure does not count against your app. Still failing after 15 min: https://percher.app/status"
108748
+ },
108749
+ PLAN_POOL_EXCEEDED: {
108750
+ errorClass: "plan_pool_exceeded",
108751
+ phase: "start",
108752
+ cause: "plan_pool_exceeded",
108753
+ title: "Plan resource pool is full",
108754
+ explanation: (raw) => raw ? `${trimMessage(raw)} The platform is fine — the account is at its plan's limit.` : "This deploy would commit more RAM, vCPU or disk than your plan's pool allows across all your apps. The platform is fine — the account is at its limit.",
108755
+ suggestion: "Reduce `[resources]` in this app's percher.toml, delete an app you no longer need, or upgrade at https://percher.app/account/billing. Retrying unchanged will fail the same way."
108756
+ },
108757
+ PLACEMENT_UNDETERMINED: {
108758
+ errorClass: "infra_unavailable",
108759
+ phase: "infra",
108760
+ cause: "placement_undetermined",
108761
+ title: "Percher could not place your app's container",
108762
+ explanation: (raw) => raw ? `${trimMessage(raw)} This is a platform-side read of Percher's own container placement, not anything in your app.` : "Percher could not read which internal network this app's current containers are on, and refused to guess rather than risk splitting the app from its database across two networks.",
108763
+ suggestion: "Nothing to change in your app. Retry in a minute; if it persists, the platform is having trouble reaching its container daemon and Percher's operators are alerted."
108764
+ },
108765
+ PLATFORM_MEMORY_LOW: {
108766
+ errorClass: "platform_at_capacity",
108767
+ phase: "infra",
108768
+ cause: "box_admission_rejected",
108769
+ title: "Platform is at capacity",
108770
+ explanation: (raw) => raw ? `Percher couldn't fit this deploy on the box right now: ${trimMessage(raw)} Not your app — nothing to change. Deployed apps keep running.` : "Percher couldn't fit this deploy on the box right now — the admission gate refused to start the container. Not your app — nothing to change. Deployed apps keep running.",
108771
+ suggestion: "Run `percher publish` again in a few minutes; capacity frees up as other deploys finish, and this failure does not count against your app. Still failing after an hour: https://percher.app/status"
108772
+ },
108773
+ PB_BRIDGE_UNAVAILABLE: {
108774
+ errorClass: "infra_unavailable",
108775
+ phase: "infra",
108776
+ cause: "pb_bridge_unreachable",
108777
+ title: "PocketBase is unreachable from the platform",
108778
+ explanation: (raw) => raw ? `Percher could not reach your app's database through its internal bridge: ${trimMessage(raw)}. This is a platform-side issue — your data and your code are both fine.` : "Percher could not reach your app's database through its internal bridge. This is a platform-side issue — your data and your code are both fine.",
108779
+ suggestion: "Wait a minute and run `percher publish` again; this failure does not count against your app. Your data was not touched — the platform could not read it, which is not the same as it being missing. Still failing after 15 min: https://percher.app/status"
108780
+ },
108781
+ PB_ROTATION_UNVERIFIED: {
108782
+ errorClass: "infra_unavailable",
108783
+ phase: "infra",
108784
+ cause: "pb_rotation_unverified",
108785
+ title: "PocketBase admin password was rotated but not confirmed",
108786
+ explanation: (raw) => raw ? `${trimMessage(raw)} The rotation itself ran, so the previously stored admin password may now be stale. Your app data was not modified — only the admin credential changed.` : "Percher rotated your app's PocketBase admin password, then could not confirm the rotation. The previously stored admin password may now be stale. Your app data was not modified — only the admin credential changed.",
108787
+ suggestion: "Run the superuser reset again (`percher data reset-superuser` or the dashboard's reset button) — the rotation is idempotent, and a successful re-run stores a fresh working credential. This failure does not count against your app."
108788
+ },
108789
+ PB_OPERATION_BUSY: {
108790
+ errorClass: "pb_operation_busy",
108791
+ phase: "start",
108792
+ cause: "pb_backup_restore_busy",
108793
+ title: "PocketBase is finishing another data operation",
108794
+ explanation: (raw) => raw ? `${trimMessage(raw)} The operation is scoped to this app and must finish before Percher can create the deploy checkpoint.` : "PocketBase is already creating a backup or restoring data for this app, so Percher could not create the deploy checkpoint yet.",
108795
+ suggestion: "Wait for the current backup, export or restore to finish, then publish again. If no operation is visible after a few minutes, contact support before retrying repeatedly."
108796
+ },
108797
+ CANARY_FAILED: {
108798
+ errorClass: "runtime_crash",
108799
+ phase: "start",
108800
+ cause: "canary_failed",
108801
+ title: "Your app went live, then failed its post-deploy check",
108802
+ explanation: (raw) => raw ? `${trimMessage(raw)} The container started and passed its health check, so this is something that only shows up once the app is serving real requests.` : "Percher swapped your new version in, then its own public URL did not answer correctly. The container started and passed its health check, so this is something that only shows up once the app is serving real requests.",
108803
+ suggestion: "Run `percher doctor` — it re-probes the container and the public route and will say which half is failing. The container log from the moment it was rolled back is in the build log below. Retrying unchanged will usually fail the same way."
108804
+ },
108805
+ APP_DATA_MOUNT_CONFLICT: {
108806
+ errorClass: "app_data_mount_conflict",
108807
+ phase: "start",
108808
+ cause: "app_data_mount_conflict",
108809
+ title: "Your image has a file where Percher mounts /app/data",
108810
+ explanation: () => "The image built, but Docker could not start the container: every Percher app gets its persistent data volume mounted at /app/data, and this image has a file (or a link to one) at /app or /app/data, so that directory cannot be created.",
108811
+ suggestion: "Install the binary somewhere else, such as /usr/local/bin/app, and point ENTRYPOINT/CMD at it. If the project has a file named `data` at its root, rename it. Retrying unchanged will fail the same way."
108812
+ },
108813
+ WORKER_UNAVAILABLE: {
108814
+ errorClass: "infra_unavailable",
108815
+ phase: "infra",
108816
+ cause: "worker_fetch_failed",
108817
+ title: "Build worker is unreachable",
108818
+ explanation: (raw) => raw ? `The Percher build worker did not respond in time: ${trimMessage(raw)}. This is a platform-side issue, not your code.` : "The Percher build worker did not respond in time. This is a platform-side issue, not your code.",
108819
+ suggestion: "Wait 30 seconds and run `percher publish` again. If retry #2 also fails, pause 5 minutes before the next attempt — back-to-back retries during a partial outage just waste your time. After 3 spaced-out retries, check status.percher.app or contact support. Your build never started."
108820
+ },
108821
+ WORKER_UNAVAILABLE_PERSISTENT: {
108822
+ errorClass: "infra_unavailable",
108823
+ phase: "infra",
108824
+ cause: "worker_circuit_breaker_open",
108825
+ title: "Build worker is in a crash loop",
108826
+ explanation: () => "The Percher build worker has failed repeatedly in the last minute and the platform's circuit breaker has flipped open. Retrying immediately would just queue more failures.",
108827
+ suggestion: "Wait 2-3 minutes for the platform to recover, then retry. If it persists past 5 minutes check status.percher.app or contact support — this is a platform incident, not your code."
108828
+ },
108829
+ BUILD_TIMEOUT: {
108830
+ errorClass: "build_failed",
108831
+ phase: "build",
108832
+ cause: "build_timeout",
108833
+ title: "Build timed out",
108834
+ explanation: () => "The build ran longer than the 10-minute platform limit and was aborted.",
108835
+ suggestion: "Common causes: a slow `postinstall` hook, very large dependency tree, or a Dockerfile that pulls a huge base image. Try `bun install` locally and check what's slow. If it's legitimately a heavy build, contact support to discuss raising the limit for your account."
108836
+ },
108837
+ BUILD_FAILED: {
108838
+ errorClass: "build_failed",
108839
+ phase: "build",
108840
+ cause: "build_nonzero_exit",
108841
+ title: "Build failed",
108842
+ explanation: (raw) => raw ? `The build process returned a non-zero exit code: ${trimMessage(raw)}. Read the build log below for the exact error.` : "The build process returned a non-zero exit code. Read the build log below for the exact error.",
108843
+ suggestion: "Run your build locally to reproduce: `bun install && bun run build`. The error in the build log usually points to a missing dependency, a TypeScript/syntax error, or a misconfigured framework setting."
108844
+ },
108845
+ WORKER_ERROR: {
108846
+ errorClass: "infra_unavailable",
108847
+ phase: "infra",
108848
+ cause: "worker_internal_error",
108849
+ title: "Platform error inside the build worker",
108850
+ explanation: (raw) => raw ? `The build worker hit an internal error: ${trimMessage(raw)}. This is a Percher bug, not your code.` : "The build worker hit an internal error. This is a Percher bug, not your code.",
108851
+ suggestion: "Retry once — transient platform errors usually clear within a minute. If it repeats, please report it: include the deploy ID and we'll investigate. Your account is not at fault."
108852
+ },
108853
+ WORKER_ENDPOINT_MISSING: {
108854
+ errorClass: "infra_unavailable",
108855
+ phase: "infra",
108856
+ cause: "worker_endpoint_missing",
108857
+ title: "Platform worker is mid-upgrade",
108858
+ explanation: (raw) => raw ? `${trimMessage(raw)} The platform's worker process predates an endpoint this request needs — a rolling-deploy skew, not your code.` : "The platform's worker process predates an endpoint this request needs — a rolling-deploy skew, not your code.",
108859
+ suggestion: "Retry in a few minutes; the operator finishes the platform deploy worker-first. This failure does not count against your app."
108860
+ },
108861
+ DEPLOY_STALLED: {
108862
+ errorClass: "infra_unavailable",
108863
+ phase: "infra",
108864
+ cause: "deploy_stalled",
108865
+ title: "Deploy stalled",
108866
+ explanation: () => "The platform abandoned this deploy — likely because the API or build worker restarted mid-build. Your code is fine; the build never finished.",
108867
+ suggestion: "Run `percher publish` again. The new deploy will start fresh. If you see this repeatedly across multiple publishes, check status.percher.app — it implies a recurring platform issue."
108868
+ }
108869
+ };
108870
+ function classifyByErrorCode(code, rawMessage) {
108871
+ if (!code)
108872
+ return null;
108873
+ const mapping = MAPPINGS[code];
108874
+ if (!mapping)
108875
+ return null;
108876
+ return {
108877
+ title: mapping.title,
108878
+ explanation: mapping.explanation(rawMessage),
108879
+ suggestion: mapping.suggestion,
108880
+ errorClass: mapping.errorClass,
108881
+ phase: mapping.phase,
108882
+ cause: mapping.cause,
108883
+ relevantFiles: [],
108884
+ missingEnvVars: []
108885
+ };
108886
+ }
108887
+ function trimMessage(raw) {
108888
+ const firstLine = raw.split(`
108889
+ `)[0] ?? "";
108890
+ return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
108891
+ }
108892
+ var PB_PROVISION_FAILED_ASK = {
108893
+ prompt: "The platform could not set up the database admin account. Publishing again after the platform failure is resolved is safe.",
108894
+ reasonCode: "pb_provision_failed",
108895
+ retryable: true
108896
+ };
108897
+
108462
108898
  // ../core/dist/recovery.js
108463
108899
  function recoveryNone(opts = {}) {
108464
108900
  return {
@@ -108688,12 +109124,25 @@ function unprovenPlatformClaim(failure) {
108688
109124
  }
108689
109125
  function recoveryFromErrorClass(errorClass, failure) {
108690
109126
  switch (errorClass) {
109127
+ case "build_secrets_unavailable":
109128
+ return recoveryAsk({
109129
+ prompt: "Percher cannot protect build secrets for this build. Should I remove the non-public keys from [build] pass_env and publish again, or wait and publish later?",
109130
+ options: [
109131
+ "remove non-public keys from [build] pass_env and publish again",
109132
+ "wait and publish later"
109133
+ ],
109134
+ reasonCode: "build_secrets_unavailable"
109135
+ });
108691
109136
  case "missing_env":
108692
109137
  return recoveryEnv({
108693
109138
  app: failure.appName,
108694
109139
  keys: failure.missingEnvVars ?? [],
108695
109140
  reasonCode: "missing_env"
108696
109141
  });
109142
+ case "pb_provision_failed":
109143
+ if (failure.classSource !== "code")
109144
+ return unprovenPlatformClaim(failure);
109145
+ return recoveryAsk(PB_PROVISION_FAILED_ASK);
108697
109146
  case "infra_unavailable":
108698
109147
  if (failure.classSource !== "code")
108699
109148
  return unprovenPlatformClaim(failure);
@@ -108784,9 +109233,280 @@ function recoveryFromErrorClass(errorClass, failure) {
108784
109233
  }
108785
109234
  }
108786
109235
 
109236
+ // ../core/dist/data-recovery.js
109237
+ var WRITES = new Set(["create", "update", "delete"]);
109238
+ function isDataRecordErrorCode(code) {
109239
+ return typeof code === "string" && Object.hasOwn(DATA_RECORD_ERROR_STATUS, code);
109240
+ }
109241
+
109242
+ class DataRecordError extends PercherApiError {
109243
+ target;
109244
+ constructor(err, target) {
109245
+ super(err.message, {
109246
+ status: err.status,
109247
+ code: err.code,
109248
+ requestId: err.requestId,
109249
+ extra: err.extra,
109250
+ cause: err
109251
+ });
109252
+ this.name = "DataRecordError";
109253
+ this.target = target;
109254
+ }
109255
+ }
109256
+
109257
+ // ../core/dist/commands/data-records.js
109258
+ var appArg = exports_external.string().min(1).optional().describe("App name or id. Defaults to the app in ./percher.toml.");
109259
+ var collectionArg = exports_external.string().regex(DATA_COLLECTION_RE, "a collection name: a letter, then letters, digits or _ (max 64)").describe("Collection name, exactly as percher_data_schema lists it (case-sensitive).");
109260
+ var recordIdArg = exports_external.string().regex(DATA_RECORD_ID_RE, "a record id: letters, digits, _ or - (max 64)").describe("Record id.");
109261
+ var expression = (what) => exports_external.string().max(DATA_LIMITS.expressionChars).optional().describe(what);
109262
+ var recordData = exports_external.record(exports_external.string(), exports_external.unknown()).describe("Field values keyed by field name (see percher_data_schema). File fields, hidden fields and unknown fields are refused.");
109263
+ var dataSchemaInputSchema = exports_external.object({ app: appArg });
109264
+ var dataQueryInputSchema = exports_external.object({
109265
+ app: appArg,
109266
+ collection: collectionArg,
109267
+ filter: expression("PocketBase-style filter over the collection's own fields, e.g. status = 'open' && priority >= 2. Operators = != > >= < <= ~ !~ and their ?-prefixed any-of forms; && || and parentheses. No @-variables, dotted paths, functions or backslashes."),
109268
+ sort: expression("Comma-separated fields, - for descending, e.g. -created,title (max 8)."),
109269
+ page: exports_external.number().int().min(1).max(DATA_LIMITS.maxPage).optional(),
109270
+ perPage: exports_external.number().int().min(1).max(DATA_LIMITS.maxPerPage).optional(),
109271
+ fields: expression("Comma-separated field names to return (default: every non-hidden field).")
109272
+ });
109273
+ var dataGetInputSchema = exports_external.object({
109274
+ app: appArg,
109275
+ collection: collectionArg,
109276
+ id: recordIdArg
109277
+ });
109278
+ var dataCreateInputSchema = exports_external.object({
109279
+ app: appArg,
109280
+ collection: collectionArg,
109281
+ data: recordData,
109282
+ id: recordIdArg.optional().describe("Optional id for the new record. With one, a create whose outcome is unknown can be checked with percher_data_get instead of searched for.")
109283
+ });
109284
+ var dataUpdateInputSchema = exports_external.object({
109285
+ app: appArg,
109286
+ collection: collectionArg,
109287
+ id: recordIdArg,
109288
+ data: recordData,
109289
+ ifUpdated: exports_external.string().min(1).max(64).optional().describe("The record's `updated` value as last read. The update is refused if it changed since. A best-effort timestamp check, not a lock: timestamp collisions and the app's own concurrent writes can defeat it.")
109290
+ });
109291
+ var dataDeleteInputSchema = exports_external.object({
109292
+ app: appArg,
109293
+ collection: collectionArg,
109294
+ id: recordIdArg
109295
+ });
109296
+ function resolveApp(ctx, app) {
109297
+ const name = app ?? readPercherTomlAppName(ctx.cwd);
109298
+ if (!name) {
109299
+ throw new PercherCoreError("No app specified and no percher.toml found", {
109300
+ code: "data.no-app",
109301
+ hint: "Pass an app name or run from a directory with percher.toml."
109302
+ });
109303
+ }
109304
+ return name;
109305
+ }
109306
+ async function call(target, run) {
109307
+ try {
109308
+ return await run();
109309
+ } catch (err) {
109310
+ if (err instanceof PercherApiError && isDataRecordErrorCode(err.code)) {
109311
+ throw new DataRecordError(err, target);
109312
+ }
109313
+ throw err;
109314
+ }
109315
+ }
109316
+ function target(op, app, collection, id) {
109317
+ return {
109318
+ op,
109319
+ app,
109320
+ ...collection !== undefined ? { collection } : {},
109321
+ ...id !== undefined ? { id } : {}
109322
+ };
109323
+ }
109324
+ var SCHEMA_MAX_COLLECTIONS = DATA_LIMITS.schemaPerPage * DATA_LIMITS.schemaMaxPages;
109325
+ function omittedByRulesNote(omitted) {
109326
+ if (typeof omitted !== "number" || !Number.isSafeInteger(omitted) || omitted < 1 || omitted > SCHEMA_MAX_COLLECTIONS) {
109327
+ return;
109328
+ }
109329
+ const subject = omitted === 1 ? "1 collection in this app is" : `${omitted} collections in this app are`;
109330
+ return `${subject} reachable only with full access, which the app's own server code may use. The owner can choose full access for this app in Settings → Connections.`;
109331
+ }
109332
+ async function dataSchema2(ctx, input) {
109333
+ const app = resolveApp(ctx, input.app);
109334
+ const schema = await call(target("schema", app), () => ctx.client.data.getSchema(app));
109335
+ const { note: _fromApi, ...rest } = schema;
109336
+ const note = omittedByRulesNote(rest.omittedByRules);
109337
+ return note === undefined ? rest : { ...rest, note };
109338
+ }
109339
+ async function dataQuery(ctx, input) {
109340
+ const { app: rawApp, collection, ...query } = input;
109341
+ const app = resolveApp(ctx, rawApp);
109342
+ return call(target("query", app, collection), () => ctx.client.data.queryRecords(app, collection, query));
109343
+ }
109344
+ async function dataGet(ctx, input) {
109345
+ const app = resolveApp(ctx, input.app);
109346
+ return call(target("get", app, input.collection, input.id), () => ctx.client.data.getRecord(app, input.collection, input.id));
109347
+ }
109348
+ async function dataCreate(ctx, input) {
109349
+ const app = resolveApp(ctx, input.app);
109350
+ const body = {
109351
+ data: input.data,
109352
+ ...input.id !== undefined ? { id: input.id } : {}
109353
+ };
109354
+ return call(target("create", app, input.collection, input.id), () => ctx.client.data.createRecord(app, input.collection, body));
109355
+ }
109356
+ async function dataUpdate(ctx, input) {
109357
+ const app = resolveApp(ctx, input.app);
109358
+ const body = {
109359
+ data: input.data,
109360
+ ...input.ifUpdated !== undefined ? { ifUpdated: input.ifUpdated } : {}
109361
+ };
109362
+ return call(target("update", app, input.collection, input.id), () => ctx.client.data.updateRecord(app, input.collection, input.id, body));
109363
+ }
109364
+ async function dataDelete(ctx, input) {
109365
+ const app = resolveApp(ctx, input.app);
109366
+ await call(target("delete", app, input.collection, input.id), () => ctx.client.data.deleteRecord(app, input.collection, input.id));
109367
+ return { deleted: true, collection: input.collection, id: input.id };
109368
+ }
109369
+ // ../core/dist/commands/delete.js
109370
+ var deleteInputSchema = exports_external.object({
109371
+ app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
109372
+ confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
109373
+ });
109374
+ async function deleteApp(ctx, input) {
109375
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
109376
+ if (!name) {
109377
+ throw new PercherCoreError("No app specified and no percher.toml found", {
109378
+ code: "env.no-app",
109379
+ hint: "Pass an app name or run from a directory with percher.toml."
109380
+ });
109381
+ }
109382
+ if (!input.confirm) {
109383
+ throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
109384
+ code: "delete.confirmation-required",
109385
+ hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
109386
+ });
109387
+ }
109388
+ await ctx.client.apps.delete(name);
109389
+ }
109390
+ // ../core/dist/commands/deploy-repo.js
109391
+ init_dist();
109392
+
109393
+ // ../core/dist/deployment-replaced.js
109394
+ var summary = "This deployment is no longer active. Inspect the app's current state before deploying again.";
109395
+ function recovery(app, deployId) {
109396
+ return recoveryDoctor({ app, deployId, mode: "deploy", reasonCode: "replaced_by_newer" });
109397
+ }
109398
+ function throwIfDeploymentReplaced(app, deployment) {
109399
+ if (deployment.status !== "replaced")
109400
+ return;
109401
+ throw new PercherCoreError(summary, {
109402
+ code: "push.deploy-replaced",
109403
+ hint: `Run \`percher doctor --app ${app}\` to inspect the current state.`,
109404
+ recovery: { ...recovery(app, deployment.id) }
109405
+ });
109406
+ }
109407
+ function replacedDeploymentResult(app, deployment) {
109408
+ return {
109409
+ status: "replaced",
109410
+ app,
109411
+ deployment,
109412
+ summary,
109413
+ recovery: recovery(app.name, deployment.id),
109414
+ fileCount: 0,
109415
+ bytes: 0,
109416
+ configPath: "",
109417
+ bundle: { fileCount: 0, bytes: 0 }
109418
+ };
109419
+ }
109420
+
109421
+ // ../core/dist/poll-deployment.js
109422
+ init_dist();
109423
+ class DeployPollLostContactError extends PercherCoreError {
109424
+ deploymentId;
109425
+ constructor(message, deploymentId, appName, options) {
109426
+ super(message, {
109427
+ code: "push.lost-contact",
109428
+ hint: `The deploy may already be live. Check with: percher deploys ${appName}`,
109429
+ cause: options?.cause
109430
+ });
109431
+ this.deploymentId = deploymentId;
109432
+ this.name = "DeployPollLostContactError";
109433
+ }
109434
+ }
109435
+ function isTransientPollError(err) {
109436
+ if (isTransientNetworkError(err))
109437
+ return true;
109438
+ if (err instanceof PercherApiError)
109439
+ return err.status === 429 || err.status === 502 || err.status === 503 || err.status === 504;
109440
+ return err instanceof TypeError && /terminated/i.test(err.message);
109441
+ }
109442
+ async function pollDeployment(opts) {
109443
+ const terminal = opts.terminal ?? ["live", "failed", "replaced"];
109444
+ const intervalMs = opts.intervalMs ?? 2000;
109445
+ const timeoutMs = opts.timeoutMs ?? TIMEOUTS.clientPublishPoll;
109446
+ const maxRetries = opts.maxRetries ?? Number.POSITIVE_INFINITY;
109447
+ const retryBudgetMs = opts.retryBudgetMs ?? 5 * 60000;
109448
+ let deployment = opts.initial;
109449
+ const startedAt = Date.now();
109450
+ const seenEventIds = new Set;
109451
+ if (opts.onEvent) {
109452
+ await drainNewEvents(opts, deployment.id, seenEventIds);
109453
+ }
109454
+ while (!terminal.includes(deployment.status)) {
109455
+ if (Date.now() - startedAt > timeoutMs) {
109456
+ opts.onTimeout(deployment);
109457
+ }
109458
+ opts.onTick?.(deployment);
109459
+ await new Promise((r) => setTimeout(r, intervalMs));
109460
+ let attempt = 0;
109461
+ let streakStartedAt = 0;
109462
+ let polled = false;
109463
+ while (!polled) {
109464
+ try {
109465
+ deployment = await opts.ctx.client.apps.getDeployment(opts.appId, deployment.id);
109466
+ polled = true;
109467
+ } catch (err) {
109468
+ attempt++;
109469
+ if (!isTransientPollError(err))
109470
+ throw err;
109471
+ if (attempt === 1) {
109472
+ streakStartedAt = Date.now();
109473
+ opts.ctx.status(err instanceof PercherApiError && err.status === 429 ? "Percher asked this client to slow down; retrying while the deploy continues..." : "Lost contact with Percher; retrying while the deploy continues...");
109474
+ }
109475
+ if (Date.now() - startedAt > timeoutMs)
109476
+ opts.onTimeout(deployment);
109477
+ if (attempt >= maxRetries || Date.now() - streakStartedAt >= retryBudgetMs) {
109478
+ const msg = err.message ?? String(err);
109479
+ throw new DeployPollLostContactError(`${msg}
109480
+
109481
+ The deploy may already be live — contact with Percher was lost mid-poll. Check with: percher deploys ${opts.appName}`, deployment.id, opts.appName, { cause: err });
109482
+ }
109483
+ const backoff = (opts.backoffMs ?? ((n) => Math.min(1000 * 2 ** (n - 1), 15000)))(attempt);
109484
+ await new Promise((r) => setTimeout(r, backoff));
109485
+ }
109486
+ }
109487
+ if (opts.onEvent) {
109488
+ await drainNewEvents(opts, deployment.id, seenEventIds);
109489
+ }
109490
+ }
109491
+ return deployment;
109492
+ }
109493
+ async function drainNewEvents(opts, deployId, seenEventIds) {
109494
+ if (!opts.onEvent)
109495
+ return;
109496
+ try {
109497
+ const { events } = await opts.ctx.client.apps.getDeployEvents(opts.appId, deployId);
109498
+ for (const event of events) {
109499
+ if (seenEventIds.has(event.id))
109500
+ continue;
109501
+ seenEventIds.add(event.id);
109502
+ opts.onEvent(event);
109503
+ }
109504
+ } catch {}
109505
+ }
109506
+
108787
109507
  // ../core/dist/commands/publish-failure.js
108788
109508
  init_dist();
108789
- import { join as join9 } from "node:path";
109509
+ import { join as join10 } from "node:path";
108790
109510
 
108791
109511
  // ../core/dist/error-classifier.js
108792
109512
  function extractEnvVarsFromLog(combined) {
@@ -108804,6 +109524,19 @@ function extractModuleFromLog(combined) {
108804
109524
  return match?.[1];
108805
109525
  }
108806
109526
  var patterns = [
109527
+ {
109528
+ pattern: /PB_PROVISION_FAILED|the platform could not set up the database admin account/i,
109529
+ result: {
109530
+ title: "Database admin setup failed",
109531
+ explanation: "The platform could not set up the database admin account.",
109532
+ suggestion: "Publishing again after the platform failure is resolved is safe.",
109533
+ errorClass: "pb_provision_failed",
109534
+ phase: "infra",
109535
+ cause: "pb_provision_failed",
109536
+ relevantFiles: [],
109537
+ missingEnvVars: []
109538
+ }
109539
+ },
108807
109540
  {
108808
109541
  pattern: /DEPLOY_STALLED:/,
108809
109542
  result: {
@@ -109172,171 +109905,9 @@ ${buildLog ?? ""}`;
109172
109905
  return null;
109173
109906
  }
109174
109907
 
109175
- // ../core/dist/structured-error-codes.js
109176
- var MAPPINGS = {
109177
- PLATFORM_DISK_LOW: {
109178
- errorClass: "platform_at_capacity",
109179
- phase: "infra",
109180
- cause: "disk_headroom_exhausted",
109181
- title: "Platform storage is too low",
109182
- explanation: () => "Percher paused new deploys: the platform is nearly out of disk. Not your app — nothing to change. Deployed apps keep running; only new deploys are paused.",
109183
- suggestion: "The operator is alerted automatically. Run `percher publish` again in ~15 min; this failure does not count against your app. Still failing after an hour: https://percher.app/status"
109184
- },
109185
- PLATFORM_DISK_UNKNOWN: {
109186
- errorClass: "platform_at_capacity",
109187
- phase: "infra",
109188
- cause: "disk_state_unreadable",
109189
- title: "Platform storage state is unknown",
109190
- explanation: () => "Percher paused new deploys: it can't read how much disk is left, and won't assume there's room. Not your app. Deployed apps have not been stopped by this.",
109191
- suggestion: "Run `percher publish` again in ~5 min; this failure does not count against your app. Still failing after 15 min: https://percher.app/status"
109192
- },
109193
- PLAN_POOL_EXCEEDED: {
109194
- errorClass: "plan_pool_exceeded",
109195
- phase: "start",
109196
- cause: "plan_pool_exceeded",
109197
- title: "Plan resource pool is full",
109198
- explanation: (raw) => raw ? `${trimMessage(raw)} The platform is fine — the account is at its plan's limit.` : "This deploy would commit more RAM, vCPU or disk than your plan's pool allows across all your apps. The platform is fine — the account is at its limit.",
109199
- suggestion: "Reduce `[resources]` in this app's percher.toml, delete an app you no longer need, or upgrade at https://percher.app/account/billing. Retrying unchanged will fail the same way."
109200
- },
109201
- PLACEMENT_UNDETERMINED: {
109202
- errorClass: "infra_unavailable",
109203
- phase: "infra",
109204
- cause: "placement_undetermined",
109205
- title: "Percher could not place your app's container",
109206
- explanation: (raw) => raw ? `${trimMessage(raw)} This is a platform-side read of Percher's own container placement, not anything in your app.` : "Percher could not read which internal network this app's current containers are on, and refused to guess rather than risk splitting the app from its database across two networks.",
109207
- suggestion: "Nothing to change in your app. Retry in a minute; if it persists, the platform is having trouble reaching its container daemon and Percher's operators are alerted."
109208
- },
109209
- PLATFORM_MEMORY_LOW: {
109210
- errorClass: "platform_at_capacity",
109211
- phase: "infra",
109212
- cause: "box_admission_rejected",
109213
- title: "Platform is at capacity",
109214
- explanation: (raw) => raw ? `Percher couldn't fit this deploy on the box right now: ${trimMessage(raw)} Not your app — nothing to change. Deployed apps keep running.` : "Percher couldn't fit this deploy on the box right now — the admission gate refused to start the container. Not your app — nothing to change. Deployed apps keep running.",
109215
- suggestion: "Run `percher publish` again in a few minutes; capacity frees up as other deploys finish, and this failure does not count against your app. Still failing after an hour: https://percher.app/status"
109216
- },
109217
- PB_BRIDGE_UNAVAILABLE: {
109218
- errorClass: "infra_unavailable",
109219
- phase: "infra",
109220
- cause: "pb_bridge_unreachable",
109221
- title: "PocketBase is unreachable from the platform",
109222
- explanation: (raw) => raw ? `Percher could not reach your app's database through its internal bridge: ${trimMessage(raw)}. This is a platform-side issue — your data and your code are both fine.` : "Percher could not reach your app's database through its internal bridge. This is a platform-side issue — your data and your code are both fine.",
109223
- suggestion: "Wait a minute and run `percher publish` again; this failure does not count against your app. Your data was not touched — the platform could not read it, which is not the same as it being missing. Still failing after 15 min: https://percher.app/status"
109224
- },
109225
- PB_ROTATION_UNVERIFIED: {
109226
- errorClass: "infra_unavailable",
109227
- phase: "infra",
109228
- cause: "pb_rotation_unverified",
109229
- title: "PocketBase admin password was rotated but not confirmed",
109230
- explanation: (raw) => raw ? `${trimMessage(raw)} The rotation itself ran, so the previously stored admin password may now be stale. Your app data was not modified — only the admin credential changed.` : "Percher rotated your app's PocketBase admin password, then could not confirm the rotation. The previously stored admin password may now be stale. Your app data was not modified — only the admin credential changed.",
109231
- suggestion: "Run the superuser reset again (`percher data reset-superuser` or the dashboard's reset button) — the rotation is idempotent, and a successful re-run stores a fresh working credential. This failure does not count against your app."
109232
- },
109233
- PB_OPERATION_BUSY: {
109234
- errorClass: "pb_operation_busy",
109235
- phase: "start",
109236
- cause: "pb_backup_restore_busy",
109237
- title: "PocketBase is finishing another data operation",
109238
- explanation: (raw) => raw ? `${trimMessage(raw)} The operation is scoped to this app and must finish before Percher can create the deploy checkpoint.` : "PocketBase is already creating a backup or restoring data for this app, so Percher could not create the deploy checkpoint yet.",
109239
- suggestion: "Wait for the current backup, export or restore to finish, then publish again. If no operation is visible after a few minutes, contact support before retrying repeatedly."
109240
- },
109241
- CANARY_FAILED: {
109242
- errorClass: "runtime_crash",
109243
- phase: "start",
109244
- cause: "canary_failed",
109245
- title: "Your app went live, then failed its post-deploy check",
109246
- explanation: (raw) => raw ? `${trimMessage(raw)} The container started and passed its health check, so this is something that only shows up once the app is serving real requests.` : "Percher swapped your new version in, then its own public URL did not answer correctly. The container started and passed its health check, so this is something that only shows up once the app is serving real requests.",
109247
- suggestion: "Run `percher doctor` — it re-probes the container and the public route and will say which half is failing. The container log from the moment it was rolled back is in the build log below. Retrying unchanged will usually fail the same way."
109248
- },
109249
- APP_DATA_MOUNT_CONFLICT: {
109250
- errorClass: "app_data_mount_conflict",
109251
- phase: "start",
109252
- cause: "app_data_mount_conflict",
109253
- title: "Your image has a file where Percher mounts /app/data",
109254
- explanation: () => "The image built, but Docker could not start the container: every Percher app gets its persistent data volume mounted at /app/data, and this image has a file (or a link to one) at /app or /app/data, so that directory cannot be created.",
109255
- suggestion: "Install the binary somewhere else, such as /usr/local/bin/app, and point ENTRYPOINT/CMD at it. If the project has a file named `data` at its root, rename it. Retrying unchanged will fail the same way."
109256
- },
109257
- WORKER_UNAVAILABLE: {
109258
- errorClass: "infra_unavailable",
109259
- phase: "infra",
109260
- cause: "worker_fetch_failed",
109261
- title: "Build worker is unreachable",
109262
- explanation: (raw) => raw ? `The Percher build worker did not respond in time: ${trimMessage(raw)}. This is a platform-side issue, not your code.` : "The Percher build worker did not respond in time. This is a platform-side issue, not your code.",
109263
- suggestion: "Wait 30 seconds and run `percher publish` again. If retry #2 also fails, pause 5 minutes before the next attempt — back-to-back retries during a partial outage just waste your time. After 3 spaced-out retries, check status.percher.app or contact support. Your build never started."
109264
- },
109265
- WORKER_UNAVAILABLE_PERSISTENT: {
109266
- errorClass: "infra_unavailable",
109267
- phase: "infra",
109268
- cause: "worker_circuit_breaker_open",
109269
- title: "Build worker is in a crash loop",
109270
- explanation: () => "The Percher build worker has failed repeatedly in the last minute and the platform's circuit breaker has flipped open. Retrying immediately would just queue more failures.",
109271
- suggestion: "Wait 2-3 minutes for the platform to recover, then retry. If it persists past 5 minutes check status.percher.app or contact support — this is a platform incident, not your code."
109272
- },
109273
- BUILD_TIMEOUT: {
109274
- errorClass: "build_failed",
109275
- phase: "build",
109276
- cause: "build_timeout",
109277
- title: "Build timed out",
109278
- explanation: () => "The build ran longer than the 10-minute platform limit and was aborted.",
109279
- suggestion: "Common causes: a slow `postinstall` hook, very large dependency tree, or a Dockerfile that pulls a huge base image. Try `bun install` locally and check what's slow. If it's legitimately a heavy build, contact support to discuss raising the limit for your account."
109280
- },
109281
- BUILD_FAILED: {
109282
- errorClass: "build_failed",
109283
- phase: "build",
109284
- cause: "build_nonzero_exit",
109285
- title: "Build failed",
109286
- explanation: (raw) => raw ? `The build process returned a non-zero exit code: ${trimMessage(raw)}. Read the build log below for the exact error.` : "The build process returned a non-zero exit code. Read the build log below for the exact error.",
109287
- suggestion: "Run your build locally to reproduce: `bun install && bun run build`. The error in the build log usually points to a missing dependency, a TypeScript/syntax error, or a misconfigured framework setting."
109288
- },
109289
- WORKER_ERROR: {
109290
- errorClass: "infra_unavailable",
109291
- phase: "infra",
109292
- cause: "worker_internal_error",
109293
- title: "Platform error inside the build worker",
109294
- explanation: (raw) => raw ? `The build worker hit an internal error: ${trimMessage(raw)}. This is a Percher bug, not your code.` : "The build worker hit an internal error. This is a Percher bug, not your code.",
109295
- suggestion: "Retry once — transient platform errors usually clear within a minute. If it repeats, please report it: include the deploy ID and we'll investigate. Your account is not at fault."
109296
- },
109297
- WORKER_ENDPOINT_MISSING: {
109298
- errorClass: "infra_unavailable",
109299
- phase: "infra",
109300
- cause: "worker_endpoint_missing",
109301
- title: "Platform worker is mid-upgrade",
109302
- explanation: (raw) => raw ? `${trimMessage(raw)} The platform's worker process predates an endpoint this request needs — a rolling-deploy skew, not your code.` : "The platform's worker process predates an endpoint this request needs — a rolling-deploy skew, not your code.",
109303
- suggestion: "Retry in a few minutes; the operator finishes the platform deploy worker-first. This failure does not count against your app."
109304
- },
109305
- DEPLOY_STALLED: {
109306
- errorClass: "infra_unavailable",
109307
- phase: "infra",
109308
- cause: "deploy_stalled",
109309
- title: "Deploy stalled",
109310
- explanation: () => "The platform abandoned this deploy — likely because the API or build worker restarted mid-build. Your code is fine; the build never finished.",
109311
- suggestion: "Run `percher publish` again. The new deploy will start fresh. If you see this repeatedly across multiple publishes, check status.percher.app — it implies a recurring platform issue."
109312
- }
109313
- };
109314
- function classifyByErrorCode(code, rawMessage) {
109315
- if (!code)
109316
- return null;
109317
- const mapping = MAPPINGS[code];
109318
- if (!mapping)
109319
- return null;
109320
- return {
109321
- title: mapping.title,
109322
- explanation: mapping.explanation(rawMessage),
109323
- suggestion: mapping.suggestion,
109324
- errorClass: mapping.errorClass,
109325
- phase: mapping.phase,
109326
- cause: mapping.cause,
109327
- relevantFiles: [],
109328
- missingEnvVars: []
109329
- };
109330
- }
109331
- function trimMessage(raw) {
109332
- const firstLine = raw.split(`
109333
- `)[0] ?? "";
109334
- return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
109335
- }
109336
-
109337
109908
  // ../core/dist/commands/publish-failure.js
109338
109909
  function tomlPathFor(cwd) {
109339
- return join9(cwd, "percher.toml");
109910
+ return join10(cwd, "percher.toml");
109340
109911
  }
109341
109912
  async function buildFailureError(ctx, app, deployment, signal) {
109342
109913
  let buildLogTail;
@@ -109408,7 +109979,7 @@ async function classifyDeploymentFailure(opts) {
109408
109979
  error48.suggestion = `${error48.suggestion}
109409
109980
  To return to the last version that ran cleanly: \`bunx percher rollback --last-good\`.`;
109410
109981
  }
109411
- const recovery = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
109982
+ const recovery2 = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
109412
109983
  missingEnvVars: error48.missingEnvVars,
109413
109984
  deployId: deployment.id,
109414
109985
  appName: app.name,
@@ -109422,13 +109993,13 @@ To return to the last version that ran cleanly: \`bunx percher rollback --last-g
109422
109993
  });
109423
109994
  return {
109424
109995
  error: error48,
109425
- recovery,
109996
+ recovery: recovery2,
109426
109997
  summary: `${error48.title} (${app.name}, deploy ${deployment.id}).`
109427
109998
  };
109428
109999
  }
109429
110000
  async function buildFailureResult(opts) {
109430
110001
  const { ctx, app, deployment, tarball, input } = opts;
109431
- const { error: error48, recovery, summary } = await classifyDeploymentFailure({
110002
+ const { error: error48, recovery: recovery2, summary: summary2 } = await classifyDeploymentFailure({
109432
110003
  ctx,
109433
110004
  app,
109434
110005
  deployment,
@@ -109441,8 +110012,8 @@ async function buildFailureResult(opts) {
109441
110012
  fileCount: tarball.fileCount,
109442
110013
  bytes: tarball.bytes,
109443
110014
  error: error48,
109444
- recovery,
109445
- summary,
110015
+ recovery: recovery2,
110016
+ summary: summary2,
109446
110017
  configPath: tomlPathFor(ctx.cwd),
109447
110018
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
109448
110019
  };
@@ -109521,13 +110092,13 @@ async function deploysInspect(ctx, input) {
109521
110092
  }
109522
110093
  // ../core/dist/commands/dev.js
109523
110094
  import { existsSync as existsSync8 } from "node:fs";
109524
- import { join as join12 } from "node:path";
110095
+ import { join as join13 } from "node:path";
109525
110096
 
109526
110097
  // ../core/dist/tarball.js
109527
110098
  var import_ignore = __toESM(require_ignore(), 1);
109528
110099
  import { createHash as createHash2 } from "node:crypto";
109529
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
109530
- import { join as join10, relative as relative4, sep as sep2 } from "node:path";
110100
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
110101
+ import { join as join11, relative as relative4, sep as sep2 } from "node:path";
109531
110102
  import { Readable } from "node:stream";
109532
110103
  import { createGzip } from "node:zlib";
109533
110104
  function isFilesOptions(options) {
@@ -109551,7 +110122,7 @@ var ALWAYS_IGNORE_SECRETS = [".env", ".env.*"];
109551
110122
  function* walk(dir, baseDir, ig) {
109552
110123
  const entries = readdirSync2(dir);
109553
110124
  for (const name of entries) {
109554
- const full = join10(dir, name);
110125
+ const full = join11(dir, name);
109555
110126
  const rel = relative4(baseDir, full).split(sep2).join("/");
109556
110127
  if (ig.ignores(rel))
109557
110128
  continue;
@@ -109640,20 +110211,20 @@ async function createTarball(options) {
109640
110211
  }
109641
110212
  const ig = import_ignore.default();
109642
110213
  ig.add(DEFAULT_IGNORE);
109643
- const gitignorePath = join10(options.cwd, ".gitignore");
110214
+ const gitignorePath = join11(options.cwd, ".gitignore");
109644
110215
  if (existsSync6(gitignorePath)) {
109645
- ig.add(readFileSync6(gitignorePath, "utf8"));
110216
+ ig.add(readFileSync7(gitignorePath, "utf8"));
109646
110217
  }
109647
- const percherignorePath = join10(options.cwd, ".percherignore");
110218
+ const percherignorePath = join11(options.cwd, ".percherignore");
109648
110219
  if (existsSync6(percherignorePath)) {
109649
- ig.add(readFileSync6(percherignorePath, "utf8"));
110220
+ ig.add(readFileSync7(percherignorePath, "utf8"));
109650
110221
  }
109651
110222
  if (options.extraIgnore)
109652
110223
  ig.add(options.extraIgnore);
109653
110224
  ig.add(ALWAYS_IGNORE_SECRETS);
109654
110225
  const fileBuffers = [];
109655
110226
  for (const rel of walk(options.cwd, options.cwd, ig)) {
109656
- const data2 = readFileSync6(join10(options.cwd, rel));
110227
+ const data2 = readFileSync7(join11(options.cwd, rel));
109657
110228
  fileBuffers.push({ path: rel, size: data2.length, data: data2 });
109658
110229
  }
109659
110230
  return buildTarball(fileBuffers);
@@ -109675,8 +110246,8 @@ function createTarballFromFiles(files, _config) {
109675
110246
 
109676
110247
  // ../core/dist/watcher.js
109677
110248
  var import_ignore2 = __toESM(require_ignore(), 1);
109678
- import { existsSync as existsSync7, readFileSync as readFileSync7, watch } from "node:fs";
109679
- import { join as join11, sep as sep3 } from "node:path";
110249
+ import { existsSync as existsSync7, readFileSync as readFileSync8, watch } from "node:fs";
110250
+ import { join as join12, sep as sep3 } from "node:path";
109680
110251
  var DEFAULT_IGNORE2 = [
109681
110252
  "node_modules",
109682
110253
  ".git",
@@ -109696,9 +110267,9 @@ function createWatcher(options) {
109696
110267
  const debounceMs = options.debounceMs ?? 500;
109697
110268
  const ig = import_ignore2.default();
109698
110269
  ig.add(DEFAULT_IGNORE2);
109699
- const gitignorePath = join11(options.cwd, ".gitignore");
110270
+ const gitignorePath = join12(options.cwd, ".gitignore");
109700
110271
  if (existsSync7(gitignorePath)) {
109701
- ig.add(readFileSync7(gitignorePath, "utf8"));
110272
+ ig.add(readFileSync8(gitignorePath, "utf8"));
109702
110273
  }
109703
110274
  if (options.extraIgnore)
109704
110275
  ig.add(options.extraIgnore);
@@ -109736,7 +110307,7 @@ var devInputSchema = exports_external.object({
109736
110307
  app: exports_external.string().optional()
109737
110308
  });
109738
110309
  async function loadConfig2(ctx, appOverride) {
109739
- const tomlPath = join12(ctx.cwd, "percher.toml");
110310
+ const tomlPath = join13(ctx.cwd, "percher.toml");
109740
110311
  if (!existsSync8(tomlPath)) {
109741
110312
  throw new PercherCoreError("No percher.toml found. Run: percher init", {
109742
110313
  code: "dev.no-app",
@@ -109932,7 +110503,7 @@ async function diff(ctx, input = {}) {
109932
110503
  }
109933
110504
  // ../core/dist/commands/doctor.js
109934
110505
  import { existsSync as existsSync9 } from "node:fs";
109935
- import { join as join13 } from "node:path";
110506
+ import { join as join14 } from "node:path";
109936
110507
 
109937
110508
  // ../core/dist/toml-recovery.js
109938
110509
  function tomlErrorToRecoveryProblems(err) {
@@ -110018,7 +110589,7 @@ async function waitForDeploy(ctx, input, opts) {
110018
110589
  }
110019
110590
  }
110020
110591
  if (deployment.status === "failed") {
110021
- const { error: error48, recovery, summary } = await classifyDeploymentFailure({
110592
+ const { error: error48, recovery: recovery2, summary: summary2 } = await classifyDeploymentFailure({
110022
110593
  ctx,
110023
110594
  app,
110024
110595
  deployment,
@@ -110031,8 +110602,8 @@ async function waitForDeploy(ctx, input, opts) {
110031
110602
  elapsedSeconds: Math.round((Date.now() - startedAt) / 1000),
110032
110603
  totalElapsedSeconds: secondsSinceCreated(deployment),
110033
110604
  error: error48,
110034
- recovery,
110035
- summary
110605
+ recovery: recovery2,
110606
+ summary: summary2
110036
110607
  };
110037
110608
  }
110038
110609
  if (deployment.isCheck && deployment.checkPassed) {
@@ -110233,7 +110804,7 @@ async function doctor(ctx, input = {}) {
110233
110804
  });
110234
110805
  return summarize(checks3, { appProvided: !!input.app });
110235
110806
  }
110236
- const tomlPath = join13(ctx.cwd, "percher.toml");
110807
+ const tomlPath = join14(ctx.cwd, "percher.toml");
110237
110808
  const tomlExists = existsSync9(tomlPath);
110238
110809
  let tomlValid = false;
110239
110810
  let tomlProblems;
@@ -110253,7 +110824,7 @@ async function doctor(ctx, input = {}) {
110253
110824
  tomlProblems = tomlErrorToRecoveryProblems(err);
110254
110825
  }
110255
110826
  if (err instanceof PercherTomlError && err.issues && err.issues.length > 0) {
110256
- const summary = err.issues.slice(0, 3).map((i) => {
110827
+ const summary2 = err.issues.slice(0, 3).map((i) => {
110257
110828
  const where = i.path.length > 0 ? `[${i.path.join(".")}]` : "(root)";
110258
110829
  return `${where} ${i.message}`;
110259
110830
  }).join("; ");
@@ -110261,7 +110832,7 @@ async function doctor(ctx, input = {}) {
110261
110832
  checks3.push({
110262
110833
  name: "percher.toml",
110263
110834
  status: "fail",
110264
- message: `${summary}${more}`
110835
+ message: `${summary2}${more}`
110265
110836
  });
110266
110837
  } else {
110267
110838
  checks3.push({
@@ -110748,7 +111319,7 @@ var VERDICT_RULES = [
110748
111319
  const reasonCode = vctx.lastDeployStatus === "queued" ? "deploy_queued" : vctx.lastDeployStatus === "building" ? "deploy_building" : vctx.lastDeployStatus === "deploying" ? "deploy_deploying" : "deploy_queued";
110749
111320
  const what = inTransitionalApp ? `App ${vctx.appName ?? ""} is provisioning` : `Last deploy is ${vctx.lastDeployStatus}`;
110750
111321
  const waiting = inTransitionalApp ? "This app is still provisioning" : reasonCode === "deploy_building" ? "The last deploy is still building" : reasonCode === "deploy_deploying" ? "The last deploy is being rolled out" : "The last deploy is still queued";
110751
- const recovery = vctx.lastDeployId && vctx.appName ? recoveryWait({
111322
+ const recovery2 = vctx.lastDeployId && vctx.appName ? recoveryWait({
110752
111323
  app: vctx.appName,
110753
111324
  deployId: vctx.lastDeployId,
110754
111325
  reasonCode
@@ -110764,7 +111335,7 @@ var VERDICT_RULES = [
110764
111335
  reasonCode,
110765
111336
  phase: "deploy"
110766
111337
  },
110767
- recovery,
111338
+ recovery: recovery2,
110768
111339
  summary: vctx.lastDeployId ? `${what.trim()} — wait for deploy ${vctx.lastDeployId}.` : `${what.trim()} — wait a moment and re-run.`
110769
111340
  };
110770
111341
  }
@@ -111032,7 +111603,7 @@ var domainRemoveInputSchema = exports_external.object({
111032
111603
  app: exports_external.string().optional(),
111033
111604
  domain: exports_external.string().min(1, "Domain is required")
111034
111605
  });
111035
- function resolveApp(ctx, app) {
111606
+ function resolveApp2(ctx, app) {
111036
111607
  const name = app ?? readPercherTomlAppName(ctx.cwd);
111037
111608
  if (!name) {
111038
111609
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -111043,19 +111614,19 @@ function resolveApp(ctx, app) {
111043
111614
  return name;
111044
111615
  }
111045
111616
  async function domainAdd(ctx, input) {
111046
- const appRef2 = resolveApp(ctx, input.app);
111617
+ const appRef2 = resolveApp2(ctx, input.app);
111047
111618
  return ctx.client.apps.addDomain(appRef2, input.domain);
111048
111619
  }
111049
111620
  async function domainVerify(ctx, input) {
111050
- const appRef2 = resolveApp(ctx, input.app);
111621
+ const appRef2 = resolveApp2(ctx, input.app);
111051
111622
  return ctx.client.apps.verifyDomain(appRef2, input.domain);
111052
111623
  }
111053
111624
  async function domainList(ctx, input = {}) {
111054
- const appRef2 = resolveApp(ctx, input.app);
111625
+ const appRef2 = resolveApp2(ctx, input.app);
111055
111626
  return ctx.client.apps.listDomains(appRef2);
111056
111627
  }
111057
111628
  async function domainRemove(ctx, input) {
111058
- const appRef2 = resolveApp(ctx, input.app);
111629
+ const appRef2 = resolveApp2(ctx, input.app);
111059
111630
  await ctx.client.apps.removeDomain(appRef2, input.domain);
111060
111631
  }
111061
111632
  // ../core/dist/commands/env.js
@@ -111249,8 +111820,8 @@ var forgejoSetupWebhookInputSchema = exports_external.object({
111249
111820
  app: exports_external.string().optional().describe("App name (overrides percher.toml)")
111250
111821
  });
111251
111822
  // ../core/dist/commands/generate.js
111252
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
111253
- import { join as join14 } from "node:path";
111823
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
111824
+ import { join as join15 } from "node:path";
111254
111825
  var generateInputSchema = exports_external.object({
111255
111826
  cwd: exports_external.string().optional()
111256
111827
  });
@@ -111336,7 +111907,7 @@ function generateTypeScript(config2) {
111336
111907
  }
111337
111908
  async function generate(ctx, input = {}) {
111338
111909
  const cwd = input.cwd ?? ctx.cwd;
111339
- const tomlPath = join14(cwd, "percher.toml");
111910
+ const tomlPath = join15(cwd, "percher.toml");
111340
111911
  if (!existsSync10(tomlPath)) {
111341
111912
  throw new PercherCoreError("No percher.toml found", {
111342
111913
  code: "dev.no-app",
@@ -111346,14 +111917,14 @@ async function generate(ctx, input = {}) {
111346
111917
  const config2 = await parseFile(tomlPath);
111347
111918
  const files = [];
111348
111919
  const envExample = generateEnvExample(config2);
111349
- writeFileSync6(join14(cwd, ".env.example"), envExample);
111920
+ writeFileSync7(join15(cwd, ".env.example"), envExample);
111350
111921
  files.push(".env.example");
111351
111922
  const tsTypes = generateTypeScript(config2);
111352
- writeFileSync6(join14(cwd, "percher.generated.d.ts"), tsTypes);
111923
+ writeFileSync7(join15(cwd, "percher.generated.d.ts"), tsTypes);
111353
111924
  files.push("percher.generated.d.ts");
111354
111925
  const agentInstructions = generateAgentInstructions(config2);
111355
- mkdirSync6(join14(cwd, ".percher"), { recursive: true });
111356
- writeFileSync6(join14(cwd, ".percher", "agent-instructions.md"), agentInstructions);
111926
+ mkdirSync7(join15(cwd, ".percher"), { recursive: true });
111927
+ writeFileSync7(join15(cwd, ".percher", "agent-instructions.md"), agentInstructions);
111357
111928
  files.push(".percher/agent-instructions.md");
111358
111929
  return { files };
111359
111930
  }
@@ -111403,7 +111974,7 @@ var githubConnectInputSchema = exports_external.object({
111403
111974
  branch: exports_external.string().optional().describe("Branch to deploy and track (default: main)"),
111404
111975
  autoPreviews: exports_external.boolean().optional().describe("Opt-in: push to a non-tracked branch creates a preview deploy at <app>--p-<branch-slug>.percher.run. Default false.")
111405
111976
  });
111406
- function resolveApp2(ctx, app) {
111977
+ function resolveApp3(ctx, app) {
111407
111978
  const name = app ?? readPercherTomlAppName(ctx.cwd);
111408
111979
  if (!name) {
111409
111980
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -111414,7 +111985,7 @@ function resolveApp2(ctx, app) {
111414
111985
  return name;
111415
111986
  }
111416
111987
  async function githubConnect(ctx, input) {
111417
- const appRef2 = resolveApp2(ctx, input.app);
111988
+ const appRef2 = resolveApp3(ctx, input.app);
111418
111989
  const branch = input.branch ?? "main";
111419
111990
  let deployment;
111420
111991
  let created;
@@ -111494,7 +112065,7 @@ var githubSetupWebhookInputSchema = exports_external.object({
111494
112065
  app: exports_external.string().optional().describe("App name (overrides percher.toml)")
111495
112066
  });
111496
112067
  async function githubSetupWebhook(ctx, input) {
111497
- const appRef2 = resolveApp2(ctx, input.app);
112068
+ const appRef2 = resolveApp3(ctx, input.app);
111498
112069
  const webhook = await ctx.client.apps.setupWebhook(appRef2);
111499
112070
  const repoBase = webhook.instructions.repo.replace(/\.git$/, "");
111500
112071
  return {
@@ -111559,11 +112130,17 @@ async function hookRevoke(ctx, input) {
111559
112130
  await ctx.client.hooks.revoke(app.id, input.id);
111560
112131
  }
111561
112132
  // ../core/dist/commands/import-project.js
111562
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
111563
- import { join as join15 } from "node:path";
112133
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
112134
+ import { join as join16 } from "node:path";
111564
112135
 
111565
112136
  // ../core/dist/static-docker.js
111566
112137
  var STATIC_DOCKER_PORT = 8080;
112138
+ var INIT_TEMPLATE_FRAMEWORKS = new Set([
112139
+ "astro",
112140
+ "preact",
112141
+ "solid",
112142
+ "vite"
112143
+ ]);
111567
112144
  function renderStaticFrameworkDockerfile(input) {
111568
112145
  return `# Multi-stage build: a Bun stage compiles the static bundle, then a
111569
112146
  # minimal Caddy image serves it. This project uses runtime = "docker"
@@ -111581,7 +112158,7 @@ RUN bun install && bun run build
111581
112158
  # Build pinned Caddy source with refreshed security modules. The scratch
111582
112159
  # runtime carries no shell, package manager, or file capabilities and listens
111583
112160
  # on the unprivileged port :${STATIC_DOCKER_PORT}.
111584
- FROM golang:1.26.7-alpine3.23@sha256:b17af760035fc2f338eed92d448a6c67f2d45438844fc6c60678fa5f99e44b57 AS caddy-source
112161
+ FROM golang:1.26.9-alpine3.23@sha256:6750308561953d451210ed1a8086d36c8c8bdcbf672d38d9d0d424b8a7a0216c AS caddy-source
111585
112162
  ADD --checksum=sha256:78e660d838e45c0b8c89338b325946238f153641aa9925df9ef2ad5e8eced31b \\
111586
112163
  https://github.com/caddyserver/caddy/commit/b2693fb63a30e6d7be0972c3645e9a2c0a500e93.patch /tmp/caddy-cel-go.patch
111587
112164
  RUN go mod download github.com/caddyserver/caddy/v2@v2.11.4 \\
@@ -111591,9 +112168,15 @@ WORKDIR /src
111591
112168
  RUN apk add --no-cache patch \\
111592
112169
  && patch -p1 < /tmp/caddy-cel-go.patch \\
111593
112170
  && go get github.com/go-chi/chi/v5@v5.3.0 github.com/google/cel-go@v0.29.2 \\
111594
- github.com/klauspost/compress@v1.18.7 go.opentelemetry.io/otel@v1.44.0 \\
111595
- golang.org/x/crypto@v0.55.0 golang.org/x/net@v0.58.0 \\
111596
- golang.org/x/text@v0.41.0 google.golang.org/grpc@v1.83.2 \\
112171
+ github.com/klauspost/compress@v1.18.7 go.opentelemetry.io/otel@v1.45.0 \\
112172
+ go.opentelemetry.io/otel/sdk/log@v0.21.0 \\
112173
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc@v0.21.0 \\
112174
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp@v0.21.0 \\
112175
+ go.opentelemetry.io/otel/exporters/stdout/stdoutlog@v0.21.0 \\
112176
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc@v1.45.0 \\
112177
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@v1.45.0 \\
112178
+ golang.org/x/crypto@v0.57.0 golang.org/x/net@v0.60.0 \\
112179
+ golang.org/x/text@v0.42.0 google.golang.org/grpc@v1.83.2 \\
111597
112180
  && go mod tidy \\
111598
112181
  && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/caddy ./cmd/caddy \\
111599
112182
  && printf '%s\\n' \\
@@ -111631,6 +112214,15 @@ ${body}
111631
112214
  }
111632
112215
  `;
111633
112216
  }
112217
+ var TEMPLATE_GO_PINS = readGoPins(renderStaticFrameworkDockerfile({ outputDir: "dist" }));
112218
+ function readGoPins(dockerfile) {
112219
+ const modules = {};
112220
+ for (const m of dockerfile.matchAll(/([a-z0-9-]+(?:\.[a-z0-9-]+)+\/[^\s@\\]+)@v(\d[^\s\\]*)/g)) {
112221
+ if (m[1] && m[2])
112222
+ modules[m[1]] = m[2];
112223
+ }
112224
+ return { go: /^FROM golang:(\d+\.\d+\.\d+)/m.exec(dockerfile)?.[1] ?? null, modules };
112225
+ }
111634
112226
 
111635
112227
  // ../core/dist/commands/import-project.js
111636
112228
  var importInputSchema = exports_external.object({
@@ -111638,9 +112230,9 @@ var importInputSchema = exports_external.object({
111638
112230
  app: exports_external.string().optional().describe("App name to import into")
111639
112231
  });
111640
112232
  function detectProvider(cwd) {
111641
- if (existsSync11(join15(cwd, "vercel.json")))
112233
+ if (existsSync11(join16(cwd, "vercel.json")))
111642
112234
  return "vercel";
111643
- if (existsSync11(join15(cwd, "netlify.toml")))
112235
+ if (existsSync11(join16(cwd, "netlify.toml")))
111644
112236
  return "netlify";
111645
112237
  return null;
111646
112238
  }
@@ -111976,7 +112568,7 @@ function mapVercelFramework(framework) {
111976
112568
  return map2[framework ?? ""] ?? { runtime: "node" };
111977
112569
  }
111978
112570
  function parseVercelJson(cwd) {
111979
- const raw = readFileSync8(join15(cwd, "vercel.json"), "utf-8");
112571
+ const raw = readFileSync9(join16(cwd, "vercel.json"), "utf-8");
111980
112572
  const config2 = JSON.parse(raw);
111981
112573
  const warnings = [];
111982
112574
  const envVars = [];
@@ -112032,12 +112624,12 @@ health = "/api/health"
112032
112624
  return { toml, warnings, envVars, staticBuild, redirects, headers };
112033
112625
  }
112034
112626
  function parseNetlifyToml(cwd) {
112035
- const raw = readFileSync8(join15(cwd, "netlify.toml"), "utf-8");
112627
+ const raw = readFileSync9(join16(cwd, "netlify.toml"), "utf-8");
112036
112628
  const warnings = [];
112037
112629
  const envVars = [];
112038
112630
  const buildCommand = raw.match(/command\s*=\s*"([^"]+)"/)?.[1] ?? "bun run build";
112039
112631
  const publishDir = raw.match(/publish\s*=\s*"([^"]+)"/)?.[1] ?? "dist";
112040
- const hasPackageJson = existsSync11(join15(cwd, "package.json"));
112632
+ const hasPackageJson = existsSync11(join16(cwd, "package.json"));
112041
112633
  const looksLikeStaticOutput = publishDir === "dist" || publishDir === "build" || publishDir === "public";
112042
112634
  let toml = `[app]
112043
112635
  name = "imported-app"
@@ -112085,23 +112677,23 @@ health = "/health"
112085
112677
  }
112086
112678
  }
112087
112679
  const sidecarPath = (name) => {
112088
- const root = join15(cwd, name);
112680
+ const root = join16(cwd, name);
112089
112681
  if (existsSync11(root))
112090
112682
  return root;
112091
- const published = join15(cwd, publishDir, name);
112683
+ const published = join16(cwd, publishDir, name);
112092
112684
  return existsSync11(published) ? published : null;
112093
112685
  };
112094
112686
  const redirectCollector = newRedirectCollector();
112095
112687
  const redirectsPath = sidecarPath("_redirects");
112096
112688
  if (redirectsPath) {
112097
- parseNetlifyRedirectsFile(readFileSync8(redirectsPath, "utf-8"), redirectCollector, warnings);
112689
+ parseNetlifyRedirectsFile(readFileSync9(redirectsPath, "utf-8"), redirectCollector, warnings);
112098
112690
  }
112099
112691
  parseNetlifyTomlRedirects(raw, redirectCollector, warnings);
112100
112692
  const redirects = finishRedirects(redirectCollector, warnings, "netlify");
112101
112693
  const headerTable = {};
112102
112694
  const headersPath = sidecarPath("_headers");
112103
112695
  if (headersPath) {
112104
- parseNetlifyHeadersFile(readFileSync8(headersPath, "utf-8"), headerTable, warnings);
112696
+ parseNetlifyHeadersFile(readFileSync9(headersPath, "utf-8"), headerTable, warnings);
112105
112697
  }
112106
112698
  const headers = finishHeaders(headerTable, warnings, "_headers");
112107
112699
  if (/^\s*\[\[headers\]\]/m.test(raw)) {
@@ -112170,11 +112762,11 @@ ${serialize(extras)}`;
112170
112762
  mapped.headerPaths = Object.keys(headers).length;
112171
112763
  }
112172
112764
  }
112173
- const tomlPath = join15(ctx.cwd, "percher.toml");
112765
+ const tomlPath = join16(ctx.cwd, "percher.toml");
112174
112766
  if (existsSync11(tomlPath)) {
112175
112767
  result.warnings.unshift("percher.toml already exists — overwriting with imported config");
112176
112768
  }
112177
- writeFileSync7(tomlPath, toml);
112769
+ writeFileSync8(tomlPath, toml);
112178
112770
  const scaffoldedFiles = [];
112179
112771
  if (result.staticBuild) {
112180
112772
  const targets = [
@@ -112188,12 +112780,12 @@ ${serialize(extras)}`;
112188
112780
  }
112189
112781
  ];
112190
112782
  for (const { name, content } of targets) {
112191
- const target = join15(ctx.cwd, name);
112192
- if (existsSync11(target)) {
112783
+ const target2 = join16(ctx.cwd, name);
112784
+ if (existsSync11(target2)) {
112193
112785
  result.warnings.push(`${name} already exists — left untouched. The imported config expects a multi-stage static build; merge by hand if needed.`);
112194
112786
  continue;
112195
112787
  }
112196
- writeFileSync7(target, content, "utf-8");
112788
+ writeFileSync8(target2, content, "utf-8");
112197
112789
  scaffoldedFiles.push(name);
112198
112790
  }
112199
112791
  }
@@ -112215,12 +112807,12 @@ function parsesAsPercherToml(source) {
112215
112807
  }
112216
112808
  }
112217
112809
  // ../core/dist/commands/init.js
112218
- import { existsSync as existsSync13, writeFileSync as writeFileSync8 } from "node:fs";
112219
- import { basename as basename3, join as join17 } from "node:path";
112810
+ import { existsSync as existsSync13, writeFileSync as writeFileSync9 } from "node:fs";
112811
+ import { basename as basename3, join as join18 } from "node:path";
112220
112812
 
112221
112813
  // ../core/dist/detect.js
112222
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
112223
- import { join as join16 } from "node:path";
112814
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
112815
+ import { join as join17 } from "node:path";
112224
112816
  function readPackageJson(src) {
112225
112817
  const raw = src.text("package.json");
112226
112818
  if (raw === null)
@@ -112392,10 +112984,10 @@ function detectFromSource(src) {
112392
112984
  }
112393
112985
  async function detectFramework(cwd) {
112394
112986
  const src = {
112395
- has: (...names) => names.some((n) => existsSync12(join16(cwd, n))),
112987
+ has: (...names) => names.some((n) => existsSync12(join17(cwd, n))),
112396
112988
  text: (name) => {
112397
112989
  try {
112398
- return readFileSync9(join16(cwd, name), "utf8");
112990
+ return readFileSync10(join17(cwd, name), "utf8");
112399
112991
  } catch {
112400
112992
  return null;
112401
112993
  }
@@ -112404,7 +112996,7 @@ async function detectFramework(cwd) {
112404
112996
  return detectFromSource(src);
112405
112997
  }
112406
112998
  function refineBunRuntime(detected, cwd) {
112407
- if (detected.runtime === "node" && (existsSync12(join16(cwd, "bun.lockb")) || existsSync12(join16(cwd, "bun.lock")))) {
112999
+ if (detected.runtime === "node" && (existsSync12(join17(cwd, "bun.lockb")) || existsSync12(join17(cwd, "bun.lock")))) {
112408
113000
  return { ...detected, runtime: "bun" };
112409
113001
  }
112410
113002
  return detected;
@@ -112438,7 +113030,24 @@ function configFromDetection(name, detected, dataMode) {
112438
113030
  }
112439
113031
 
112440
113032
  // ../core/dist/validation/blocked-names.js
113033
+ var SENSITIVE_BRANDS = new Set([
113034
+ "paypal",
113035
+ "bankid",
113036
+ "swish",
113037
+ "swedbank",
113038
+ "handelsbanken",
113039
+ "nordea",
113040
+ "seb",
113041
+ "danskebank",
113042
+ "mitid",
113043
+ "nemid",
113044
+ "mobilepay",
113045
+ "vipps",
113046
+ "klarna",
113047
+ "skatteverket"
113048
+ ]);
112441
113049
  var BLOCKED_APP_NAMES = new Set([
113050
+ ...SENSITIVE_BRANDS,
112442
113051
  "api",
112443
113052
  "app",
112444
113053
  "admin",
@@ -112497,6 +113106,8 @@ var BLOCKED_APP_NAMES = new Set([
112497
113106
  function isBlockedName(name) {
112498
113107
  if (BLOCKED_APP_NAMES.has(name))
112499
113108
  return true;
113109
+ if (name.split("-").some((part) => SENSITIVE_BRANDS.has(part)))
113110
+ return true;
112500
113111
  const prefixes = [
112501
113112
  "login-",
112502
113113
  "signin-",
@@ -112534,6 +113145,8 @@ function adjustReservedName(name) {
112534
113145
  let adjusted = `app-${name}`;
112535
113146
  if (adjusted.length > 40)
112536
113147
  adjusted = adjusted.slice(0, 40).replace(/-+$/g, "");
113148
+ if (isBlockedName(adjusted))
113149
+ return `app-${crypto.randomUUID().slice(0, 8)}`;
112537
113150
  return adjusted;
112538
113151
  }
112539
113152
  function detectInteractive() {
@@ -112549,7 +113162,7 @@ function scanEnvVars(_cwd) {
112549
113162
  return {};
112550
113163
  }
112551
113164
  async function init(ctx, input = {}) {
112552
- const path = join17(ctx.cwd, "percher.toml");
113165
+ const path = join18(ctx.cwd, "percher.toml");
112553
113166
  const exists = existsSync13(path);
112554
113167
  if (exists && !input.force) {
112555
113168
  throw new PercherCoreError("percher.toml already exists", {
@@ -112594,7 +113207,7 @@ async function init(ctx, input = {}) {
112594
113207
  if (Object.keys(envVars).length > 0) {
112595
113208
  config2.env = envVars;
112596
113209
  }
112597
- writeFileSync8(path, serialize(config2), "utf8");
113210
+ writeFileSync9(path, serialize(config2), "utf8");
112598
113211
  const scaffoldedFiles = [];
112599
113212
  if (detected?.staticBuild) {
112600
113213
  const targets = [
@@ -112610,10 +113223,10 @@ async function init(ctx, input = {}) {
112610
113223
  }
112611
113224
  ];
112612
113225
  for (const { name: name2, content } of targets) {
112613
- const target = join17(ctx.cwd, name2);
112614
- if (existsSync13(target) && !input.force)
113226
+ const target2 = join18(ctx.cwd, name2);
113227
+ if (existsSync13(target2) && !input.force)
112615
113228
  continue;
112616
- writeFileSync8(target, content, "utf8");
113229
+ writeFileSync9(target2, content, "utf8");
112617
113230
  scaffoldedFiles.push(name2);
112618
113231
  }
112619
113232
  }
@@ -113147,7 +113760,7 @@ async function inspectSupabase(ctx, input) {
113147
113760
  const storageBuckets = normalizeBuckets(schemaBlob.storageBuckets);
113148
113761
  const authUsersCount = toFiniteNumber(schemaBlob.authUsersCount);
113149
113762
  const warnings = collectWarnings(tables, storageBuckets, authUsersCount);
113150
- const summary = renderSummary({
113763
+ const summary2 = renderSummary({
113151
113764
  projectRef: input.projectRef,
113152
113765
  tables,
113153
113766
  authUsersCount,
@@ -113162,7 +113775,7 @@ async function inspectSupabase(ctx, input) {
113162
113775
  storageBuckets,
113163
113776
  warnings,
113164
113777
  recovery: recoveryNone(),
113165
- summary
113778
+ summary: summary2
113166
113779
  };
113167
113780
  }
113168
113781
  function failure(args) {
@@ -113690,7 +114303,7 @@ function collectWarnings2(collections, rlsPolicies, unsupported) {
113690
114303
  }
113691
114304
  // ../core/dist/commands/migrate-supabase-rewrite.js
113692
114305
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
113693
- import { dirname as dirname6, join as join20, relative as relative7, resolve as resolve9, sep as sep5 } from "node:path";
114306
+ import { dirname as dirname7, join as join21, relative as relative7, resolve as resolve9, sep as sep5 } from "node:path";
113694
114307
 
113695
114308
  // ../core/dist/commands/migrate-supabase-recipes.js
113696
114309
  var AUTH_RECIPES = {
@@ -113956,7 +114569,7 @@ function isSupabaseClientModule(source) {
113956
114569
  // ../core/dist/commands/migrate-supabase-status.js
113957
114570
  var import_cross_spawn = __toESM(require_cross_spawn(), 1);
113958
114571
  import { readFile as readFile4 } from "node:fs/promises";
113959
- import { join as join19, relative as relative6, resolve as resolve8 } from "node:path";
114572
+ import { join as join20, relative as relative6, resolve as resolve8 } from "node:path";
113960
114573
 
113961
114574
  // ../core/dist/commands/migrate-supabase-sdk.js
113962
114575
  var import_jscodeshift2 = __toESM(require_core3(), 1);
@@ -114173,12 +114786,12 @@ function isSupabaseFromTerminal(path, supabaseNames) {
114173
114786
  let fromCall = null;
114174
114787
  let cursor = path.node;
114175
114788
  while (cursor.type === "CallExpression") {
114176
- const call = cursor;
114177
- if (call.callee.type !== "MemberExpression")
114789
+ const call2 = cursor;
114790
+ if (call2.callee.type !== "MemberExpression")
114178
114791
  break;
114179
- const callee = call.callee;
114792
+ const callee = call2.callee;
114180
114793
  if (callee.property.type === "Identifier" && callee.property.name === "from") {
114181
- fromCall = call;
114794
+ fromCall = call2;
114182
114795
  break;
114183
114796
  }
114184
114797
  cursor = callee.object;
@@ -114233,15 +114846,15 @@ function decomposeFromChain(outer) {
114233
114846
  const reversedSteps = [];
114234
114847
  let node = outer;
114235
114848
  while (node.type === "CallExpression") {
114236
- const call = node;
114237
- if (call.callee.type !== "MemberExpression")
114849
+ const call2 = node;
114850
+ if (call2.callee.type !== "MemberExpression")
114238
114851
  break;
114239
- const callee = call.callee;
114852
+ const callee = call2.callee;
114240
114853
  const method = callee.property.type === "Identifier" ? callee.property.name : null;
114241
114854
  if (method === null)
114242
114855
  return null;
114243
114856
  if (method === "from") {
114244
- const arg = call.arguments[0];
114857
+ const arg = call2.arguments[0];
114245
114858
  if (!arg || arg.type !== "Literal" && arg.type !== "StringLiteral")
114246
114859
  return null;
114247
114860
  const table = arg.value;
@@ -114249,7 +114862,7 @@ function decomposeFromChain(outer) {
114249
114862
  return null;
114250
114863
  return { table, steps: reversedSteps.reverse() };
114251
114864
  }
114252
- reversedSteps.push({ method, args: call.arguments });
114865
+ reversedSteps.push({ method, args: call2.arguments });
114253
114866
  node = callee.object;
114254
114867
  }
114255
114868
  return null;
@@ -114674,14 +115287,14 @@ function readChainSteps(outer) {
114674
115287
  let cursor = outer;
114675
115288
  while (true) {
114676
115289
  if (cursor.type === "CallExpression") {
114677
- const call = cursor;
114678
- if (call.callee.type !== "MemberExpression")
115290
+ const call2 = cursor;
115291
+ if (call2.callee.type !== "MemberExpression")
114679
115292
  break;
114680
- const callee = call.callee;
115293
+ const callee = call2.callee;
114681
115294
  if (callee.property.type === "Identifier") {
114682
115295
  reversed.push({
114683
115296
  method: callee.property.name,
114684
- call
115297
+ call: call2
114685
115298
  });
114686
115299
  }
114687
115300
  cursor = callee.object;
@@ -114702,8 +115315,8 @@ function readChainSteps(outer) {
114702
115315
  }
114703
115316
  return reversed.reverse();
114704
115317
  }
114705
- function readFirstStringArg(call) {
114706
- const a = call.arguments[0];
115318
+ function readFirstStringArg(call2) {
115319
+ const a = call2.arguments[0];
114707
115320
  if (!a)
114708
115321
  return null;
114709
115322
  if (a.type === "Literal" || a.type === "StringLiteral") {
@@ -114893,7 +115506,7 @@ function applyRewrite(path, replacement, kind, pattern, note, flags, state) {
114893
115506
  // ../core/dist/commands/migrate-supabase-walker.js
114894
115507
  var import_ignore3 = __toESM(require_ignore(), 1);
114895
115508
  import { mkdir as mkdir2, readdir, readFile as readFile3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
114896
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join18, relative as relative5, resolve as resolve7, sep as sep4 } from "node:path";
115509
+ import { dirname as dirname6, isAbsolute as isAbsolute3, join as join19, relative as relative5, resolve as resolve7, sep as sep4 } from "node:path";
114897
115510
  var REWRITABLE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
114898
115511
  var DEFAULT_EXCLUDES = [
114899
115512
  "node_modules",
@@ -115045,7 +115658,7 @@ async function* walkTree(dir, rootAbs, ig) {
115045
115658
  return;
115046
115659
  }
115047
115660
  for (const name of entries) {
115048
- const abs = join18(dir, name);
115661
+ const abs = join19(dir, name);
115049
115662
  const rel = relative5(rootAbs, abs).split(sep4).join("/");
115050
115663
  if (rel.length > 0 && ig.ignores(rel))
115051
115664
  continue;
@@ -115207,7 +115820,7 @@ async function writeWalkResult(input) {
115207
115820
  if (rel.length === 0 || isParentTraversal || isAbsolute3(rel)) {
115208
115821
  throw new Error(`writeWalkResult refused path-traversing file "${file2.relativePath}" → "${abs}" (would escape targetDir "${targetAbs}").`);
115209
115822
  }
115210
- await mkdir2(dirname5(abs), { recursive: true });
115823
+ await mkdir2(dirname6(abs), { recursive: true });
115211
115824
  await writeFile2(abs, file2.rewritten, "utf8");
115212
115825
  written.push(file2.relativePath);
115213
115826
  }
@@ -115448,11 +116061,11 @@ async function migrationStatus(ctx, input) {
115448
116061
  const warning = analysis.checks.filter((c) => c.status === "warn");
115449
116062
  const buildFailed = build?.ran === true && !build.passed;
115450
116063
  const allGreen = failing.length === 0 && warning.length === 0 && !buildFailed;
115451
- const recovery = allGreen ? recoveryNone() : recoveryAsk({
116064
+ const recovery2 = allGreen ? recoveryNone() : recoveryAsk({
115452
116065
  prompt: buildPrompt(analysis.state, failing, warning, build),
115453
116066
  reasonCode: "unknown"
115454
116067
  });
115455
- const summary = `${STATE_SUMMARY[analysis.state]}${build ? ` Build: ${build.ran ? build.passed ? "passed" : "FAILED" : "skipped"}.` : ""}`;
116068
+ const summary2 = `${STATE_SUMMARY[analysis.state]}${build ? ` Build: ${build.ran ? build.passed ? "passed" : "FAILED" : "skipped"}.` : ""}`;
115456
116069
  return {
115457
116070
  status: "ok",
115458
116071
  rootDir: rootAbs,
@@ -115460,8 +116073,8 @@ async function migrationStatus(ctx, input) {
115460
116073
  migrationComplete: analysis.migrationComplete,
115461
116074
  checks: analysis.checks,
115462
116075
  ...build ? { build } : {},
115463
- recovery,
115464
- summary,
116076
+ recovery: recovery2,
116077
+ summary: summary2,
115465
116078
  checklist
115466
116079
  };
115467
116080
  }
@@ -115482,7 +116095,7 @@ function buildPrompt(state, failing, warning, build) {
115482
116095
  async function readManualRlsCount(rootAbs) {
115483
116096
  for (const dir of ["migration-preview", "."]) {
115484
116097
  try {
115485
- const notes = await readFile4(join19(rootAbs, dir, "MIGRATION_NOTES.md"), "utf8");
116098
+ const notes = await readFile4(join20(rootAbs, dir, "MIGRATION_NOTES.md"), "utf8");
115486
116099
  const m = notes.match(/\*\*RLS policies:\*\*\s*\d+\s*\((\d+)\s*need manual review\)/);
115487
116100
  if (m?.[1] !== undefined)
115488
116101
  return Number.parseInt(m[1], 10);
@@ -115494,7 +116107,7 @@ async function readManualRlsCount(rootAbs) {
115494
116107
  async function runProjectBuild(rootAbs) {
115495
116108
  let hasBuildScript = false;
115496
116109
  try {
115497
- const pkg = JSON.parse(await readFile4(join19(rootAbs, "package.json"), "utf8"));
116110
+ const pkg = JSON.parse(await readFile4(join20(rootAbs, "package.json"), "utf8"));
115498
116111
  hasBuildScript = typeof pkg.scripts?.build === "string";
115499
116112
  } catch {
115500
116113
  return { ran: false, passed: false, detail: "No readable package.json — nothing to build." };
@@ -115526,7 +116139,7 @@ ${tail}` : ""}`
115526
116139
  async function detectPackageManager(rootAbs) {
115527
116140
  const has = async (name) => {
115528
116141
  try {
115529
- await readFile4(join19(rootAbs, name), "utf8");
116142
+ await readFile4(join20(rootAbs, name), "utf8");
115530
116143
  return true;
115531
116144
  } catch {
115532
116145
  return false;
@@ -115656,8 +116269,8 @@ async function rewriteSupabaseClient(ctx, input) {
115656
116269
  flags.push(...scaffold.flags);
115657
116270
  }
115658
116271
  const reportNeeded = hadRewrites || flags.length > 0 || walk2.skipped.length > 0;
115659
- const backupDir = apply && changed.length > 0 ? join20(rootAbs, "migration-preview", "backup") : null;
115660
- const rewriteTarget = apply ? rootAbs : input.outputDir ? resolve9(ctx.cwd, input.outputDir) : join20(rootAbs, "migration-preview", "rewritten");
116272
+ const backupDir = apply && changed.length > 0 ? join21(rootAbs, "migration-preview", "backup") : null;
116273
+ const rewriteTarget = apply ? rootAbs : input.outputDir ? resolve9(ctx.cwd, input.outputDir) : join21(rootAbs, "migration-preview", "rewritten");
115661
116274
  const filesWritten = [];
115662
116275
  let clientModuleWritten = null;
115663
116276
  try {
@@ -115677,19 +116290,19 @@ async function rewriteSupabaseClient(ctx, input) {
115677
116290
  changedOnly: true
115678
116291
  });
115679
116292
  for (const rel of out.written) {
115680
- filesWritten.push(relative7(ctx.cwd, join20(rewriteTarget, rel)));
116293
+ filesWritten.push(relative7(ctx.cwd, join21(rewriteTarget, rel)));
115681
116294
  }
115682
116295
  if (scaffold?.clientModule) {
115683
- const moduleAbs = join20(rewriteTarget, scaffold.clientModule.relativePath);
115684
- await mkdir3(dirname6(moduleAbs), { recursive: true });
116296
+ const moduleAbs = join21(rewriteTarget, scaffold.clientModule.relativePath);
116297
+ await mkdir3(dirname7(moduleAbs), { recursive: true });
115685
116298
  await writeFile3(moduleAbs, scaffold.clientModule.content, "utf8");
115686
116299
  clientModuleWritten = relative7(ctx.cwd, moduleAbs).split(sep5).join("/");
115687
116300
  filesWritten.push(clientModuleWritten);
115688
116301
  }
115689
116302
  }
115690
116303
  if (reportNeeded) {
115691
- const reportAbs = join20(rootAbs, "migration-preview", "REWRITE_NOTES.md");
115692
- await mkdir3(dirname6(reportAbs), { recursive: true });
116304
+ const reportAbs = join21(rootAbs, "migration-preview", "REWRITE_NOTES.md");
116305
+ await mkdir3(dirname7(reportAbs), { recursive: true });
115693
116306
  const analysis = analyzeMigration(walk2.files.map((f) => ({
115694
116307
  relativePath: f.relativePath,
115695
116308
  kind: f.kind,
@@ -115714,7 +116327,7 @@ async function rewriteSupabaseClient(ctx, input) {
115714
116327
  for (const flag of flags) {
115715
116328
  flagsByPattern[flag.pattern] = (flagsByPattern[flag.pattern] ?? 0) + 1;
115716
116329
  }
115717
- const recovery = reportNeeded ? recoveryAsk({
116330
+ const recovery2 = reportNeeded ? recoveryAsk({
115718
116331
  prompt: buildPrompt2(flags, walk2.skipped.length, hadRewrites, clientModuleWritten),
115719
116332
  reasonCode: "unknown"
115720
116333
  }) : recoveryNone();
@@ -115732,7 +116345,7 @@ async function rewriteSupabaseClient(ctx, input) {
115732
116345
  envFilesChanged: walk2.summary.envFilesChanged,
115733
116346
  clientModule: clientModuleWritten,
115734
116347
  skipped: walk2.skipped,
115735
- recovery,
116348
+ recovery: recovery2,
115736
116349
  summary: renderSummary2({
115737
116350
  mode,
115738
116351
  changed: changed.length,
@@ -115941,7 +116554,7 @@ function failure3(rootAbs, mode, err, suggestion) {
115941
116554
  }
115942
116555
  // ../core/dist/commands/migrate-supabase-schema.js
115943
116556
  import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
115944
- import { isAbsolute as isAbsolute4, join as join21, relative as relative8, resolve as resolve10 } from "node:path";
116557
+ import { isAbsolute as isAbsolute4, join as join22, relative as relative8, resolve as resolve10 } from "node:path";
115945
116558
 
115946
116559
  // ../core/dist/commands/migrate-supabase-scripts.js
115947
116560
  function generateDataImportScripts(preview) {
@@ -115974,13 +116587,13 @@ function topologicalSort(collections) {
115974
116587
  for (const field of c.schema) {
115975
116588
  if (field.type !== "relation")
115976
116589
  continue;
115977
- const target = field.options.collectionName;
115978
- if (target === c.name)
116590
+ const target2 = field.options.collectionName;
116591
+ if (target2 === c.name)
115979
116592
  continue;
115980
- if (!byName.has(target))
116593
+ if (!byName.has(target2))
115981
116594
  continue;
115982
- dependsOn.get(c.name)?.add(target);
115983
- dependents.get(target)?.add(c.name);
116595
+ dependsOn.get(c.name)?.add(target2);
116596
+ dependents.get(target2)?.add(c.name);
115984
116597
  }
115985
116598
  }
115986
116599
  const inputOrder = collections.map((c) => c.name);
@@ -116221,7 +116834,7 @@ function renderRowTransform(collection) {
116221
116834
  }
116222
116835
  for (const rel of relations) {
116223
116836
  const fieldName = rel.name;
116224
- const target = rel.options.collectionName;
116837
+ const target2 = rel.options.collectionName;
116225
116838
  const isAuthUsersRef = rel.source.flag === "auth_users_reference";
116226
116839
  if (isAuthUsersRef) {
116227
116840
  parts.push(` if (rest[${JSON.stringify(fieldName)}] != null) {`);
@@ -116231,9 +116844,9 @@ function renderRowTransform(collection) {
116231
116844
  continue;
116232
116845
  }
116233
116846
  parts.push(` if (rest[${JSON.stringify(fieldName)}] != null) {`);
116234
- parts.push(` const mapped = lookupId(${JSON.stringify(target)}, rest[${JSON.stringify(fieldName)}]);`);
116847
+ parts.push(` const mapped = lookupId(${JSON.stringify(target2)}, rest[${JSON.stringify(fieldName)}]);`);
116235
116848
  parts.push(" if (mapped === undefined) {");
116236
- parts.push(` throw new Error(\`[${collection.name}.${fieldName}] no PB id for source value '\${rest[${JSON.stringify(fieldName)}]}' in ${target} map — was ${target} migrated first?\`);`);
116849
+ parts.push(` throw new Error(\`[${collection.name}.${fieldName}] no PB id for source value '\${rest[${JSON.stringify(fieldName)}]}' in ${target2} map — was ${target2} migrated first?\`);`);
116237
116850
  parts.push(" }");
116238
116851
  parts.push(` rest[${JSON.stringify(fieldName)}] = mapped;`);
116239
116852
  parts.push(" }");
@@ -116436,7 +117049,7 @@ async function migrateSupabaseSchema(ctx, input) {
116436
117049
  }
116437
117050
  };
116438
117051
  }
116439
- const summary = renderSummary3({
117052
+ const summary2 = renderSummary3({
116440
117053
  projectRef: input.projectRef,
116441
117054
  outputDirRel,
116442
117055
  inspection,
@@ -116449,7 +117062,7 @@ async function migrateSupabaseSchema(ctx, input) {
116449
117062
  filesWritten,
116450
117063
  preview,
116451
117064
  recovery: recoveryNone(),
116452
- summary
117065
+ summary: summary2
116453
117066
  };
116454
117067
  }
116455
117068
  function resolveOutputDir(cwd, requested) {
@@ -116461,10 +117074,10 @@ async function writePreviewFiles(ctx, outputDir, preview) {
116461
117074
  const schemaJson = renderSchemaJson(preview);
116462
117075
  const notesMd = renderMigrationNotes(preview);
116463
117076
  const scripts = generateDataImportScripts(preview);
116464
- const schemaPath = join21(outputDir, "pb_schema.json");
116465
- const notesPath = join21(outputDir, "MIGRATION_NOTES.md");
116466
- const migratePath = join21(outputDir, "pb_migrate.js");
116467
- const storagePath = join21(outputDir, "pb_migrate_storage.js");
117077
+ const schemaPath = join22(outputDir, "pb_schema.json");
117078
+ const notesPath = join22(outputDir, "MIGRATION_NOTES.md");
117079
+ const migratePath = join22(outputDir, "pb_migrate.js");
117080
+ const storagePath = join22(outputDir, "pb_migrate_storage.js");
116468
117081
  await writeFile4(schemaPath, schemaJson, "utf8");
116469
117082
  await writeFile4(notesPath, notesMd, "utf8");
116470
117083
  await writeFile4(migratePath, scripts.pbMigrate, "utf8");
@@ -116549,8 +117162,8 @@ function renderMigrationNotes(preview) {
116549
117162
  lines.push("| Field | Type | Required | Postgres source | Flag |");
116550
117163
  lines.push("|---|---|---|---|---|");
116551
117164
  for (const field of collection.schema) {
116552
- const target = field.type === "relation" ? `relation → \`${field.options.collectionName}\`` : field.type;
116553
- lines.push(`| \`${field.name}\` | ${target} | ${field.required ? "yes" : "no"} | \`${field.source.postgresType}\` | ${field.source.flag ? `\`${field.source.flag}\`` : "—"} |`);
117165
+ const target2 = field.type === "relation" ? `relation → \`${field.options.collectionName}\`` : field.type;
117166
+ lines.push(`| \`${field.name}\` | ${target2} | ${field.required ? "yes" : "no"} | \`${field.source.postgresType}\` | ${field.source.flag ? `\`${field.source.flag}\`` : "—"} |`);
116554
117167
  }
116555
117168
  lines.push("");
116556
117169
  }
@@ -116658,7 +117271,7 @@ var previewBranchSetInputSchema = exports_external.object({
116658
117271
  var previewBranchInputSchema = exports_external.object({
116659
117272
  app: exports_external.string().optional()
116660
117273
  });
116661
- function resolveApp3(ctx, app) {
117274
+ function resolveApp4(ctx, app) {
116662
117275
  const name = app ?? readPercherTomlAppName(ctx.cwd);
116663
117276
  if (!name) {
116664
117277
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -116669,11 +117282,11 @@ function resolveApp3(ctx, app) {
116669
117282
  return name;
116670
117283
  }
116671
117284
  async function previewBranchSet(ctx, input) {
116672
- const appRef2 = resolveApp3(ctx, input.app);
117285
+ const appRef2 = resolveApp4(ctx, input.app);
116673
117286
  return ctx.client.apps.setPreviewBranch(appRef2, input.branch);
116674
117287
  }
116675
117288
  async function previewBranchUnset(ctx, input = {}) {
116676
- const appRef2 = resolveApp3(ctx, input.app);
117289
+ const appRef2 = resolveApp4(ctx, input.app);
116677
117290
  return ctx.client.apps.unsetPreviewBranch(appRef2);
116678
117291
  }
116679
117292
  var setPreviewBranchToolInputSchema = exports_external.object({
@@ -116681,7 +117294,7 @@ var setPreviewBranchToolInputSchema = exports_external.object({
116681
117294
  branch: exports_external.string().nullable().describe("Branch to pin as the preview address; pass null to clear it.")
116682
117295
  });
116683
117296
  async function previewBranchShow(ctx, input = {}) {
116684
- const appRef2 = resolveApp3(ctx, input.app);
117297
+ const appRef2 = resolveApp4(ctx, input.app);
116685
117298
  const app = await ctx.client.apps.get(appRef2);
116686
117299
  return {
116687
117300
  previewBranch: app.previewBranch ?? null,
@@ -116690,14 +117303,14 @@ async function previewBranchShow(ctx, input = {}) {
116690
117303
  };
116691
117304
  }
116692
117305
  async function previewList(ctx, input = {}) {
116693
- const appRef2 = resolveApp3(ctx, input.app);
117306
+ const appRef2 = resolveApp4(ctx, input.app);
116694
117307
  const app = await ctx.client.apps.get(appRef2);
116695
117308
  return { app: app.name, previews: app.previews ?? [] };
116696
117309
  }
116697
117310
  // ../core/dist/commands/publish.js
116698
117311
  init_dist();
116699
117312
  import { existsSync as existsSync15 } from "node:fs";
116700
- import { join as join24 } from "node:path";
117313
+ import { join as join25 } from "node:path";
116701
117314
 
116702
117315
  // ../shared/dist/pow.js
116703
117316
  import { createHash as createHash3 } from "node:crypto";
@@ -117101,8 +117714,8 @@ async function runDeployWithRetry(opts) {
117101
117714
 
117102
117715
  // ../core/dist/commands/env-scan.js
117103
117716
  init_dist();
117104
- import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
117105
- import { join as join22 } from "node:path";
117717
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
117718
+ import { join as join23 } from "node:path";
117106
117719
  function platformInjectedClientEnvKeys(config2) {
117107
117720
  const data2 = config2.data;
117108
117721
  if (!data2)
@@ -117160,7 +117773,7 @@ function scanForMissingBuildEnvRefs(cwd, existingKeys) {
117160
117773
  continue;
117161
117774
  let content;
117162
117775
  try {
117163
- content = readFileSync10(join22(cwd, rel), "utf8");
117776
+ content = readFileSync11(join23(cwd, rel), "utf8");
117164
117777
  } catch {
117165
117778
  continue;
117166
117779
  }
@@ -117192,7 +117805,7 @@ function* walkDir(dir, base) {
117192
117805
  for (const name of entries) {
117193
117806
  if (SKIP_DIRS.has(name))
117194
117807
  continue;
117195
- const full = join22(dir, name);
117808
+ const full = join23(dir, name);
117196
117809
  let st;
117197
117810
  try {
117198
117811
  st = statSync3(full);
@@ -117209,6 +117822,47 @@ function* walkDir(dir, base) {
117209
117822
 
117210
117823
  // ../core/dist/commands/publish-api-error.js
117211
117824
  init_dist();
117825
+
117826
+ // ../core/dist/commands/publish-rate-limit.js
117827
+ var RATE_LIMIT_CODES = new Set([
117828
+ "DEPLOY_RATE_LIMIT",
117829
+ "DEPLOY_RATE_LIMITED",
117830
+ "RATE_LIMIT_EXCEEDED"
117831
+ ]);
117832
+ function matchRateLimit(err) {
117833
+ if (!RATE_LIMIT_CODES.has(err.code))
117834
+ return null;
117835
+ const wait = boundedSeconds(err.extra?.retryAfterSec);
117836
+ return {
117837
+ title: "Rate limit hit",
117838
+ explanation: err.message,
117839
+ suggestion: "Wait a few minutes and try again. If you need a higher cap, upgrade at https://percher.app/account/billing.",
117840
+ recovery: recoveryAsk({
117841
+ prompt: `Percher is rate-limiting deploys for this app${wait ? `; wait at least ${wait}s` : " — the window is up to an hour"}. This is a burst/hourly limit, not the daily deploy quota. Wait and retry, or upgrade the plan for a higher cap — which would you like?`,
117842
+ options: ["wait and retry", "upgrade", "cancel"],
117843
+ reasonCode: "quota_exceeded"
117844
+ }),
117845
+ summary: `Rate limit hit: ${err.message}`
117846
+ };
117847
+ }
117848
+ function matchConnectionRateLimit(err) {
117849
+ if (err.code !== "OAUTH_GRANT_RATE_LIMITED")
117850
+ return null;
117851
+ const wait = boundedSeconds(err.extra?.retryAfterSec) ?? 60;
117852
+ return {
117853
+ title: "Connection rate limit hit",
117854
+ explanation: err.message,
117855
+ suggestion: `Wait ${wait}s, then publish again. Nothing in the app needs to change.`,
117856
+ recovery: recoveryAsk({
117857
+ prompt: `Percher is limiting requests on this connection; wait at least ${wait}s, then publish again. Nothing in the app needs to change. Wait and retry, or cancel?`,
117858
+ options: ["wait and retry", "cancel"],
117859
+ reasonCode: "quota_exceeded"
117860
+ }),
117861
+ summary: `Connection rate limit hit. Retry in ${wait}s.`
117862
+ };
117863
+ }
117864
+
117865
+ // ../core/dist/commands/publish-api-error.js
117212
117866
  var DEPLOY_GATE_CODES = new Set([
117213
117867
  "RETRY_LIMIT_REACHED",
117214
117868
  "REQUIRED_ENV_MISSING",
@@ -117228,6 +117882,9 @@ function classifyPublishApiError(err, input) {
117228
117882
  const rateLimit = matchRateLimit(err);
117229
117883
  if (rateLimit)
117230
117884
  return buildResult(rateLimit, input);
117885
+ const connectionLimit = matchConnectionRateLimit(err);
117886
+ if (connectionLimit)
117887
+ return buildResult(connectionLimit, input);
117231
117888
  const concurrent = matchConcurrentLimit(err);
117232
117889
  if (concurrent)
117233
117890
  return buildResult(concurrent, input);
@@ -117313,27 +117970,6 @@ function matchPlanLimit(err) {
117313
117970
  summary: `Plan limit reached: ${err.message}`
117314
117971
  };
117315
117972
  }
117316
- var RATE_LIMIT_CODES = new Set([
117317
- "DEPLOY_RATE_LIMIT",
117318
- "DEPLOY_RATE_LIMITED",
117319
- "RATE_LIMIT_EXCEEDED"
117320
- ]);
117321
- function matchRateLimit(err) {
117322
- if (!RATE_LIMIT_CODES.has(err.code))
117323
- return null;
117324
- const wait = boundedSeconds(err.extra?.retryAfterSec);
117325
- return {
117326
- title: "Rate limit hit",
117327
- explanation: err.message,
117328
- suggestion: "Wait a few minutes and try again. If you need a higher cap, upgrade at https://percher.app/account/billing.",
117329
- recovery: recoveryAsk({
117330
- prompt: `Percher is rate-limiting deploys for this app${wait ? `; wait at least ${wait}s` : " — the window is up to an hour"}. This is a burst/hourly limit, not the daily deploy quota. Wait and retry, or upgrade the plan for a higher cap — which would you like?`,
117331
- options: ["wait and retry", "upgrade", "cancel"],
117332
- reasonCode: "quota_exceeded"
117333
- }),
117334
- summary: `Rate limit hit: ${err.message}`
117335
- };
117336
- }
117337
117973
  var PLATFORM_DISK_CODES2 = new Set(["PLATFORM_DISK_LOW", "PLATFORM_DISK_UNKNOWN"]);
117338
117974
  function matchPlatformDisk(err) {
117339
117975
  if (!PLATFORM_DISK_CODES2.has(err.code))
@@ -117691,22 +118327,22 @@ function inFlightPublishResult(p, why) {
117691
118327
  }
117692
118328
 
117693
118329
  // ../core/dist/commands/publish-node.js
117694
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
117695
- import { join as join23 } from "node:path";
118330
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
118331
+ import { join as join24 } from "node:path";
117696
118332
  function resolveNodeVersion(cwd) {
117697
- const nvmrc = join23(cwd, ".nvmrc");
118333
+ const nvmrc = join24(cwd, ".nvmrc");
117698
118334
  if (existsSync14(nvmrc)) {
117699
118335
  try {
117700
- const v = readFileSync11(nvmrc, "utf8").trim();
118336
+ const v = readFileSync12(nvmrc, "utf8").trim();
117701
118337
  if (v)
117702
118338
  return { value: v, source: ".nvmrc" };
117703
118339
  } catch {}
117704
118340
  }
117705
- const pkgPath = join23(cwd, "package.json");
118341
+ const pkgPath = join24(cwd, "package.json");
117706
118342
  if (!existsSync14(pkgPath))
117707
118343
  return null;
117708
118344
  try {
117709
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
118345
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
117710
118346
  const engines = pkg?.engines?.node;
117711
118347
  if (typeof engines === "string" && engines.length > 0) {
117712
118348
  return { value: engines, source: "engines.node" };
@@ -117714,6 +118350,34 @@ function resolveNodeVersion(cwd) {
117714
118350
  } catch {}
117715
118351
  return { value: DEFAULT_NODE_VERSION_LABEL, source: "default" };
117716
118352
  }
118353
+ function emitPreflight(ctx, config2, tarball) {
118354
+ const lines = [];
118355
+ const fw = config2.app.framework ?? (config2.app.runtime === "docker" ? "Dockerfile" : config2.app.runtime);
118356
+ lines.push(`Detected: ${fw} (runtime=${config2.app.runtime})`);
118357
+ if (config2.app.runtime === "bun") {
118358
+ lines.push("Bun: 1.x (default)");
118359
+ } else if (config2.app.runtime === "node") {
118360
+ const node = resolveNodeVersion(tarball.cwd);
118361
+ if (node) {
118362
+ lines.push(`Node: ${node.value} (${node.source})`);
118363
+ }
118364
+ }
118365
+ if (config2.build?.command) {
118366
+ lines.push(`Build: ${config2.build.command}`);
118367
+ }
118368
+ if (config2.web?.health) {
118369
+ lines.push(`Health: ${config2.web.health}`);
118370
+ }
118371
+ if (config2.web?.port) {
118372
+ lines.push(`Port: ${config2.web.port}`);
118373
+ }
118374
+ const sizeKb = (tarball.bytes / 1024).toFixed(1);
118375
+ lines.push(`Bundle: ${tarball.fileCount} files, ${sizeKb} KB`);
118376
+ lines.push(`Target: ${config2.app.name}`);
118377
+ for (const line of lines) {
118378
+ ctx.status(line);
118379
+ }
118380
+ }
117717
118381
 
117718
118382
  // ../core/dist/commands/publish.js
117719
118383
  var publishInputSchema = exports_external.object({
@@ -117729,7 +118393,7 @@ var publishInputSchema = exports_external.object({
117729
118393
  var SUSPICIOUS_BYTES = 50 * 1024 * 1024;
117730
118394
  var SUSPICIOUS_FILES = 1e4;
117731
118395
  function tomlPathFor2(cwd) {
117732
- return join24(cwd, "percher.toml");
118396
+ return join25(cwd, "percher.toml");
117733
118397
  }
117734
118398
  async function buildReplacedResult2(opts) {
117735
118399
  const { ctx, app, deployment, tarball, cwd } = opts;
@@ -117885,7 +118549,7 @@ ${bullets}`,
117885
118549
  bundle: { fileCount: 0, bytes: 0 }
117886
118550
  };
117887
118551
  }
117888
- if (preParsedConfig && !input.dryRun && !existsSync15(join24(ctx.cwd, "percher.toml"))) {
118552
+ if (preParsedConfig && !input.dryRun && !existsSync15(join25(ctx.cwd, "percher.toml"))) {
117889
118553
  return {
117890
118554
  status: "failed",
117891
118555
  fileCount: 0,
@@ -118178,7 +118842,6 @@ async function finalizeDeploy(opts) {
118178
118842
  intervalMs: 2000,
118179
118843
  timeoutMs,
118180
118844
  appName: app.name,
118181
- terminal: ["live", "failed", "replaced"],
118182
118845
  onTick: (d) => {
118183
118846
  const elapsedMs = Date.now() - buildStart;
118184
118847
  if (d.status !== lastStatus) {
@@ -118202,7 +118865,8 @@ async function finalizeDeploy(opts) {
118202
118865
  }
118203
118866
  });
118204
118867
  } catch (err) {
118205
- if (err instanceof DeployPollLostContactError)
118868
+ const serverError = err instanceof PercherApiError && err.status >= 500 && err.status < 600;
118869
+ if (err instanceof DeployPollLostContactError || serverError)
118206
118870
  return inFlightPublishResult(inFlight(), "lost_contact");
118207
118871
  if (!timedOut)
118208
118872
  throw err;
@@ -118234,8 +118898,8 @@ async function finalizeDeploy(opts) {
118234
118898
  const buildMs = Date.now() - buildStart;
118235
118899
  if (deployment.isCheck && deployment.checkPassed) {
118236
118900
  const totalSeconds2 = (Date.now() - t0) / 1000;
118237
- const summary = `${app.name} check passed (${Math.round(totalSeconds2)}s) — would build and boot. The live app was not changed.`;
118238
- ctx.status(summary);
118901
+ const summary2 = `${app.name} check passed (${Math.round(totalSeconds2)}s) — would build and boot. The live app was not changed.`;
118902
+ ctx.status(summary2);
118239
118903
  return {
118240
118904
  status: "check_passed",
118241
118905
  app,
@@ -118252,7 +118916,7 @@ async function finalizeDeploy(opts) {
118252
118916
  missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
118253
118917
  traceId: deployment.id,
118254
118918
  recovery: RECOVERY_NONE,
118255
- summary,
118919
+ summary: summary2,
118256
118920
  configPath: tomlPathFor2(ctx.cwd),
118257
118921
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
118258
118922
  };
@@ -118420,6 +119084,7 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
118420
119084
  async function handleAnonymousPublish(ctx, config2, input, tarball) {
118421
119085
  const anonClient = new PercherClient({
118422
119086
  apiUrl: ctx.client.apiUrl,
119087
+ token: null,
118423
119088
  sessionId: ctx.client.sessionId
118424
119089
  });
118425
119090
  let created;
@@ -118432,7 +119097,8 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
118432
119097
  created = await anonClient.anon.createAnonApp({
118433
119098
  name: config2.app.name,
118434
119099
  powChallengeId: ch.challengeId,
118435
- powSolution: solution
119100
+ powSolution: solution,
119101
+ ...ctx.syntheticFixture ? { testFixturePermit: ctx.syntheticFixture.permit } : {}
118436
119102
  });
118437
119103
  } catch (err) {
118438
119104
  const httpStatus = err.status;
@@ -118473,13 +119139,15 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
118473
119139
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
118474
119140
  };
118475
119141
  }
118476
- if (!hasPersistedToken())
118477
- saveConfig({ token: created.token });
118478
- setAnonClaim({
118479
- app: created.app.name,
118480
- claimToken: created.claimToken,
118481
- expiresAt: created.expiresAt
118482
- });
119142
+ if (!ctx.syntheticFixture) {
119143
+ if (!hasPersistedToken())
119144
+ saveConfig({ token: created.token });
119145
+ setAnonClaim({
119146
+ app: created.app.name,
119147
+ claimToken: created.claimToken,
119148
+ expiresAt: created.expiresAt
119149
+ });
119150
+ }
118483
119151
  const newClient = new PercherClient({
118484
119152
  apiUrl: ctx.client.apiUrl,
118485
119153
  token: created.token,
@@ -118487,6 +119155,10 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
118487
119155
  });
118488
119156
  ctx.client = newClient;
118489
119157
  ctx.setClient?.(newClient);
119158
+ if (ctx.syntheticFixture) {
119159
+ ctx.syntheticFixture.appId = created.app.id;
119160
+ ctx.syntheticFixture.created(created.app);
119161
+ }
118490
119162
  config2.app.name = created.app.name;
118491
119163
  const t0 = Date.now();
118492
119164
  const freshTarball = await createTarball({ cwd: ctx.cwd, config: config2 });
@@ -118521,7 +119193,7 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
118521
119193
  async function loadOrGenerate(ctx, preParsed) {
118522
119194
  if (preParsed)
118523
119195
  return structuredClone(preParsed);
118524
- const tomlPath = join24(ctx.cwd, "percher.toml");
119196
+ const tomlPath = join25(ctx.cwd, "percher.toml");
118525
119197
  if (!existsSync15(tomlPath)) {
118526
119198
  ctx.status("No percher.toml found — detecting framework...");
118527
119199
  const generated = await init(ctx);
@@ -118532,45 +119204,27 @@ async function loadOrGenerate(ctx, preParsed) {
118532
119204
  async function ensureApp2(ctx, config2) {
118533
119205
  try {
118534
119206
  const app = await ctx.client.apps.get(config2.app.name);
119207
+ if (ctx.syntheticFixture && app.id !== ctx.syntheticFixture.appId) {
119208
+ throw new Error("Disposable fixture publishing requires a new app.");
119209
+ }
118535
119210
  return { app, firstDeploy: false };
118536
119211
  } catch (err) {
118537
119212
  if (err.code === "APP_NOT_FOUND") {
118538
119213
  const app = await ctx.client.apps.create({
118539
119214
  name: config2.app.name,
118540
119215
  runtime: config2.app.runtime,
118541
- framework: config2.app.framework
119216
+ framework: config2.app.framework,
119217
+ ...ctx.syntheticFixture ? { testFixturePermit: ctx.syntheticFixture.permit } : {}
118542
119218
  });
119219
+ if (ctx.syntheticFixture) {
119220
+ ctx.syntheticFixture.appId = app.id;
119221
+ ctx.syntheticFixture.created(app);
119222
+ }
118543
119223
  return { app, firstDeploy: true };
118544
119224
  }
118545
119225
  throw err;
118546
119226
  }
118547
119227
  }
118548
- function emitPreflight(ctx, config2, tarball) {
118549
- const lines = [];
118550
- const fw = config2.app.framework ?? (config2.app.runtime === "docker" ? "Dockerfile" : config2.app.runtime);
118551
- lines.push(`Detected: ${fw} (runtime=${config2.app.runtime})`);
118552
- if (config2.app.runtime === "node" || config2.app.runtime === "bun") {
118553
- const node = resolveNodeVersion(tarball.cwd);
118554
- if (node) {
118555
- lines.push(`Node: ${node.value} (${node.source})`);
118556
- }
118557
- }
118558
- if (config2.build?.command) {
118559
- lines.push(`Build: ${config2.build.command}`);
118560
- }
118561
- if (config2.web?.health) {
118562
- lines.push(`Health: ${config2.web.health}`);
118563
- }
118564
- if (config2.web?.port) {
118565
- lines.push(`Port: ${config2.web.port}`);
118566
- }
118567
- const sizeKb = (tarball.bytes / 1024).toFixed(1);
118568
- lines.push(`Bundle: ${tarball.fileCount} files, ${sizeKb} KB`);
118569
- lines.push(`Target: ${config2.app.name}`);
118570
- for (const line of lines) {
118571
- ctx.status(line);
118572
- }
118573
- }
118574
119228
  // ../core/dist/commands/publish-from-repo.js
118575
119229
  init_dist();
118576
119230
  var publishFromRepoInputSchema = exports_external.object({
@@ -118601,7 +119255,7 @@ var publishInlineInputSchema = exports_external.object({
118601
119255
  // ../core/dist/commands/push.js
118602
119256
  init_dist();
118603
119257
  import { existsSync as existsSync16 } from "node:fs";
118604
- import { join as join25 } from "node:path";
119258
+ import { join as join26 } from "node:path";
118605
119259
  var pushInputSchema = exports_external.object({
118606
119260
  dryRun: exports_external.boolean().optional(),
118607
119261
  force: exports_external.boolean().optional(),
@@ -118612,7 +119266,7 @@ var SUSPICIOUS_BYTES2 = 50 * 1024 * 1024;
118612
119266
  var SUSPICIOUS_FILES2 = 1e4;
118613
119267
  var PLACEHOLDER_PREFIXES = ["add-your-", "your-key-here"];
118614
119268
  async function loadOrGenerate2(ctx) {
118615
- const tomlPath = join25(ctx.cwd, "percher.toml");
119269
+ const tomlPath = join26(ctx.cwd, "percher.toml");
118616
119270
  if (!existsSync16(tomlPath)) {
118617
119271
  ctx.status("No percher.toml found — detecting framework...");
118618
119272
  const result = await init(ctx);
@@ -118724,7 +119378,7 @@ async function push(ctx, input = {}, internals = {}) {
118724
119378
  fileCount: tarball.fileCount,
118725
119379
  recovery: RECOVERY_NONE,
118726
119380
  summary: `Dry run for ${config2.app.name}: ${tarball.fileCount} files, ${(tarball.bytes / 1024).toFixed(1)} KB — no deploy performed.`,
118727
- configPath: join25(ctx.cwd, "percher.toml"),
119381
+ configPath: join26(ctx.cwd, "percher.toml"),
118728
119382
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
118729
119383
  };
118730
119384
  }
@@ -118876,12 +119530,13 @@ async function push(ctx, input = {}, internals = {}) {
118876
119530
  });
118877
119531
  }
118878
119532
  });
119533
+ throwIfDeploymentReplaced(app.name, deployment);
118879
119534
  if (deployment.status === "failed") {
118880
119535
  const errorDetail = deployment.errorMessage ? `
118881
119536
  ${deployment.errorMessage}` : "";
118882
119537
  const fromCode = classifyByErrorCode(deployment.errorCode, deployment.errorMessage ?? "");
118883
119538
  const classified = fromCode ?? classifyError(deployment.errorMessage ?? "", "");
118884
- const recovery = classified ? recoveryFromErrorClass(classified.errorClass, {
119539
+ const recovery2 = classified ? recoveryFromErrorClass(classified.errorClass, {
118885
119540
  missingEnvVars: classified.missingEnvVars,
118886
119541
  deployId: deployment.id,
118887
119542
  appName: app.name,
@@ -118895,7 +119550,7 @@ ${deployment.errorMessage}` : "";
118895
119550
  throw new PercherCoreError(`Deployment failed (deploy ${deployment.id})${errorDetail}`, {
118896
119551
  code: "push.deploy-failed",
118897
119552
  hint: `Run \`percher deploys inspect ${deployment.id}\` to see the full build log and error. ${deployment.errorMessage ? "Fix the issue above and re-run." : "Look for the failing step in the build log."}`,
118898
- recovery
119553
+ recovery: recovery2
118899
119554
  });
118900
119555
  }
118901
119556
  const totalSec = ((Date.now() - startedAt) / 1000).toFixed(0);
@@ -118903,7 +119558,7 @@ ${deployment.errorMessage}` : "";
118903
119558
  ctx.status(displayUrl ? `Preview at ${displayUrl} (${totalSec}s)` : `Live at ${deployment.url ?? app.url} (${totalSec}s)`);
118904
119559
  const finalUrl = displayUrl ?? deployment.url ?? app.url;
118905
119560
  const fwSuffix = config2.app.framework ? ` (${config2.app.framework}, ${totalSec}s)` : ` (${totalSec}s)`;
118906
- const summary = `${app.name} ${displayUrl ? "preview deployed" : "deployed"}${fwSuffix} → ${finalUrl}`;
119561
+ const summary2 = `${app.name} ${displayUrl ? "preview deployed" : "deployed"}${fwSuffix} → ${finalUrl}`;
118907
119562
  return {
118908
119563
  app,
118909
119564
  deployment,
@@ -118911,8 +119566,8 @@ ${deployment.errorMessage}` : "";
118911
119566
  bytes: tarball.bytes,
118912
119567
  fileCount: tarball.fileCount,
118913
119568
  recovery: RECOVERY_NONE,
118914
- summary,
118915
- configPath: join25(ctx.cwd, "percher.toml"),
119569
+ summary: summary2,
119570
+ configPath: join26(ctx.cwd, "percher.toml"),
118916
119571
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
118917
119572
  };
118918
119573
  }
@@ -119183,9 +119838,11 @@ async function redeploy(ctx, input = {}) {
119183
119838
  });
119184
119839
  }
119185
119840
  const totalSeconds = (Date.now() - t0) / 1000;
119841
+ if (deployment.status === "replaced")
119842
+ return replacedDeploymentResult(app, deployment);
119186
119843
  const url2 = deployment.previewUrl ?? deployment.url ?? app.url;
119187
119844
  if (deployment.status === "failed") {
119188
- const { error: error48, recovery, summary } = await classifyDeploymentFailure({
119845
+ const { error: error48, recovery: recovery2, summary: summary2 } = await classifyDeploymentFailure({
119189
119846
  ctx,
119190
119847
  app,
119191
119848
  deployment
@@ -119197,8 +119854,8 @@ async function redeploy(ctx, input = {}) {
119197
119854
  fileCount: 0,
119198
119855
  bytes: 0,
119199
119856
  error: error48,
119200
- recovery,
119201
- summary,
119857
+ recovery: recovery2,
119858
+ summary: summary2,
119202
119859
  configPath: "",
119203
119860
  bundle: { fileCount: 0, bytes: 0 }
119204
119861
  };
@@ -119241,8 +119898,8 @@ function failureResult(error48) {
119241
119898
  };
119242
119899
  }
119243
119900
  // ../core/dist/commands/rename.js
119244
- import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
119245
- import { join as join26 } from "node:path";
119901
+ import { existsSync as existsSync17, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "node:fs";
119902
+ import { join as join27 } from "node:path";
119246
119903
  var renameInputSchema = exports_external.object({
119247
119904
  app: exports_external.string().optional(),
119248
119905
  name: exports_external.string().min(3).max(40)
@@ -119262,17 +119919,17 @@ async function rename3(ctx, input) {
119262
119919
  };
119263
119920
  }
119264
119921
  function updateLocalTomlName(cwd, oldName, newName) {
119265
- const p = join26(cwd, "percher.toml");
119922
+ const p = join27(cwd, "percher.toml");
119266
119923
  if (!existsSync17(p))
119267
119924
  return false;
119268
119925
  try {
119269
- const src = readFileSync12(p, "utf8");
119926
+ const src = readFileSync13(p, "utf8");
119270
119927
  if (parse5(src).app.name !== oldName)
119271
119928
  return false;
119272
119929
  const next = src.replace(new RegExp(`(name\\s*=\\s*")${oldName}(")`), `$1${newName}$2`);
119273
119930
  if (next === src || parse5(next).app.name !== newName)
119274
119931
  return false;
119275
- writeFileSync9(p, next);
119932
+ writeFileSync10(p, next);
119276
119933
  return true;
119277
119934
  } catch {
119278
119935
  return false;
@@ -119281,9 +119938,9 @@ function updateLocalTomlName(cwd, oldName, newName) {
119281
119938
  // ../core/dist/commands/reproduce.js
119282
119939
  init_dist();
119283
119940
  var import_cross_spawn3 = __toESM(require_cross_spawn(), 1);
119284
- import { mkdtempSync, writeFileSync as writeFileSync10 } from "node:fs";
119941
+ import { mkdtempSync, writeFileSync as writeFileSync11 } from "node:fs";
119285
119942
  import { tmpdir } from "node:os";
119286
- import { join as join27 } from "node:path";
119943
+ import { join as join28 } from "node:path";
119287
119944
  function runProcessExit(cmd, args, opts = {}) {
119288
119945
  return new Promise((resolve11, reject) => {
119289
119946
  const child = import_cross_spawn3.default(cmd, args, {
@@ -119377,9 +120034,9 @@ async function reproduce(ctx, input) {
119377
120034
  const envMap = await ctx.client.env.list(app.id);
119378
120035
  currentAppEnvKeys = Object.keys(envMap).sort();
119379
120036
  } catch {}
119380
- const tmpDir = mkdtempSync(join27(tmpdir(), `percher-reproduce-${input.deployId}-`));
119381
- const tarballPath = join27(tmpDir, "source.tar.gz");
119382
- writeFileSync10(tarballPath, bytes);
120037
+ const tmpDir = mkdtempSync(join28(tmpdir(), `percher-reproduce-${input.deployId}-`));
120038
+ const tarballPath = join28(tmpDir, "source.tar.gz");
120039
+ writeFileSync11(tarballPath, bytes);
119383
120040
  let extracted = false;
119384
120041
  try {
119385
120042
  extracted = await runProcessExit("tar", ["-xzf", tarballPath, "-C", tmpDir]) === 0;
@@ -119596,19 +120253,19 @@ function versionAge(iso, now = Date.now()) {
119596
120253
  return `${d}d ago`;
119597
120254
  return `${Math.floor(d / 30)}mo ago`;
119598
120255
  }
119599
- function rollbackTargetMarker(target, isLive) {
119600
- if (isLive || !target)
120256
+ function rollbackTargetMarker(target2, isLive) {
120257
+ if (isLive || !target2)
119601
120258
  return "";
119602
- if (target.mode === "rebuild")
120259
+ if (target2.mode === "rebuild")
119603
120260
  return " [rebuild required]";
119604
- if (target.mode === "none")
120261
+ if (target2.mode === "none")
119605
120262
  return " [not a rollback target]";
119606
120263
  return "";
119607
120264
  }
119608
- function rollbackTargetDetail(target, isLive) {
119609
- if (isLive || !target || target.mode === "in-window")
120265
+ function rollbackTargetDetail(target2, isLive) {
120266
+ if (isLive || !target2 || target2.mode === "in-window")
119610
120267
  return [];
119611
- const explanation = target.explanation?.trim();
120268
+ const explanation = target2.explanation?.trim();
119612
120269
  return explanation ? [` ${explanation}`] : [];
119613
120270
  }
119614
120271
  async function writeLine(ctx, line) {
@@ -119643,14 +120300,15 @@ async function resolveCommitSha(ctx, name, input) {
119643
120300
  const pick2 = versions2.slice(0, BARE_PICK_LIMIT);
119644
120301
  if (!ctx.stdinIsInteractive) {
119645
120302
  const appFlag = ` --app ${name}`;
119646
- const lines = pick2.flatMap((v, i) => {
120303
+ const lines = pick2.flatMap((v) => {
120304
+ const isLive = v.rollbackTarget?.reason === "ALREADY_LIVE";
119647
120305
  const note = v.message.split(`
119648
120306
  `)[0]?.trim() || "Published";
119649
- const marker = rollbackTargetMarker(v.rollbackTarget, i === 0);
119650
- const detail = rollbackTargetDetail(v.rollbackTarget, i === 0);
120307
+ const marker = isLive ? " (live now)" : rollbackTargetMarker(v.rollbackTarget, false);
120308
+ const detail = rollbackTargetDetail(v.rollbackTarget, isLive);
119651
120309
  const git = v.remoteSha ? ` git ${v.remoteSha.slice(0, 7)}` : "";
119652
120310
  const suffix = ` # ${note}${git} (${versionAge(v.timestamp)})${marker}`;
119653
- if (v.rollbackTarget?.mode === "none" && i !== 0) {
120311
+ if (v.rollbackTarget?.mode === "none") {
119654
120312
  return [` ${v.sha}${suffix}`, ...detail];
119655
120313
  }
119656
120314
  const rebuildFlag = v.rollbackTarget?.mode === "rebuild" ? " --rebuild" : "";
@@ -119668,10 +120326,11 @@ ${lines.join(`
119668
120326
  const note = v.message.split(`
119669
120327
  `)[0]?.trim() || "Published";
119670
120328
  const git = v.remoteSha ? ` git ${v.remoteSha.slice(0, 7)}` : "";
119671
- const live = i === 0 ? " (live now)" : "";
119672
- const marker = rollbackTargetMarker(v.rollbackTarget, i === 0);
120329
+ const isLive = v.rollbackTarget?.reason === "ALREADY_LIVE";
120330
+ const live = isLive ? " (live now)" : "";
120331
+ const marker = rollbackTargetMarker(v.rollbackTarget, isLive);
119673
120332
  await writeLine(ctx, ` ${i + 1}. ${v.sha.slice(0, 10)} ${versionAge(v.timestamp)} ${note}${git}${live}${marker}`);
119674
- for (const detail of rollbackTargetDetail(v.rollbackTarget, i === 0)) {
120333
+ for (const detail of rollbackTargetDetail(v.rollbackTarget, isLive)) {
119675
120334
  await writeLine(ctx, detail);
119676
120335
  }
119677
120336
  }
@@ -119724,6 +120383,7 @@ async function rollback(ctx, input, internals = {}) {
119724
120383
  });
119725
120384
  }
119726
120385
  });
120386
+ throwIfDeploymentReplaced(name, deployment);
119727
120387
  if (deployment.status === "failed") {
119728
120388
  throw new PercherCoreError("Rollback failed", {
119729
120389
  code: "push.deploy-failed",
@@ -119734,7 +120394,7 @@ async function rollback(ctx, input, internals = {}) {
119734
120394
  }
119735
120395
  // ../core/dist/commands/sharing.js
119736
120396
  var ShareRoleSchema = exports_external.enum(["viewer", "admin"]);
119737
- function resolveApp4(ctx, app) {
120397
+ function resolveApp5(ctx, app) {
119738
120398
  const name = app ?? readPercherTomlAppName(ctx.cwd);
119739
120399
  if (!name) {
119740
120400
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -119762,19 +120422,19 @@ var shareRevokeInputSchema = exports_external.object({
119762
120422
  shareId: exports_external.string().min(1, "shareId is required")
119763
120423
  });
119764
120424
  async function shareList(ctx, input = {}) {
119765
- const appRef2 = resolveApp4(ctx, input.app);
120425
+ const appRef2 = resolveApp5(ctx, input.app);
119766
120426
  return ctx.client.sharing.list(appRef2);
119767
120427
  }
119768
120428
  async function shareInvite(ctx, input) {
119769
- const appRef2 = resolveApp4(ctx, input.app);
120429
+ const appRef2 = resolveApp5(ctx, input.app);
119770
120430
  return ctx.client.sharing.create(appRef2, { email: input.email, role: input.role });
119771
120431
  }
119772
120432
  async function shareSetRole(ctx, input) {
119773
- const appRef2 = resolveApp4(ctx, input.app);
120433
+ const appRef2 = resolveApp5(ctx, input.app);
119774
120434
  return ctx.client.sharing.setRole(appRef2, input.shareId, input.role);
119775
120435
  }
119776
120436
  async function shareRevoke(ctx, input) {
119777
- const appRef2 = resolveApp4(ctx, input.app);
120437
+ const appRef2 = resolveApp5(ctx, input.app);
119778
120438
  await ctx.client.sharing.revoke(appRef2, input.shareId);
119779
120439
  }
119780
120440
  var invitesListInputSchema = exports_external.object({});
@@ -119882,7 +120542,7 @@ async function tokenRevoke(ctx, input) {
119882
120542
  await ctx.client.account.revokeToken(input.id);
119883
120543
  }
119884
120544
  // ../core/dist/commands/transfers.js
119885
- function resolveApp5(ctx, app) {
120545
+ function resolveApp6(ctx, app) {
119886
120546
  const name = app ?? readPercherTomlAppName(ctx.cwd);
119887
120547
  if (!name) {
119888
120548
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -119904,15 +120564,15 @@ var transferListForAppInputSchema = exports_external.object({
119904
120564
  app: exports_external.string().optional()
119905
120565
  });
119906
120566
  async function transferInitiate(ctx, input) {
119907
- const appRef2 = resolveApp5(ctx, input.app);
120567
+ const appRef2 = resolveApp6(ctx, input.app);
119908
120568
  return ctx.client.transfers.initiate(appRef2, { email: input.email });
119909
120569
  }
119910
120570
  async function transferCancel(ctx, input) {
119911
- const appRef2 = resolveApp5(ctx, input.app);
120571
+ const appRef2 = resolveApp6(ctx, input.app);
119912
120572
  await ctx.client.transfers.cancel(appRef2, input.transferId);
119913
120573
  }
119914
120574
  async function transferListForApp(ctx, input = {}) {
119915
- const appRef2 = resolveApp5(ctx, input.app);
120575
+ const appRef2 = resolveApp6(ctx, input.app);
119916
120576
  return ctx.client.transfers.listForApp(appRef2);
119917
120577
  }
119918
120578
  var transfersIncomingInputSchema = exports_external.object({});
@@ -119977,6 +120637,7 @@ var SOURCE_EXTS2 = new Set([
119977
120637
  ".py"
119978
120638
  ]);
119979
120639
  var DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
120640
+ var IMPORT_META_CONSTANTS = new Set(["DEV", "PROD", "MODE", "BASE_URL", "SSR"]);
119980
120641
  // ../core/dist/plans.js
119981
120642
  var PB_INITIAL_FOOTPRINT_MB = 256;
119982
120643
  var EGRESS_GB_PER_MONTH = {
@@ -120183,6 +120844,15 @@ async function writeLine2(ctx, line) {
120183
120844
  writer.releaseLock();
120184
120845
  }
120185
120846
  }
120847
+ async function writeErrLine(ctx, line) {
120848
+ const writer = ctx.stderr.getWriter();
120849
+ try {
120850
+ await writer.write(new TextEncoder().encode(`${line}
120851
+ `));
120852
+ } finally {
120853
+ writer.releaseLock();
120854
+ }
120855
+ }
120186
120856
  async function writeUnknownCommand(ctx, name) {
120187
120857
  const writer = ctx.stderr.getWriter();
120188
120858
  try {
@@ -120750,8 +121420,8 @@ function registerAdminCommands(cli, ctx) {
120750
121420
  if (topology?.topologyReady !== true) {
120751
121421
  await writeLine2(ctx, `${import_picocolors3.default.red("✗")} tenant topology audit is red or unavailable — no launch all-clear.`);
120752
121422
  for (const finding of topology?.findings ?? []) {
120753
- const target = finding.network ?? finding.containerId ?? finding.appId ?? "fleet";
120754
- await writeLine2(ctx, ` ${import_picocolors3.default.red("✗")} ${finding.code} [${target}] — ${finding.detail}`);
121423
+ const target2 = finding.network ?? finding.containerId ?? finding.appId ?? "fleet";
121424
+ await writeLine2(ctx, ` ${import_picocolors3.default.red("✗")} ${finding.code} [${target2}] — ${finding.detail}`);
120755
121425
  }
120756
121426
  }
120757
121427
  if (options.app || options.appId) {
@@ -120765,8 +121435,8 @@ function registerAdminCommands(cli, ctx) {
120765
121435
  await writeLine2(ctx, `${import_picocolors3.default.yellow("!")} drift: ${missing} missing route(s), ${hostDrift} missing host(s), ${extra} extra route(s)`);
120766
121436
  for (const d of drift) {
120767
121437
  const label = d.kind === "missing_route" ? import_picocolors3.default.red("MISSING") : d.kind === "missing_host" ? import_picocolors3.default.yellow("HOST_DRIFT") : import_picocolors3.default.dim("EXTRA");
120768
- const target = d.appName ? `[${d.appName}] ` : "";
120769
- await writeLine2(ctx, ` ${label} ${target}${d.routeId} (${d.routeType}) — ${d.hosts.join(", ")}`);
121438
+ const target2 = d.appName ? `[${d.appName}] ` : "";
121439
+ await writeLine2(ctx, ` ${label} ${target2}${d.routeId} (${d.routeType}) — ${d.hosts.join(", ")}`);
120770
121440
  }
120771
121441
  }
120772
121442
  await writeLine2(ctx, import_picocolors3.default.dim(`Repair: percher admin reconcile-routes${options.app ? ` --app ${options.app}` : ""}`));
@@ -120842,8 +121512,8 @@ async function printList(ctx, title, rows) {
120842
121512
 
120843
121513
  // src/commands/apps.ts
120844
121514
  var import_picocolors5 = __toESM(require_picocolors(), 1);
120845
- function assertNever2(status2, summary) {
120846
- throw new Error(`Unhandled publish status: ${JSON.stringify(status2)}${summary ? ` (${summary})` : ""}`);
121515
+ function assertNever2(status2, summary2) {
121516
+ throw new Error(`Unhandled publish status: ${JSON.stringify(status2)}${summary2 ? ` (${summary2})` : ""}`);
120847
121517
  }
120848
121518
  async function handlePublishResult(ctx, result, options = {}) {
120849
121519
  switch (result.status) {
@@ -121082,6 +121752,10 @@ function registerAppsCommands(cli, ctx) {
121082
121752
  deployId,
121083
121753
  noCache: options.cache === false
121084
121754
  });
121755
+ if (result.status === "replaced") {
121756
+ await handlePublishResult(ctx, result);
121757
+ throw new SilentExitError;
121758
+ }
121085
121759
  if (result.status === "needs_login") {
121086
121760
  await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
121087
121761
  throw new SilentExitError;
@@ -121399,6 +122073,154 @@ function registerAuthCommands(cli, ctx) {
121399
122073
 
121400
122074
  // src/commands/data.ts
121401
122075
  var import_picocolors7 = __toESM(require_picocolors(), 1);
122076
+
122077
+ // src/commands/data-records.ts
122078
+ var DATA_RECORD_SUBCOMMANDS = new Set([
122079
+ "schema",
122080
+ "query",
122081
+ "get",
122082
+ "create",
122083
+ "update",
122084
+ "delete"
122085
+ ]);
122086
+
122087
+ class DataArgumentError extends PercherCoreError {
122088
+ constructor(message) {
122089
+ super(message, { code: "data.missing-argument" });
122090
+ }
122091
+ }
122092
+ function cacKey(name) {
122093
+ return name.replaceAll(/([a-z])-([a-z])/g, (_, a, b) => a + b.toUpperCase());
122094
+ }
122095
+ function textOption(argv, options, key, flag) {
122096
+ const parsed = options[key];
122097
+ if (parsed === undefined)
122098
+ return;
122099
+ if (typeof parsed === "string")
122100
+ return parsed;
122101
+ if (typeof parsed !== "number") {
122102
+ throw new DataArgumentError(`${flag} needs exactly one value`);
122103
+ }
122104
+ const end = argv.includes("--") ? argv.indexOf("--") : argv.length;
122105
+ let raw;
122106
+ for (let i = 0;i < end; i++) {
122107
+ const m = /^--([^=]+)(?:=(.*))?$/s.exec(argv[i] ?? "");
122108
+ if (!m || cacKey(m[1] ?? "") !== key)
122109
+ continue;
122110
+ raw = m[2] ?? argv[i + 1];
122111
+ }
122112
+ if (raw === undefined || Number(raw) !== parsed) {
122113
+ throw new DataArgumentError(`${flag} could not be read as typed`);
122114
+ }
122115
+ return raw;
122116
+ }
122117
+ function intOption(options, key, flag) {
122118
+ const parsed = options[key];
122119
+ if (parsed === undefined)
122120
+ return;
122121
+ if (typeof parsed === "number")
122122
+ return parsed;
122123
+ if (typeof parsed === "string")
122124
+ return Number(parsed);
122125
+ throw new DataArgumentError(`${flag} needs exactly one value`);
122126
+ }
122127
+ function recordPositionals(rest, options) {
122128
+ const after = Array.isArray(options["--"]) ? options["--"] : [];
122129
+ const all = [...rest, ...after];
122130
+ if (!all.every((v) => typeof v === "string")) {
122131
+ throw new DataArgumentError("Could not read the collection or record id as typed; put boolean flags after them");
122132
+ }
122133
+ return all;
122134
+ }
122135
+ function recordData2(value) {
122136
+ let parsed;
122137
+ try {
122138
+ parsed = JSON.parse(value ?? "");
122139
+ } catch {
122140
+ parsed = undefined;
122141
+ }
122142
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
122143
+ throw new PercherCoreError("--data must be a JSON object of field values", {
122144
+ code: "data.invalid-json",
122145
+ hint: `Example: --data '{"title":"Buy milk","done":false}'`
122146
+ });
122147
+ }
122148
+ return parsed;
122149
+ }
122150
+ function missing(usage) {
122151
+ throw new PercherCoreError(`Missing arguments. Usage: percher ${usage}`, {
122152
+ code: "data.missing-argument"
122153
+ });
122154
+ }
122155
+ async function runDataRecordCommand(ctx, argv, sub, rest, options) {
122156
+ const [collection, id] = recordPositionals(rest, options);
122157
+ const text = (key, flag) => textOption(argv, options, key, flag);
122158
+ const app = text("app", "--app");
122159
+ let result;
122160
+ switch (sub) {
122161
+ case "schema":
122162
+ result = await dataSchema2(ctx, dataSchemaInputSchema.parse({ app }));
122163
+ break;
122164
+ case "query":
122165
+ if (!collection)
122166
+ missing("data query <collection> [--filter …] [--app <name>]");
122167
+ result = await dataQuery(ctx, dataQueryInputSchema.parse({
122168
+ app,
122169
+ collection,
122170
+ filter: text("filter", "--filter"),
122171
+ sort: text("sort", "--sort"),
122172
+ page: intOption(options, "page", "--page"),
122173
+ perPage: intOption(options, "perPage", "--per-page"),
122174
+ fields: text("fields", "--fields")
122175
+ }));
122176
+ break;
122177
+ case "get":
122178
+ if (!collection || !id)
122179
+ missing("data get <collection> <id> [--app <name>]");
122180
+ result = await dataGet(ctx, dataGetInputSchema.parse({ app, collection, id }));
122181
+ break;
122182
+ case "create":
122183
+ if (!collection)
122184
+ missing("data create <collection> --data '<json>' [--id <id>]");
122185
+ result = await dataCreate(ctx, dataCreateInputSchema.parse({
122186
+ app,
122187
+ collection,
122188
+ data: recordData2(text("data", "--data")),
122189
+ id: text("id", "--id")
122190
+ }));
122191
+ break;
122192
+ case "update":
122193
+ if (!collection || !id)
122194
+ missing("data update <collection> <id> --data '<json>'");
122195
+ result = await dataUpdate(ctx, dataUpdateInputSchema.parse({
122196
+ app,
122197
+ collection,
122198
+ id,
122199
+ data: recordData2(text("data", "--data")),
122200
+ ifUpdated: text("ifUpdated", "--if-updated")
122201
+ }));
122202
+ break;
122203
+ case "delete":
122204
+ if (!collection || !id)
122205
+ missing("data delete <collection> <id> [--app <name>]");
122206
+ result = await dataDelete(ctx, dataDeleteInputSchema.parse({ app, collection, id }));
122207
+ break;
122208
+ default:
122209
+ return;
122210
+ }
122211
+ await writeLine2(ctx, JSON.stringify(result, null, 2));
122212
+ }
122213
+ function registerDataRecordHelp(cli) {
122214
+ const none = async () => {};
122215
+ cli.command("data schema", "List the collections and fields the data tools can use").action(none);
122216
+ cli.command("data query <collection>", "Query records (--filter, --sort, --page, --per-page, --fields)").action(none);
122217
+ cli.command("data get <collection> <id>", "Get one record").action(none);
122218
+ cli.command("data create <collection>", "Create a record from --data '<json>' (optional --id)").action(none);
122219
+ cli.command("data update <collection> <id>", "Change fields from --data '<json>' (optional --if-updated)").action(none);
122220
+ cli.command("data delete <collection> <id>", "Delete one record").action(none);
122221
+ }
122222
+
122223
+ // src/commands/data.ts
121402
122224
  var DATA_VALUE_FLAGS = new Set([
121403
122225
  "--out",
121404
122226
  "--app",
@@ -121407,7 +122229,11 @@ var DATA_VALUE_FLAGS = new Set([
121407
122229
  "--client-secret"
121408
122230
  ]);
121409
122231
  function registerDataCommands(cli, ctx, argv) {
121410
- cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "(export, oauth) target app (default: read percher.toml)").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret (a command-line value lands in shell history)").option("--remove", "(oauth only) remove the provider instead of setting it").action(async (app, _rest, options) => {
122232
+ cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "Target app (default: read percher.toml); names an app called like a subcommand, e.g. `data --app get`").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret (a command-line value lands in shell history)").option("--remove", "(oauth only) remove the provider instead of setting it").option("--filter <expr>", "(query only) filter over the collection's fields").option("--sort <fields>", "(query only) comma-separated fields, - for descending (write --sort=-field)").option("--page <n>", "(query only) page number").option("--per-page <n>", "(query only) records per page (max 100)").option("--fields <fields>", "(query only) comma-separated fields to return").option("--data <json>", "(create, update) field values as a JSON object").option("--id <id>", "(create only) id for the new record").option("--if-updated <timestamp>", "(update only) refuse if the record changed since").action(async (app, _rest, options) => {
122233
+ if (app !== undefined && DATA_RECORD_SUBCOMMANDS.has(app)) {
122234
+ await runDataRecordCommand(ctx, argv, app, _rest, options);
122235
+ return;
122236
+ }
121411
122237
  if (app === "reset-superuser") {
121412
122238
  const idx = argv.indexOf("reset-superuser");
121413
122239
  const realApp = idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined;
@@ -121458,7 +122284,7 @@ function registerDataCommands(cli, ctx, argv) {
121458
122284
  }
121459
122285
  return;
121460
122286
  }
121461
- const result = await data(ctx, { app, json: options.json });
122287
+ const result = await data(ctx, { app: app ?? options.app, json: options.json });
121462
122288
  if (options.json) {
121463
122289
  await writeLine2(ctx, JSON.stringify(result, null, 2));
121464
122290
  } else {
@@ -121483,6 +122309,7 @@ function registerDataCommands(cli, ctx, argv) {
121483
122309
  }
121484
122310
  }
121485
122311
  });
122312
+ registerDataRecordHelp(cli);
121486
122313
  cli.command("data reset-superuser [app]", "Reset PocketBase superuser and inject new credentials").option("--reveal", "Also print the plaintext password once (default: hidden)").action(async (app, options) => {
121487
122314
  const result = await resetSuperuser(ctx, { app, reveal: options.reveal });
121488
122315
  await printSuperuserResetResult(ctx, result);
@@ -121578,6 +122405,7 @@ function registerDeploysCommands(cli, ctx, argv) {
121578
122405
  rebuild: Boolean(options.rebuild)
121579
122406
  });
121580
122407
  await writeLine2(ctx, `${import_picocolors8.default.green("✓")} rolled back to ${result.rolledBackTo.slice(0, 10)} (status: ${result.status})`);
122408
+ await writeLine2(ctx, "Env vars are not rolled back; the current values stay.");
121581
122409
  });
121582
122410
  cli.command("reproduce <deployId>", "Download a deploy's source and reproduce the build locally").option("--app <name>", "App name (defaults to percher.toml app.name)").action(async (deployId, options) => {
121583
122411
  const result = await reproduce(ctx, {
@@ -121656,6 +122484,8 @@ function registerDeploysCommands(cli, ctx, argv) {
121656
122484
  for (const v of list) {
121657
122485
  const message = v.message.split(`
121658
122486
  `)[0]?.trim() ?? "";
122487
+ const note = Array.from((v.note ?? "").replace(/[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]/gu, " ").replace(/\s+/g, " ").trim());
122488
+ const noteLabel = note.length > 80 ? `${note.slice(0, 79).join("")}…` : note.join("");
121659
122489
  const channelLabels = {
121660
122490
  cli: "CLI",
121661
122491
  mcp: "MCP",
@@ -121666,11 +122496,11 @@ function registerDeploysCommands(cli, ctx, argv) {
121666
122496
  const label = v.channel ? channelLabels[v.channel] ?? v.channel : null;
121667
122497
  const git = v.remoteSha ? ` · git ${v.remoteSha.slice(0, 7)}` : "";
121668
122498
  const suffix = `${label ? ` · via ${label}` : ""}${git}`;
121669
- const target = v.rollbackTarget;
121670
- const marker = target && target.mode !== "in-window" ? target.mode === "rebuild" ? "[rebuild required]" : "[not a rollback target]" : null;
121671
- if (marker && target)
121672
- reasons.set(marker, target.explanation);
121673
- await writeLine2(ctx, `${v.sha} ${v.timestamp} ${message}${suffix}${marker ? ` ${marker}` : ""}`);
122499
+ const target2 = v.rollbackTarget;
122500
+ const marker = target2 && target2.mode !== "in-window" ? target2.mode === "rebuild" ? "[rebuild required]" : "[not a rollback target]" : null;
122501
+ if (marker && target2)
122502
+ reasons.set(marker, target2.explanation);
122503
+ await writeLine2(ctx, `${v.sha} ${v.timestamp} ${message}${noteLabel ? ` · ${noteLabel}` : ""}${suffix}${marker ? ` ${marker}` : ""}`);
121674
122504
  }
121675
122505
  for (const [marker, explanation] of reasons) {
121676
122506
  await writeLine2(ctx, `
@@ -121684,8 +122514,8 @@ ${marker} ${explanation}`);
121684
122514
  head,
121685
122515
  patch: options.patch
121686
122516
  });
121687
- const { summary, files } = result;
121688
- await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors8.default.green(`+${summary.additions}`)} ${import_picocolors8.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
122517
+ const { summary: summary2, files } = result;
122518
+ await writeLine2(ctx, `${summary2.filesChanged} file${summary2.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors8.default.green(`+${summary2.additions}`)} ${import_picocolors8.default.red(`−${summary2.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
121689
122519
  for (const f of files) {
121690
122520
  const stat3 = f.binary ? import_picocolors8.default.dim("binary") : `${import_picocolors8.default.green(`+${f.additions}`)} ${import_picocolors8.default.red(`−${f.deletions}`)}`;
121691
122521
  await writeLine2(ctx, ` ${f.status} ${f.path} ${stat3}${f.truncated ? import_picocolors8.default.dim(" (truncated)") : ""}`);
@@ -122376,8 +123206,209 @@ async function renderHumanStatus(ctx, result) {
122376
123206
  }
122377
123207
  }
122378
123208
 
122379
- // src/commands/token.ts
123209
+ // src/commands/telemetry.ts
123210
+ init_dist();
122380
123211
  var import_picocolors16 = __toESM(require_picocolors(), 1);
123212
+
123213
+ // src/telemetry.ts
123214
+ init_dist();
123215
+ import { createInterface } from "node:readline";
123216
+ // package.json
123217
+ var package_default2 = {
123218
+ name: "percher",
123219
+ version: "1.3.0",
123220
+ description: "CLI for the Percher hosting platform — publish apps with one command",
123221
+ type: "module",
123222
+ bin: {
123223
+ percher: "./dist/bin/percher.js"
123224
+ },
123225
+ main: "./dist/src/index.js",
123226
+ types: "./dist/src/index.d.ts",
123227
+ exports: {
123228
+ ".": {
123229
+ types: "./dist/src/index.d.ts",
123230
+ import: "./dist/src/index.js",
123231
+ default: "./dist/src/index.js"
123232
+ }
123233
+ },
123234
+ files: [
123235
+ "dist",
123236
+ "THIRD-PARTY-NOTICES.txt"
123237
+ ],
123238
+ publishConfig: {
123239
+ access: "public"
123240
+ },
123241
+ scripts: {
123242
+ build: `tsc -p tsconfig.json && tsc-alias -p tsconfig.json && bun build bin/percher.ts --outfile dist/bin/percher.js --target node --format esm --metafile=bundle-meta.json && node ../../scripts/bundle-notices.mjs . bundle-meta.json && node scripts/copy-templates.mjs && node -e "require('fs').chmodSync('dist/bin/percher.js',0o755)"`,
123243
+ test: "bun test",
123244
+ typecheck: "tsc --noEmit"
123245
+ },
123246
+ dependencies: {
123247
+ "@percher/client": "^1.3.0",
123248
+ "@percher/core": "^1.3.0",
123249
+ "@percher/toml": "^1.0.4",
123250
+ cac: "^7.0.0",
123251
+ picocolors: "^1.1.1"
123252
+ },
123253
+ devDependencies: {
123254
+ "bun-types": "^1.1.0",
123255
+ "tsc-alias": "^1.8.17"
123256
+ },
123257
+ engines: {
123258
+ bun: ">=1.0.0",
123259
+ node: ">=22.0.0"
123260
+ },
123261
+ license: "FSL-1.1-MIT",
123262
+ author: "Mattias Malmborg",
123263
+ repository: {
123264
+ type: "git",
123265
+ url: "https://github.com/mattiasmalmborg/percher"
123266
+ },
123267
+ keywords: [
123268
+ "hosting",
123269
+ "deploy",
123270
+ "cli",
123271
+ "paas",
123272
+ "bun",
123273
+ "ai"
123274
+ ]
123275
+ };
123276
+
123277
+ // src/telemetry.ts
123278
+ var VERSION = package_default2.version;
123279
+ function trackEvent(command, result, errorType) {
123280
+ if (!telemetryState().enabled)
123281
+ return;
123282
+ const body = JSON.stringify({
123283
+ cliVersion: VERSION,
123284
+ command,
123285
+ result,
123286
+ errorType,
123287
+ os: `${process.platform}-${process.arch}`,
123288
+ bunVersion: typeof Bun !== "undefined" ? Bun.version : undefined
123289
+ });
123290
+ fetch(`${resolveApiUrl()}/telemetry/cli`, {
123291
+ method: "POST",
123292
+ headers: { "Content-Type": "application/json" },
123293
+ body,
123294
+ signal: AbortSignal.timeout(3000)
123295
+ }).catch(() => {});
123296
+ }
123297
+ function lineAsker(input, output, onInterrupt = () => process.exit(130)) {
123298
+ return (question, signal) => new Promise((resolve11) => {
123299
+ const rl = createInterface({
123300
+ input,
123301
+ output,
123302
+ terminal: input.isTTY === true
123303
+ });
123304
+ let settled = false;
123305
+ const finish = (answer) => {
123306
+ if (settled)
123307
+ return;
123308
+ settled = true;
123309
+ signal.removeEventListener("abort", onAbort);
123310
+ if (answer === null)
123311
+ output.write(`
123312
+ `);
123313
+ resolve11(answer);
123314
+ rl.close();
123315
+ };
123316
+ const onAbort = () => finish(null);
123317
+ signal.addEventListener("abort", onAbort, { once: true });
123318
+ rl.on("line", (line) => finish(line));
123319
+ rl.on("close", () => finish(null));
123320
+ rl.on("SIGINT", () => {
123321
+ finish(null);
123322
+ onInterrupt();
123323
+ });
123324
+ rl.setPrompt(question);
123325
+ rl.prompt();
123326
+ if (signal.aborted)
123327
+ finish(null);
123328
+ });
123329
+ }
123330
+ var askOnStdin = lineAsker(process.stdin, process.stderr);
123331
+ var ANSWER_TIMEOUT_MS = 20000;
123332
+ var CI_VARS = ["CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "TF_BUILD", "JENKINS_URL"];
123333
+ var AGENT_VARS = [
123334
+ "CLAUDECODE",
123335
+ "CURSOR_AGENT",
123336
+ "CURSOR_TRACE_ID",
123337
+ "CODEX_SANDBOX",
123338
+ "CODEX_CI",
123339
+ "GITHUB_ACTIONS"
123340
+ ];
123341
+ function isUnattended(env) {
123342
+ const ci = env.CI;
123343
+ if (ci !== undefined && ci !== "" && ci !== "0" && ci !== "false")
123344
+ return true;
123345
+ return [...CI_VARS, ...AGENT_VARS].some((key) => Boolean(env[key]));
123346
+ }
123347
+ var realTimer = (fire, ms) => {
123348
+ const handle = setTimeout(fire, ms);
123349
+ return () => clearTimeout(handle);
123350
+ };
123351
+ async function askTelemetryConsentOnce(ctx, argv, command, question) {
123352
+ if (command === "telemetry" || argv.includes("--json"))
123353
+ return;
123354
+ if (ctx.stdinIsInteractive !== true || !question.stderrIsTTY)
123355
+ return;
123356
+ if (isUnattended(ctx.env))
123357
+ return;
123358
+ const state = telemetryState();
123359
+ if (state.envOptOut || state.stored)
123360
+ return;
123361
+ const timeout = new AbortController;
123362
+ const cancel = (question.setTimer ?? realTimer)(() => timeout.abort(), ANSWER_TIMEOUT_MS);
123363
+ let answer;
123364
+ try {
123365
+ answer = await question.ask("Send anonymous usage statistics to Percher? (y/N) ", timeout.signal);
123366
+ } finally {
123367
+ cancel();
123368
+ }
123369
+ if (answer === null)
123370
+ return;
123371
+ const enabled = /^y(es)?$/i.test(answer.trim());
123372
+ try {
123373
+ saveTelemetryAnswer(enabled, VERSION);
123374
+ } catch {
123375
+ await writeErrLine(ctx, "Could not save the answer; you will be asked again.");
123376
+ return;
123377
+ }
123378
+ await writeErrLine(ctx, enabled ? "Usage statistics on. Turn off: percher telemetry off" : "Usage statistics off. Turn on: percher telemetry on");
123379
+ }
123380
+ function telemetryStatusLine(state = telemetryState()) {
123381
+ const stored = state.stored ? `stored answer: ${state.stored.enabled ? "on" : "off"}` : null;
123382
+ if (state.envOptOut) {
123383
+ return `Telemetry: off (env opt-out: ${state.envOptOut}${stored ? `; ${stored}` : ""})`;
123384
+ }
123385
+ if (state.stored)
123386
+ return `Telemetry: ${state.stored.enabled ? "on" : "off"} (stored answer)`;
123387
+ return "Telemetry: off (undecided)";
123388
+ }
123389
+
123390
+ // src/commands/telemetry.ts
123391
+ function registerTelemetryCommands(cli, ctx) {
123392
+ cli.command("telemetry [action]", "Anonymous usage statistics: on | off | status").action(async (action) => {
123393
+ if (action === "on" || action === "off") {
123394
+ try {
123395
+ saveTelemetryAnswer(action === "on", VERSION);
123396
+ } catch {
123397
+ await writeErrLine(ctx, `${import_picocolors16.default.red("✗")} Could not save the answer.`);
123398
+ throw new SilentExitError;
123399
+ }
123400
+ } else if (action !== undefined && action !== "status") {
123401
+ await writeErrLine(ctx, `${import_picocolors16.default.red("✗")} Usage: percher telemetry on|off|status`);
123402
+ throw new SilentExitError;
123403
+ }
123404
+ const state = telemetryState();
123405
+ const overridden = action === "on" && state.envOptOut !== null;
123406
+ await writeLine2(ctx, `${telemetryStatusLine(state)}${overridden ? "; the environment variable still wins." : ""}`);
123407
+ });
123408
+ }
123409
+
123410
+ // src/commands/token.ts
123411
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
122381
123412
  var VALID_SCOPES = ["deploy", "env", "read", "full"];
122382
123413
  function scopeLabel(scopes, appName) {
122383
123414
  const s = scopes && scopes.length > 0 ? scopes.join("+") : "full";
@@ -122386,22 +123417,22 @@ function scopeLabel(scopes, appName) {
122386
123417
  function registerTokenCommands(cli, ctx) {
122387
123418
  cli.command("token [action] [arg]", "Manage API tokens: create | list | revoke").option("--scope <scope>", "(create) deploy | env | read | full (default: full, account-wide unless --app)").option("--app <name>", "(create) bind the token to a single app").option("--ttl-days <n>", "(create) token lifetime in days (default 90, max 365)").action(async (action, arg, options) => {
122388
123419
  if (!ctx.client.token) {
122389
- await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Not logged in. Run: percher login`);
123420
+ await writeLine2(ctx, `${import_picocolors17.default.red("✗")} Not logged in. Run: percher login`);
122390
123421
  throw new SilentExitError;
122391
123422
  }
122392
123423
  if (action === "create") {
122393
123424
  if (!arg) {
122394
- await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
123425
+ await writeLine2(ctx, `${import_picocolors17.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
122395
123426
  throw new SilentExitError;
122396
123427
  }
122397
123428
  const scope = options.scope ?? "full";
122398
123429
  if (!VALID_SCOPES.includes(scope)) {
122399
- await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
123430
+ await writeLine2(ctx, `${import_picocolors17.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
122400
123431
  throw new SilentExitError;
122401
123432
  }
122402
123433
  const ttlDays = options.ttlDays !== undefined ? Number(options.ttlDays) : undefined;
122403
123434
  if (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {
122404
- await writeLine2(ctx, `${import_picocolors16.default.red("✗")} --ttl-days must be a positive whole number.`);
123435
+ await writeLine2(ctx, `${import_picocolors17.default.red("✗")} --ttl-days must be a positive whole number.`);
122405
123436
  throw new SilentExitError;
122406
123437
  }
122407
123438
  const created = await tokenCreate(ctx, {
@@ -122410,36 +123441,36 @@ function registerTokenCommands(cli, ctx) {
122410
123441
  app: options.app,
122411
123442
  ttlDays
122412
123443
  });
122413
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token created: ${import_picocolors16.default.bold(created.name)} ${import_picocolors16.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
123444
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Token created: ${import_picocolors17.default.bold(created.name)} ${import_picocolors17.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
122414
123445
  await writeLine2(ctx, "");
122415
- await writeLine2(ctx, ` ${import_picocolors16.default.bold(created.token)}`);
122416
- await writeLine2(ctx, import_picocolors16.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
123446
+ await writeLine2(ctx, ` ${import_picocolors17.default.bold(created.token)}`);
123447
+ await writeLine2(ctx, import_picocolors17.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
122417
123448
  await writeLine2(ctx, "");
122418
- await writeLine2(ctx, import_picocolors16.default.dim(" Use it in CI / an agent:"));
122419
- await writeLine2(ctx, import_picocolors16.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
123449
+ await writeLine2(ctx, import_picocolors17.default.dim(" Use it in CI / an agent:"));
123450
+ await writeLine2(ctx, import_picocolors17.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
122420
123451
  return;
122421
123452
  }
122422
123453
  if (action === "list") {
122423
123454
  const tokens = await tokenList(ctx);
122424
123455
  if (tokens.length === 0) {
122425
- await writeLine2(ctx, import_picocolors16.default.dim("No API tokens."));
123456
+ await writeLine2(ctx, import_picocolors17.default.dim("No API tokens."));
122426
123457
  return;
122427
123458
  }
122428
123459
  for (const t of tokens) {
122429
123460
  const expires = t.expiresAt ? `expires ${new Date(t.expiresAt).toISOString().slice(0, 10)}` : "no expiry";
122430
- await writeLine2(ctx, `${import_picocolors16.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors16.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors16.default.dim(expires)}`);
123461
+ await writeLine2(ctx, `${import_picocolors17.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors17.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors17.default.dim(expires)}`);
122431
123462
  }
122432
123463
  await writeLine2(ctx, "");
122433
- await writeLine2(ctx, import_picocolors16.default.dim("Revoke one with: percher token revoke <id>"));
123464
+ await writeLine2(ctx, import_picocolors17.default.dim("Revoke one with: percher token revoke <id>"));
122434
123465
  return;
122435
123466
  }
122436
123467
  if (action === "revoke") {
122437
123468
  if (!arg) {
122438
- await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
123469
+ await writeLine2(ctx, `${import_picocolors17.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
122439
123470
  throw new SilentExitError;
122440
123471
  }
122441
123472
  await tokenRevoke(ctx, { id: arg });
122442
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token revoked.`);
123473
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Token revoked.`);
122443
123474
  return;
122444
123475
  }
122445
123476
  throw new Error(`Unknown token action "${action ?? ""}". Use: create | list | revoke.`);
@@ -122450,17 +123481,17 @@ function registerTokenCommands(cli, ctx) {
122450
123481
  }
122451
123482
 
122452
123483
  // src/commands/transfers.ts
122453
- var import_picocolors17 = __toESM(require_picocolors(), 1);
123484
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
122454
123485
  function registerTransfersCommands(cli, ctx) {
122455
123486
  cli.command("transfer [emailOrTransferId]", "Transfer ownership of an app: `transfer <email>` initiates; `transfer --cancel <id>` cancels; `transfer --list` shows history").option("--app <name>", "App name (overrides percher.toml)").option("--cancel", "Cancel a pending transfer by id (positional arg is the transferId)").option("--list", "List the transfer history for this app").action(async (emailOrTransferId, options) => {
122456
123487
  if (options.list) {
122457
123488
  const rows = await transferListForApp(ctx, { app: options.app });
122458
123489
  if (rows.length === 0) {
122459
- await writeLine2(ctx, import_picocolors17.default.dim("No transfers for this app."));
123490
+ await writeLine2(ctx, import_picocolors18.default.dim("No transfers for this app."));
122460
123491
  return;
122461
123492
  }
122462
123493
  for (const r of rows) {
122463
- const status2 = r.status === "pending" ? import_picocolors17.default.yellow("pending") : r.status === "accepted" ? import_picocolors17.default.green("accepted") : import_picocolors17.default.dim(r.status);
123494
+ const status2 = r.status === "pending" ? import_picocolors18.default.yellow("pending") : r.status === "accepted" ? import_picocolors18.default.green("accepted") : import_picocolors18.default.dim(r.status);
122464
123495
  await writeLine2(ctx, `${r.id} ${status2.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
122465
123496
  }
122466
123497
  return;
@@ -122473,7 +123504,7 @@ function registerTransfersCommands(cli, ctx) {
122473
123504
  app: options.app,
122474
123505
  transferId: emailOrTransferId
122475
123506
  });
122476
- await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
123507
+ await writeLine2(ctx, `${import_picocolors18.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
122477
123508
  return;
122478
123509
  }
122479
123510
  if (!emailOrTransferId) {
@@ -122483,17 +123514,17 @@ function registerTransfersCommands(cli, ctx) {
122483
123514
  app: options.app,
122484
123515
  email: emailOrTransferId
122485
123516
  });
122486
- await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
122487
- await writeLine2(ctx, import_picocolors17.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
123517
+ await writeLine2(ctx, `${import_picocolors18.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
123518
+ await writeLine2(ctx, import_picocolors18.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
122488
123519
  if (initiated.notes) {
122489
- await writeLine2(ctx, import_picocolors17.default.yellow(` ⚠ ${initiated.notes}`));
123520
+ await writeLine2(ctx, import_picocolors18.default.yellow(` ⚠ ${initiated.notes}`));
122490
123521
  }
122491
123522
  });
122492
123523
  cli.command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>").action(async (action, transferId) => {
122493
123524
  if (action === undefined || action === "list") {
122494
123525
  const rows = await transfersIncoming(ctx, {});
122495
123526
  if (rows.length === 0) {
122496
- await writeLine2(ctx, import_picocolors17.default.dim("No pending incoming transfers."));
123527
+ await writeLine2(ctx, import_picocolors18.default.dim("No pending incoming transfers."));
122497
123528
  return;
122498
123529
  }
122499
123530
  for (const r of rows) {
@@ -122506,7 +123537,7 @@ function registerTransfersCommands(cli, ctx) {
122506
123537
  throw new Error("Usage: percher transfers accept <transferId>");
122507
123538
  }
122508
123539
  const accepted = await transfersAccept(ctx, { transferId });
122509
- await writeLine2(ctx, `${import_picocolors17.default.green("✓")} You now own ${import_picocolors17.default.cyan(accepted.appName)}. Run 'percher open ${accepted.appName}' to see it.`);
123540
+ await writeLine2(ctx, `${import_picocolors18.default.green("✓")} You now own ${import_picocolors18.default.cyan(accepted.appName)}. Run 'percher open ${accepted.appName}' to see it.`);
122510
123541
  return;
122511
123542
  }
122512
123543
  if (action === "decline") {
@@ -122514,7 +123545,7 @@ function registerTransfersCommands(cli, ctx) {
122514
123545
  throw new Error("Usage: percher transfers decline <transferId>");
122515
123546
  }
122516
123547
  await transfersDecline(ctx, { transferId });
122517
- await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Declined transfer ${transferId}.`);
123548
+ await writeLine2(ctx, `${import_picocolors18.default.green("✓")} Declined transfer ${transferId}.`);
122518
123549
  return;
122519
123550
  }
122520
123551
  throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
@@ -122543,9 +123574,9 @@ async function openUrl(url2) {
122543
123574
 
122544
123575
  // src/prompt.ts
122545
123576
  import { stdin as input, stdout as output } from "node:process";
122546
- import { createInterface } from "node:readline/promises";
123577
+ import { createInterface as createInterface2 } from "node:readline/promises";
122547
123578
  async function prompt(question, options) {
122548
- const rl = createInterface({ input, output });
123579
+ const rl = createInterface2({ input, output });
122549
123580
  try {
122550
123581
  const suffix = options?.default ? ` [${options.default}]` : "";
122551
123582
  const answer = await rl.question(`${question}${suffix} `);
@@ -122555,111 +123586,6 @@ async function prompt(question, options) {
122555
123586
  }
122556
123587
  }
122557
123588
 
122558
- // src/telemetry.ts
122559
- init_dist();
122560
- // package.json
122561
- var package_default2 = {
122562
- name: "percher",
122563
- version: "1.1.0",
122564
- description: "CLI for the Percher hosting platform — publish apps with one command",
122565
- type: "module",
122566
- bin: {
122567
- percher: "./dist/bin/percher.js"
122568
- },
122569
- main: "./dist/src/index.js",
122570
- types: "./dist/src/index.d.ts",
122571
- exports: {
122572
- ".": {
122573
- types: "./dist/src/index.d.ts",
122574
- import: "./dist/src/index.js",
122575
- default: "./dist/src/index.js"
122576
- }
122577
- },
122578
- files: [
122579
- "dist",
122580
- "THIRD-PARTY-NOTICES.txt"
122581
- ],
122582
- publishConfig: {
122583
- access: "public"
122584
- },
122585
- scripts: {
122586
- build: `tsc -p tsconfig.json && tsc-alias -p tsconfig.json && bun build bin/percher.ts --outfile dist/bin/percher.js --target node --format esm --metafile=bundle-meta.json && node ../../scripts/bundle-notices.mjs . bundle-meta.json && node scripts/copy-templates.mjs && node -e "require('fs').chmodSync('dist/bin/percher.js',0o755)"`,
122587
- test: "bun test",
122588
- typecheck: "tsc --noEmit"
122589
- },
122590
- dependencies: {
122591
- "@percher/client": "^1.1.0",
122592
- "@percher/core": "^1.1.0",
122593
- "@percher/toml": "^1.0.3",
122594
- cac: "^7.0.0",
122595
- picocolors: "^1.1.1"
122596
- },
122597
- devDependencies: {
122598
- "bun-types": "^1.1.0",
122599
- "tsc-alias": "^1.8.17"
122600
- },
122601
- engines: {
122602
- bun: ">=1.0.0",
122603
- node: ">=22.0.0"
122604
- },
122605
- license: "FSL-1.1-MIT",
122606
- author: "Mattias Malmborg",
122607
- repository: {
122608
- type: "git",
122609
- url: "https://github.com/mattiasmalmborg/percher"
122610
- },
122611
- keywords: [
122612
- "hosting",
122613
- "deploy",
122614
- "cli",
122615
- "paas",
122616
- "bun",
122617
- "ai"
122618
- ]
122619
- };
122620
-
122621
- // src/telemetry.ts
122622
- var VERSION = package_default2.version;
122623
- function isDisabled() {
122624
- return process.env.PERCHER_NO_TELEMETRY === "1" || process.env.PERCHER_NO_TELEMETRY === "true" || process.env.DO_NOT_TRACK === "1";
122625
- }
122626
- function trackEvent(command, result, errorType) {
122627
- if (isDisabled())
122628
- return;
122629
- const body = JSON.stringify({
122630
- cliVersion: VERSION,
122631
- command,
122632
- result,
122633
- errorType,
122634
- os: `${process.platform}-${process.arch}`,
122635
- bunVersion: typeof Bun !== "undefined" ? Bun.version : undefined
122636
- });
122637
- fetch(`${resolveApiUrl()}/telemetry/cli`, {
122638
- method: "POST",
122639
- headers: { "Content-Type": "application/json" },
122640
- body,
122641
- signal: AbortSignal.timeout(3000)
122642
- }).catch(() => {});
122643
- }
122644
- async function showTelemetryNotice() {
122645
- if (isDisabled())
122646
- return;
122647
- const { homedir: homedir2 } = await import("node:os");
122648
- const { existsSync: existsSync18, mkdirSync: mkdirSync7, writeFileSync: writeFileSync11 } = await import("node:fs");
122649
- const { join: join28 } = await import("node:path");
122650
- const configDir = join28(homedir2(), ".percher");
122651
- const markerFile = join28(configDir, ".telemetry-notice-shown");
122652
- if (existsSync18(markerFile))
122653
- return;
122654
- console.error("Percher sends non-account-linked usage stats (CLI version, command, result, error type, OS, Bun version).");
122655
- console.error(`Disable with PERCHER_NO_TELEMETRY=1 or DO_NOT_TRACK=1
122656
- `);
122657
- try {
122658
- mkdirSync7(configDir, { recursive: true });
122659
- writeFileSync11(markerFile, new Date().toISOString());
122660
- } catch {}
122661
- }
122662
-
122663
123589
  // src/context.ts
122664
123590
  async function createCliContext() {
122665
123591
  const client = new PercherClient({ clientVersion: `percher-cli/${VERSION}` });
@@ -122693,52 +123619,52 @@ async function createCliContext() {
122693
123619
 
122694
123620
  // src/output.ts
122695
123621
  init_dist();
122696
- var import_picocolors18 = __toESM(require_picocolors(), 1);
123622
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
122697
123623
  async function renderError(stderr, error48) {
122698
123624
  const writer = stderr.getWriter();
122699
123625
  const encoder = new TextEncoder;
122700
123626
  const lines = [];
122701
123627
  if (error48 instanceof PercherTomlError) {
122702
- lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
123628
+ lines.push(`${import_picocolors19.default.red(`error (${error48.code}):`)} ${error48.message}`);
122703
123629
  if (error48.tomlLine !== undefined) {
122704
- lines.push(import_picocolors18.default.dim(` at percher.toml line ${error48.tomlLine}`));
123630
+ lines.push(import_picocolors19.default.dim(` at percher.toml line ${error48.tomlLine}`));
122705
123631
  }
122706
123632
  for (const issue2 of error48.issues ?? []) {
122707
- lines.push(`${import_picocolors18.default.dim(" · ") + import_picocolors18.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
123633
+ lines.push(`${import_picocolors19.default.dim(" · ") + import_picocolors19.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
122708
123634
  }
122709
123635
  } else if (error48 instanceof SunsetError) {
122710
123636
  if (error48.gateCode === "platform_delete_gated") {
122711
- lines.push(`${import_picocolors18.default.yellow("Percher is shutting down — deletes are paused.")}`);
123637
+ lines.push(`${import_picocolors19.default.yellow("Percher is shutting down — deletes are paused.")}`);
122712
123638
  lines.push(error48.message);
122713
- lines.push(import_picocolors18.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
122714
- lines.push(import_picocolors18.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
123639
+ lines.push(import_picocolors19.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
123640
+ lines.push(import_picocolors19.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
122715
123641
  } else {
122716
- lines.push(`${import_picocolors18.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
123642
+ lines.push(`${import_picocolors19.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
122717
123643
  lines.push(error48.message);
122718
- lines.push(import_picocolors18.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
123644
+ lines.push(import_picocolors19.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
122719
123645
  }
122720
- lines.push(import_picocolors18.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
123646
+ lines.push(import_picocolors19.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
122721
123647
  } else if (error48 instanceof PercherApiError) {
122722
123648
  if (error48.status === 502 || error48.status === 503) {
122723
- lines.push(`${import_picocolors18.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
123649
+ lines.push(`${import_picocolors19.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
122724
123650
  } else {
122725
- lines.push(`${import_picocolors18.default.red("error:")} ${error48.message}`);
122726
- lines.push(import_picocolors18.default.dim(` ${error48.code} (HTTP ${error48.status})`));
123651
+ lines.push(`${import_picocolors19.default.red("error:")} ${error48.message}`);
123652
+ lines.push(import_picocolors19.default.dim(` ${error48.code} (HTTP ${error48.status})`));
122727
123653
  }
122728
123654
  if (error48.requestId)
122729
- lines.push(import_picocolors18.default.dim(` request: ${error48.requestId}`));
123655
+ lines.push(import_picocolors19.default.dim(` request: ${error48.requestId}`));
122730
123656
  } else if (error48 instanceof PercherCoreError) {
122731
- lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
123657
+ lines.push(`${import_picocolors19.default.red(`error (${error48.code}):`)} ${error48.message}`);
122732
123658
  if (error48.hint)
122733
- lines.push(import_picocolors18.default.dim(` ${error48.hint}`));
123659
+ lines.push(import_picocolors19.default.dim(` ${error48.hint}`));
122734
123660
  } else if (error48 instanceof Error) {
122735
- lines.push(`${import_picocolors18.default.red("unexpected error:")} ${error48.message}`);
123661
+ lines.push(`${import_picocolors19.default.red("unexpected error:")} ${error48.message}`);
122736
123662
  if (error48.stack)
122737
- lines.push(import_picocolors18.default.dim(error48.stack.split(`
123663
+ lines.push(import_picocolors19.default.dim(error48.stack.split(`
122738
123664
  `).slice(1, 4).join(`
122739
123665
  `)));
122740
123666
  } else {
122741
- lines.push(`${import_picocolors18.default.red("unexpected error:")} ${String(error48)}`);
123667
+ lines.push(`${import_picocolors19.default.red("unexpected error:")} ${String(error48)}`);
122742
123668
  }
122743
123669
  try {
122744
123670
  await writer.write(encoder.encode(`${lines.join(`
@@ -122750,7 +123676,7 @@ async function renderError(stderr, error48) {
122750
123676
  }
122751
123677
 
122752
123678
  // src/version-check.ts
122753
- var import_picocolors19 = __toESM(require_picocolors(), 1);
123679
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
122754
123680
  function isNewerStableVersion(latest, current) {
122755
123681
  const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
122756
123682
  if (typeof latest !== "string" || latest.length > 64 || current.length > 64 || !stable.test(latest) || !stable.test(current))
@@ -122777,16 +123703,16 @@ async function checkForUpdates() {
122777
123703
  const data2 = await res.json();
122778
123704
  const latest = data2.version;
122779
123705
  if (isNewerStableVersion(latest, VERSION)) {
122780
- console.error(import_picocolors19.default.yellow(`
123706
+ console.error(import_picocolors20.default.yellow(`
122781
123707
  Update available: ${VERSION} → ${latest}`));
122782
- console.error(import_picocolors19.default.dim(`Run: bunx percher@latest
123708
+ console.error(import_picocolors20.default.dim(`Run: bunx percher@latest
122783
123709
  `));
122784
123710
  }
122785
123711
  } catch {}
122786
123712
  }
122787
123713
 
122788
123714
  // src/index.ts
122789
- async function run(argv, ctxOverride) {
123715
+ async function run(argv, ctxOverride, options = {}) {
122790
123716
  const cli = cac("percher");
122791
123717
  const ctx = ctxOverride ?? await createCliContext();
122792
123718
  registerAppsCommands(cli, ctx);
@@ -122802,6 +123728,7 @@ async function run(argv, ctxOverride) {
122802
123728
  registerMigrateCommands(cli, ctx);
122803
123729
  registerSharingCommands(cli, ctx);
122804
123730
  registerStatusCommands(cli, ctx);
123731
+ registerTelemetryCommands(cli, ctx);
122805
123732
  registerTokenCommands(cli, ctx);
122806
123733
  registerTransfersCommands(cli, ctx);
122807
123734
  registerAdminCommands(cli, ctx);
@@ -122809,10 +123736,14 @@ async function run(argv, ctxOverride) {
122809
123736
  cli.version(VERSION);
122810
123737
  const command = argv[0] ?? "help";
122811
123738
  try {
122812
- await showTelemetryNotice();
122813
123739
  cli.parse(["percher", "percher", ...argv], { run: false });
122814
123740
  const requested = argv[0];
122815
123741
  if (cli.matchedCommand) {
123742
+ await askTelemetryConsentOnce(ctx, argv, cli.matchedCommand.name, {
123743
+ ask: options.askTelemetry ?? askOnStdin,
123744
+ stderrIsTTY: options.stderrIsTTY ?? process.stderr.isTTY === true,
123745
+ setTimer: options.setTimer
123746
+ });
122816
123747
  await cli.runMatchedCommand();
122817
123748
  } else if (requested === undefined || requested === "help") {
122818
123749
  cli.outputHelp();