percher 0.5.10 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/percher.js
CHANGED
|
@@ -90873,6 +90873,46 @@ var CAC = class extends EventTarget {
|
|
|
90873
90873
|
};
|
|
90874
90874
|
var cac = (name = "") => new CAC(name);
|
|
90875
90875
|
|
|
90876
|
+
// ../shared/dist/app-name.js
|
|
90877
|
+
var APP_NAME_RE = /^[a-z][a-z0-9-]{2,39}$/;
|
|
90878
|
+
// ../shared/dist/limits.js
|
|
90879
|
+
var MAX_TARBALL_BYTES = 500 * 1024 * 1024;
|
|
90880
|
+
var MAX_INLINE_PUBLISH_BYTES = 25 * 1024 * 1024;
|
|
90881
|
+
// ../shared/dist/logs.js
|
|
90882
|
+
var MAX_LOG_SEARCH_LENGTH = 256;
|
|
90883
|
+
// ../shared/dist/platform-env.js
|
|
90884
|
+
var PERCHER_INJECTED_ENV_KEYS = new Set([
|
|
90885
|
+
"NODE_ENV",
|
|
90886
|
+
"PORT",
|
|
90887
|
+
"DATABASE_URL",
|
|
90888
|
+
"POCKETBASE_URL",
|
|
90889
|
+
"POCKETBASE_ADMIN_EMAIL",
|
|
90890
|
+
"POCKETBASE_ADMIN_PASSWORD",
|
|
90891
|
+
"PERCHER_APP_NAME",
|
|
90892
|
+
"PERCHER_APP_URL",
|
|
90893
|
+
"HTTP_PROXY",
|
|
90894
|
+
"HTTPS_PROXY",
|
|
90895
|
+
"NO_PROXY",
|
|
90896
|
+
"NODE_USE_ENV_PROXY"
|
|
90897
|
+
]);
|
|
90898
|
+
var PROXY_LOWERCASE_ALIASES = new Set([
|
|
90899
|
+
"http_proxy",
|
|
90900
|
+
"https_proxy",
|
|
90901
|
+
"no_proxy"
|
|
90902
|
+
]);
|
|
90903
|
+
// ../shared/dist/runtime.js
|
|
90904
|
+
var DEFAULT_NODE_VERSION_MAJOR = "24";
|
|
90905
|
+
var DEFAULT_NODE_VERSION_LABEL = `${DEFAULT_NODE_VERSION_MAJOR} LTS`;
|
|
90906
|
+
// ../shared/dist/timeouts.js
|
|
90907
|
+
var TIMEOUTS = {
|
|
90908
|
+
workerBuildSubprocess: 10 * 60000,
|
|
90909
|
+
apiToWorkerBuildFetch: 11 * 60000,
|
|
90910
|
+
clientPublishPoll: 12 * 60000,
|
|
90911
|
+
workerHealthcheck: 5000,
|
|
90912
|
+
watchdogList: 15000,
|
|
90913
|
+
mcpWaitDeployDefault: 30000,
|
|
90914
|
+
mcpWaitDeployMax: 12 * 60000
|
|
90915
|
+
};
|
|
90876
90916
|
// ../toml/dist/errors.js
|
|
90877
90917
|
class PercherTomlError extends Error {
|
|
90878
90918
|
code;
|
|
@@ -104440,45 +104480,6 @@ function date4(params) {
|
|
|
104440
104480
|
|
|
104441
104481
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
104442
104482
|
config(en_default());
|
|
104443
|
-
// ../shared/dist/app-name.js
|
|
104444
|
-
var APP_NAME_RE = /^[a-z][a-z0-9-]{2,39}$/;
|
|
104445
|
-
// ../shared/dist/limits.js
|
|
104446
|
-
var MAX_TARBALL_BYTES = 500 * 1024 * 1024;
|
|
104447
|
-
// ../shared/dist/logs.js
|
|
104448
|
-
var MAX_LOG_SEARCH_LENGTH = 256;
|
|
104449
|
-
// ../shared/dist/platform-env.js
|
|
104450
|
-
var PERCHER_INJECTED_ENV_KEYS = new Set([
|
|
104451
|
-
"NODE_ENV",
|
|
104452
|
-
"PORT",
|
|
104453
|
-
"DATABASE_URL",
|
|
104454
|
-
"POCKETBASE_URL",
|
|
104455
|
-
"POCKETBASE_ADMIN_EMAIL",
|
|
104456
|
-
"POCKETBASE_ADMIN_PASSWORD",
|
|
104457
|
-
"PERCHER_APP_NAME",
|
|
104458
|
-
"PERCHER_APP_URL",
|
|
104459
|
-
"HTTP_PROXY",
|
|
104460
|
-
"HTTPS_PROXY",
|
|
104461
|
-
"NO_PROXY",
|
|
104462
|
-
"NODE_USE_ENV_PROXY"
|
|
104463
|
-
]);
|
|
104464
|
-
var PROXY_LOWERCASE_ALIASES = new Set([
|
|
104465
|
-
"http_proxy",
|
|
104466
|
-
"https_proxy",
|
|
104467
|
-
"no_proxy"
|
|
104468
|
-
]);
|
|
104469
|
-
// ../shared/dist/runtime.js
|
|
104470
|
-
var DEFAULT_NODE_VERSION_MAJOR = "24";
|
|
104471
|
-
var DEFAULT_NODE_VERSION_LABEL = `${DEFAULT_NODE_VERSION_MAJOR} LTS`;
|
|
104472
|
-
// ../shared/dist/timeouts.js
|
|
104473
|
-
var TIMEOUTS = {
|
|
104474
|
-
workerBuildSubprocess: 10 * 60000,
|
|
104475
|
-
apiToWorkerBuildFetch: 11 * 60000,
|
|
104476
|
-
clientPublishPoll: 12 * 60000,
|
|
104477
|
-
workerHealthcheck: 5000,
|
|
104478
|
-
watchdogList: 15000,
|
|
104479
|
-
mcpWaitDeployDefault: 30000,
|
|
104480
|
-
mcpWaitDeployMax: 12 * 60000
|
|
104481
|
-
};
|
|
104482
104483
|
// ../toml/dist/schema.js
|
|
104483
104484
|
var appSchema = exports_external.object({
|
|
104484
104485
|
name: exports_external.string().regex(APP_NAME_RE, "must be 3–40 chars, lowercase, start with a letter, [a-z0-9-] only"),
|
|
@@ -105963,6 +105964,12 @@ class AccountResource {
|
|
|
105963
105964
|
revokeToken(id) {
|
|
105964
105965
|
return this.http.delete(`/account/tokens/${encodeURIComponent(id)}`);
|
|
105965
105966
|
}
|
|
105967
|
+
listOAuthGrants() {
|
|
105968
|
+
return this.http.get("/account/oauth-grants");
|
|
105969
|
+
}
|
|
105970
|
+
revokeOAuthGrant(grantId) {
|
|
105971
|
+
return this.http.delete(`/account/oauth-grants/${encodeURIComponent(grantId)}`);
|
|
105972
|
+
}
|
|
105966
105973
|
exportData() {
|
|
105967
105974
|
return this.http.get("/account/export");
|
|
105968
105975
|
}
|
|
@@ -106058,12 +106065,32 @@ class AppsResource {
|
|
|
106058
106065
|
}
|
|
106059
106066
|
return this.http.postBinary(`/apps/${encodeURIComponent(id)}/deploys${qs ? `?${qs}` : ""}`, input.tarball, headers);
|
|
106060
106067
|
}
|
|
106068
|
+
publishInline(input) {
|
|
106069
|
+
const params = new URLSearchParams;
|
|
106070
|
+
params.set("name", input.name);
|
|
106071
|
+
params.set("runtime", input.runtime);
|
|
106072
|
+
if (input.framework)
|
|
106073
|
+
params.set("framework", input.framework);
|
|
106074
|
+
if (input.type === "preview")
|
|
106075
|
+
params.set("type", "preview");
|
|
106076
|
+
if (input.note)
|
|
106077
|
+
params.set("note", input.note);
|
|
106078
|
+
if (input.noCache)
|
|
106079
|
+
params.set("noCache", "1");
|
|
106080
|
+
const headers = {};
|
|
106081
|
+
if (input.tarballHash)
|
|
106082
|
+
headers["X-Tarball-Hash"] = input.tarballHash;
|
|
106083
|
+
return this.http.postBinary(`/apps/publish?${params.toString()}`, input.tarball, headers);
|
|
106084
|
+
}
|
|
106061
106085
|
promotePreview(appRef, deployId) {
|
|
106062
106086
|
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/${encodeURIComponent(deployId)}/promote`, {});
|
|
106063
106087
|
}
|
|
106064
106088
|
rerunDeploy(appRef, deployId) {
|
|
106065
106089
|
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/${encodeURIComponent(deployId)}/rerun`, {});
|
|
106066
106090
|
}
|
|
106091
|
+
deployLatestCommit(appRef, input) {
|
|
106092
|
+
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/from-latest-commit`, input ?? {});
|
|
106093
|
+
}
|
|
106067
106094
|
setCrashAnalysisEnabled(appRef, enabled) {
|
|
106068
106095
|
return this.http.put(`/apps/${encodeURIComponent(appRef)}/crash-analysis`, {
|
|
106069
106096
|
crashAnalysisEnabled: enabled
|
|
@@ -106702,9 +106729,10 @@ class PercherClient {
|
|
|
106702
106729
|
invites;
|
|
106703
106730
|
transfers;
|
|
106704
106731
|
constructor(options = {}) {
|
|
106705
|
-
const
|
|
106706
|
-
|
|
106707
|
-
this.
|
|
106732
|
+
const bothExplicit = options.apiUrl !== undefined && options.token !== undefined;
|
|
106733
|
+
const defaults = bothExplicit ? undefined : loadConfig();
|
|
106734
|
+
this.apiUrl = options.apiUrl ?? defaults?.apiUrl ?? "";
|
|
106735
|
+
this.token = options.token !== undefined ? options.token : defaults?.token ?? null;
|
|
106708
106736
|
this.sessionId = options.sessionId;
|
|
106709
106737
|
const http = new Http({
|
|
106710
106738
|
apiUrl: this.apiUrl,
|
|
@@ -106988,427 +107016,278 @@ async function deleteApp(ctx, input) {
|
|
|
106988
107016
|
}
|
|
106989
107017
|
await ctx.client.apps.delete(name);
|
|
106990
107018
|
}
|
|
106991
|
-
// ../core/dist/
|
|
106992
|
-
|
|
106993
|
-
|
|
106994
|
-
|
|
106995
|
-
|
|
106996
|
-
|
|
106997
|
-
|
|
106998
|
-
|
|
106999
|
-
|
|
107000
|
-
|
|
107001
|
-
|
|
107002
|
-
ctx.addIssue({
|
|
107003
|
-
code: exports_external.ZodIssueCode.custom,
|
|
107004
|
-
message: "either `id` or `latestFailed: true` must be provided"
|
|
107005
|
-
});
|
|
107006
|
-
}
|
|
107007
|
-
});
|
|
107008
|
-
function resolveAppName(ctx, app) {
|
|
107009
|
-
const name = app ?? readPercherTomlAppName(ctx.cwd);
|
|
107010
|
-
if (!name) {
|
|
107011
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107012
|
-
code: "deploys.no-app",
|
|
107013
|
-
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
107014
|
-
});
|
|
107019
|
+
// ../core/dist/poll-deployment.js
|
|
107020
|
+
async function pollDeployment(opts) {
|
|
107021
|
+
const terminal = opts.terminal ?? ["live", "failed"];
|
|
107022
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
107023
|
+
const timeoutMs = opts.timeoutMs ?? TIMEOUTS.clientPublishPoll;
|
|
107024
|
+
const maxRetries = opts.maxRetries ?? 5;
|
|
107025
|
+
let deployment = opts.initial;
|
|
107026
|
+
const startedAt = Date.now();
|
|
107027
|
+
const seenEventIds = new Set;
|
|
107028
|
+
if (opts.onEvent) {
|
|
107029
|
+
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
107015
107030
|
}
|
|
107016
|
-
|
|
107017
|
-
|
|
107018
|
-
|
|
107019
|
-
const name = resolveAppName(ctx, input.app);
|
|
107020
|
-
return ctx.client.apps.listDeploys(name, { limit: input.limit ?? 10 });
|
|
107021
|
-
}
|
|
107022
|
-
async function deploysInspect(ctx, input) {
|
|
107023
|
-
const name = resolveAppName(ctx, input.app);
|
|
107024
|
-
let deployId = input.id;
|
|
107025
|
-
if (!deployId && input.latestFailed) {
|
|
107026
|
-
const recent = await ctx.client.apps.listDeploys(name, { limit: 25 });
|
|
107027
|
-
const failed = recent.find((d) => d.status === "failed");
|
|
107028
|
-
if (!failed) {
|
|
107029
|
-
throw new PercherCoreError(`No failed deploys found for "${name}"`, {
|
|
107030
|
-
code: "deploys.no-failed",
|
|
107031
|
-
hint: "Try `percher deploys` to list recent deploys, or omit --latest-failed."
|
|
107032
|
-
});
|
|
107031
|
+
while (!terminal.includes(deployment.status)) {
|
|
107032
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
107033
|
+
opts.onTimeout(deployment);
|
|
107033
107034
|
}
|
|
107034
|
-
|
|
107035
|
-
|
|
107036
|
-
|
|
107037
|
-
|
|
107038
|
-
|
|
107039
|
-
|
|
107040
|
-
|
|
107041
|
-
|
|
107042
|
-
|
|
107043
|
-
|
|
107044
|
-
|
|
107045
|
-
|
|
107046
|
-
|
|
107047
|
-
|
|
107048
|
-
|
|
107049
|
-
|
|
107050
|
-
|
|
107035
|
+
opts.onTick?.(deployment);
|
|
107036
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
107037
|
+
let attempt = 0;
|
|
107038
|
+
let polled = false;
|
|
107039
|
+
while (!polled) {
|
|
107040
|
+
try {
|
|
107041
|
+
deployment = await opts.ctx.client.apps.getDeployment(opts.appId, deployment.id);
|
|
107042
|
+
polled = true;
|
|
107043
|
+
} catch (err) {
|
|
107044
|
+
attempt++;
|
|
107045
|
+
const isNetwork = isTransientNetworkError(err);
|
|
107046
|
+
if (!isNetwork || attempt >= maxRetries) {
|
|
107047
|
+
if (isNetwork) {
|
|
107048
|
+
const msg = err.message ?? String(err);
|
|
107049
|
+
throw new Error(`${msg}
|
|
107050
|
+
|
|
107051
|
+
The deploy may already be live — your network connection dropped mid-poll. Check with: percher deploys list ${opts.appName}`, { cause: err });
|
|
107052
|
+
}
|
|
107053
|
+
throw err;
|
|
107054
|
+
}
|
|
107055
|
+
const backoff = (opts.backoffMs ?? ((n) => 1000 * 2 ** (n - 1)))(attempt);
|
|
107056
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
107057
|
+
}
|
|
107058
|
+
}
|
|
107059
|
+
if (opts.onEvent) {
|
|
107060
|
+
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
107051
107061
|
}
|
|
107052
107062
|
}
|
|
107053
|
-
return
|
|
107063
|
+
return deployment;
|
|
107054
107064
|
}
|
|
107055
|
-
|
|
107056
|
-
|
|
107057
|
-
|
|
107058
|
-
|
|
107059
|
-
|
|
107060
|
-
|
|
107061
|
-
|
|
107062
|
-
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
107063
|
-
import { join as join9, relative as relative3, sep as sep2 } from "node:path";
|
|
107064
|
-
import { Readable } from "node:stream";
|
|
107065
|
-
import { createGzip } from "node:zlib";
|
|
107066
|
-
|
|
107067
|
-
// ../../node_modules/.bun/tar-stream@3.1.8/node_modules/tar-stream/index.js
|
|
107068
|
-
var $extract = require_extract();
|
|
107069
|
-
var $pack = require_pack();
|
|
107070
|
-
|
|
107071
|
-
// ../core/dist/tarball.js
|
|
107072
|
-
var DEFAULT_IGNORE = [
|
|
107073
|
-
"node_modules",
|
|
107074
|
-
".git",
|
|
107075
|
-
"dist",
|
|
107076
|
-
".next",
|
|
107077
|
-
".svelte-kit",
|
|
107078
|
-
"build",
|
|
107079
|
-
"coverage",
|
|
107080
|
-
".env",
|
|
107081
|
-
".env.*",
|
|
107082
|
-
".bun",
|
|
107083
|
-
"*.log",
|
|
107084
|
-
".DS_Store"
|
|
107085
|
-
];
|
|
107086
|
-
function* walk(dir, baseDir, ig) {
|
|
107087
|
-
const entries = readdirSync2(dir);
|
|
107088
|
-
for (const name of entries) {
|
|
107089
|
-
const full = join9(dir, name);
|
|
107090
|
-
const rel = relative3(baseDir, full).split(sep2).join("/");
|
|
107091
|
-
if (ig.ignores(rel))
|
|
107092
|
-
continue;
|
|
107093
|
-
const stats = statSync2(full);
|
|
107094
|
-
if (stats.isDirectory()) {
|
|
107095
|
-
if (ig.ignores(`${rel}/`))
|
|
107065
|
+
async function drainNewEvents(opts, deployId, seenEventIds) {
|
|
107066
|
+
if (!opts.onEvent)
|
|
107067
|
+
return;
|
|
107068
|
+
try {
|
|
107069
|
+
const { events } = await opts.ctx.client.apps.getDeployEvents(opts.appId, deployId);
|
|
107070
|
+
for (const event of events) {
|
|
107071
|
+
if (seenEventIds.has(event.id))
|
|
107096
107072
|
continue;
|
|
107097
|
-
|
|
107098
|
-
|
|
107099
|
-
yield rel;
|
|
107073
|
+
seenEventIds.add(event.id);
|
|
107074
|
+
opts.onEvent(event);
|
|
107100
107075
|
}
|
|
107101
|
-
}
|
|
107076
|
+
} catch {}
|
|
107102
107077
|
}
|
|
107103
|
-
|
|
107104
|
-
|
|
107105
|
-
|
|
107106
|
-
const gitignorePath = join9(options.cwd, ".gitignore");
|
|
107107
|
-
if (existsSync6(gitignorePath)) {
|
|
107108
|
-
ig.add(readFileSync6(gitignorePath, "utf8"));
|
|
107109
|
-
}
|
|
107110
|
-
if (options.extraIgnore)
|
|
107111
|
-
ig.add(options.extraIgnore);
|
|
107112
|
-
const fileBuffers = [];
|
|
107113
|
-
for (const rel of walk(options.cwd, options.cwd, ig)) {
|
|
107114
|
-
const data2 = readFileSync6(join9(options.cwd, rel));
|
|
107115
|
-
fileBuffers.push({ path: rel, size: data2.length, data: data2 });
|
|
107116
|
-
}
|
|
107117
|
-
fileBuffers.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
107118
|
-
const totalBytes = fileBuffers.reduce((sum, f) => sum + f.size, 0);
|
|
107119
|
-
const contentHasher = createHash2("sha256");
|
|
107120
|
-
for (const f of fileBuffers) {
|
|
107121
|
-
contentHasher.update(`PATH:${f.path.length}:${f.path}
|
|
107122
|
-
`);
|
|
107123
|
-
contentHasher.update(`SIZE:${f.size}
|
|
107124
|
-
`);
|
|
107125
|
-
contentHasher.update(f.data);
|
|
107126
|
-
contentHasher.update(`
|
|
107127
|
-
`);
|
|
107128
|
-
}
|
|
107129
|
-
const contentHash = contentHasher.digest("hex");
|
|
107130
|
-
const pack = $pack();
|
|
107131
|
-
(async () => {
|
|
107132
|
-
for (const file2 of fileBuffers) {
|
|
107133
|
-
await new Promise((resolve7, reject) => {
|
|
107134
|
-
pack.entry({ name: file2.path, size: file2.data.length }, file2.data, (err) => err ? reject(err) : resolve7());
|
|
107135
|
-
});
|
|
107136
|
-
}
|
|
107137
|
-
pack.finalize();
|
|
107138
|
-
})();
|
|
107139
|
-
const gzip = createGzip();
|
|
107140
|
-
const nodeStream = pack.pipe(gzip);
|
|
107141
|
-
const webStream = Readable.toWeb(nodeStream);
|
|
107142
|
-
const topFiles = [...fileBuffers].sort((a, b) => b.size - a.size).slice(0, 5).map((f) => ({ path: f.path, size: f.size }));
|
|
107078
|
+
|
|
107079
|
+
// ../core/dist/recovery.js
|
|
107080
|
+
function recoveryNone(opts = {}) {
|
|
107143
107081
|
return {
|
|
107144
|
-
|
|
107145
|
-
|
|
107146
|
-
|
|
107147
|
-
|
|
107148
|
-
contentHash
|
|
107082
|
+
retryable: false,
|
|
107083
|
+
nextAction: "none",
|
|
107084
|
+
reasonCode: opts.reasonCode ?? "none",
|
|
107085
|
+
...opts.url !== undefined ? { url: opts.url } : {}
|
|
107149
107086
|
};
|
|
107150
107087
|
}
|
|
107151
|
-
|
|
107152
|
-
// ../core/dist/watcher.js
|
|
107153
|
-
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
107154
|
-
import { existsSync as existsSync7, readFileSync as readFileSync7, watch } from "node:fs";
|
|
107155
|
-
import { join as join10, sep as sep3 } from "node:path";
|
|
107156
|
-
var DEFAULT_IGNORE2 = [
|
|
107157
|
-
"node_modules",
|
|
107158
|
-
".git",
|
|
107159
|
-
"dist",
|
|
107160
|
-
".next",
|
|
107161
|
-
".svelte-kit",
|
|
107162
|
-
"build",
|
|
107163
|
-
"coverage",
|
|
107164
|
-
".env",
|
|
107165
|
-
".env.*",
|
|
107166
|
-
".bun",
|
|
107167
|
-
"*.log",
|
|
107168
|
-
".DS_Store",
|
|
107169
|
-
"__pycache__"
|
|
107170
|
-
];
|
|
107171
|
-
function createWatcher(options) {
|
|
107172
|
-
const debounceMs = options.debounceMs ?? 500;
|
|
107173
|
-
const ig = import_ignore2.default();
|
|
107174
|
-
ig.add(DEFAULT_IGNORE2);
|
|
107175
|
-
const gitignorePath = join10(options.cwd, ".gitignore");
|
|
107176
|
-
if (existsSync7(gitignorePath)) {
|
|
107177
|
-
ig.add(readFileSync7(gitignorePath, "utf8"));
|
|
107178
|
-
}
|
|
107179
|
-
if (options.extraIgnore)
|
|
107180
|
-
ig.add(options.extraIgnore);
|
|
107181
|
-
let timer = null;
|
|
107182
|
-
let pending = new Set;
|
|
107183
|
-
const fsWatcher = watch(options.cwd, { recursive: true }, (_event, filename) => {
|
|
107184
|
-
if (!filename)
|
|
107185
|
-
return;
|
|
107186
|
-
const rel = filename.split(sep3).join("/");
|
|
107187
|
-
if (ig.ignores(rel))
|
|
107188
|
-
return;
|
|
107189
|
-
pending.add(rel);
|
|
107190
|
-
if (timer)
|
|
107191
|
-
clearTimeout(timer);
|
|
107192
|
-
timer = setTimeout(() => {
|
|
107193
|
-
const files = [...pending];
|
|
107194
|
-
pending = new Set;
|
|
107195
|
-
timer = null;
|
|
107196
|
-
if (files.length > 0) {
|
|
107197
|
-
options.onChange(files);
|
|
107198
|
-
}
|
|
107199
|
-
}, debounceMs);
|
|
107200
|
-
});
|
|
107088
|
+
function recoveryLogin(opts = {}) {
|
|
107201
107089
|
return {
|
|
107202
|
-
|
|
107203
|
-
|
|
107204
|
-
|
|
107205
|
-
|
|
107206
|
-
|
|
107090
|
+
retryable: false,
|
|
107091
|
+
nextAction: "open_login",
|
|
107092
|
+
suggestedTool: "percher_login",
|
|
107093
|
+
args: {},
|
|
107094
|
+
reasonCode: opts.reasonCode ?? "auth_required"
|
|
107207
107095
|
};
|
|
107208
107096
|
}
|
|
107209
|
-
|
|
107210
|
-
|
|
107211
|
-
|
|
107212
|
-
|
|
107213
|
-
|
|
107214
|
-
|
|
107215
|
-
|
|
107216
|
-
|
|
107217
|
-
|
|
107218
|
-
|
|
107219
|
-
|
|
107220
|
-
|
|
107221
|
-
}
|
|
107222
|
-
const config2 = await parseFile(tomlPath);
|
|
107223
|
-
if (appOverride)
|
|
107224
|
-
return { ...config2, app: { ...config2.app, name: appOverride } };
|
|
107225
|
-
return config2;
|
|
107097
|
+
function recoveryWait(opts) {
|
|
107098
|
+
return {
|
|
107099
|
+
retryable: false,
|
|
107100
|
+
nextAction: "wait_deploy",
|
|
107101
|
+
suggestedTool: "percher_wait_for_deploy",
|
|
107102
|
+
args: {
|
|
107103
|
+
app: opts.app,
|
|
107104
|
+
deployId: opts.deployId,
|
|
107105
|
+
timeoutSeconds: opts.timeoutSeconds ?? TIMEOUTS.mcpWaitDeployDefault / 1000
|
|
107106
|
+
},
|
|
107107
|
+
reasonCode: opts.reasonCode
|
|
107108
|
+
};
|
|
107226
107109
|
}
|
|
107227
|
-
|
|
107228
|
-
|
|
107229
|
-
|
|
107230
|
-
|
|
107231
|
-
|
|
107232
|
-
|
|
107233
|
-
|
|
107234
|
-
|
|
107235
|
-
|
|
107236
|
-
|
|
107237
|
-
|
|
107238
|
-
|
|
107239
|
-
|
|
107110
|
+
function recoveryDoctor(opts) {
|
|
107111
|
+
const args = {};
|
|
107112
|
+
if (opts.app !== undefined)
|
|
107113
|
+
args.app = opts.app;
|
|
107114
|
+
if (opts.deployId !== undefined)
|
|
107115
|
+
args.deployId = opts.deployId;
|
|
107116
|
+
if (opts.mode !== undefined)
|
|
107117
|
+
args.mode = opts.mode;
|
|
107118
|
+
return {
|
|
107119
|
+
retryable: false,
|
|
107120
|
+
nextAction: "run_doctor",
|
|
107121
|
+
suggestedTool: "percher_doctor",
|
|
107122
|
+
args,
|
|
107123
|
+
reasonCode: opts.reasonCode,
|
|
107124
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107125
|
+
};
|
|
107240
107126
|
}
|
|
107241
|
-
|
|
107242
|
-
const
|
|
107243
|
-
|
|
107244
|
-
|
|
107245
|
-
|
|
107246
|
-
|
|
107247
|
-
|
|
107248
|
-
|
|
107249
|
-
|
|
107250
|
-
|
|
107251
|
-
|
|
107252
|
-
|
|
107253
|
-
|
|
107254
|
-
const pollInterval = 2000;
|
|
107255
|
-
let lastStatus = "";
|
|
107256
|
-
while (deployment.status !== "live" && deployment.status !== "failed") {
|
|
107257
|
-
if (Date.now() - startedAt > timeoutMs) {
|
|
107258
|
-
await callbacks.writeLine("Deploy timed out");
|
|
107259
|
-
return { success: false, appId: app.id };
|
|
107260
|
-
}
|
|
107261
|
-
if (callbacks.signal.aborted)
|
|
107262
|
-
return { success: false, appId: app.id };
|
|
107263
|
-
if (deployment.status !== lastStatus) {
|
|
107264
|
-
lastStatus = deployment.status;
|
|
107265
|
-
await callbacks.writeLine(` ${deployment.status}...`);
|
|
107266
|
-
}
|
|
107267
|
-
await new Promise((r) => setTimeout(r, pollInterval));
|
|
107268
|
-
deployment = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
107269
|
-
}
|
|
107270
|
-
if (deployment.status === "failed") {
|
|
107271
|
-
try {
|
|
107272
|
-
const insp = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
107273
|
-
if (insp.errorMessage) {
|
|
107274
|
-
await callbacks.writeLine("Deploy failed:");
|
|
107275
|
-
for (const line of insp.errorMessage.split(`
|
|
107276
|
-
`).slice(0, 30)) {
|
|
107277
|
-
await callbacks.writeLine(` ${line}`);
|
|
107278
|
-
}
|
|
107279
|
-
} else {
|
|
107280
|
-
await callbacks.writeLine("Deploy failed.");
|
|
107281
|
-
}
|
|
107282
|
-
} catch {
|
|
107283
|
-
await callbacks.writeLine("Deploy failed.");
|
|
107284
|
-
}
|
|
107285
|
-
return { success: false, appId: app.id };
|
|
107286
|
-
}
|
|
107287
|
-
await callbacks.writeLine(`Live at ${deployment.url ?? ""}`);
|
|
107288
|
-
return { success: true, appId: app.id };
|
|
107127
|
+
function recoveryEnv(opts) {
|
|
107128
|
+
const args = {};
|
|
107129
|
+
if (opts.app !== undefined)
|
|
107130
|
+
args.app = opts.app;
|
|
107131
|
+
if (opts.keys.length > 0)
|
|
107132
|
+
args.keys = opts.keys;
|
|
107133
|
+
return {
|
|
107134
|
+
retryable: false,
|
|
107135
|
+
nextAction: "set_env_vars",
|
|
107136
|
+
suggestedTool: "percher_env_set",
|
|
107137
|
+
args,
|
|
107138
|
+
reasonCode: opts.reasonCode ?? "missing_env"
|
|
107139
|
+
};
|
|
107289
107140
|
}
|
|
107290
|
-
function
|
|
107291
|
-
|
|
107292
|
-
|
|
107293
|
-
|
|
107141
|
+
function recoveryFixConfig(opts) {
|
|
107142
|
+
return {
|
|
107143
|
+
retryable: false,
|
|
107144
|
+
nextAction: "fix_config",
|
|
107145
|
+
problems: opts.problems,
|
|
107146
|
+
reasonCode: opts.reasonCode ?? "config_invalid"
|
|
107147
|
+
};
|
|
107294
107148
|
}
|
|
107295
|
-
|
|
107296
|
-
|
|
107297
|
-
|
|
107298
|
-
|
|
107299
|
-
|
|
107300
|
-
|
|
107301
|
-
}
|
|
107302
|
-
}
|
|
107303
|
-
if (signal.aborted)
|
|
107304
|
-
return;
|
|
107305
|
-
await callbacks.writeLine(`Log stream error: ${err.message}`);
|
|
107306
|
-
}
|
|
107149
|
+
function recoveryFixProblems(opts) {
|
|
107150
|
+
return {
|
|
107151
|
+
retryable: false,
|
|
107152
|
+
nextAction: "fix_problems",
|
|
107153
|
+
problems: opts.problems,
|
|
107154
|
+
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107155
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107156
|
+
};
|
|
107307
107157
|
}
|
|
107308
|
-
|
|
107309
|
-
|
|
107310
|
-
|
|
107311
|
-
|
|
107312
|
-
|
|
107313
|
-
|
|
107314
|
-
|
|
107315
|
-
|
|
107316
|
-
|
|
107317
|
-
|
|
107318
|
-
|
|
107319
|
-
|
|
107158
|
+
function recoveryRetry(opts) {
|
|
107159
|
+
return {
|
|
107160
|
+
retryable: true,
|
|
107161
|
+
nextAction: "retry",
|
|
107162
|
+
suggestedTool: opts.suggestedTool,
|
|
107163
|
+
args: opts.args,
|
|
107164
|
+
reasonCode: opts.reasonCode
|
|
107165
|
+
};
|
|
107166
|
+
}
|
|
107167
|
+
function recoveryAsk(opts) {
|
|
107168
|
+
if (!opts.prompt || opts.prompt.trim().length === 0) {
|
|
107169
|
+
throw new Error("recoveryAsk requires a non-empty prompt");
|
|
107320
107170
|
}
|
|
107321
|
-
|
|
107322
|
-
|
|
107323
|
-
|
|
107324
|
-
|
|
107325
|
-
|
|
107326
|
-
|
|
107327
|
-
|
|
107328
|
-
|
|
107329
|
-
|
|
107330
|
-
|
|
107331
|
-
|
|
107332
|
-
|
|
107333
|
-
|
|
107334
|
-
|
|
107335
|
-
|
|
107336
|
-
|
|
107337
|
-
|
|
107338
|
-
|
|
107339
|
-
|
|
107340
|
-
|
|
107341
|
-
|
|
107342
|
-
|
|
107171
|
+
return {
|
|
107172
|
+
retryable: opts.retryable ?? false,
|
|
107173
|
+
nextAction: "ask_user",
|
|
107174
|
+
prompt: opts.prompt,
|
|
107175
|
+
...opts.options !== undefined ? { options: opts.options } : {},
|
|
107176
|
+
reasonCode: opts.reasonCode
|
|
107177
|
+
};
|
|
107178
|
+
}
|
|
107179
|
+
function recoveryInspectBuildLog(opts) {
|
|
107180
|
+
const args = opts.deployId ? { id: opts.deployId, app: opts.app } : { latestFailed: true, app: opts.app };
|
|
107181
|
+
return {
|
|
107182
|
+
retryable: false,
|
|
107183
|
+
nextAction: "inspect_build_log",
|
|
107184
|
+
suggestedTool: "percher_deploys_inspect",
|
|
107185
|
+
args,
|
|
107186
|
+
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107187
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107188
|
+
};
|
|
107189
|
+
}
|
|
107190
|
+
var RECOVERY_NONE = recoveryNone();
|
|
107191
|
+
var RECOVERY_NEEDS_LOGIN = recoveryLogin();
|
|
107192
|
+
function buildProblemToRecoveryProblem(p) {
|
|
107193
|
+
const file2 = p.file ?? "";
|
|
107194
|
+
const message = p.hint && p.hint.length > 0 ? `${p.message}
|
|
107195
|
+
Hint: ${p.hint}` : p.message;
|
|
107196
|
+
return {
|
|
107197
|
+
file: file2,
|
|
107198
|
+
...typeof p.line === "number" ? { line: p.line } : {},
|
|
107199
|
+
...typeof p.column === "number" ? { column: p.column } : {},
|
|
107200
|
+
message
|
|
107201
|
+
};
|
|
107202
|
+
}
|
|
107203
|
+
function recoveryFromErrorClass(errorClass, failure = {}) {
|
|
107204
|
+
switch (errorClass) {
|
|
107205
|
+
case "missing_env":
|
|
107206
|
+
return recoveryEnv({
|
|
107207
|
+
keys: failure.missingEnvVars ?? [],
|
|
107208
|
+
reasonCode: "missing_env"
|
|
107209
|
+
});
|
|
107210
|
+
case "infra_unavailable":
|
|
107211
|
+
return recoveryRetry({
|
|
107212
|
+
suggestedTool: "percher_publish",
|
|
107213
|
+
args: failure.publishInput ?? {},
|
|
107214
|
+
reasonCode: "infra_unavailable"
|
|
107215
|
+
});
|
|
107216
|
+
case "platform_at_capacity":
|
|
107217
|
+
return recoveryRetry({
|
|
107218
|
+
suggestedTool: "percher_publish",
|
|
107219
|
+
args: failure.publishInput ?? {},
|
|
107220
|
+
reasonCode: "infra_transient"
|
|
107221
|
+
});
|
|
107222
|
+
case "config_invalid":
|
|
107223
|
+
return recoveryFixConfig({
|
|
107224
|
+
problems: [
|
|
107225
|
+
{
|
|
107226
|
+
file: "percher.toml",
|
|
107227
|
+
message: "percher.toml failed validation — fix the file and re-run."
|
|
107343
107228
|
}
|
|
107344
|
-
|
|
107345
|
-
|
|
107346
|
-
|
|
107347
|
-
|
|
107348
|
-
|
|
107349
|
-
|
|
107229
|
+
],
|
|
107230
|
+
reasonCode: "config_invalid"
|
|
107231
|
+
});
|
|
107232
|
+
case "build_failed":
|
|
107233
|
+
case "missing_dependency":
|
|
107234
|
+
case "health_check_timeout":
|
|
107235
|
+
case "port_mismatch":
|
|
107236
|
+
case "permission_denied":
|
|
107237
|
+
case "runtime_crash":
|
|
107238
|
+
case "oom_killed": {
|
|
107239
|
+
const fileProblems = (failure.problems ?? []).filter((p) => p.file);
|
|
107240
|
+
const reproduceAlt = failure.deployId ? {
|
|
107241
|
+
action: "reproduce_locally",
|
|
107242
|
+
suggestedTool: "percher_reproduce",
|
|
107243
|
+
args: { deployId: failure.deployId, app: failure.appName },
|
|
107244
|
+
when: "after the primary path didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107245
|
+
} : undefined;
|
|
107246
|
+
if (fileProblems.length > 0) {
|
|
107247
|
+
return recoveryFixProblems({
|
|
107248
|
+
problems: fileProblems.map(buildProblemToRecoveryProblem),
|
|
107249
|
+
reasonCode: "build_failed",
|
|
107250
|
+
...reproduceAlt ? {
|
|
107251
|
+
alternativeActions: [
|
|
107252
|
+
{
|
|
107253
|
+
...reproduceAlt,
|
|
107254
|
+
when: "after fixing the file-located problems didn't make the build pass and the user has nixpacks installed locally"
|
|
107255
|
+
}
|
|
107256
|
+
]
|
|
107257
|
+
} : {}
|
|
107258
|
+
});
|
|
107350
107259
|
}
|
|
107351
|
-
|
|
107352
|
-
|
|
107353
|
-
|
|
107354
|
-
|
|
107355
|
-
|
|
107356
|
-
|
|
107260
|
+
const inspectAlt = {
|
|
107261
|
+
action: "inspect_build_log",
|
|
107262
|
+
suggestedTool: "percher_deploys_inspect",
|
|
107263
|
+
args: failure.deployId ? { id: failure.deployId, app: failure.appName } : { latestFailed: true, app: failure.appName },
|
|
107264
|
+
when: "after percher_doctor's recovery didn't yield a fix and you want to read the raw build log directly"
|
|
107265
|
+
};
|
|
107266
|
+
const alternativeActions = [inspectAlt];
|
|
107267
|
+
if (reproduceAlt) {
|
|
107268
|
+
alternativeActions.push({
|
|
107269
|
+
...reproduceAlt,
|
|
107270
|
+
when: "after percher_doctor + percher_deploys_inspect didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107271
|
+
});
|
|
107357
107272
|
}
|
|
107273
|
+
return recoveryDoctor({
|
|
107274
|
+
app: failure.appName,
|
|
107275
|
+
deployId: failure.deployId,
|
|
107276
|
+
mode: "deploy",
|
|
107277
|
+
reasonCode: "build_failed",
|
|
107278
|
+
alternativeActions
|
|
107279
|
+
});
|
|
107358
107280
|
}
|
|
107281
|
+
default:
|
|
107282
|
+
return recoveryAsk({
|
|
107283
|
+
prompt: "Unexpected failure — Percher couldn't classify this error. Try `percher doctor` for diagnostics, or surface the error to the user.",
|
|
107284
|
+
reasonCode: "unknown"
|
|
107285
|
+
});
|
|
107359
107286
|
}
|
|
107360
|
-
const devConfig = config2.dev;
|
|
107361
|
-
const watcher = createWatcher({
|
|
107362
|
-
cwd: ctx.cwd,
|
|
107363
|
-
extraIgnore: devConfig?.ignore,
|
|
107364
|
-
debounceMs: devConfig?.debounce,
|
|
107365
|
-
onChange(files) {
|
|
107366
|
-
handleChange(files);
|
|
107367
|
-
}
|
|
107368
|
-
});
|
|
107369
|
-
try {
|
|
107370
|
-
await new Promise((resolve7) => {
|
|
107371
|
-
if (callbacks.signal.aborted) {
|
|
107372
|
-
resolve7();
|
|
107373
|
-
return;
|
|
107374
|
-
}
|
|
107375
|
-
callbacks.signal.addEventListener("abort", () => resolve7(), { once: true });
|
|
107376
|
-
});
|
|
107377
|
-
} finally {
|
|
107378
|
-
logStream.ctrl?.abort();
|
|
107379
|
-
watcher.close();
|
|
107380
|
-
}
|
|
107381
|
-
}
|
|
107382
|
-
// ../core/dist/commands/diagnose.js
|
|
107383
|
-
var diagnoseInputSchema = exports_external.object({
|
|
107384
|
-
app: exports_external.string().optional(),
|
|
107385
|
-
logLines: exports_external.number().int().min(0).max(200).optional(),
|
|
107386
|
-
recencyHours: exports_external.number().positive().max(24 * 30).optional()
|
|
107387
|
-
});
|
|
107388
|
-
// ../core/dist/commands/diff.js
|
|
107389
|
-
var diffInputSchema = exports_external.object({
|
|
107390
|
-
app: exports_external.string().optional(),
|
|
107391
|
-
base: exports_external.string().optional(),
|
|
107392
|
-
head: exports_external.string().optional(),
|
|
107393
|
-
patch: exports_external.boolean().optional()
|
|
107394
|
-
});
|
|
107395
|
-
async function diff(ctx, input = {}) {
|
|
107396
|
-
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
107397
|
-
if (!name) {
|
|
107398
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107399
|
-
code: "diff.no-app",
|
|
107400
|
-
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
107401
|
-
});
|
|
107402
|
-
}
|
|
107403
|
-
return ctx.client.apps.getChanges(name, {
|
|
107404
|
-
base: input.base,
|
|
107405
|
-
head: input.head,
|
|
107406
|
-
patch: input.patch
|
|
107407
|
-
});
|
|
107408
107287
|
}
|
|
107409
|
-
|
|
107410
|
-
|
|
107411
|
-
import { join as
|
|
107288
|
+
|
|
107289
|
+
// ../core/dist/commands/publish-failure.js
|
|
107290
|
+
import { join as join9 } from "node:path";
|
|
107412
107291
|
|
|
107413
107292
|
// ../core/dist/error-classifier.js
|
|
107414
107293
|
function extractEnvVarsFromLog(combined) {
|
|
@@ -107742,391 +107621,650 @@ ${buildLog ?? ""}`;
|
|
|
107742
107621
|
return null;
|
|
107743
107622
|
}
|
|
107744
107623
|
|
|
107745
|
-
// ../core/dist/
|
|
107746
|
-
|
|
107747
|
-
|
|
107748
|
-
|
|
107749
|
-
|
|
107750
|
-
|
|
107751
|
-
|
|
107752
|
-
|
|
107753
|
-
|
|
107754
|
-
|
|
107755
|
-
|
|
107756
|
-
|
|
107757
|
-
|
|
107758
|
-
|
|
107759
|
-
|
|
107760
|
-
|
|
107761
|
-
|
|
107762
|
-
}
|
|
107763
|
-
|
|
107764
|
-
|
|
107765
|
-
|
|
107766
|
-
|
|
107767
|
-
|
|
107768
|
-
|
|
107769
|
-
|
|
107770
|
-
|
|
107771
|
-
|
|
107772
|
-
|
|
107773
|
-
|
|
107774
|
-
|
|
107775
|
-
|
|
107776
|
-
|
|
107777
|
-
|
|
107778
|
-
|
|
107779
|
-
|
|
107780
|
-
|
|
107781
|
-
|
|
107782
|
-
|
|
107783
|
-
|
|
107784
|
-
|
|
107785
|
-
|
|
107786
|
-
|
|
107787
|
-
|
|
107788
|
-
|
|
107789
|
-
|
|
107790
|
-
|
|
107791
|
-
|
|
107792
|
-
|
|
107793
|
-
|
|
107794
|
-
|
|
107795
|
-
|
|
107796
|
-
|
|
107797
|
-
if (
|
|
107798
|
-
|
|
107799
|
-
|
|
107800
|
-
|
|
107801
|
-
|
|
107802
|
-
suggestedTool: "percher_env_set",
|
|
107803
|
-
args,
|
|
107804
|
-
reasonCode: opts.reasonCode ?? "missing_env"
|
|
107805
|
-
};
|
|
107806
|
-
}
|
|
107807
|
-
function recoveryFixConfig(opts) {
|
|
107624
|
+
// ../core/dist/structured-error-codes.js
|
|
107625
|
+
var MAPPINGS = {
|
|
107626
|
+
WORKER_UNAVAILABLE: {
|
|
107627
|
+
errorClass: "infra_unavailable",
|
|
107628
|
+
phase: "infra",
|
|
107629
|
+
cause: "worker_fetch_failed",
|
|
107630
|
+
title: "Build worker is unreachable",
|
|
107631
|
+
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.",
|
|
107632
|
+
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."
|
|
107633
|
+
},
|
|
107634
|
+
WORKER_UNAVAILABLE_PERSISTENT: {
|
|
107635
|
+
errorClass: "infra_unavailable",
|
|
107636
|
+
phase: "infra",
|
|
107637
|
+
cause: "worker_circuit_breaker_open",
|
|
107638
|
+
title: "Build worker is in a crash loop",
|
|
107639
|
+
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.",
|
|
107640
|
+
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."
|
|
107641
|
+
},
|
|
107642
|
+
BUILD_TIMEOUT: {
|
|
107643
|
+
errorClass: "build_failed",
|
|
107644
|
+
phase: "build",
|
|
107645
|
+
cause: "build_timeout",
|
|
107646
|
+
title: "Build timed out",
|
|
107647
|
+
explanation: () => "The build ran longer than the 10-minute platform limit and was aborted.",
|
|
107648
|
+
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."
|
|
107649
|
+
},
|
|
107650
|
+
BUILD_FAILED: {
|
|
107651
|
+
errorClass: "build_failed",
|
|
107652
|
+
phase: "build",
|
|
107653
|
+
cause: "build_nonzero_exit",
|
|
107654
|
+
title: "Build failed",
|
|
107655
|
+
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.",
|
|
107656
|
+
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."
|
|
107657
|
+
},
|
|
107658
|
+
WORKER_ERROR: {
|
|
107659
|
+
errorClass: "infra_unavailable",
|
|
107660
|
+
phase: "infra",
|
|
107661
|
+
cause: "worker_internal_error",
|
|
107662
|
+
title: "Platform error inside the build worker",
|
|
107663
|
+
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.",
|
|
107664
|
+
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."
|
|
107665
|
+
},
|
|
107666
|
+
DEPLOY_STALLED: {
|
|
107667
|
+
errorClass: "infra_unavailable",
|
|
107668
|
+
phase: "infra",
|
|
107669
|
+
cause: "deploy_stalled",
|
|
107670
|
+
title: "Deploy stalled",
|
|
107671
|
+
explanation: () => "The platform abandoned this deploy — likely because the API or build worker restarted mid-build. Your code is fine; the build never finished.",
|
|
107672
|
+
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."
|
|
107673
|
+
}
|
|
107674
|
+
};
|
|
107675
|
+
function classifyByErrorCode(code, rawMessage) {
|
|
107676
|
+
if (!code)
|
|
107677
|
+
return null;
|
|
107678
|
+
const mapping = MAPPINGS[code];
|
|
107679
|
+
if (!mapping)
|
|
107680
|
+
return null;
|
|
107808
107681
|
return {
|
|
107809
|
-
|
|
107810
|
-
|
|
107811
|
-
|
|
107812
|
-
|
|
107682
|
+
title: mapping.title,
|
|
107683
|
+
explanation: mapping.explanation(rawMessage),
|
|
107684
|
+
suggestion: mapping.suggestion,
|
|
107685
|
+
errorClass: mapping.errorClass,
|
|
107686
|
+
phase: mapping.phase,
|
|
107687
|
+
cause: mapping.cause,
|
|
107688
|
+
relevantFiles: [],
|
|
107689
|
+
missingEnvVars: []
|
|
107813
107690
|
};
|
|
107814
107691
|
}
|
|
107815
|
-
function
|
|
107816
|
-
|
|
107817
|
-
|
|
107818
|
-
|
|
107819
|
-
problems: opts.problems,
|
|
107820
|
-
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107821
|
-
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107822
|
-
};
|
|
107692
|
+
function trimMessage(raw) {
|
|
107693
|
+
const firstLine = raw.split(`
|
|
107694
|
+
`)[0] ?? "";
|
|
107695
|
+
return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
|
|
107823
107696
|
}
|
|
107824
|
-
|
|
107825
|
-
|
|
107826
|
-
|
|
107827
|
-
|
|
107828
|
-
suggestedTool: opts.suggestedTool,
|
|
107829
|
-
args: opts.args,
|
|
107830
|
-
reasonCode: opts.reasonCode
|
|
107831
|
-
};
|
|
107697
|
+
|
|
107698
|
+
// ../core/dist/commands/publish-failure.js
|
|
107699
|
+
function tomlPathFor(cwd) {
|
|
107700
|
+
return join9(cwd, "percher.toml");
|
|
107832
107701
|
}
|
|
107833
|
-
function
|
|
107834
|
-
|
|
107835
|
-
|
|
107702
|
+
async function buildFailureError(ctx, app, deployment, signal) {
|
|
107703
|
+
let buildLogTail;
|
|
107704
|
+
let buildLogFetchFailed = false;
|
|
107705
|
+
let fullLog = "";
|
|
107706
|
+
try {
|
|
107707
|
+
fullLog = await ctx.client.apps.getBuildLog(app.id, deployment.id, { signal });
|
|
107708
|
+
if (!fullLog && !signal?.aborted) {
|
|
107709
|
+
await abortableSleep(1500, signal);
|
|
107710
|
+
if (!signal?.aborted) {
|
|
107711
|
+
fullLog = await ctx.client.apps.getBuildLog(app.id, deployment.id, { signal });
|
|
107712
|
+
}
|
|
107713
|
+
}
|
|
107714
|
+
if (fullLog) {
|
|
107715
|
+
const lines = fullLog.split(`
|
|
107716
|
+
`);
|
|
107717
|
+
buildLogTail = lines.slice(-50).join(`
|
|
107718
|
+
`);
|
|
107719
|
+
}
|
|
107720
|
+
} catch {
|
|
107721
|
+
buildLogFetchFailed = true;
|
|
107722
|
+
}
|
|
107723
|
+
const errorMessage = deployment.errorMessage ?? "";
|
|
107724
|
+
const classified = classifyByErrorCode(deployment.errorCode, errorMessage) ?? classifyError(errorMessage || "Deployment failed", fullLog);
|
|
107725
|
+
const rawErrorMessage = errorMessage && !buildLogTail && !classified?.explanation.includes(errorMessage) ? errorMessage : undefined;
|
|
107726
|
+
const problems = deployment.problems && deployment.problems.length > 0 ? deployment.problems : undefined;
|
|
107727
|
+
if (classified) {
|
|
107728
|
+
return {
|
|
107729
|
+
title: classified.title,
|
|
107730
|
+
explanation: classified.explanation,
|
|
107731
|
+
suggestion: classified.suggestion,
|
|
107732
|
+
buildLogTail,
|
|
107733
|
+
rawErrorMessage,
|
|
107734
|
+
errorClass: classified.errorClass,
|
|
107735
|
+
phase: classified.phase,
|
|
107736
|
+
cause: classified.cause,
|
|
107737
|
+
relevantFiles: classified.relevantFiles,
|
|
107738
|
+
missingEnvVars: classified.missingEnvVars,
|
|
107739
|
+
problems
|
|
107740
|
+
};
|
|
107836
107741
|
}
|
|
107837
107742
|
return {
|
|
107838
|
-
|
|
107839
|
-
|
|
107840
|
-
|
|
107841
|
-
|
|
107842
|
-
|
|
107743
|
+
title: "Deployment failed",
|
|
107744
|
+
explanation: errorMessage ? `The deployment failed: ${errorMessage}` : "The deployment failed. See the build log for details.",
|
|
107745
|
+
suggestion: buildLogTail ? "Check the build log below for errors. Run your build command locally to reproduce, fix, and publish again." : buildLogFetchFailed ? `The deploy failed and we could not retrieve the build log (network/API error after retries). The log itself may exist on the server — fetch it directly with: percher logs ${app.name} --build ${deployment.id}` : errorMessage ? `The deploy failed with: ${errorMessage.split(`
|
|
107746
|
+
`)[0]}. The build log column is empty — try \`percher logs ${app.name} --build ${deployment.id}\` to fetch it directly. If it remains empty, run \`percher deploys inspect ${deployment.id}\` for full details.` : `No build log was produced — the failure happened before the build started. This usually means a Percher infra issue. Retry once, and if it persists, fetch the deploy details with: percher deploys inspect ${deployment.id}`,
|
|
107747
|
+
buildLogTail,
|
|
107748
|
+
errorClass: "build_failed",
|
|
107749
|
+
phase: "build",
|
|
107750
|
+
cause: "unknown",
|
|
107751
|
+
relevantFiles: [],
|
|
107752
|
+
missingEnvVars: [],
|
|
107753
|
+
problems
|
|
107843
107754
|
};
|
|
107844
107755
|
}
|
|
107845
|
-
function
|
|
107846
|
-
const
|
|
107756
|
+
async function classifyDeploymentFailure(opts) {
|
|
107757
|
+
const { ctx, app, deployment, publishInput } = opts;
|
|
107758
|
+
const error48 = await buildFailureError(ctx, app, deployment, opts.signal);
|
|
107759
|
+
const recovery = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
|
|
107760
|
+
missingEnvVars: error48.missingEnvVars,
|
|
107761
|
+
deployId: deployment.id,
|
|
107762
|
+
appName: app.name,
|
|
107763
|
+
publishInput,
|
|
107764
|
+
problems: error48.problems
|
|
107765
|
+
}) : recoveryAsk({
|
|
107766
|
+
prompt: `${app.name} deploy ${deployment.id} failed: ${error48.title}. ${error48.suggestion}`.trim(),
|
|
107767
|
+
reasonCode: "build_failed"
|
|
107768
|
+
});
|
|
107847
107769
|
return {
|
|
107848
|
-
|
|
107849
|
-
|
|
107850
|
-
|
|
107851
|
-
args,
|
|
107852
|
-
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107853
|
-
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107770
|
+
error: error48,
|
|
107771
|
+
recovery,
|
|
107772
|
+
summary: `${error48.title} (${app.name}, deploy ${deployment.id}).`
|
|
107854
107773
|
};
|
|
107855
107774
|
}
|
|
107856
|
-
|
|
107857
|
-
|
|
107858
|
-
|
|
107859
|
-
|
|
107860
|
-
|
|
107861
|
-
|
|
107775
|
+
async function buildFailureResult(opts) {
|
|
107776
|
+
const { ctx, app, deployment, tarball, input } = opts;
|
|
107777
|
+
const { error: error48, recovery, summary } = await classifyDeploymentFailure({
|
|
107778
|
+
ctx,
|
|
107779
|
+
app,
|
|
107780
|
+
deployment,
|
|
107781
|
+
publishInput: input
|
|
107782
|
+
});
|
|
107862
107783
|
return {
|
|
107863
|
-
|
|
107864
|
-
|
|
107865
|
-
|
|
107866
|
-
|
|
107784
|
+
status: "failed",
|
|
107785
|
+
app,
|
|
107786
|
+
deployment,
|
|
107787
|
+
fileCount: tarball.fileCount,
|
|
107788
|
+
bytes: tarball.bytes,
|
|
107789
|
+
error: error48,
|
|
107790
|
+
recovery,
|
|
107791
|
+
summary,
|
|
107792
|
+
configPath: tomlPathFor(ctx.cwd),
|
|
107793
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
107867
107794
|
};
|
|
107868
107795
|
}
|
|
107869
|
-
|
|
107870
|
-
|
|
107871
|
-
|
|
107872
|
-
|
|
107873
|
-
|
|
107874
|
-
|
|
107875
|
-
|
|
107876
|
-
|
|
107877
|
-
|
|
107878
|
-
|
|
107879
|
-
|
|
107880
|
-
|
|
107881
|
-
|
|
107882
|
-
|
|
107883
|
-
|
|
107884
|
-
|
|
107885
|
-
|
|
107886
|
-
|
|
107887
|
-
|
|
107888
|
-
|
|
107889
|
-
|
|
107890
|
-
|
|
107891
|
-
|
|
107892
|
-
|
|
107893
|
-
|
|
107894
|
-
|
|
107895
|
-
|
|
107896
|
-
|
|
107796
|
+
|
|
107797
|
+
// ../core/dist/commands/deploy-repo.js
|
|
107798
|
+
var deployRepoInputSchema = exports_external.object({
|
|
107799
|
+
app: exports_external.string().describe("App name or id (required — hosted has no cwd)."),
|
|
107800
|
+
branch: exports_external.string().optional().describe("Branch to deploy. Defaults to the app's tracked branch."),
|
|
107801
|
+
note: exports_external.string().optional().describe("Optional note recorded against the deploy.")
|
|
107802
|
+
});
|
|
107803
|
+
// ../core/dist/commands/deploys.js
|
|
107804
|
+
var deploysListInputSchema = exports_external.object({
|
|
107805
|
+
app: exports_external.string().optional(),
|
|
107806
|
+
limit: exports_external.number().int().positive().max(100).optional()
|
|
107807
|
+
});
|
|
107808
|
+
var deploysInspectInputSchema = exports_external.object({
|
|
107809
|
+
app: exports_external.string().optional(),
|
|
107810
|
+
id: exports_external.string().optional(),
|
|
107811
|
+
latestFailed: exports_external.boolean().optional()
|
|
107812
|
+
}).superRefine((val, ctx) => {
|
|
107813
|
+
if (!val.id && !val.latestFailed) {
|
|
107814
|
+
ctx.addIssue({
|
|
107815
|
+
code: exports_external.ZodIssueCode.custom,
|
|
107816
|
+
message: "either `id` or `latestFailed: true` must be provided"
|
|
107817
|
+
});
|
|
107818
|
+
}
|
|
107819
|
+
});
|
|
107820
|
+
function resolveAppName(ctx, app) {
|
|
107821
|
+
const name = app ?? readPercherTomlAppName(ctx.cwd);
|
|
107822
|
+
if (!name) {
|
|
107823
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107824
|
+
code: "deploys.no-app",
|
|
107825
|
+
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
107826
|
+
});
|
|
107827
|
+
}
|
|
107828
|
+
return name;
|
|
107829
|
+
}
|
|
107830
|
+
async function deploysList(ctx, input = {}) {
|
|
107831
|
+
const name = resolveAppName(ctx, input.app);
|
|
107832
|
+
return ctx.client.apps.listDeploys(name, { limit: input.limit ?? 10 });
|
|
107833
|
+
}
|
|
107834
|
+
async function deploysInspect(ctx, input) {
|
|
107835
|
+
const name = resolveAppName(ctx, input.app);
|
|
107836
|
+
let deployId = input.id;
|
|
107837
|
+
if (!deployId && input.latestFailed) {
|
|
107838
|
+
const recent = await ctx.client.apps.listDeploys(name, { limit: 25 });
|
|
107839
|
+
const failed = recent.find((d) => d.status === "failed");
|
|
107840
|
+
if (!failed) {
|
|
107841
|
+
throw new PercherCoreError(`No failed deploys found for "${name}"`, {
|
|
107842
|
+
code: "deploys.no-failed",
|
|
107843
|
+
hint: "Try `percher deploys` to list recent deploys, or omit --latest-failed."
|
|
107897
107844
|
});
|
|
107898
|
-
|
|
107899
|
-
|
|
107900
|
-
|
|
107901
|
-
|
|
107902
|
-
|
|
107903
|
-
|
|
107904
|
-
|
|
107905
|
-
|
|
107906
|
-
|
|
107907
|
-
|
|
107908
|
-
|
|
107909
|
-
|
|
107910
|
-
|
|
107911
|
-
|
|
107912
|
-
|
|
107913
|
-
|
|
107914
|
-
|
|
107915
|
-
|
|
107916
|
-
|
|
107917
|
-
|
|
107918
|
-
|
|
107919
|
-
|
|
107920
|
-
|
|
107921
|
-
|
|
107922
|
-
|
|
107923
|
-
|
|
107924
|
-
|
|
107925
|
-
|
|
107926
|
-
|
|
107927
|
-
|
|
107928
|
-
|
|
107929
|
-
|
|
107930
|
-
|
|
107931
|
-
|
|
107932
|
-
|
|
107933
|
-
|
|
107934
|
-
|
|
107935
|
-
|
|
107936
|
-
|
|
107937
|
-
|
|
107938
|
-
|
|
107939
|
-
|
|
107940
|
-
|
|
107941
|
-
|
|
107942
|
-
|
|
107943
|
-
|
|
107944
|
-
|
|
107845
|
+
}
|
|
107846
|
+
deployId = failed.id;
|
|
107847
|
+
}
|
|
107848
|
+
if (!deployId) {
|
|
107849
|
+
throw new PercherCoreError("Deploy ID is required", {
|
|
107850
|
+
code: "deploys.no-id"
|
|
107851
|
+
});
|
|
107852
|
+
}
|
|
107853
|
+
const deploy = await ctx.client.apps.getDeployment(name, deployId);
|
|
107854
|
+
let buildLog = null;
|
|
107855
|
+
try {
|
|
107856
|
+
const log = await ctx.client.apps.getBuildLog(name, deployId);
|
|
107857
|
+
buildLog = log.length > 0 ? log : null;
|
|
107858
|
+
} catch (err) {
|
|
107859
|
+
if (err instanceof PercherApiError && err.status === 404) {
|
|
107860
|
+
buildLog = null;
|
|
107861
|
+
} else {
|
|
107862
|
+
throw err;
|
|
107863
|
+
}
|
|
107864
|
+
}
|
|
107865
|
+
return { deploy, buildLog };
|
|
107866
|
+
}
|
|
107867
|
+
// ../core/dist/commands/dev.js
|
|
107868
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
107869
|
+
import { join as join12 } from "node:path";
|
|
107870
|
+
|
|
107871
|
+
// ../core/dist/tarball.js
|
|
107872
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
107873
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
107874
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
107875
|
+
import { join as join10, relative as relative3, sep as sep2 } from "node:path";
|
|
107876
|
+
import { Readable } from "node:stream";
|
|
107877
|
+
import { createGzip } from "node:zlib";
|
|
107878
|
+
|
|
107879
|
+
// ../../node_modules/.bun/tar-stream@3.1.8/node_modules/tar-stream/index.js
|
|
107880
|
+
var $extract = require_extract();
|
|
107881
|
+
var $pack = require_pack();
|
|
107882
|
+
|
|
107883
|
+
// ../core/dist/tarball.js
|
|
107884
|
+
function isFilesOptions(options) {
|
|
107885
|
+
return "files" in options;
|
|
107886
|
+
}
|
|
107887
|
+
var DEFAULT_IGNORE = [
|
|
107888
|
+
"node_modules",
|
|
107889
|
+
".git",
|
|
107890
|
+
"dist",
|
|
107891
|
+
".next",
|
|
107892
|
+
".svelte-kit",
|
|
107893
|
+
"build",
|
|
107894
|
+
"coverage",
|
|
107895
|
+
".env",
|
|
107896
|
+
".env.*",
|
|
107897
|
+
".bun",
|
|
107898
|
+
"*.log",
|
|
107899
|
+
".DS_Store"
|
|
107900
|
+
];
|
|
107901
|
+
function* walk(dir, baseDir, ig) {
|
|
107902
|
+
const entries = readdirSync2(dir);
|
|
107903
|
+
for (const name of entries) {
|
|
107904
|
+
const full = join10(dir, name);
|
|
107905
|
+
const rel = relative3(baseDir, full).split(sep2).join("/");
|
|
107906
|
+
if (ig.ignores(rel))
|
|
107907
|
+
continue;
|
|
107908
|
+
const stats = statSync2(full);
|
|
107909
|
+
if (stats.isDirectory()) {
|
|
107910
|
+
if (ig.ignores(`${rel}/`))
|
|
107911
|
+
continue;
|
|
107912
|
+
yield* walk(full, baseDir, ig);
|
|
107913
|
+
} else if (stats.isFile()) {
|
|
107914
|
+
yield rel;
|
|
107915
|
+
}
|
|
107916
|
+
}
|
|
107917
|
+
}
|
|
107918
|
+
function normalizeInlinePath(raw) {
|
|
107919
|
+
const path = raw.replace(/\\/g, "/");
|
|
107920
|
+
if (path.length === 0) {
|
|
107921
|
+
throw new Error("file path must not be empty");
|
|
107922
|
+
}
|
|
107923
|
+
if (path.startsWith("/")) {
|
|
107924
|
+
throw new Error(`file path must be relative, got absolute path: ${raw}`);
|
|
107925
|
+
}
|
|
107926
|
+
if (/^[a-zA-Z]:/.test(path)) {
|
|
107927
|
+
throw new Error(`file path must be relative, got drive path: ${raw}`);
|
|
107928
|
+
}
|
|
107929
|
+
const segments = path.split("/");
|
|
107930
|
+
if (segments.some((s) => s === "..")) {
|
|
107931
|
+
throw new Error(`file path must not contain "..": ${raw}`);
|
|
107932
|
+
}
|
|
107933
|
+
const cleaned = segments.filter((s) => s !== "" && s !== ".").join("/");
|
|
107934
|
+
if (cleaned.length === 0) {
|
|
107935
|
+
throw new Error(`file path resolves to nothing: ${raw}`);
|
|
107936
|
+
}
|
|
107937
|
+
return cleaned;
|
|
107938
|
+
}
|
|
107939
|
+
function buildTarball(fileBuffers) {
|
|
107940
|
+
fileBuffers.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
107941
|
+
const totalBytes = fileBuffers.reduce((sum, f) => sum + f.size, 0);
|
|
107942
|
+
const contentHasher = createHash2("sha256");
|
|
107943
|
+
for (const f of fileBuffers) {
|
|
107944
|
+
contentHasher.update(`PATH:${f.path.length}:${f.path}
|
|
107945
|
+
`);
|
|
107946
|
+
contentHasher.update(`SIZE:${f.size}
|
|
107947
|
+
`);
|
|
107948
|
+
contentHasher.update(f.data);
|
|
107949
|
+
contentHasher.update(`
|
|
107950
|
+
`);
|
|
107951
|
+
}
|
|
107952
|
+
const contentHash = contentHasher.digest("hex");
|
|
107953
|
+
const pack = $pack();
|
|
107954
|
+
(async () => {
|
|
107955
|
+
for (const file2 of fileBuffers) {
|
|
107956
|
+
await new Promise((resolve7, reject) => {
|
|
107957
|
+
pack.entry({ name: file2.path, size: file2.data.length }, file2.data, (err) => err ? reject(err) : resolve7());
|
|
107945
107958
|
});
|
|
107946
107959
|
}
|
|
107947
|
-
|
|
107948
|
-
|
|
107949
|
-
|
|
107950
|
-
|
|
107951
|
-
|
|
107960
|
+
pack.finalize();
|
|
107961
|
+
})();
|
|
107962
|
+
const gzip = createGzip();
|
|
107963
|
+
const nodeStream = pack.pipe(gzip);
|
|
107964
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
107965
|
+
const topFiles = [...fileBuffers].sort((a, b) => b.size - a.size).slice(0, 5).map((f) => ({ path: f.path, size: f.size }));
|
|
107966
|
+
return {
|
|
107967
|
+
stream: webStream,
|
|
107968
|
+
bytes: totalBytes,
|
|
107969
|
+
fileCount: fileBuffers.length,
|
|
107970
|
+
topFiles,
|
|
107971
|
+
contentHash
|
|
107972
|
+
};
|
|
107973
|
+
}
|
|
107974
|
+
async function createTarball(options) {
|
|
107975
|
+
if (isFilesOptions(options)) {
|
|
107976
|
+
return createTarballFromFiles(options.files, options.config);
|
|
107977
|
+
}
|
|
107978
|
+
const ig = import_ignore.default();
|
|
107979
|
+
ig.add(DEFAULT_IGNORE);
|
|
107980
|
+
const gitignorePath = join10(options.cwd, ".gitignore");
|
|
107981
|
+
if (existsSync6(gitignorePath)) {
|
|
107982
|
+
ig.add(readFileSync6(gitignorePath, "utf8"));
|
|
107983
|
+
}
|
|
107984
|
+
if (options.extraIgnore)
|
|
107985
|
+
ig.add(options.extraIgnore);
|
|
107986
|
+
const fileBuffers = [];
|
|
107987
|
+
for (const rel of walk(options.cwd, options.cwd, ig)) {
|
|
107988
|
+
const data2 = readFileSync6(join10(options.cwd, rel));
|
|
107989
|
+
fileBuffers.push({ path: rel, size: data2.length, data: data2 });
|
|
107990
|
+
}
|
|
107991
|
+
return buildTarball(fileBuffers);
|
|
107992
|
+
}
|
|
107993
|
+
function createTarballFromFiles(files, _config) {
|
|
107994
|
+
const seen = new Set;
|
|
107995
|
+
const fileBuffers = [];
|
|
107996
|
+
for (const file2 of files) {
|
|
107997
|
+
const path = normalizeInlinePath(file2.path);
|
|
107998
|
+
if (seen.has(path)) {
|
|
107999
|
+
throw new Error(`duplicate file path in inline source: ${path}`);
|
|
108000
|
+
}
|
|
108001
|
+
seen.add(path);
|
|
108002
|
+
const data2 = Buffer.from(file2.data);
|
|
108003
|
+
fileBuffers.push({ path, size: data2.length, data: data2 });
|
|
107952
108004
|
}
|
|
108005
|
+
return buildTarball(fileBuffers);
|
|
107953
108006
|
}
|
|
107954
108007
|
|
|
107955
|
-
// ../core/dist/
|
|
107956
|
-
|
|
107957
|
-
|
|
107958
|
-
|
|
107959
|
-
var
|
|
107960
|
-
|
|
107961
|
-
|
|
107962
|
-
|
|
107963
|
-
|
|
107964
|
-
|
|
107965
|
-
|
|
107966
|
-
|
|
107967
|
-
|
|
107968
|
-
|
|
107969
|
-
|
|
107970
|
-
|
|
107971
|
-
|
|
107972
|
-
|
|
107973
|
-
|
|
107974
|
-
|
|
107975
|
-
|
|
107976
|
-
|
|
107977
|
-
|
|
107978
|
-
|
|
107979
|
-
|
|
107980
|
-
|
|
107981
|
-
explanation: () => "The build ran longer than the 10-minute platform limit and was aborted.",
|
|
107982
|
-
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."
|
|
107983
|
-
},
|
|
107984
|
-
BUILD_FAILED: {
|
|
107985
|
-
errorClass: "build_failed",
|
|
107986
|
-
phase: "build",
|
|
107987
|
-
cause: "build_nonzero_exit",
|
|
107988
|
-
title: "Build failed",
|
|
107989
|
-
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.",
|
|
107990
|
-
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."
|
|
107991
|
-
},
|
|
107992
|
-
WORKER_ERROR: {
|
|
107993
|
-
errorClass: "infra_unavailable",
|
|
107994
|
-
phase: "infra",
|
|
107995
|
-
cause: "worker_internal_error",
|
|
107996
|
-
title: "Platform error inside the build worker",
|
|
107997
|
-
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.",
|
|
107998
|
-
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."
|
|
107999
|
-
},
|
|
108000
|
-
DEPLOY_STALLED: {
|
|
108001
|
-
errorClass: "infra_unavailable",
|
|
108002
|
-
phase: "infra",
|
|
108003
|
-
cause: "deploy_stalled",
|
|
108004
|
-
title: "Deploy stalled",
|
|
108005
|
-
explanation: () => "The platform abandoned this deploy — likely because the API or build worker restarted mid-build. Your code is fine; the build never finished.",
|
|
108006
|
-
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."
|
|
108008
|
+
// ../core/dist/watcher.js
|
|
108009
|
+
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
108010
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, watch } from "node:fs";
|
|
108011
|
+
import { join as join11, sep as sep3 } from "node:path";
|
|
108012
|
+
var DEFAULT_IGNORE2 = [
|
|
108013
|
+
"node_modules",
|
|
108014
|
+
".git",
|
|
108015
|
+
"dist",
|
|
108016
|
+
".next",
|
|
108017
|
+
".svelte-kit",
|
|
108018
|
+
"build",
|
|
108019
|
+
"coverage",
|
|
108020
|
+
".env",
|
|
108021
|
+
".env.*",
|
|
108022
|
+
".bun",
|
|
108023
|
+
"*.log",
|
|
108024
|
+
".DS_Store",
|
|
108025
|
+
"__pycache__"
|
|
108026
|
+
];
|
|
108027
|
+
function createWatcher(options) {
|
|
108028
|
+
const debounceMs = options.debounceMs ?? 500;
|
|
108029
|
+
const ig = import_ignore2.default();
|
|
108030
|
+
ig.add(DEFAULT_IGNORE2);
|
|
108031
|
+
const gitignorePath = join11(options.cwd, ".gitignore");
|
|
108032
|
+
if (existsSync7(gitignorePath)) {
|
|
108033
|
+
ig.add(readFileSync7(gitignorePath, "utf8"));
|
|
108007
108034
|
}
|
|
108008
|
-
|
|
108009
|
-
|
|
108010
|
-
|
|
108011
|
-
|
|
108012
|
-
const
|
|
108013
|
-
|
|
108014
|
-
|
|
108035
|
+
if (options.extraIgnore)
|
|
108036
|
+
ig.add(options.extraIgnore);
|
|
108037
|
+
let timer = null;
|
|
108038
|
+
let pending = new Set;
|
|
108039
|
+
const fsWatcher = watch(options.cwd, { recursive: true }, (_event, filename) => {
|
|
108040
|
+
if (!filename)
|
|
108041
|
+
return;
|
|
108042
|
+
const rel = filename.split(sep3).join("/");
|
|
108043
|
+
if (ig.ignores(rel))
|
|
108044
|
+
return;
|
|
108045
|
+
pending.add(rel);
|
|
108046
|
+
if (timer)
|
|
108047
|
+
clearTimeout(timer);
|
|
108048
|
+
timer = setTimeout(() => {
|
|
108049
|
+
const files = [...pending];
|
|
108050
|
+
pending = new Set;
|
|
108051
|
+
timer = null;
|
|
108052
|
+
if (files.length > 0) {
|
|
108053
|
+
options.onChange(files);
|
|
108054
|
+
}
|
|
108055
|
+
}, debounceMs);
|
|
108056
|
+
});
|
|
108015
108057
|
return {
|
|
108016
|
-
|
|
108017
|
-
|
|
108018
|
-
|
|
108019
|
-
|
|
108020
|
-
|
|
108021
|
-
cause: mapping.cause,
|
|
108022
|
-
relevantFiles: [],
|
|
108023
|
-
missingEnvVars: []
|
|
108058
|
+
close() {
|
|
108059
|
+
if (timer)
|
|
108060
|
+
clearTimeout(timer);
|
|
108061
|
+
fsWatcher.close();
|
|
108062
|
+
}
|
|
108024
108063
|
};
|
|
108025
108064
|
}
|
|
108026
|
-
function trimMessage(raw) {
|
|
108027
|
-
const firstLine = raw.split(`
|
|
108028
|
-
`)[0] ?? "";
|
|
108029
|
-
return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
|
|
108030
|
-
}
|
|
108031
108065
|
|
|
108032
|
-
// ../core/dist/commands/
|
|
108033
|
-
|
|
108034
|
-
|
|
108066
|
+
// ../core/dist/commands/dev.js
|
|
108067
|
+
var devInputSchema = exports_external.object({
|
|
108068
|
+
app: exports_external.string().optional()
|
|
108069
|
+
});
|
|
108070
|
+
async function loadConfig2(ctx, appOverride) {
|
|
108071
|
+
const tomlPath = join12(ctx.cwd, "percher.toml");
|
|
108072
|
+
if (!existsSync8(tomlPath)) {
|
|
108073
|
+
throw new PercherCoreError("No percher.toml found. Run: percher init", {
|
|
108074
|
+
code: "dev.no-app",
|
|
108075
|
+
hint: "Create a percher.toml first with `percher init`."
|
|
108076
|
+
});
|
|
108077
|
+
}
|
|
108078
|
+
const config2 = await parseFile(tomlPath);
|
|
108079
|
+
if (appOverride)
|
|
108080
|
+
return { ...config2, app: { ...config2.app, name: appOverride } };
|
|
108081
|
+
return config2;
|
|
108035
108082
|
}
|
|
108036
|
-
async function
|
|
108037
|
-
let buildLogTail;
|
|
108038
|
-
let buildLogFetchFailed = false;
|
|
108039
|
-
let fullLog = "";
|
|
108083
|
+
async function ensureApp(ctx, config2) {
|
|
108040
108084
|
try {
|
|
108041
|
-
|
|
108042
|
-
|
|
108043
|
-
|
|
108044
|
-
|
|
108045
|
-
|
|
108085
|
+
return await ctx.client.apps.get(config2.app.name);
|
|
108086
|
+
} catch (err) {
|
|
108087
|
+
if (err.code === "APP_NOT_FOUND") {
|
|
108088
|
+
return ctx.client.apps.create({
|
|
108089
|
+
name: config2.app.name,
|
|
108090
|
+
runtime: config2.app.runtime,
|
|
108091
|
+
framework: config2.app.framework
|
|
108092
|
+
});
|
|
108093
|
+
}
|
|
108094
|
+
throw err;
|
|
108095
|
+
}
|
|
108096
|
+
}
|
|
108097
|
+
async function doPush(ctx, config2, callbacks) {
|
|
108098
|
+
const tarball = await createTarball({ cwd: ctx.cwd, config: config2 });
|
|
108099
|
+
const app = await ensureApp(ctx, config2);
|
|
108100
|
+
await callbacks.writeLine(`Pushing ${tarball.fileCount} files (${(tarball.bytes / 1024).toFixed(0)} KB)...`);
|
|
108101
|
+
const deployResponse = await ctx.client.apps.deploy(app.id, { tarball: tarball.stream });
|
|
108102
|
+
if (deployResponse.status === "already_in_progress") {
|
|
108103
|
+
const active = deployResponse;
|
|
108104
|
+
await callbacks.writeLine(` Skipped — deploy already in progress (${active.deployId}, ${active.deployStatus}).`);
|
|
108105
|
+
return { success: false, appId: app.id };
|
|
108106
|
+
}
|
|
108107
|
+
let deployment = deployResponse;
|
|
108108
|
+
const startedAt = Date.now();
|
|
108109
|
+
const timeoutMs = TIMEOUTS.clientPublishPoll;
|
|
108110
|
+
const pollInterval = 2000;
|
|
108111
|
+
let lastStatus = "";
|
|
108112
|
+
while (deployment.status !== "live" && deployment.status !== "failed") {
|
|
108113
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
108114
|
+
await callbacks.writeLine("Deploy timed out");
|
|
108115
|
+
return { success: false, appId: app.id };
|
|
108116
|
+
}
|
|
108117
|
+
if (callbacks.signal.aborted)
|
|
108118
|
+
return { success: false, appId: app.id };
|
|
108119
|
+
if (deployment.status !== lastStatus) {
|
|
108120
|
+
lastStatus = deployment.status;
|
|
108121
|
+
await callbacks.writeLine(` ${deployment.status}...`);
|
|
108122
|
+
}
|
|
108123
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
108124
|
+
deployment = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
108125
|
+
}
|
|
108126
|
+
if (deployment.status === "failed") {
|
|
108127
|
+
try {
|
|
108128
|
+
const insp = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
108129
|
+
if (insp.errorMessage) {
|
|
108130
|
+
await callbacks.writeLine("Deploy failed:");
|
|
108131
|
+
for (const line of insp.errorMessage.split(`
|
|
108132
|
+
`).slice(0, 30)) {
|
|
108133
|
+
await callbacks.writeLine(` ${line}`);
|
|
108134
|
+
}
|
|
108135
|
+
} else {
|
|
108136
|
+
await callbacks.writeLine("Deploy failed.");
|
|
108046
108137
|
}
|
|
108138
|
+
} catch {
|
|
108139
|
+
await callbacks.writeLine("Deploy failed.");
|
|
108140
|
+
}
|
|
108141
|
+
return { success: false, appId: app.id };
|
|
108142
|
+
}
|
|
108143
|
+
await callbacks.writeLine(`Live at ${deployment.url ?? ""}`);
|
|
108144
|
+
return { success: true, appId: app.id };
|
|
108145
|
+
}
|
|
108146
|
+
function formatLogLine(l) {
|
|
108147
|
+
const level = l.level.toUpperCase().padEnd(5);
|
|
108148
|
+
const src = l.source === "runtime" ? "" : `[${l.source}] `;
|
|
108149
|
+
return ` ${src}${level} ${l.message}`;
|
|
108150
|
+
}
|
|
108151
|
+
async function streamLogs(ctx, appId, callbacks, signal) {
|
|
108152
|
+
try {
|
|
108153
|
+
for await (const line of ctx.client.logs.stream(appId, { signal })) {
|
|
108154
|
+
if (signal.aborted)
|
|
108155
|
+
return;
|
|
108156
|
+
await callbacks.writeLine(formatLogLine(line));
|
|
108157
|
+
}
|
|
108158
|
+
} catch (err) {
|
|
108159
|
+
if (signal.aborted)
|
|
108160
|
+
return;
|
|
108161
|
+
await callbacks.writeLine(`Log stream error: ${err.message}`);
|
|
108162
|
+
}
|
|
108163
|
+
}
|
|
108164
|
+
async function dev(ctx, input, callbacks) {
|
|
108165
|
+
const config2 = await loadConfig2(ctx, input.app);
|
|
108166
|
+
const app = await ensureApp(ctx, config2);
|
|
108167
|
+
await callbacks.writeLine(`App: ${app.url}`);
|
|
108168
|
+
await callbacks.writeLine("Watching for changes (logs streaming below)...");
|
|
108169
|
+
await callbacks.writeLine("");
|
|
108170
|
+
const logStream = { ctrl: null };
|
|
108171
|
+
function startLogStream(appId) {
|
|
108172
|
+
logStream.ctrl?.abort();
|
|
108173
|
+
logStream.ctrl = new AbortController;
|
|
108174
|
+
const linkedSignal = logStream.ctrl.signal;
|
|
108175
|
+
streamLogs(ctx, appId, callbacks, linkedSignal);
|
|
108176
|
+
}
|
|
108177
|
+
startLogStream(app.id);
|
|
108178
|
+
let busy = false;
|
|
108179
|
+
let queued = null;
|
|
108180
|
+
async function handleChange(files) {
|
|
108181
|
+
if (busy) {
|
|
108182
|
+
queued = files;
|
|
108183
|
+
return;
|
|
108047
108184
|
}
|
|
108048
|
-
|
|
108049
|
-
|
|
108050
|
-
|
|
108051
|
-
|
|
108052
|
-
|
|
108185
|
+
busy = true;
|
|
108186
|
+
try {
|
|
108187
|
+
const shortList = files.length <= 3 ? files.join(", ") : `${files.slice(0, 3).join(", ")} +${files.length - 3} more`;
|
|
108188
|
+
await callbacks.writeLine(`Change detected: ${shortList}`);
|
|
108189
|
+
logStream.ctrl?.abort();
|
|
108190
|
+
const shouldPush = await callbacks.onFilesChanged(files);
|
|
108191
|
+
if (shouldPush) {
|
|
108192
|
+
try {
|
|
108193
|
+
const freshConfig = await loadConfig2(ctx, input.app);
|
|
108194
|
+
const result = await doPush(ctx, freshConfig, callbacks);
|
|
108195
|
+
if (result.success) {
|
|
108196
|
+
startLogStream(result.appId);
|
|
108197
|
+
} else {
|
|
108198
|
+
startLogStream(app.id);
|
|
108199
|
+
}
|
|
108200
|
+
} catch (err) {
|
|
108201
|
+
await callbacks.writeLine(`Push error: ${err.message}`);
|
|
108202
|
+
startLogStream(app.id);
|
|
108203
|
+
}
|
|
108204
|
+
} else {
|
|
108205
|
+
startLogStream(app.id);
|
|
108206
|
+
}
|
|
108207
|
+
} finally {
|
|
108208
|
+
busy = false;
|
|
108209
|
+
if (queued) {
|
|
108210
|
+
const next = queued;
|
|
108211
|
+
queued = null;
|
|
108212
|
+
await handleChange(next);
|
|
108213
|
+
}
|
|
108053
108214
|
}
|
|
108054
|
-
} catch {
|
|
108055
|
-
buildLogFetchFailed = true;
|
|
108056
|
-
}
|
|
108057
|
-
const errorMessage = deployment.errorMessage ?? "";
|
|
108058
|
-
const classified = classifyByErrorCode(deployment.errorCode, errorMessage) ?? classifyError(errorMessage || "Deployment failed", fullLog);
|
|
108059
|
-
const rawErrorMessage = errorMessage && !buildLogTail && !classified?.explanation.includes(errorMessage) ? errorMessage : undefined;
|
|
108060
|
-
const problems = deployment.problems && deployment.problems.length > 0 ? deployment.problems : undefined;
|
|
108061
|
-
if (classified) {
|
|
108062
|
-
return {
|
|
108063
|
-
title: classified.title,
|
|
108064
|
-
explanation: classified.explanation,
|
|
108065
|
-
suggestion: classified.suggestion,
|
|
108066
|
-
buildLogTail,
|
|
108067
|
-
rawErrorMessage,
|
|
108068
|
-
errorClass: classified.errorClass,
|
|
108069
|
-
phase: classified.phase,
|
|
108070
|
-
cause: classified.cause,
|
|
108071
|
-
relevantFiles: classified.relevantFiles,
|
|
108072
|
-
missingEnvVars: classified.missingEnvVars,
|
|
108073
|
-
problems
|
|
108074
|
-
};
|
|
108075
108215
|
}
|
|
108076
|
-
|
|
108077
|
-
|
|
108078
|
-
|
|
108079
|
-
|
|
108080
|
-
|
|
108081
|
-
|
|
108082
|
-
|
|
108083
|
-
|
|
108084
|
-
cause: "unknown",
|
|
108085
|
-
relevantFiles: [],
|
|
108086
|
-
missingEnvVars: [],
|
|
108087
|
-
problems
|
|
108088
|
-
};
|
|
108089
|
-
}
|
|
108090
|
-
async function classifyDeploymentFailure(opts) {
|
|
108091
|
-
const { ctx, app, deployment, publishInput } = opts;
|
|
108092
|
-
const error48 = await buildFailureError(ctx, app, deployment, opts.signal);
|
|
108093
|
-
const recovery = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
|
|
108094
|
-
missingEnvVars: error48.missingEnvVars,
|
|
108095
|
-
deployId: deployment.id,
|
|
108096
|
-
appName: app.name,
|
|
108097
|
-
publishInput,
|
|
108098
|
-
problems: error48.problems
|
|
108099
|
-
}) : recoveryAsk({
|
|
108100
|
-
prompt: `${app.name} deploy ${deployment.id} failed: ${error48.title}. ${error48.suggestion}`.trim(),
|
|
108101
|
-
reasonCode: "build_failed"
|
|
108216
|
+
const devConfig = config2.dev;
|
|
108217
|
+
const watcher = createWatcher({
|
|
108218
|
+
cwd: ctx.cwd,
|
|
108219
|
+
extraIgnore: devConfig?.ignore,
|
|
108220
|
+
debounceMs: devConfig?.debounce,
|
|
108221
|
+
onChange(files) {
|
|
108222
|
+
handleChange(files);
|
|
108223
|
+
}
|
|
108102
108224
|
});
|
|
108103
|
-
|
|
108104
|
-
|
|
108105
|
-
|
|
108106
|
-
|
|
108107
|
-
|
|
108225
|
+
try {
|
|
108226
|
+
await new Promise((resolve7) => {
|
|
108227
|
+
if (callbacks.signal.aborted) {
|
|
108228
|
+
resolve7();
|
|
108229
|
+
return;
|
|
108230
|
+
}
|
|
108231
|
+
callbacks.signal.addEventListener("abort", () => resolve7(), { once: true });
|
|
108232
|
+
});
|
|
108233
|
+
} finally {
|
|
108234
|
+
logStream.ctrl?.abort();
|
|
108235
|
+
watcher.close();
|
|
108236
|
+
}
|
|
108108
108237
|
}
|
|
108109
|
-
|
|
108110
|
-
|
|
108111
|
-
|
|
108112
|
-
|
|
108113
|
-
|
|
108114
|
-
|
|
108115
|
-
|
|
108238
|
+
// ../core/dist/commands/diagnose.js
|
|
108239
|
+
var diagnoseInputSchema = exports_external.object({
|
|
108240
|
+
app: exports_external.string().optional(),
|
|
108241
|
+
logLines: exports_external.number().int().min(0).max(200).optional(),
|
|
108242
|
+
recencyHours: exports_external.number().positive().max(24 * 30).optional()
|
|
108243
|
+
});
|
|
108244
|
+
// ../core/dist/commands/diff.js
|
|
108245
|
+
var diffInputSchema = exports_external.object({
|
|
108246
|
+
app: exports_external.string().optional(),
|
|
108247
|
+
base: exports_external.string().optional(),
|
|
108248
|
+
head: exports_external.string().optional(),
|
|
108249
|
+
patch: exports_external.boolean().optional()
|
|
108250
|
+
});
|
|
108251
|
+
async function diff(ctx, input = {}) {
|
|
108252
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
108253
|
+
if (!name) {
|
|
108254
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
108255
|
+
code: "diff.no-app",
|
|
108256
|
+
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
108257
|
+
});
|
|
108258
|
+
}
|
|
108259
|
+
return ctx.client.apps.getChanges(name, {
|
|
108260
|
+
base: input.base,
|
|
108261
|
+
head: input.head,
|
|
108262
|
+
patch: input.patch
|
|
108116
108263
|
});
|
|
108117
|
-
return {
|
|
108118
|
-
status: "failed",
|
|
108119
|
-
app,
|
|
108120
|
-
deployment,
|
|
108121
|
-
fileCount: tarball.fileCount,
|
|
108122
|
-
bytes: tarball.bytes,
|
|
108123
|
-
error: error48,
|
|
108124
|
-
recovery,
|
|
108125
|
-
summary,
|
|
108126
|
-
configPath: tomlPathFor(ctx.cwd),
|
|
108127
|
-
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
108128
|
-
};
|
|
108129
108264
|
}
|
|
108265
|
+
// ../core/dist/commands/doctor.js
|
|
108266
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
108267
|
+
import { join as join13 } from "node:path";
|
|
108130
108268
|
|
|
108131
108269
|
// ../core/dist/commands/wait-deploy.js
|
|
108132
108270
|
var waitForDeployInputSchema = exports_external.object({
|
|
@@ -110225,6 +110363,7 @@ async function logs(ctx, input = {}) {
|
|
|
110225
110363
|
}
|
|
110226
110364
|
// ../core/dist/commands/mcp.js
|
|
110227
110365
|
var mcpInputSchema = exports_external.object({});
|
|
110366
|
+
var HOSTED_MCP_URL = "https://mcp.percher.app/mcp";
|
|
110228
110367
|
function mcp(_ctx, _input = {}) {
|
|
110229
110368
|
return {
|
|
110230
110369
|
config: {
|
|
@@ -110235,19 +110374,43 @@ function mcp(_ctx, _input = {}) {
|
|
|
110235
110374
|
}
|
|
110236
110375
|
}
|
|
110237
110376
|
},
|
|
110377
|
+
hosted: {
|
|
110378
|
+
config: {
|
|
110379
|
+
mcpServers: {
|
|
110380
|
+
percher: {
|
|
110381
|
+
transport: "http",
|
|
110382
|
+
url: HOSTED_MCP_URL
|
|
110383
|
+
}
|
|
110384
|
+
}
|
|
110385
|
+
},
|
|
110386
|
+
url: HOSTED_MCP_URL,
|
|
110387
|
+
auth: "oauth"
|
|
110388
|
+
},
|
|
110238
110389
|
installLocations: [
|
|
110239
110390
|
{
|
|
110240
110391
|
client: "Claude Code",
|
|
110241
|
-
path: "~/.claude.json (under mcpServers, or a per-project .mcp.json)"
|
|
110392
|
+
path: "~/.claude.json (under mcpServers, or a per-project .mcp.json)",
|
|
110393
|
+
note: "Local stdio — recommended for Claude Code (full tool set, incl. publish from local files)."
|
|
110394
|
+
},
|
|
110395
|
+
{
|
|
110396
|
+
client: "Claude.ai (web/desktop)",
|
|
110397
|
+
path: `Settings → Connectors → Add custom connector → ${HOSTED_MCP_URL}`,
|
|
110398
|
+
note: "Hosted — OAuth, no token to paste. Remote-safe subset (operate existing apps + deploy connected repos)."
|
|
110242
110399
|
},
|
|
110243
110400
|
{
|
|
110244
110401
|
client: "Cursor / Windsurf",
|
|
110245
|
-
path: "settings → MCP → add server (
|
|
110402
|
+
path: "settings → MCP → add server (local: the same npx command; hosted: add the URL as a connector)"
|
|
110246
110403
|
},
|
|
110247
110404
|
{
|
|
110248
110405
|
client: "VS Code (Continue / Cline)",
|
|
110249
|
-
path: "extension settings → MCP servers"
|
|
110406
|
+
path: "extension settings → MCP servers (local npx command)"
|
|
110250
110407
|
}
|
|
110408
|
+
],
|
|
110409
|
+
notes: [
|
|
110410
|
+
"Two ways to connect:",
|
|
110411
|
+
" • Local (npx): full tool set, reads your project files — required for first publish from local files. Recommended for Claude Code.",
|
|
110412
|
+
` • Hosted connector: add ${HOSTED_MCP_URL} as a custom connector. OAuth login in the browser — no token to paste. Remote-safe subset (operate existing apps + deploy connected repos); first-publish-from-local-files stays on the npx path.`,
|
|
110413
|
+
"Manage or revoke hosted connections from the Percher dashboard."
|
|
110251
110414
|
]
|
|
110252
110415
|
};
|
|
110253
110416
|
}
|
|
@@ -114209,66 +114372,6 @@ function withDuration(label, durationMs) {
|
|
|
114209
114372
|
return `${label} (${seconds}s)`;
|
|
114210
114373
|
}
|
|
114211
114374
|
|
|
114212
|
-
// ../core/dist/poll-deployment.js
|
|
114213
|
-
async function pollDeployment(opts) {
|
|
114214
|
-
const terminal = opts.terminal ?? ["live", "failed"];
|
|
114215
|
-
const intervalMs = opts.intervalMs ?? 2000;
|
|
114216
|
-
const timeoutMs = opts.timeoutMs ?? TIMEOUTS.clientPublishPoll;
|
|
114217
|
-
const maxRetries = opts.maxRetries ?? 5;
|
|
114218
|
-
let deployment = opts.initial;
|
|
114219
|
-
const startedAt = Date.now();
|
|
114220
|
-
const seenEventIds = new Set;
|
|
114221
|
-
if (opts.onEvent) {
|
|
114222
|
-
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
114223
|
-
}
|
|
114224
|
-
while (!terminal.includes(deployment.status)) {
|
|
114225
|
-
if (Date.now() - startedAt > timeoutMs) {
|
|
114226
|
-
opts.onTimeout(deployment);
|
|
114227
|
-
}
|
|
114228
|
-
opts.onTick?.(deployment);
|
|
114229
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
114230
|
-
let attempt = 0;
|
|
114231
|
-
let polled = false;
|
|
114232
|
-
while (!polled) {
|
|
114233
|
-
try {
|
|
114234
|
-
deployment = await opts.ctx.client.apps.getDeployment(opts.appId, deployment.id);
|
|
114235
|
-
polled = true;
|
|
114236
|
-
} catch (err) {
|
|
114237
|
-
attempt++;
|
|
114238
|
-
const isNetwork = isTransientNetworkError(err);
|
|
114239
|
-
if (!isNetwork || attempt >= maxRetries) {
|
|
114240
|
-
if (isNetwork) {
|
|
114241
|
-
const msg = err.message ?? String(err);
|
|
114242
|
-
throw new Error(`${msg}
|
|
114243
|
-
|
|
114244
|
-
The deploy may already be live — your network connection dropped mid-poll. Check with: percher deploys list ${opts.appName}`, { cause: err });
|
|
114245
|
-
}
|
|
114246
|
-
throw err;
|
|
114247
|
-
}
|
|
114248
|
-
const backoff = (opts.backoffMs ?? ((n) => 1000 * 2 ** (n - 1)))(attempt);
|
|
114249
|
-
await new Promise((r) => setTimeout(r, backoff));
|
|
114250
|
-
}
|
|
114251
|
-
}
|
|
114252
|
-
if (opts.onEvent) {
|
|
114253
|
-
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
114254
|
-
}
|
|
114255
|
-
}
|
|
114256
|
-
return deployment;
|
|
114257
|
-
}
|
|
114258
|
-
async function drainNewEvents(opts, deployId, seenEventIds) {
|
|
114259
|
-
if (!opts.onEvent)
|
|
114260
|
-
return;
|
|
114261
|
-
try {
|
|
114262
|
-
const { events } = await opts.ctx.client.apps.getDeployEvents(opts.appId, deployId);
|
|
114263
|
-
for (const event of events) {
|
|
114264
|
-
if (seenEventIds.has(event.id))
|
|
114265
|
-
continue;
|
|
114266
|
-
seenEventIds.add(event.id);
|
|
114267
|
-
opts.onEvent(event);
|
|
114268
|
-
}
|
|
114269
|
-
} catch {}
|
|
114270
|
-
}
|
|
114271
|
-
|
|
114272
114375
|
// ../core/dist/publish-retry.js
|
|
114273
114376
|
var NON_RETRYABLE_CODES = new Set([
|
|
114274
114377
|
"WORKER_UNAVAILABLE_PERSISTENT",
|
|
@@ -115282,11 +115385,29 @@ async function executeDeploy(opts) {
|
|
|
115282
115385
|
throw err;
|
|
115283
115386
|
}
|
|
115284
115387
|
}
|
|
115388
|
+
const uploadMs = Date.now() - uploadStart;
|
|
115389
|
+
return finalizeDeploy({
|
|
115390
|
+
ctx,
|
|
115391
|
+
config: config2,
|
|
115392
|
+
input,
|
|
115393
|
+
app,
|
|
115394
|
+
firstDeploy,
|
|
115395
|
+
tarball: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
115396
|
+
deployment,
|
|
115397
|
+
t0,
|
|
115398
|
+
packageMs,
|
|
115399
|
+
uploadMs,
|
|
115400
|
+
missingBuildEnvKeys,
|
|
115401
|
+
cacheReused: cacheReusedDeployment !== null
|
|
115402
|
+
});
|
|
115403
|
+
}
|
|
115404
|
+
async function finalizeDeploy(opts) {
|
|
115405
|
+
const { ctx, config: config2, input, app, firstDeploy, tarball, t0, packageMs, uploadMs, missingBuildEnvKeys, cacheReused } = opts;
|
|
115406
|
+
let deployment = opts.deployment;
|
|
115285
115407
|
const replacedPreview = deployment.replacedPreview === true;
|
|
115286
115408
|
if (replacedPreview) {
|
|
115287
115409
|
ctx.status("Replaced previous preview...");
|
|
115288
115410
|
}
|
|
115289
|
-
const uploadMs = Date.now() - uploadStart;
|
|
115290
115411
|
if (input.waitForLive === false) {
|
|
115291
115412
|
ctx.status(`Queued (${deployment.id}) — resume with percher_wait_for_deploy.`);
|
|
115292
115413
|
return {
|
|
@@ -115426,7 +115547,7 @@ async function executeDeploy(opts) {
|
|
|
115426
115547
|
firstDeploy: firstDeploy || undefined,
|
|
115427
115548
|
cacheHit,
|
|
115428
115549
|
cacheStatus,
|
|
115429
|
-
cacheReused:
|
|
115550
|
+
cacheReused: cacheReused || undefined,
|
|
115430
115551
|
missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
|
|
115431
115552
|
traceId: deployment.id,
|
|
115432
115553
|
recovery: RECOVERY_NONE,
|
|
@@ -115665,6 +115786,18 @@ function emitPreflight(ctx, config2, tarball) {
|
|
|
115665
115786
|
ctx.status(line);
|
|
115666
115787
|
}
|
|
115667
115788
|
}
|
|
115789
|
+
// ../core/dist/commands/publish-inline.js
|
|
115790
|
+
var publishInlineInputSchema = exports_external.object({
|
|
115791
|
+
name: exports_external.string().optional().describe("Override the app name from percher.toml. Optional when percher.toml sets [app].name."),
|
|
115792
|
+
files: exports_external.array(exports_external.object({
|
|
115793
|
+
path: exports_external.string().min(1).describe("Repo-relative path (no leading / and no ..)."),
|
|
115794
|
+
content: exports_external.string().describe("UTF-8 file contents.")
|
|
115795
|
+
})).min(1).describe("Inline source files. MUST include a percher.toml."),
|
|
115796
|
+
preview: exports_external.boolean().optional().describe("Deploy as a preview (does not replace the live version)."),
|
|
115797
|
+
message: exports_external.string().optional().describe("Deploy note (visible in deploy history)."),
|
|
115798
|
+
noCache: exports_external.boolean().optional().describe("Skip the image cache and force a fresh build."),
|
|
115799
|
+
waitForLive: exports_external.boolean().optional().describe("If true (default), block until the deploy is live or failed. If false, return as soon as it is queued so the agent can resume with percher_wait_for_deploy.")
|
|
115800
|
+
});
|
|
115668
115801
|
// ../core/dist/commands/push.js
|
|
115669
115802
|
import { existsSync as existsSync16 } from "node:fs";
|
|
115670
115803
|
import { join as join25 } from "node:path";
|
|
@@ -117314,13 +117447,23 @@ function registerAccountCommands(cli, ctx) {
|
|
|
117314
117447
|
return;
|
|
117315
117448
|
}
|
|
117316
117449
|
await writeLine(ctx, import_picocolors2.default.bold("Percher MCP server"));
|
|
117317
|
-
await writeLine(ctx, import_picocolors2.default.dim("Add this entry to your MCP-aware client:"));
|
|
117318
117450
|
await writeLine(ctx, "");
|
|
117451
|
+
await writeLine(ctx, import_picocolors2.default.bold("Local (npx) — full tool set, reads your project files:"));
|
|
117319
117452
|
await writeLine(ctx, JSON.stringify(result.config, null, 2));
|
|
117320
117453
|
await writeLine(ctx, "");
|
|
117454
|
+
await writeLine(ctx, import_picocolors2.default.bold("Hosted connector — add this URL, OAuth login in the browser (no token):"));
|
|
117455
|
+
await writeLine(ctx, ` ${result.hosted.url}`);
|
|
117456
|
+
await writeLine(ctx, "");
|
|
117321
117457
|
await writeLine(ctx, import_picocolors2.default.bold("Where to paste:"));
|
|
117322
117458
|
for (const loc of result.installLocations) {
|
|
117323
117459
|
await writeLine(ctx, ` ${loc.client.padEnd(28)} ${import_picocolors2.default.dim(loc.path)}`);
|
|
117460
|
+
if (loc.note) {
|
|
117461
|
+
await writeLine(ctx, ` ${"".padEnd(28)} ${import_picocolors2.default.dim(loc.note)}`);
|
|
117462
|
+
}
|
|
117463
|
+
}
|
|
117464
|
+
await writeLine(ctx, "");
|
|
117465
|
+
for (const note of result.notes) {
|
|
117466
|
+
await writeLine(ctx, import_picocolors2.default.dim(note));
|
|
117324
117467
|
}
|
|
117325
117468
|
await writeLine(ctx, "");
|
|
117326
117469
|
await writeLine(ctx, import_picocolors2.default.dim("Once installed, your assistant gets percher_publish + 28 other tools."));
|
|
@@ -119048,7 +119191,7 @@ async function renderError(stderr, error48) {
|
|
|
119048
119191
|
// package.json
|
|
119049
119192
|
var package_default = {
|
|
119050
119193
|
name: "percher",
|
|
119051
|
-
version: "0.5.
|
|
119194
|
+
version: "0.5.11",
|
|
119052
119195
|
description: "CLI for the Percher hosting platform — publish apps with one command",
|
|
119053
119196
|
type: "module",
|
|
119054
119197
|
bin: {
|