percher 0.5.2 → 0.5.4

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,19 @@ 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 { createWriteStream } from "node:fs";
108497
+ import { rename, unlink } from "node:fs/promises";
108453
108498
  import { isAbsolute, resolve as resolvePath } from "node:path";
108454
108499
  var exportInputSchema = exports_external.object({
108455
108500
  app: exports_external.string().optional(),
108456
108501
  out: exports_external.string().optional(),
108457
108502
  deliveryKind: exports_external.enum(["cli", "mcp"]).default("cli")
108458
108503
  });
108504
+ var exportGitInputSchema = exports_external.object({
108505
+ app: exports_external.string().optional(),
108506
+ out: exports_external.string().optional()
108507
+ });
108459
108508
  function resolveAppName3(ctx, provided) {
108460
108509
  const name = provided ?? readPercherTomlAppName(ctx.cwd);
108461
108510
  if (!name) {
@@ -108467,29 +108516,46 @@ function resolveAppName3(ctx, provided) {
108467
108516
  return name;
108468
108517
  }
108469
108518
  async function streamToFile(outPath, source) {
108470
- const file2 = Bun.file(outPath);
108471
- const writer = file2.writer();
108472
- let bytes = 0;
108519
+ const tmpPath = `${outPath}.part-${randomUUID()}`;
108520
+ const out = createWriteStream(tmpPath);
108473
108521
  const reader = source.getReader();
108522
+ const closed = new Promise((resolve6) => out.once("close", () => resolve6()));
108523
+ let onWriteError;
108524
+ const writeFailed = new Promise((_, reject) => {
108525
+ onWriteError = reject;
108526
+ });
108527
+ out.once("error", onWriteError);
108528
+ writeFailed.catch(() => {});
108529
+ let bytes = 0;
108474
108530
  try {
108475
- while (true) {
108476
- const { done, value } = await reader.read();
108531
+ for (;; ) {
108532
+ const { done, value } = await Promise.race([reader.read(), writeFailed]);
108477
108533
  if (done)
108478
108534
  break;
108479
- if (value && value.length > 0) {
108480
- writer.write(value);
108481
- bytes += value.length;
108535
+ if (value && value.byteLength > 0) {
108536
+ bytes += value.byteLength;
108537
+ if (!out.write(value)) {
108538
+ await Promise.race([
108539
+ new Promise((resolve6) => out.once("drain", resolve6)),
108540
+ writeFailed
108541
+ ]);
108542
+ }
108482
108543
  }
108483
108544
  }
108484
- await writer.end();
108545
+ await new Promise((resolve6, reject) => {
108546
+ out.end((err) => err ? reject(err) : resolve6());
108547
+ });
108548
+ await closed;
108485
108549
  } catch (err) {
108486
- try {
108487
- await writer.end();
108488
- } catch {}
108489
- try {
108490
- await Bun.write(outPath, "");
108491
- await Bun.file(outPath).delete?.();
108492
- } catch {}
108550
+ out.destroy();
108551
+ await closed;
108552
+ await unlink(tmpPath).catch(() => {});
108553
+ throw err;
108554
+ }
108555
+ try {
108556
+ await rename(tmpPath, outPath);
108557
+ } catch (err) {
108558
+ await unlink(tmpPath).catch(() => {});
108493
108559
  throw err;
108494
108560
  }
108495
108561
  return { bytes };
@@ -108525,6 +108591,25 @@ async function exportApp(ctx, input) {
108525
108591
  includedSecrets: false
108526
108592
  };
108527
108593
  }
108594
+ async function exportGitBundle(ctx, input) {
108595
+ const name = resolveAppName3(ctx, input.app);
108596
+ const res = await ctx.client.export.gitBundle(name);
108597
+ if (!res.body) {
108598
+ throw new PercherCoreError("Git bundle download returned an empty body", {
108599
+ code: "export.empty-body",
108600
+ hint: "Try again — if it persists, the app may have no deploy history yet."
108601
+ });
108602
+ }
108603
+ const filenameFromHeader = (() => {
108604
+ const cd = res.headers.get("Content-Disposition") ?? "";
108605
+ const m = cd.match(/filename="?([^";]+)"?/);
108606
+ return m?.[1];
108607
+ })();
108608
+ const filename = input.out ?? filenameFromHeader ?? `${name}.bundle`;
108609
+ const outPath = resolveOutPath(ctx.cwd, filename);
108610
+ const { bytes } = await streamToFile(outPath, res.body);
108611
+ return { appName: name, outPath, bytes };
108612
+ }
108528
108613
  // ../core/dist/commands/forgejo.js
108529
108614
  var FORGEJO_URL_RE = /^https:\/\/[\w.-]+\/[\w.-]+\/[\w.-]+(\.git)?$/;
108530
108615
  var forgejoConnectInputSchema = exports_external.object({
@@ -113301,7 +113386,7 @@ var renameInputSchema = exports_external.object({
113301
113386
  app: exports_external.string().optional(),
113302
113387
  name: exports_external.string().min(3).max(40)
113303
113388
  });
113304
- async function rename(ctx, input) {
113389
+ async function rename2(ctx, input) {
113305
113390
  const appRef = input.app ?? readPercherTomlAppName(ctx.cwd);
113306
113391
  if (!appRef) {
113307
113392
  throw new PercherCoreError("No app specified and no percher.toml found", {
@@ -114829,7 +114914,7 @@ function registerAppsCommands(cli, ctx) {
114829
114914
  }
114830
114915
  });
114831
114916
  cli.command("rename <name> [app]", "Rename an app (changes its subdomain)").action(async (name, app) => {
114832
- const result = await rename(ctx, { app, name });
114917
+ const result = await rename2(ctx, { app, name });
114833
114918
  await writeLine(ctx, `${import_picocolors4.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
114834
114919
  await writeLine(ctx, ` Previous name: ${result.previousName} (reserved for 60 days)`);
114835
114920
  if (result.tomlUpdated) {
@@ -114941,6 +115026,20 @@ function registerAppsCommands(cli, ctx) {
114941
115026
  const counterColor = result.status === "ok" ? import_picocolors4.default.green : result.status === "in_progress" ? import_picocolors4.default.yellow : import_picocolors4.default.red;
114942
115027
  await writeLine(ctx, counterColor(counter));
114943
115028
  await writeLine(ctx, result.summary);
115029
+ if (result.suspectDeploy) {
115030
+ const s = result.suspectDeploy;
115031
+ const head = (s.commitSha ?? s.deployId).slice(0, 10);
115032
+ await writeLine(ctx, "");
115033
+ await writeLine(ctx, import_picocolors4.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
115034
+ await writeLine(ctx, ` Suspect: ${head}`);
115035
+ if (s.lastHealthyCommitSha) {
115036
+ const base = s.lastHealthyCommitSha.slice(0, 10);
115037
+ await writeLine(ctx, ` Last working: ${base}`);
115038
+ await writeLine(ctx, import_picocolors4.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
115039
+ }
115040
+ for (const e of s.evidence)
115041
+ await writeLine(ctx, import_picocolors4.default.dim(` · ${e}`));
115042
+ }
114944
115043
  }
114945
115044
  if (result.status === "blocked" || result.status === "needs_action") {
114946
115045
  throw new SilentExitError;
@@ -115201,7 +115300,36 @@ function registerDeploysCommands(cli, ctx, argv) {
115201
115300
  for (const v of list) {
115202
115301
  const message = v.message.split(`
115203
115302
  `)[0]?.trim() ?? "";
115204
- await writeLine(ctx, `${v.sha} ${v.timestamp} ${message}`);
115303
+ const channelLabels = {
115304
+ cli: "CLI",
115305
+ mcp: "MCP",
115306
+ dashboard: "Dashboard",
115307
+ github: "GitHub",
115308
+ "forgejo-webhook": "Forgejo"
115309
+ };
115310
+ const label = v.channel ? channelLabels[v.channel] ?? v.channel : null;
115311
+ const suffix = label ? ` · via ${label}` : "";
115312
+ await writeLine(ctx, `${v.sha} ${v.timestamp} ${message}${suffix}`);
115313
+ }
115314
+ });
115315
+ 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) => {
115316
+ const result = await diff(ctx, {
115317
+ app: options.app,
115318
+ base,
115319
+ head,
115320
+ patch: options.patch
115321
+ });
115322
+ const { summary, files } = result;
115323
+ 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)})`);
115324
+ for (const f of files) {
115325
+ const stat = f.binary ? import_picocolors7.default.dim("binary") : `${import_picocolors7.default.green(`+${f.additions}`)} ${import_picocolors7.default.red(`−${f.deletions}`)}`;
115326
+ await writeLine(ctx, ` ${f.status} ${f.path} ${stat}${f.truncated ? import_picocolors7.default.dim(" (truncated)") : ""}`);
115327
+ if (options.patch && f.patch) {
115328
+ await writeLine(ctx, f.patch.replace(/\n$/, ""));
115329
+ }
115330
+ }
115331
+ if (result.truncated) {
115332
+ await writeLine(ctx, import_picocolors7.default.dim("… some patches omitted (response size limit)"));
115205
115333
  }
115206
115334
  });
115207
115335
  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) => {
@@ -115317,7 +115445,13 @@ function registerEnvCommands(cli, ctx) {
115317
115445
  // src/commands/export.ts
115318
115446
  var import_picocolors9 = __toESM(require_picocolors(), 1);
115319
115447
  function registerExportCommands(cli, ctx) {
115320
- 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) => {
115448
+ 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) => {
115449
+ if (options.git) {
115450
+ const result2 = await exportGitBundle(ctx, { app, out: options.out });
115451
+ await writeLine(ctx, `${import_picocolors9.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
115452
+ await writeLine(ctx, import_picocolors9.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
115453
+ return;
115454
+ }
115321
115455
  const result = await exportApp(ctx, {
115322
115456
  app,
115323
115457
  out: options.out,
@@ -115852,7 +115986,7 @@ async function renderError(stderr, error48) {
115852
115986
  // package.json
115853
115987
  var package_default = {
115854
115988
  name: "percher",
115855
- version: "0.5.2",
115989
+ version: "0.5.4",
115856
115990
  description: "CLI for the Percher hosting platform — publish apps with one command",
115857
115991
  type: "module",
115858
115992
  bin: {
@@ -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"}