percher 0.5.1 → 0.5.3

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.
@@ -17228,8 +17228,8 @@ var require_lines = __commonJS((exports) => {
17228
17228
  if (isOnlyWhitespace(sliced)) {
17229
17229
  continue;
17230
17230
  }
17231
- var diff = Math.abs(info.indent - lastIndent);
17232
- counts[diff] = ~~counts[diff] + 1;
17231
+ var diff2 = Math.abs(info.indent - lastIndent);
17232
+ counts[diff2] = ~~counts[diff2] + 1;
17233
17233
  lastIndent = info.indent;
17234
17234
  }
17235
17235
  var maxCount = -1;
@@ -17255,12 +17255,12 @@ var require_lines = __commonJS((exports) => {
17255
17255
  Lines2.prototype.isPrecededOnlyByWhitespace = function(pos) {
17256
17256
  var info = this.infos[pos.line - 1];
17257
17257
  var indent = Math.max(info.indent, 0);
17258
- var diff = pos.column - indent;
17259
- if (diff <= 0) {
17258
+ var diff2 = pos.column - indent;
17259
+ if (diff2 <= 0) {
17260
17260
  return true;
17261
17261
  }
17262
17262
  var start = info.sliceStart;
17263
- var end = Math.min(start + diff, info.sliceEnd);
17263
+ var end = Math.min(start + diff2, info.sliceEnd);
17264
17264
  var prefix = info.line.slice(start, end);
17265
17265
  return isOnlyWhitespace(prefix);
17266
17266
  };
@@ -105518,6 +105518,17 @@ class AppsResource {
105518
105518
  commitSha
105519
105519
  });
105520
105520
  }
105521
+ getChanges(appRef, opts = {}) {
105522
+ const params = new URLSearchParams;
105523
+ if (opts.base)
105524
+ params.set("base", opts.base);
105525
+ if (opts.head)
105526
+ params.set("head", opts.head);
105527
+ if (opts.patch)
105528
+ params.set("patch", "1");
105529
+ const qs = params.toString();
105530
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/changes${qs ? `?${qs}` : ""}`);
105531
+ }
105521
105532
  async getCrashReport(appRef) {
105522
105533
  try {
105523
105534
  return await this.http.get(`/apps/${encodeURIComponent(appRef)}/crash-report`);
@@ -105748,6 +105759,12 @@ class ExportResource {
105748
105759
  const path = downloadUrl.startsWith("http") ? new URL(downloadUrl).pathname + new URL(downloadUrl).search : downloadUrl;
105749
105760
  return this.http.stream(path);
105750
105761
  }
105762
+ gitBundle(appRef, signal) {
105763
+ return this.http.stream(`/apps/${encodeURIComponent(appRef)}/export/git`, signal);
105764
+ }
105765
+ checkGitBundle(appRef) {
105766
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/export/git/check`);
105767
+ }
105751
105768
  }
105752
105769
 
105753
105770
  // ../client/dist/sse.js
@@ -106092,7 +106109,7 @@ import { join as join7, resolve as resolve4 } from "node:path";
106092
106109
  // ../core/dist/templates.js
106093
106110
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
106094
106111
  import { basename, join as join6, normalize, relative, resolve as resolve3 } from "node:path";
106095
- var __dirname = "C:\\dev\\Percher-lakebed-clean\\packages\\core\\dist";
106112
+ var __dirname = "C:\\dev\\Percher\\packages\\core\\dist";
106096
106113
  var TEMPLATES_DIR = resolve3(import.meta.dirname ?? __dirname, "../templates");
106097
106114
  var PLACEHOLDER = "__APP_NAME__";
106098
106115
  function listTemplates() {
@@ -106708,6 +106725,27 @@ var diagnoseInputSchema = exports_external.object({
106708
106725
  logLines: exports_external.number().int().min(0).max(200).optional(),
106709
106726
  recencyHours: exports_external.number().positive().max(24 * 30).optional()
106710
106727
  });
106728
+ // ../core/dist/commands/diff.js
106729
+ var diffInputSchema = exports_external.object({
106730
+ app: exports_external.string().optional(),
106731
+ base: exports_external.string().optional(),
106732
+ head: exports_external.string().optional(),
106733
+ patch: exports_external.boolean().optional()
106734
+ });
106735
+ async function diff(ctx, input = {}) {
106736
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
106737
+ if (!name) {
106738
+ throw new PercherCoreError("No app specified and no percher.toml found", {
106739
+ code: "diff.no-app",
106740
+ hint: "Pass --app <name> or run from a directory with percher.toml."
106741
+ });
106742
+ }
106743
+ return ctx.client.apps.getChanges(name, {
106744
+ base: input.base,
106745
+ head: input.head,
106746
+ patch: input.patch
106747
+ });
106748
+ }
106711
106749
  // ../core/dist/commands/doctor.js
106712
106750
  import { existsSync as existsSync8 } from "node:fs";
106713
106751
  import { join as join12 } from "node:path";
@@ -107730,9 +107768,11 @@ async function doctor(ctx, input = {}) {
107730
107768
  let buildLogClassification;
107731
107769
  let buildProblems;
107732
107770
  let crashReport;
107771
+ let suspectDeploy;
107733
107772
  try {
107734
107773
  const diag = await ctx.client.apps.getDiagnostics(appName);
107735
107774
  appStatus = diag.app.status;
107775
+ suspectDeploy = diag.suspectDeploy ?? undefined;
107736
107776
  lastDeployStatus = diag.lastDeploy?.status;
107737
107777
  lastDeployId = diag.lastDeploy?.id;
107738
107778
  containerState = diag.container.state;
@@ -107928,7 +107968,8 @@ async function doctor(ctx, input = {}) {
107928
107968
  inputMode: input.mode,
107929
107969
  buildLogClassification,
107930
107970
  buildProblems,
107931
- crashReport
107971
+ crashReport,
107972
+ suspectDeploy
107932
107973
  });
107933
107974
  }
107934
107975
  var TRANSITIONAL_DEPLOY_STATUSES = new Set(["queued", "building", "deploying"]);
@@ -107948,7 +107989,8 @@ function summarize(checks3, vctx = {}) {
107948
107989
  status: verdict.status,
107949
107990
  diagnosis: verdict.diagnosis,
107950
107991
  recovery: verdict.recovery,
107951
- summary: verdict.summary
107992
+ summary: verdict.summary,
107993
+ ...vctx.suspectDeploy ? { suspectDeploy: vctx.suspectDeploy } : {}
107952
107994
  };
107953
107995
  }
107954
107996
  function buildVerdictRuleContext(checks3, vctx) {
@@ -108450,12 +108492,18 @@ async function envList(ctx, input = {}) {
108450
108492
  return masked;
108451
108493
  }
108452
108494
  // ../core/dist/commands/export.js
108495
+ import { randomUUID } from "node:crypto";
108496
+ import { rename } from "node:fs/promises";
108453
108497
  import { isAbsolute, resolve as resolvePath } from "node:path";
108454
108498
  var exportInputSchema = exports_external.object({
108455
108499
  app: exports_external.string().optional(),
108456
108500
  out: exports_external.string().optional(),
108457
108501
  deliveryKind: exports_external.enum(["cli", "mcp"]).default("cli")
108458
108502
  });
108503
+ var exportGitInputSchema = exports_external.object({
108504
+ app: exports_external.string().optional(),
108505
+ out: exports_external.string().optional()
108506
+ });
108459
108507
  function resolveAppName3(ctx, provided) {
108460
108508
  const name = provided ?? readPercherTomlAppName(ctx.cwd);
108461
108509
  if (!name) {
@@ -108467,8 +108515,8 @@ function resolveAppName3(ctx, provided) {
108467
108515
  return name;
108468
108516
  }
108469
108517
  async function streamToFile(outPath, source) {
108470
- const file2 = Bun.file(outPath);
108471
- const writer = file2.writer();
108518
+ const tmpPath = `${outPath}.part-${randomUUID()}`;
108519
+ const writer = Bun.file(tmpPath).writer();
108472
108520
  let bytes = 0;
108473
108521
  const reader = source.getReader();
108474
108522
  try {
@@ -108487,8 +108535,15 @@ async function streamToFile(outPath, source) {
108487
108535
  await writer.end();
108488
108536
  } catch {}
108489
108537
  try {
108490
- await Bun.write(outPath, "");
108491
- await Bun.file(outPath).delete?.();
108538
+ await Bun.file(tmpPath).delete?.();
108539
+ } catch {}
108540
+ throw err;
108541
+ }
108542
+ try {
108543
+ await rename(tmpPath, outPath);
108544
+ } catch (err) {
108545
+ try {
108546
+ await Bun.file(tmpPath).delete?.();
108492
108547
  } catch {}
108493
108548
  throw err;
108494
108549
  }
@@ -108525,6 +108580,25 @@ async function exportApp(ctx, input) {
108525
108580
  includedSecrets: false
108526
108581
  };
108527
108582
  }
108583
+ async function exportGitBundle(ctx, input) {
108584
+ const name = resolveAppName3(ctx, input.app);
108585
+ const res = await ctx.client.export.gitBundle(name);
108586
+ if (!res.body) {
108587
+ throw new PercherCoreError("Git bundle download returned an empty body", {
108588
+ code: "export.empty-body",
108589
+ hint: "Try again — if it persists, the app may have no deploy history yet."
108590
+ });
108591
+ }
108592
+ const filenameFromHeader = (() => {
108593
+ const cd = res.headers.get("Content-Disposition") ?? "";
108594
+ const m = cd.match(/filename="?([^";]+)"?/);
108595
+ return m?.[1];
108596
+ })();
108597
+ const filename = input.out ?? filenameFromHeader ?? `${name}.bundle`;
108598
+ const outPath = resolveOutPath(ctx.cwd, filename);
108599
+ const { bytes } = await streamToFile(outPath, res.body);
108600
+ return { appName: name, outPath, bytes };
108601
+ }
108528
108602
  // ../core/dist/commands/forgejo.js
108529
108603
  var FORGEJO_URL_RE = /^https:\/\/[\w.-]+\/[\w.-]+\/[\w.-]+(\.git)?$/;
108530
108604
  var forgejoConnectInputSchema = exports_external.object({
@@ -112525,7 +112599,27 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
112525
112599
  powSolution: solution
112526
112600
  });
112527
112601
  } catch (err) {
112602
+ const httpStatus = err.status;
112528
112603
  const code = err.code;
112604
+ if (httpStatus === 429) {
112605
+ return {
112606
+ status: "failed",
112607
+ fileCount: tarball.fileCount,
112608
+ bytes: tarball.bytes,
112609
+ error: {
112610
+ title: "Too many anonymous deploys",
112611
+ explanation: "Anonymous publishing is rate-limited per network to deter abuse. Wait a bit and try again, or log in to publish with an account.",
112612
+ suggestion: "Retry in a little while, or run percher login to publish with an account."
112613
+ },
112614
+ recovery: recoveryAsk({
112615
+ prompt: "Anonymous publishing is rate-limited per network. Wait and retry, or run `percher login` to publish with an account.",
112616
+ reasonCode: "quota_exceeded"
112617
+ }),
112618
+ summary: "Rate limited — too many anonymous deploys from your network. Try again later.",
112619
+ configPath: tomlPathFor2(ctx.cwd),
112620
+ bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
112621
+ };
112622
+ }
112529
112623
  const detail = code === "ANON_CAPACITY" ? "Anonymous capacity is full right now." : "Anonymous deploys are not enabled on this instance.";
112530
112624
  return {
112531
112625
  status: "needs_login",
@@ -113281,7 +113375,7 @@ var renameInputSchema = exports_external.object({
113281
113375
  app: exports_external.string().optional(),
113282
113376
  name: exports_external.string().min(3).max(40)
113283
113377
  });
113284
- async function rename(ctx, input) {
113378
+ async function rename2(ctx, input) {
113285
113379
  const appRef = input.app ?? readPercherTomlAppName(ctx.cwd);
113286
113380
  if (!appRef) {
113287
113381
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -114809,7 +114903,7 @@ function registerAppsCommands(cli, ctx) {
114809
114903
  }
114810
114904
  });
114811
114905
  cli.command("rename <name> [app]", "Rename an app (changes its subdomain)").action(async (name, app) => {
114812
- const result = await rename(ctx, { app, name });
114906
+ const result = await rename2(ctx, { app, name });
114813
114907
  await writeLine(ctx, `${import_picocolors4.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
114814
114908
  await writeLine(ctx, ` Previous name: ${result.previousName} (reserved for 60 days)`);
114815
114909
  if (result.tomlUpdated) {
@@ -114921,6 +115015,20 @@ function registerAppsCommands(cli, ctx) {
114921
115015
  const counterColor = result.status === "ok" ? import_picocolors4.default.green : result.status === "in_progress" ? import_picocolors4.default.yellow : import_picocolors4.default.red;
114922
115016
  await writeLine(ctx, counterColor(counter));
114923
115017
  await writeLine(ctx, result.summary);
115018
+ if (result.suspectDeploy) {
115019
+ const s = result.suspectDeploy;
115020
+ const head = (s.commitSha ?? s.deployId).slice(0, 10);
115021
+ await writeLine(ctx, "");
115022
+ await writeLine(ctx, import_picocolors4.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
115023
+ await writeLine(ctx, ` Suspect: ${head}`);
115024
+ if (s.lastHealthyCommitSha) {
115025
+ const base = s.lastHealthyCommitSha.slice(0, 10);
115026
+ await writeLine(ctx, ` Last working: ${base}`);
115027
+ await writeLine(ctx, import_picocolors4.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
115028
+ }
115029
+ for (const e of s.evidence)
115030
+ await writeLine(ctx, import_picocolors4.default.dim(` · ${e}`));
115031
+ }
114924
115032
  }
114925
115033
  if (result.status === "blocked" || result.status === "needs_action") {
114926
115034
  throw new SilentExitError;
@@ -115181,7 +115289,36 @@ function registerDeploysCommands(cli, ctx, argv) {
115181
115289
  for (const v of list) {
115182
115290
  const message = v.message.split(`
115183
115291
  `)[0]?.trim() ?? "";
115184
- await writeLine(ctx, `${v.sha} ${v.timestamp} ${message}`);
115292
+ const channelLabels = {
115293
+ cli: "CLI",
115294
+ mcp: "MCP",
115295
+ dashboard: "Dashboard",
115296
+ github: "GitHub",
115297
+ "forgejo-webhook": "Forgejo"
115298
+ };
115299
+ const label = v.channel ? channelLabels[v.channel] ?? v.channel : null;
115300
+ const suffix = label ? ` · via ${label}` : "";
115301
+ await writeLine(ctx, `${v.sha} ${v.timestamp} ${message}${suffix}`);
115302
+ }
115303
+ });
115304
+ cli.command("diff [base] [head]", "Show what changed between two deploy versions").option("--app <name>", "App name (overrides percher.toml)").option("--patch", "Include the unified diff per file (owner-only)").action(async (base, head, options) => {
115305
+ const result = await diff(ctx, {
115306
+ app: options.app,
115307
+ base,
115308
+ head,
115309
+ patch: options.patch
115310
+ });
115311
+ const { summary, files } = result;
115312
+ await writeLine(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors7.default.green(`+${summary.additions}`)} ${import_picocolors7.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
115313
+ for (const f of files) {
115314
+ const stat = f.binary ? import_picocolors7.default.dim("binary") : `${import_picocolors7.default.green(`+${f.additions}`)} ${import_picocolors7.default.red(`−${f.deletions}`)}`;
115315
+ await writeLine(ctx, ` ${f.status} ${f.path} ${stat}${f.truncated ? import_picocolors7.default.dim(" (truncated)") : ""}`);
115316
+ if (options.patch && f.patch) {
115317
+ await writeLine(ctx, f.patch.replace(/\n$/, ""));
115318
+ }
115319
+ }
115320
+ if (result.truncated) {
115321
+ await writeLine(ctx, import_picocolors7.default.dim("… some patches omitted (response size limit)"));
115185
115322
  }
115186
115323
  });
115187
115324
  cli.command("preview <action> [arg] [arg2]", "Preview deploys: promote <id> | discard <id> | branch set <branch> | branch unset | branch show").option("--app <name>", "App name (overrides percher.toml)").action(async (action, arg, arg2, options) => {
@@ -115297,7 +115434,13 @@ function registerEnvCommands(cli, ctx) {
115297
115434
  // src/commands/export.ts
115298
115435
  var import_picocolors9 = __toESM(require_picocolors(), 1);
115299
115436
  function registerExportCommands(cli, ctx) {
115300
- cli.command("export [app]", "Download an export bundle for an app (no secrets)").option("--out <path>", "Where to write the .tar.gz (defaults to ./{name}-{date}.tar.gz)").action(async (app, options) => {
115437
+ cli.command("export [app]", "Download an export bundle for an app (no secrets)").option("--out <path>", "Where to write the file (defaults to ./{name}-{date}.tar.gz)").option("--git", "Export the whole deploy history as a git bundle (.bundle) — re-clone with `git clone`").action(async (app, options) => {
115438
+ if (options.git) {
115439
+ const result2 = await exportGitBundle(ctx, { app, out: options.out });
115440
+ await writeLine(ctx, `${import_picocolors9.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
115441
+ await writeLine(ctx, import_picocolors9.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
115442
+ return;
115443
+ }
115301
115444
  const result = await exportApp(ctx, {
115302
115445
  app,
115303
115446
  out: options.out,
@@ -115832,7 +115975,7 @@ async function renderError(stderr, error48) {
115832
115975
  // package.json
115833
115976
  var package_default = {
115834
115977
  name: "percher",
115835
- version: "0.5.1",
115978
+ version: "0.5.3",
115836
115979
  description: "CLI for the Percher hosting platform — publish apps with one command",
115837
115980
  type: "module",
115838
115981
  bin: {
@@ -115857,7 +116000,7 @@ var package_default = {
115857
116000
  },
115858
116001
  dependencies: {
115859
116002
  "@percher/client": "^0.2.10",
115860
- "@percher/core": "^0.4.4",
116003
+ "@percher/core": "^0.4.5",
115861
116004
  "@percher/toml": "^0.1.5",
115862
116005
  cac: "^7.0.0",
115863
116006
  picocolors: "^1.1.1"
@@ -0,0 +1,3 @@
1
+ import type { Context } from "@percher/core";
2
+ export declare function createCliContext(): Promise<Context>;
3
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CA4BzD"}
@@ -0,0 +1,31 @@
1
+ import { PercherClient } from "@percher/client";
2
+ import { openUrl } from "./open";
3
+ import { prompt } from "./prompt";
4
+ export async function createCliContext() {
5
+ const client = new PercherClient();
6
+ const encoder = new TextEncoder();
7
+ const stdout = new WritableStream({
8
+ write(chunk) {
9
+ process.stdout.write(chunk);
10
+ },
11
+ });
12
+ const stderr = new WritableStream({
13
+ write(chunk) {
14
+ process.stderr.write(chunk);
15
+ },
16
+ });
17
+ return {
18
+ client,
19
+ cwd: process.cwd(),
20
+ stdout,
21
+ stderr,
22
+ env: process.env,
23
+ prompt,
24
+ openUrl,
25
+ interactiveLogin: true,
26
+ status(message) {
27
+ process.stderr.write(encoder.encode(`${message}\n`));
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAElC,MAAM,MAAM,GAAG,IAAI,cAAc,CAAa;QAC5C,KAAK,CAAC,KAAK;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,cAAc,CAAa;QAC5C,KAAK,CAAC,KAAK;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;QACN,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM;QACN,MAAM;QACN,GAAG,EAAE,OAAO,CAAC,GAAyC;QACtD,MAAM;QACN,OAAO;QACP,gBAAgB,EAAE,IAAI;QACtB,MAAM,CAAC,OAAe;YACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC;QACvD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Context } from "@percher/core";
2
+ export declare function run(argv: string[], ctxOverride?: Context): Promise<number>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAgC7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAmYhF"}