percher 0.5.10 → 0.5.12
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 +1154 -894
- package/dist/package.json +3 -3
- package/dist/src/commands/account.d.ts.map +1 -1
- package/dist/src/commands/account.js +11 -1
- package/dist/src/commands/account.js.map +1 -1
- package/dist/src/commands/data.d.ts.map +1 -1
- package/dist/src/commands/data.js +48 -0
- package/dist/src/commands/data.js.map +1 -1
- package/dist/templates/capsule/Caddyfile +7 -0
- package/dist/templates/capsule/Dockerfile +39 -0
- package/dist/templates/capsule/index.html +12 -0
- package/dist/templates/capsule/package.json +20 -0
- package/dist/templates/capsule/pb/schema.json +18 -0
- package/dist/templates/capsule/percher.toml +13 -0
- package/dist/templates/capsule/src/App.tsx +65 -0
- package/dist/templates/capsule/src/components/Auth.tsx +75 -0
- package/dist/templates/capsule/src/components/SignInWithGoogle.tsx +27 -0
- package/dist/templates/capsule/src/lib/ai.ts +32 -0
- package/dist/templates/capsule/src/lib/db.ts +14 -0
- package/dist/templates/capsule/src/lib/realtime.ts +16 -0
- package/dist/templates/capsule/src/main.tsx +5 -0
- package/dist/templates/capsule/tsconfig.json +15 -0
- package/dist/templates/capsule/vite.config.ts +6 -0
- package/package.json +3 -3
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,43 @@ 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
|
+
}
|
|
106094
|
+
publishFromGit(input) {
|
|
106095
|
+
const params = new URLSearchParams;
|
|
106096
|
+
params.set("url", input.url);
|
|
106097
|
+
if (input.name)
|
|
106098
|
+
params.set("name", input.name);
|
|
106099
|
+
if (input.branch)
|
|
106100
|
+
params.set("branch", input.branch);
|
|
106101
|
+
if (input.note)
|
|
106102
|
+
params.set("note", input.note);
|
|
106103
|
+
return this.http.post(`/apps/publish-from-git?${params.toString()}`, {});
|
|
106104
|
+
}
|
|
106067
106105
|
setCrashAnalysisEnabled(appRef, enabled) {
|
|
106068
106106
|
return this.http.put(`/apps/${encodeURIComponent(appRef)}/crash-analysis`, {
|
|
106069
106107
|
crashAnalysisEnabled: enabled
|
|
@@ -106368,6 +106406,15 @@ class DataResource {
|
|
|
106368
106406
|
getAdminLink(appRef) {
|
|
106369
106407
|
return this.http.get(`/apps/${encodeURIComponent(appRef)}/data/admin-link`);
|
|
106370
106408
|
}
|
|
106409
|
+
setOAuthProvider(appRef, body) {
|
|
106410
|
+
return this.http.post(`/apps/${encodeURIComponent(appRef)}/data/oauth`, body);
|
|
106411
|
+
}
|
|
106412
|
+
getOAuthStatus(appRef) {
|
|
106413
|
+
return this.http.get(`/apps/${encodeURIComponent(appRef)}/data/oauth`);
|
|
106414
|
+
}
|
|
106415
|
+
removeOAuthProvider(appRef, provider) {
|
|
106416
|
+
return this.http.delete(`/apps/${encodeURIComponent(appRef)}/data/oauth/${encodeURIComponent(provider)}`);
|
|
106417
|
+
}
|
|
106371
106418
|
}
|
|
106372
106419
|
|
|
106373
106420
|
// ../client/dist/resources/env.js
|
|
@@ -106702,9 +106749,10 @@ class PercherClient {
|
|
|
106702
106749
|
invites;
|
|
106703
106750
|
transfers;
|
|
106704
106751
|
constructor(options = {}) {
|
|
106705
|
-
const
|
|
106706
|
-
|
|
106707
|
-
this.
|
|
106752
|
+
const bothExplicit = options.apiUrl !== undefined && options.token !== undefined;
|
|
106753
|
+
const defaults = bothExplicit ? undefined : loadConfig();
|
|
106754
|
+
this.apiUrl = options.apiUrl ?? defaults?.apiUrl ?? "";
|
|
106755
|
+
this.token = options.token !== undefined ? options.token : defaults?.token ?? null;
|
|
106708
106756
|
this.sessionId = options.sessionId;
|
|
106709
106757
|
const http = new Http({
|
|
106710
106758
|
apiUrl: this.apiUrl,
|
|
@@ -106967,6 +107015,49 @@ async function dataExport(ctx, input) {
|
|
|
106967
107015
|
exportedAt: new Date().toISOString()
|
|
106968
107016
|
};
|
|
106969
107017
|
}
|
|
107018
|
+
// ../core/dist/commands/data-oauth.js
|
|
107019
|
+
var dataOauthInputSchema = exports_external.object({
|
|
107020
|
+
app: exports_external.string().optional(),
|
|
107021
|
+
provider: exports_external.string().default("google"),
|
|
107022
|
+
clientId: exports_external.string().optional(),
|
|
107023
|
+
clientSecret: exports_external.string().optional(),
|
|
107024
|
+
remove: exports_external.boolean().optional(),
|
|
107025
|
+
json: exports_external.boolean().optional()
|
|
107026
|
+
});
|
|
107027
|
+
async function dataOauth(ctx, input = { provider: "google" }) {
|
|
107028
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
107029
|
+
if (!name) {
|
|
107030
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107031
|
+
code: "data.no-app",
|
|
107032
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
107033
|
+
});
|
|
107034
|
+
}
|
|
107035
|
+
const provider = input.provider || "google";
|
|
107036
|
+
if (input.remove) {
|
|
107037
|
+
await ctx.client.data.removeOAuthProvider(name, provider);
|
|
107038
|
+
return { action: "removed", provider };
|
|
107039
|
+
}
|
|
107040
|
+
if (input.clientId && input.clientSecret) {
|
|
107041
|
+
const res = await ctx.client.data.setOAuthProvider(name, {
|
|
107042
|
+
provider,
|
|
107043
|
+
clientId: input.clientId,
|
|
107044
|
+
clientSecret: input.clientSecret
|
|
107045
|
+
});
|
|
107046
|
+
return {
|
|
107047
|
+
action: "set",
|
|
107048
|
+
provider: res.provider,
|
|
107049
|
+
redirectUri: res.redirectUri,
|
|
107050
|
+
note: res.note
|
|
107051
|
+
};
|
|
107052
|
+
}
|
|
107053
|
+
const status = await ctx.client.data.getOAuthStatus(name);
|
|
107054
|
+
return {
|
|
107055
|
+
action: "status",
|
|
107056
|
+
provider,
|
|
107057
|
+
providers: status.providers,
|
|
107058
|
+
redirectUri: status.redirectUri
|
|
107059
|
+
};
|
|
107060
|
+
}
|
|
106970
107061
|
// ../core/dist/commands/delete.js
|
|
106971
107062
|
var deleteInputSchema = exports_external.object({
|
|
106972
107063
|
app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
|
|
@@ -106988,427 +107079,278 @@ async function deleteApp(ctx, input) {
|
|
|
106988
107079
|
}
|
|
106989
107080
|
await ctx.client.apps.delete(name);
|
|
106990
107081
|
}
|
|
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
|
-
});
|
|
107082
|
+
// ../core/dist/poll-deployment.js
|
|
107083
|
+
async function pollDeployment(opts) {
|
|
107084
|
+
const terminal = opts.terminal ?? ["live", "failed"];
|
|
107085
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
107086
|
+
const timeoutMs = opts.timeoutMs ?? TIMEOUTS.clientPublishPoll;
|
|
107087
|
+
const maxRetries = opts.maxRetries ?? 5;
|
|
107088
|
+
let deployment = opts.initial;
|
|
107089
|
+
const startedAt = Date.now();
|
|
107090
|
+
const seenEventIds = new Set;
|
|
107091
|
+
if (opts.onEvent) {
|
|
107092
|
+
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
107015
107093
|
}
|
|
107016
|
-
|
|
107017
|
-
|
|
107018
|
-
|
|
107019
|
-
|
|
107020
|
-
|
|
107021
|
-
|
|
107022
|
-
|
|
107023
|
-
|
|
107024
|
-
|
|
107025
|
-
|
|
107026
|
-
|
|
107027
|
-
|
|
107028
|
-
|
|
107029
|
-
|
|
107030
|
-
|
|
107031
|
-
|
|
107032
|
-
|
|
107094
|
+
while (!terminal.includes(deployment.status)) {
|
|
107095
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
107096
|
+
opts.onTimeout(deployment);
|
|
107097
|
+
}
|
|
107098
|
+
opts.onTick?.(deployment);
|
|
107099
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
107100
|
+
let attempt = 0;
|
|
107101
|
+
let polled = false;
|
|
107102
|
+
while (!polled) {
|
|
107103
|
+
try {
|
|
107104
|
+
deployment = await opts.ctx.client.apps.getDeployment(opts.appId, deployment.id);
|
|
107105
|
+
polled = true;
|
|
107106
|
+
} catch (err) {
|
|
107107
|
+
attempt++;
|
|
107108
|
+
const isNetwork = isTransientNetworkError(err);
|
|
107109
|
+
if (!isNetwork || attempt >= maxRetries) {
|
|
107110
|
+
if (isNetwork) {
|
|
107111
|
+
const msg = err.message ?? String(err);
|
|
107112
|
+
throw new Error(`${msg}
|
|
107113
|
+
|
|
107114
|
+
The deploy may already be live — your network connection dropped mid-poll. Check with: percher deploys list ${opts.appName}`, { cause: err });
|
|
107115
|
+
}
|
|
107116
|
+
throw err;
|
|
107117
|
+
}
|
|
107118
|
+
const backoff = (opts.backoffMs ?? ((n) => 1000 * 2 ** (n - 1)))(attempt);
|
|
107119
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
107120
|
+
}
|
|
107121
|
+
}
|
|
107122
|
+
if (opts.onEvent) {
|
|
107123
|
+
await drainNewEvents(opts, deployment.id, seenEventIds);
|
|
107033
107124
|
}
|
|
107034
|
-
deployId = failed.id;
|
|
107035
|
-
}
|
|
107036
|
-
if (!deployId) {
|
|
107037
|
-
throw new PercherCoreError("Deploy ID is required", {
|
|
107038
|
-
code: "deploys.no-id"
|
|
107039
|
-
});
|
|
107040
107125
|
}
|
|
107041
|
-
|
|
107042
|
-
|
|
107126
|
+
return deployment;
|
|
107127
|
+
}
|
|
107128
|
+
async function drainNewEvents(opts, deployId, seenEventIds) {
|
|
107129
|
+
if (!opts.onEvent)
|
|
107130
|
+
return;
|
|
107043
107131
|
try {
|
|
107044
|
-
const
|
|
107045
|
-
|
|
107046
|
-
|
|
107047
|
-
|
|
107048
|
-
|
|
107049
|
-
|
|
107050
|
-
throw err;
|
|
107132
|
+
const { events } = await opts.ctx.client.apps.getDeployEvents(opts.appId, deployId);
|
|
107133
|
+
for (const event of events) {
|
|
107134
|
+
if (seenEventIds.has(event.id))
|
|
107135
|
+
continue;
|
|
107136
|
+
seenEventIds.add(event.id);
|
|
107137
|
+
opts.onEvent(event);
|
|
107051
107138
|
}
|
|
107052
|
-
}
|
|
107053
|
-
return { deploy, buildLog };
|
|
107139
|
+
} catch {}
|
|
107054
107140
|
}
|
|
107055
|
-
// ../core/dist/commands/dev.js
|
|
107056
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
107057
|
-
import { join as join11 } from "node:path";
|
|
107058
|
-
|
|
107059
|
-
// ../core/dist/tarball.js
|
|
107060
|
-
var import_ignore = __toESM(require_ignore(), 1);
|
|
107061
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
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
107141
|
|
|
107071
|
-
// ../core/dist/
|
|
107072
|
-
|
|
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}/`))
|
|
107096
|
-
continue;
|
|
107097
|
-
yield* walk(full, baseDir, ig);
|
|
107098
|
-
} else if (stats.isFile()) {
|
|
107099
|
-
yield rel;
|
|
107100
|
-
}
|
|
107101
|
-
}
|
|
107102
|
-
}
|
|
107103
|
-
async function createTarball(options) {
|
|
107104
|
-
const ig = import_ignore.default();
|
|
107105
|
-
ig.add(DEFAULT_IGNORE);
|
|
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 }));
|
|
107142
|
+
// ../core/dist/recovery.js
|
|
107143
|
+
function recoveryNone(opts = {}) {
|
|
107143
107144
|
return {
|
|
107144
|
-
|
|
107145
|
-
|
|
107146
|
-
|
|
107147
|
-
|
|
107148
|
-
contentHash
|
|
107145
|
+
retryable: false,
|
|
107146
|
+
nextAction: "none",
|
|
107147
|
+
reasonCode: opts.reasonCode ?? "none",
|
|
107148
|
+
...opts.url !== undefined ? { url: opts.url } : {}
|
|
107149
107149
|
};
|
|
107150
107150
|
}
|
|
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
|
-
});
|
|
107151
|
+
function recoveryLogin(opts = {}) {
|
|
107201
107152
|
return {
|
|
107202
|
-
|
|
107203
|
-
|
|
107204
|
-
|
|
107205
|
-
|
|
107206
|
-
|
|
107153
|
+
retryable: false,
|
|
107154
|
+
nextAction: "open_login",
|
|
107155
|
+
suggestedTool: "percher_login",
|
|
107156
|
+
args: {},
|
|
107157
|
+
reasonCode: opts.reasonCode ?? "auth_required"
|
|
107207
107158
|
};
|
|
107208
107159
|
}
|
|
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;
|
|
107160
|
+
function recoveryWait(opts) {
|
|
107161
|
+
return {
|
|
107162
|
+
retryable: false,
|
|
107163
|
+
nextAction: "wait_deploy",
|
|
107164
|
+
suggestedTool: "percher_wait_for_deploy",
|
|
107165
|
+
args: {
|
|
107166
|
+
app: opts.app,
|
|
107167
|
+
deployId: opts.deployId,
|
|
107168
|
+
timeoutSeconds: opts.timeoutSeconds ?? TIMEOUTS.mcpWaitDeployDefault / 1000
|
|
107169
|
+
},
|
|
107170
|
+
reasonCode: opts.reasonCode
|
|
107171
|
+
};
|
|
107226
107172
|
}
|
|
107227
|
-
|
|
107228
|
-
|
|
107229
|
-
|
|
107230
|
-
|
|
107231
|
-
|
|
107232
|
-
|
|
107233
|
-
|
|
107234
|
-
|
|
107235
|
-
|
|
107236
|
-
|
|
107237
|
-
|
|
107238
|
-
|
|
107239
|
-
|
|
107173
|
+
function recoveryDoctor(opts) {
|
|
107174
|
+
const args = {};
|
|
107175
|
+
if (opts.app !== undefined)
|
|
107176
|
+
args.app = opts.app;
|
|
107177
|
+
if (opts.deployId !== undefined)
|
|
107178
|
+
args.deployId = opts.deployId;
|
|
107179
|
+
if (opts.mode !== undefined)
|
|
107180
|
+
args.mode = opts.mode;
|
|
107181
|
+
return {
|
|
107182
|
+
retryable: false,
|
|
107183
|
+
nextAction: "run_doctor",
|
|
107184
|
+
suggestedTool: "percher_doctor",
|
|
107185
|
+
args,
|
|
107186
|
+
reasonCode: opts.reasonCode,
|
|
107187
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107188
|
+
};
|
|
107240
107189
|
}
|
|
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 };
|
|
107190
|
+
function recoveryEnv(opts) {
|
|
107191
|
+
const args = {};
|
|
107192
|
+
if (opts.app !== undefined)
|
|
107193
|
+
args.app = opts.app;
|
|
107194
|
+
if (opts.keys.length > 0)
|
|
107195
|
+
args.keys = opts.keys;
|
|
107196
|
+
return {
|
|
107197
|
+
retryable: false,
|
|
107198
|
+
nextAction: "set_env_vars",
|
|
107199
|
+
suggestedTool: "percher_env_set",
|
|
107200
|
+
args,
|
|
107201
|
+
reasonCode: opts.reasonCode ?? "missing_env"
|
|
107202
|
+
};
|
|
107289
107203
|
}
|
|
107290
|
-
function
|
|
107291
|
-
|
|
107292
|
-
|
|
107293
|
-
|
|
107204
|
+
function recoveryFixConfig(opts) {
|
|
107205
|
+
return {
|
|
107206
|
+
retryable: false,
|
|
107207
|
+
nextAction: "fix_config",
|
|
107208
|
+
problems: opts.problems,
|
|
107209
|
+
reasonCode: opts.reasonCode ?? "config_invalid"
|
|
107210
|
+
};
|
|
107294
107211
|
}
|
|
107295
|
-
|
|
107296
|
-
|
|
107297
|
-
|
|
107298
|
-
|
|
107299
|
-
|
|
107300
|
-
|
|
107301
|
-
}
|
|
107302
|
-
}
|
|
107303
|
-
if (signal.aborted)
|
|
107304
|
-
return;
|
|
107305
|
-
await callbacks.writeLine(`Log stream error: ${err.message}`);
|
|
107306
|
-
}
|
|
107212
|
+
function recoveryFixProblems(opts) {
|
|
107213
|
+
return {
|
|
107214
|
+
retryable: false,
|
|
107215
|
+
nextAction: "fix_problems",
|
|
107216
|
+
problems: opts.problems,
|
|
107217
|
+
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107218
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107219
|
+
};
|
|
107307
107220
|
}
|
|
107308
|
-
|
|
107309
|
-
|
|
107310
|
-
|
|
107311
|
-
|
|
107312
|
-
|
|
107313
|
-
|
|
107314
|
-
|
|
107315
|
-
|
|
107316
|
-
|
|
107317
|
-
|
|
107318
|
-
|
|
107319
|
-
|
|
107221
|
+
function recoveryRetry(opts) {
|
|
107222
|
+
return {
|
|
107223
|
+
retryable: true,
|
|
107224
|
+
nextAction: "retry",
|
|
107225
|
+
suggestedTool: opts.suggestedTool,
|
|
107226
|
+
args: opts.args,
|
|
107227
|
+
reasonCode: opts.reasonCode
|
|
107228
|
+
};
|
|
107229
|
+
}
|
|
107230
|
+
function recoveryAsk(opts) {
|
|
107231
|
+
if (!opts.prompt || opts.prompt.trim().length === 0) {
|
|
107232
|
+
throw new Error("recoveryAsk requires a non-empty prompt");
|
|
107320
107233
|
}
|
|
107321
|
-
|
|
107322
|
-
|
|
107323
|
-
|
|
107324
|
-
|
|
107325
|
-
|
|
107326
|
-
|
|
107327
|
-
|
|
107328
|
-
|
|
107329
|
-
|
|
107330
|
-
|
|
107331
|
-
|
|
107332
|
-
|
|
107333
|
-
|
|
107334
|
-
|
|
107335
|
-
|
|
107336
|
-
|
|
107337
|
-
|
|
107338
|
-
|
|
107339
|
-
|
|
107340
|
-
|
|
107341
|
-
|
|
107342
|
-
|
|
107234
|
+
return {
|
|
107235
|
+
retryable: opts.retryable ?? false,
|
|
107236
|
+
nextAction: "ask_user",
|
|
107237
|
+
prompt: opts.prompt,
|
|
107238
|
+
...opts.options !== undefined ? { options: opts.options } : {},
|
|
107239
|
+
reasonCode: opts.reasonCode
|
|
107240
|
+
};
|
|
107241
|
+
}
|
|
107242
|
+
function recoveryInspectBuildLog(opts) {
|
|
107243
|
+
const args = opts.deployId ? { id: opts.deployId, app: opts.app } : { latestFailed: true, app: opts.app };
|
|
107244
|
+
return {
|
|
107245
|
+
retryable: false,
|
|
107246
|
+
nextAction: "inspect_build_log",
|
|
107247
|
+
suggestedTool: "percher_deploys_inspect",
|
|
107248
|
+
args,
|
|
107249
|
+
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107250
|
+
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107251
|
+
};
|
|
107252
|
+
}
|
|
107253
|
+
var RECOVERY_NONE = recoveryNone();
|
|
107254
|
+
var RECOVERY_NEEDS_LOGIN = recoveryLogin();
|
|
107255
|
+
function buildProblemToRecoveryProblem(p) {
|
|
107256
|
+
const file2 = p.file ?? "";
|
|
107257
|
+
const message = p.hint && p.hint.length > 0 ? `${p.message}
|
|
107258
|
+
Hint: ${p.hint}` : p.message;
|
|
107259
|
+
return {
|
|
107260
|
+
file: file2,
|
|
107261
|
+
...typeof p.line === "number" ? { line: p.line } : {},
|
|
107262
|
+
...typeof p.column === "number" ? { column: p.column } : {},
|
|
107263
|
+
message
|
|
107264
|
+
};
|
|
107265
|
+
}
|
|
107266
|
+
function recoveryFromErrorClass(errorClass, failure = {}) {
|
|
107267
|
+
switch (errorClass) {
|
|
107268
|
+
case "missing_env":
|
|
107269
|
+
return recoveryEnv({
|
|
107270
|
+
keys: failure.missingEnvVars ?? [],
|
|
107271
|
+
reasonCode: "missing_env"
|
|
107272
|
+
});
|
|
107273
|
+
case "infra_unavailable":
|
|
107274
|
+
return recoveryRetry({
|
|
107275
|
+
suggestedTool: "percher_publish",
|
|
107276
|
+
args: failure.publishInput ?? {},
|
|
107277
|
+
reasonCode: "infra_unavailable"
|
|
107278
|
+
});
|
|
107279
|
+
case "platform_at_capacity":
|
|
107280
|
+
return recoveryRetry({
|
|
107281
|
+
suggestedTool: "percher_publish",
|
|
107282
|
+
args: failure.publishInput ?? {},
|
|
107283
|
+
reasonCode: "infra_transient"
|
|
107284
|
+
});
|
|
107285
|
+
case "config_invalid":
|
|
107286
|
+
return recoveryFixConfig({
|
|
107287
|
+
problems: [
|
|
107288
|
+
{
|
|
107289
|
+
file: "percher.toml",
|
|
107290
|
+
message: "percher.toml failed validation — fix the file and re-run."
|
|
107343
107291
|
}
|
|
107344
|
-
|
|
107345
|
-
|
|
107346
|
-
|
|
107347
|
-
|
|
107348
|
-
|
|
107349
|
-
|
|
107292
|
+
],
|
|
107293
|
+
reasonCode: "config_invalid"
|
|
107294
|
+
});
|
|
107295
|
+
case "build_failed":
|
|
107296
|
+
case "missing_dependency":
|
|
107297
|
+
case "health_check_timeout":
|
|
107298
|
+
case "port_mismatch":
|
|
107299
|
+
case "permission_denied":
|
|
107300
|
+
case "runtime_crash":
|
|
107301
|
+
case "oom_killed": {
|
|
107302
|
+
const fileProblems = (failure.problems ?? []).filter((p) => p.file);
|
|
107303
|
+
const reproduceAlt = failure.deployId ? {
|
|
107304
|
+
action: "reproduce_locally",
|
|
107305
|
+
suggestedTool: "percher_reproduce",
|
|
107306
|
+
args: { deployId: failure.deployId, app: failure.appName },
|
|
107307
|
+
when: "after the primary path didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107308
|
+
} : undefined;
|
|
107309
|
+
if (fileProblems.length > 0) {
|
|
107310
|
+
return recoveryFixProblems({
|
|
107311
|
+
problems: fileProblems.map(buildProblemToRecoveryProblem),
|
|
107312
|
+
reasonCode: "build_failed",
|
|
107313
|
+
...reproduceAlt ? {
|
|
107314
|
+
alternativeActions: [
|
|
107315
|
+
{
|
|
107316
|
+
...reproduceAlt,
|
|
107317
|
+
when: "after fixing the file-located problems didn't make the build pass and the user has nixpacks installed locally"
|
|
107318
|
+
}
|
|
107319
|
+
]
|
|
107320
|
+
} : {}
|
|
107321
|
+
});
|
|
107350
107322
|
}
|
|
107351
|
-
|
|
107352
|
-
|
|
107353
|
-
|
|
107354
|
-
|
|
107355
|
-
|
|
107356
|
-
|
|
107323
|
+
const inspectAlt = {
|
|
107324
|
+
action: "inspect_build_log",
|
|
107325
|
+
suggestedTool: "percher_deploys_inspect",
|
|
107326
|
+
args: failure.deployId ? { id: failure.deployId, app: failure.appName } : { latestFailed: true, app: failure.appName },
|
|
107327
|
+
when: "after percher_doctor's recovery didn't yield a fix and you want to read the raw build log directly"
|
|
107328
|
+
};
|
|
107329
|
+
const alternativeActions = [inspectAlt];
|
|
107330
|
+
if (reproduceAlt) {
|
|
107331
|
+
alternativeActions.push({
|
|
107332
|
+
...reproduceAlt,
|
|
107333
|
+
when: "after percher_doctor + percher_deploys_inspect didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107334
|
+
});
|
|
107357
107335
|
}
|
|
107336
|
+
return recoveryDoctor({
|
|
107337
|
+
app: failure.appName,
|
|
107338
|
+
deployId: failure.deployId,
|
|
107339
|
+
mode: "deploy",
|
|
107340
|
+
reasonCode: "build_failed",
|
|
107341
|
+
alternativeActions
|
|
107342
|
+
});
|
|
107358
107343
|
}
|
|
107344
|
+
default:
|
|
107345
|
+
return recoveryAsk({
|
|
107346
|
+
prompt: "Unexpected failure — Percher couldn't classify this error. Try `percher doctor` for diagnostics, or surface the error to the user.",
|
|
107347
|
+
reasonCode: "unknown"
|
|
107348
|
+
});
|
|
107359
107349
|
}
|
|
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
107350
|
}
|
|
107409
|
-
|
|
107410
|
-
|
|
107411
|
-
import { join as
|
|
107351
|
+
|
|
107352
|
+
// ../core/dist/commands/publish-failure.js
|
|
107353
|
+
import { join as join9 } from "node:path";
|
|
107412
107354
|
|
|
107413
107355
|
// ../core/dist/error-classifier.js
|
|
107414
107356
|
function extractEnvVarsFromLog(combined) {
|
|
@@ -107742,219 +107684,6 @@ ${buildLog ?? ""}`;
|
|
|
107742
107684
|
return null;
|
|
107743
107685
|
}
|
|
107744
107686
|
|
|
107745
|
-
// ../core/dist/recovery.js
|
|
107746
|
-
function recoveryNone(opts = {}) {
|
|
107747
|
-
return {
|
|
107748
|
-
retryable: false,
|
|
107749
|
-
nextAction: "none",
|
|
107750
|
-
reasonCode: opts.reasonCode ?? "none",
|
|
107751
|
-
...opts.url !== undefined ? { url: opts.url } : {}
|
|
107752
|
-
};
|
|
107753
|
-
}
|
|
107754
|
-
function recoveryLogin(opts = {}) {
|
|
107755
|
-
return {
|
|
107756
|
-
retryable: false,
|
|
107757
|
-
nextAction: "open_login",
|
|
107758
|
-
suggestedTool: "percher_login",
|
|
107759
|
-
args: {},
|
|
107760
|
-
reasonCode: opts.reasonCode ?? "auth_required"
|
|
107761
|
-
};
|
|
107762
|
-
}
|
|
107763
|
-
function recoveryWait(opts) {
|
|
107764
|
-
return {
|
|
107765
|
-
retryable: false,
|
|
107766
|
-
nextAction: "wait_deploy",
|
|
107767
|
-
suggestedTool: "percher_wait_for_deploy",
|
|
107768
|
-
args: {
|
|
107769
|
-
app: opts.app,
|
|
107770
|
-
deployId: opts.deployId,
|
|
107771
|
-
timeoutSeconds: opts.timeoutSeconds ?? TIMEOUTS.mcpWaitDeployDefault / 1000
|
|
107772
|
-
},
|
|
107773
|
-
reasonCode: opts.reasonCode
|
|
107774
|
-
};
|
|
107775
|
-
}
|
|
107776
|
-
function recoveryDoctor(opts) {
|
|
107777
|
-
const args = {};
|
|
107778
|
-
if (opts.app !== undefined)
|
|
107779
|
-
args.app = opts.app;
|
|
107780
|
-
if (opts.deployId !== undefined)
|
|
107781
|
-
args.deployId = opts.deployId;
|
|
107782
|
-
if (opts.mode !== undefined)
|
|
107783
|
-
args.mode = opts.mode;
|
|
107784
|
-
return {
|
|
107785
|
-
retryable: false,
|
|
107786
|
-
nextAction: "run_doctor",
|
|
107787
|
-
suggestedTool: "percher_doctor",
|
|
107788
|
-
args,
|
|
107789
|
-
reasonCode: opts.reasonCode,
|
|
107790
|
-
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107791
|
-
};
|
|
107792
|
-
}
|
|
107793
|
-
function recoveryEnv(opts) {
|
|
107794
|
-
const args = {};
|
|
107795
|
-
if (opts.app !== undefined)
|
|
107796
|
-
args.app = opts.app;
|
|
107797
|
-
if (opts.keys.length > 0)
|
|
107798
|
-
args.keys = opts.keys;
|
|
107799
|
-
return {
|
|
107800
|
-
retryable: false,
|
|
107801
|
-
nextAction: "set_env_vars",
|
|
107802
|
-
suggestedTool: "percher_env_set",
|
|
107803
|
-
args,
|
|
107804
|
-
reasonCode: opts.reasonCode ?? "missing_env"
|
|
107805
|
-
};
|
|
107806
|
-
}
|
|
107807
|
-
function recoveryFixConfig(opts) {
|
|
107808
|
-
return {
|
|
107809
|
-
retryable: false,
|
|
107810
|
-
nextAction: "fix_config",
|
|
107811
|
-
problems: opts.problems,
|
|
107812
|
-
reasonCode: opts.reasonCode ?? "config_invalid"
|
|
107813
|
-
};
|
|
107814
|
-
}
|
|
107815
|
-
function recoveryFixProblems(opts) {
|
|
107816
|
-
return {
|
|
107817
|
-
retryable: false,
|
|
107818
|
-
nextAction: "fix_problems",
|
|
107819
|
-
problems: opts.problems,
|
|
107820
|
-
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107821
|
-
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107822
|
-
};
|
|
107823
|
-
}
|
|
107824
|
-
function recoveryRetry(opts) {
|
|
107825
|
-
return {
|
|
107826
|
-
retryable: true,
|
|
107827
|
-
nextAction: "retry",
|
|
107828
|
-
suggestedTool: opts.suggestedTool,
|
|
107829
|
-
args: opts.args,
|
|
107830
|
-
reasonCode: opts.reasonCode
|
|
107831
|
-
};
|
|
107832
|
-
}
|
|
107833
|
-
function recoveryAsk(opts) {
|
|
107834
|
-
if (!opts.prompt || opts.prompt.trim().length === 0) {
|
|
107835
|
-
throw new Error("recoveryAsk requires a non-empty prompt");
|
|
107836
|
-
}
|
|
107837
|
-
return {
|
|
107838
|
-
retryable: opts.retryable ?? false,
|
|
107839
|
-
nextAction: "ask_user",
|
|
107840
|
-
prompt: opts.prompt,
|
|
107841
|
-
...opts.options !== undefined ? { options: opts.options } : {},
|
|
107842
|
-
reasonCode: opts.reasonCode
|
|
107843
|
-
};
|
|
107844
|
-
}
|
|
107845
|
-
function recoveryInspectBuildLog(opts) {
|
|
107846
|
-
const args = opts.deployId ? { id: opts.deployId, app: opts.app } : { latestFailed: true, app: opts.app };
|
|
107847
|
-
return {
|
|
107848
|
-
retryable: false,
|
|
107849
|
-
nextAction: "inspect_build_log",
|
|
107850
|
-
suggestedTool: "percher_deploys_inspect",
|
|
107851
|
-
args,
|
|
107852
|
-
reasonCode: opts.reasonCode ?? "build_failed",
|
|
107853
|
-
...opts.alternativeActions !== undefined ? { alternativeActions: opts.alternativeActions } : {}
|
|
107854
|
-
};
|
|
107855
|
-
}
|
|
107856
|
-
var RECOVERY_NONE = recoveryNone();
|
|
107857
|
-
var RECOVERY_NEEDS_LOGIN = recoveryLogin();
|
|
107858
|
-
function buildProblemToRecoveryProblem(p) {
|
|
107859
|
-
const file2 = p.file ?? "";
|
|
107860
|
-
const message = p.hint && p.hint.length > 0 ? `${p.message}
|
|
107861
|
-
Hint: ${p.hint}` : p.message;
|
|
107862
|
-
return {
|
|
107863
|
-
file: file2,
|
|
107864
|
-
...typeof p.line === "number" ? { line: p.line } : {},
|
|
107865
|
-
...typeof p.column === "number" ? { column: p.column } : {},
|
|
107866
|
-
message
|
|
107867
|
-
};
|
|
107868
|
-
}
|
|
107869
|
-
function recoveryFromErrorClass(errorClass, failure = {}) {
|
|
107870
|
-
switch (errorClass) {
|
|
107871
|
-
case "missing_env":
|
|
107872
|
-
return recoveryEnv({
|
|
107873
|
-
keys: failure.missingEnvVars ?? [],
|
|
107874
|
-
reasonCode: "missing_env"
|
|
107875
|
-
});
|
|
107876
|
-
case "infra_unavailable":
|
|
107877
|
-
return recoveryRetry({
|
|
107878
|
-
suggestedTool: "percher_publish",
|
|
107879
|
-
args: failure.publishInput ?? {},
|
|
107880
|
-
reasonCode: "infra_unavailable"
|
|
107881
|
-
});
|
|
107882
|
-
case "platform_at_capacity":
|
|
107883
|
-
return recoveryRetry({
|
|
107884
|
-
suggestedTool: "percher_publish",
|
|
107885
|
-
args: failure.publishInput ?? {},
|
|
107886
|
-
reasonCode: "infra_transient"
|
|
107887
|
-
});
|
|
107888
|
-
case "config_invalid":
|
|
107889
|
-
return recoveryFixConfig({
|
|
107890
|
-
problems: [
|
|
107891
|
-
{
|
|
107892
|
-
file: "percher.toml",
|
|
107893
|
-
message: "percher.toml failed validation — fix the file and re-run."
|
|
107894
|
-
}
|
|
107895
|
-
],
|
|
107896
|
-
reasonCode: "config_invalid"
|
|
107897
|
-
});
|
|
107898
|
-
case "build_failed":
|
|
107899
|
-
case "missing_dependency":
|
|
107900
|
-
case "health_check_timeout":
|
|
107901
|
-
case "port_mismatch":
|
|
107902
|
-
case "permission_denied":
|
|
107903
|
-
case "runtime_crash":
|
|
107904
|
-
case "oom_killed": {
|
|
107905
|
-
const fileProblems = (failure.problems ?? []).filter((p) => p.file);
|
|
107906
|
-
const reproduceAlt = failure.deployId ? {
|
|
107907
|
-
action: "reproduce_locally",
|
|
107908
|
-
suggestedTool: "percher_reproduce",
|
|
107909
|
-
args: { deployId: failure.deployId, app: failure.appName },
|
|
107910
|
-
when: "after the primary path didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107911
|
-
} : undefined;
|
|
107912
|
-
if (fileProblems.length > 0) {
|
|
107913
|
-
return recoveryFixProblems({
|
|
107914
|
-
problems: fileProblems.map(buildProblemToRecoveryProblem),
|
|
107915
|
-
reasonCode: "build_failed",
|
|
107916
|
-
...reproduceAlt ? {
|
|
107917
|
-
alternativeActions: [
|
|
107918
|
-
{
|
|
107919
|
-
...reproduceAlt,
|
|
107920
|
-
when: "after fixing the file-located problems didn't make the build pass and the user has nixpacks installed locally"
|
|
107921
|
-
}
|
|
107922
|
-
]
|
|
107923
|
-
} : {}
|
|
107924
|
-
});
|
|
107925
|
-
}
|
|
107926
|
-
const inspectAlt = {
|
|
107927
|
-
action: "inspect_build_log",
|
|
107928
|
-
suggestedTool: "percher_deploys_inspect",
|
|
107929
|
-
args: failure.deployId ? { id: failure.deployId, app: failure.appName } : { latestFailed: true, app: failure.appName },
|
|
107930
|
-
when: "after percher_doctor's recovery didn't yield a fix and you want to read the raw build log directly"
|
|
107931
|
-
};
|
|
107932
|
-
const alternativeActions = [inspectAlt];
|
|
107933
|
-
if (reproduceAlt) {
|
|
107934
|
-
alternativeActions.push({
|
|
107935
|
-
...reproduceAlt,
|
|
107936
|
-
when: "after percher_doctor + percher_deploys_inspect didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107937
|
-
});
|
|
107938
|
-
}
|
|
107939
|
-
return recoveryDoctor({
|
|
107940
|
-
app: failure.appName,
|
|
107941
|
-
deployId: failure.deployId,
|
|
107942
|
-
mode: "deploy",
|
|
107943
|
-
reasonCode: "build_failed",
|
|
107944
|
-
alternativeActions
|
|
107945
|
-
});
|
|
107946
|
-
}
|
|
107947
|
-
default:
|
|
107948
|
-
return recoveryAsk({
|
|
107949
|
-
prompt: "Unexpected failure — Percher couldn't classify this error. Try `percher doctor` for diagnostics, or surface the error to the user.",
|
|
107950
|
-
reasonCode: "unknown"
|
|
107951
|
-
});
|
|
107952
|
-
}
|
|
107953
|
-
}
|
|
107954
|
-
|
|
107955
|
-
// ../core/dist/commands/publish-failure.js
|
|
107956
|
-
import { join as join12 } from "node:path";
|
|
107957
|
-
|
|
107958
107687
|
// ../core/dist/structured-error-codes.js
|
|
107959
107688
|
var MAPPINGS = {
|
|
107960
107689
|
WORKER_UNAVAILABLE: {
|
|
@@ -108005,128 +107734,600 @@ var MAPPINGS = {
|
|
|
108005
107734
|
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
107735
|
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."
|
|
108007
107736
|
}
|
|
108008
|
-
};
|
|
108009
|
-
function classifyByErrorCode(code, rawMessage) {
|
|
108010
|
-
if (!code)
|
|
108011
|
-
return null;
|
|
108012
|
-
const mapping = MAPPINGS[code];
|
|
108013
|
-
if (!mapping)
|
|
108014
|
-
return null;
|
|
107737
|
+
};
|
|
107738
|
+
function classifyByErrorCode(code, rawMessage) {
|
|
107739
|
+
if (!code)
|
|
107740
|
+
return null;
|
|
107741
|
+
const mapping = MAPPINGS[code];
|
|
107742
|
+
if (!mapping)
|
|
107743
|
+
return null;
|
|
107744
|
+
return {
|
|
107745
|
+
title: mapping.title,
|
|
107746
|
+
explanation: mapping.explanation(rawMessage),
|
|
107747
|
+
suggestion: mapping.suggestion,
|
|
107748
|
+
errorClass: mapping.errorClass,
|
|
107749
|
+
phase: mapping.phase,
|
|
107750
|
+
cause: mapping.cause,
|
|
107751
|
+
relevantFiles: [],
|
|
107752
|
+
missingEnvVars: []
|
|
107753
|
+
};
|
|
107754
|
+
}
|
|
107755
|
+
function trimMessage(raw) {
|
|
107756
|
+
const firstLine = raw.split(`
|
|
107757
|
+
`)[0] ?? "";
|
|
107758
|
+
return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
|
|
107759
|
+
}
|
|
107760
|
+
|
|
107761
|
+
// ../core/dist/commands/publish-failure.js
|
|
107762
|
+
function tomlPathFor(cwd) {
|
|
107763
|
+
return join9(cwd, "percher.toml");
|
|
107764
|
+
}
|
|
107765
|
+
async function buildFailureError(ctx, app, deployment, signal) {
|
|
107766
|
+
let buildLogTail;
|
|
107767
|
+
let buildLogFetchFailed = false;
|
|
107768
|
+
let fullLog = "";
|
|
107769
|
+
try {
|
|
107770
|
+
fullLog = await ctx.client.apps.getBuildLog(app.id, deployment.id, { signal });
|
|
107771
|
+
if (!fullLog && !signal?.aborted) {
|
|
107772
|
+
await abortableSleep(1500, signal);
|
|
107773
|
+
if (!signal?.aborted) {
|
|
107774
|
+
fullLog = await ctx.client.apps.getBuildLog(app.id, deployment.id, { signal });
|
|
107775
|
+
}
|
|
107776
|
+
}
|
|
107777
|
+
if (fullLog) {
|
|
107778
|
+
const lines = fullLog.split(`
|
|
107779
|
+
`);
|
|
107780
|
+
buildLogTail = lines.slice(-50).join(`
|
|
107781
|
+
`);
|
|
107782
|
+
}
|
|
107783
|
+
} catch {
|
|
107784
|
+
buildLogFetchFailed = true;
|
|
107785
|
+
}
|
|
107786
|
+
const errorMessage = deployment.errorMessage ?? "";
|
|
107787
|
+
const classified = classifyByErrorCode(deployment.errorCode, errorMessage) ?? classifyError(errorMessage || "Deployment failed", fullLog);
|
|
107788
|
+
const rawErrorMessage = errorMessage && !buildLogTail && !classified?.explanation.includes(errorMessage) ? errorMessage : undefined;
|
|
107789
|
+
const problems = deployment.problems && deployment.problems.length > 0 ? deployment.problems : undefined;
|
|
107790
|
+
if (classified) {
|
|
107791
|
+
return {
|
|
107792
|
+
title: classified.title,
|
|
107793
|
+
explanation: classified.explanation,
|
|
107794
|
+
suggestion: classified.suggestion,
|
|
107795
|
+
buildLogTail,
|
|
107796
|
+
rawErrorMessage,
|
|
107797
|
+
errorClass: classified.errorClass,
|
|
107798
|
+
phase: classified.phase,
|
|
107799
|
+
cause: classified.cause,
|
|
107800
|
+
relevantFiles: classified.relevantFiles,
|
|
107801
|
+
missingEnvVars: classified.missingEnvVars,
|
|
107802
|
+
problems
|
|
107803
|
+
};
|
|
107804
|
+
}
|
|
107805
|
+
return {
|
|
107806
|
+
title: "Deployment failed",
|
|
107807
|
+
explanation: errorMessage ? `The deployment failed: ${errorMessage}` : "The deployment failed. See the build log for details.",
|
|
107808
|
+
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(`
|
|
107809
|
+
`)[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}`,
|
|
107810
|
+
buildLogTail,
|
|
107811
|
+
errorClass: "build_failed",
|
|
107812
|
+
phase: "build",
|
|
107813
|
+
cause: "unknown",
|
|
107814
|
+
relevantFiles: [],
|
|
107815
|
+
missingEnvVars: [],
|
|
107816
|
+
problems
|
|
107817
|
+
};
|
|
107818
|
+
}
|
|
107819
|
+
async function classifyDeploymentFailure(opts) {
|
|
107820
|
+
const { ctx, app, deployment, publishInput } = opts;
|
|
107821
|
+
const error48 = await buildFailureError(ctx, app, deployment, opts.signal);
|
|
107822
|
+
const recovery = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
|
|
107823
|
+
missingEnvVars: error48.missingEnvVars,
|
|
107824
|
+
deployId: deployment.id,
|
|
107825
|
+
appName: app.name,
|
|
107826
|
+
publishInput,
|
|
107827
|
+
problems: error48.problems
|
|
107828
|
+
}) : recoveryAsk({
|
|
107829
|
+
prompt: `${app.name} deploy ${deployment.id} failed: ${error48.title}. ${error48.suggestion}`.trim(),
|
|
107830
|
+
reasonCode: "build_failed"
|
|
107831
|
+
});
|
|
107832
|
+
return {
|
|
107833
|
+
error: error48,
|
|
107834
|
+
recovery,
|
|
107835
|
+
summary: `${error48.title} (${app.name}, deploy ${deployment.id}).`
|
|
107836
|
+
};
|
|
107837
|
+
}
|
|
107838
|
+
async function buildFailureResult(opts) {
|
|
107839
|
+
const { ctx, app, deployment, tarball, input } = opts;
|
|
107840
|
+
const { error: error48, recovery, summary } = await classifyDeploymentFailure({
|
|
107841
|
+
ctx,
|
|
107842
|
+
app,
|
|
107843
|
+
deployment,
|
|
107844
|
+
publishInput: input
|
|
107845
|
+
});
|
|
107846
|
+
return {
|
|
107847
|
+
status: "failed",
|
|
107848
|
+
app,
|
|
107849
|
+
deployment,
|
|
107850
|
+
fileCount: tarball.fileCount,
|
|
107851
|
+
bytes: tarball.bytes,
|
|
107852
|
+
error: error48,
|
|
107853
|
+
recovery,
|
|
107854
|
+
summary,
|
|
107855
|
+
configPath: tomlPathFor(ctx.cwd),
|
|
107856
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
107857
|
+
};
|
|
107858
|
+
}
|
|
107859
|
+
|
|
107860
|
+
// ../core/dist/commands/deploy-repo.js
|
|
107861
|
+
var deployRepoInputSchema = exports_external.object({
|
|
107862
|
+
app: exports_external.string().describe("App name or id (required — hosted has no cwd)."),
|
|
107863
|
+
branch: exports_external.string().optional().describe("Branch to deploy. Defaults to the app's tracked branch."),
|
|
107864
|
+
note: exports_external.string().optional().describe("Optional note recorded against the deploy.")
|
|
107865
|
+
});
|
|
107866
|
+
// ../core/dist/commands/deploys.js
|
|
107867
|
+
var deploysListInputSchema = exports_external.object({
|
|
107868
|
+
app: exports_external.string().optional(),
|
|
107869
|
+
limit: exports_external.number().int().positive().max(100).optional()
|
|
107870
|
+
});
|
|
107871
|
+
var deploysInspectInputSchema = exports_external.object({
|
|
107872
|
+
app: exports_external.string().optional(),
|
|
107873
|
+
id: exports_external.string().optional(),
|
|
107874
|
+
latestFailed: exports_external.boolean().optional()
|
|
107875
|
+
}).superRefine((val, ctx) => {
|
|
107876
|
+
if (!val.id && !val.latestFailed) {
|
|
107877
|
+
ctx.addIssue({
|
|
107878
|
+
code: exports_external.ZodIssueCode.custom,
|
|
107879
|
+
message: "either `id` or `latestFailed: true` must be provided"
|
|
107880
|
+
});
|
|
107881
|
+
}
|
|
107882
|
+
});
|
|
107883
|
+
function resolveAppName(ctx, app) {
|
|
107884
|
+
const name = app ?? readPercherTomlAppName(ctx.cwd);
|
|
107885
|
+
if (!name) {
|
|
107886
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107887
|
+
code: "deploys.no-app",
|
|
107888
|
+
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
107889
|
+
});
|
|
107890
|
+
}
|
|
107891
|
+
return name;
|
|
107892
|
+
}
|
|
107893
|
+
async function deploysList(ctx, input = {}) {
|
|
107894
|
+
const name = resolveAppName(ctx, input.app);
|
|
107895
|
+
return ctx.client.apps.listDeploys(name, { limit: input.limit ?? 10 });
|
|
107896
|
+
}
|
|
107897
|
+
async function deploysInspect(ctx, input) {
|
|
107898
|
+
const name = resolveAppName(ctx, input.app);
|
|
107899
|
+
let deployId = input.id;
|
|
107900
|
+
if (!deployId && input.latestFailed) {
|
|
107901
|
+
const recent = await ctx.client.apps.listDeploys(name, { limit: 25 });
|
|
107902
|
+
const failed = recent.find((d) => d.status === "failed");
|
|
107903
|
+
if (!failed) {
|
|
107904
|
+
throw new PercherCoreError(`No failed deploys found for "${name}"`, {
|
|
107905
|
+
code: "deploys.no-failed",
|
|
107906
|
+
hint: "Try `percher deploys` to list recent deploys, or omit --latest-failed."
|
|
107907
|
+
});
|
|
107908
|
+
}
|
|
107909
|
+
deployId = failed.id;
|
|
107910
|
+
}
|
|
107911
|
+
if (!deployId) {
|
|
107912
|
+
throw new PercherCoreError("Deploy ID is required", {
|
|
107913
|
+
code: "deploys.no-id"
|
|
107914
|
+
});
|
|
107915
|
+
}
|
|
107916
|
+
const deploy = await ctx.client.apps.getDeployment(name, deployId);
|
|
107917
|
+
let buildLog = null;
|
|
107918
|
+
try {
|
|
107919
|
+
const log = await ctx.client.apps.getBuildLog(name, deployId);
|
|
107920
|
+
buildLog = log.length > 0 ? log : null;
|
|
107921
|
+
} catch (err) {
|
|
107922
|
+
if (err instanceof PercherApiError && err.status === 404) {
|
|
107923
|
+
buildLog = null;
|
|
107924
|
+
} else {
|
|
107925
|
+
throw err;
|
|
107926
|
+
}
|
|
107927
|
+
}
|
|
107928
|
+
return { deploy, buildLog };
|
|
107929
|
+
}
|
|
107930
|
+
// ../core/dist/commands/dev.js
|
|
107931
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
107932
|
+
import { join as join12 } from "node:path";
|
|
107933
|
+
|
|
107934
|
+
// ../core/dist/tarball.js
|
|
107935
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
107936
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
107937
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
107938
|
+
import { join as join10, relative as relative3, sep as sep2 } from "node:path";
|
|
107939
|
+
import { Readable } from "node:stream";
|
|
107940
|
+
import { createGzip } from "node:zlib";
|
|
107941
|
+
|
|
107942
|
+
// ../../node_modules/.bun/tar-stream@3.1.8/node_modules/tar-stream/index.js
|
|
107943
|
+
var $extract = require_extract();
|
|
107944
|
+
var $pack = require_pack();
|
|
107945
|
+
|
|
107946
|
+
// ../core/dist/tarball.js
|
|
107947
|
+
function isFilesOptions(options) {
|
|
107948
|
+
return "files" in options;
|
|
107949
|
+
}
|
|
107950
|
+
var DEFAULT_IGNORE = [
|
|
107951
|
+
"node_modules",
|
|
107952
|
+
".git",
|
|
107953
|
+
"dist",
|
|
107954
|
+
".next",
|
|
107955
|
+
".svelte-kit",
|
|
107956
|
+
"build",
|
|
107957
|
+
"coverage",
|
|
107958
|
+
".env",
|
|
107959
|
+
".env.*",
|
|
107960
|
+
".bun",
|
|
107961
|
+
"*.log",
|
|
107962
|
+
".DS_Store"
|
|
107963
|
+
];
|
|
107964
|
+
function* walk(dir, baseDir, ig) {
|
|
107965
|
+
const entries = readdirSync2(dir);
|
|
107966
|
+
for (const name of entries) {
|
|
107967
|
+
const full = join10(dir, name);
|
|
107968
|
+
const rel = relative3(baseDir, full).split(sep2).join("/");
|
|
107969
|
+
if (ig.ignores(rel))
|
|
107970
|
+
continue;
|
|
107971
|
+
const stats = statSync2(full);
|
|
107972
|
+
if (stats.isDirectory()) {
|
|
107973
|
+
if (ig.ignores(`${rel}/`))
|
|
107974
|
+
continue;
|
|
107975
|
+
yield* walk(full, baseDir, ig);
|
|
107976
|
+
} else if (stats.isFile()) {
|
|
107977
|
+
yield rel;
|
|
107978
|
+
}
|
|
107979
|
+
}
|
|
107980
|
+
}
|
|
107981
|
+
function normalizeInlinePath(raw) {
|
|
107982
|
+
const path = raw.replace(/\\/g, "/");
|
|
107983
|
+
if (path.length === 0) {
|
|
107984
|
+
throw new Error("file path must not be empty");
|
|
107985
|
+
}
|
|
107986
|
+
if (path.startsWith("/")) {
|
|
107987
|
+
throw new Error(`file path must be relative, got absolute path: ${raw}`);
|
|
107988
|
+
}
|
|
107989
|
+
if (/^[a-zA-Z]:/.test(path)) {
|
|
107990
|
+
throw new Error(`file path must be relative, got drive path: ${raw}`);
|
|
107991
|
+
}
|
|
107992
|
+
const segments = path.split("/");
|
|
107993
|
+
if (segments.some((s) => s === "..")) {
|
|
107994
|
+
throw new Error(`file path must not contain "..": ${raw}`);
|
|
107995
|
+
}
|
|
107996
|
+
const cleaned = segments.filter((s) => s !== "" && s !== ".").join("/");
|
|
107997
|
+
if (cleaned.length === 0) {
|
|
107998
|
+
throw new Error(`file path resolves to nothing: ${raw}`);
|
|
107999
|
+
}
|
|
108000
|
+
return cleaned;
|
|
108001
|
+
}
|
|
108002
|
+
function buildTarball(fileBuffers) {
|
|
108003
|
+
fileBuffers.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
108004
|
+
const totalBytes = fileBuffers.reduce((sum, f) => sum + f.size, 0);
|
|
108005
|
+
const contentHasher = createHash2("sha256");
|
|
108006
|
+
for (const f of fileBuffers) {
|
|
108007
|
+
contentHasher.update(`PATH:${f.path.length}:${f.path}
|
|
108008
|
+
`);
|
|
108009
|
+
contentHasher.update(`SIZE:${f.size}
|
|
108010
|
+
`);
|
|
108011
|
+
contentHasher.update(f.data);
|
|
108012
|
+
contentHasher.update(`
|
|
108013
|
+
`);
|
|
108014
|
+
}
|
|
108015
|
+
const contentHash = contentHasher.digest("hex");
|
|
108016
|
+
const pack = $pack();
|
|
108017
|
+
(async () => {
|
|
108018
|
+
for (const file2 of fileBuffers) {
|
|
108019
|
+
await new Promise((resolve7, reject) => {
|
|
108020
|
+
pack.entry({ name: file2.path, size: file2.data.length }, file2.data, (err) => err ? reject(err) : resolve7());
|
|
108021
|
+
});
|
|
108022
|
+
}
|
|
108023
|
+
pack.finalize();
|
|
108024
|
+
})();
|
|
108025
|
+
const gzip = createGzip();
|
|
108026
|
+
const nodeStream = pack.pipe(gzip);
|
|
108027
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
108028
|
+
const topFiles = [...fileBuffers].sort((a, b) => b.size - a.size).slice(0, 5).map((f) => ({ path: f.path, size: f.size }));
|
|
108029
|
+
return {
|
|
108030
|
+
stream: webStream,
|
|
108031
|
+
bytes: totalBytes,
|
|
108032
|
+
fileCount: fileBuffers.length,
|
|
108033
|
+
topFiles,
|
|
108034
|
+
contentHash
|
|
108035
|
+
};
|
|
108036
|
+
}
|
|
108037
|
+
async function createTarball(options) {
|
|
108038
|
+
if (isFilesOptions(options)) {
|
|
108039
|
+
return createTarballFromFiles(options.files, options.config);
|
|
108040
|
+
}
|
|
108041
|
+
const ig = import_ignore.default();
|
|
108042
|
+
ig.add(DEFAULT_IGNORE);
|
|
108043
|
+
const gitignorePath = join10(options.cwd, ".gitignore");
|
|
108044
|
+
if (existsSync6(gitignorePath)) {
|
|
108045
|
+
ig.add(readFileSync6(gitignorePath, "utf8"));
|
|
108046
|
+
}
|
|
108047
|
+
if (options.extraIgnore)
|
|
108048
|
+
ig.add(options.extraIgnore);
|
|
108049
|
+
const fileBuffers = [];
|
|
108050
|
+
for (const rel of walk(options.cwd, options.cwd, ig)) {
|
|
108051
|
+
const data2 = readFileSync6(join10(options.cwd, rel));
|
|
108052
|
+
fileBuffers.push({ path: rel, size: data2.length, data: data2 });
|
|
108053
|
+
}
|
|
108054
|
+
return buildTarball(fileBuffers);
|
|
108055
|
+
}
|
|
108056
|
+
function createTarballFromFiles(files, _config) {
|
|
108057
|
+
const seen = new Set;
|
|
108058
|
+
const fileBuffers = [];
|
|
108059
|
+
for (const file2 of files) {
|
|
108060
|
+
const path = normalizeInlinePath(file2.path);
|
|
108061
|
+
if (seen.has(path)) {
|
|
108062
|
+
throw new Error(`duplicate file path in inline source: ${path}`);
|
|
108063
|
+
}
|
|
108064
|
+
seen.add(path);
|
|
108065
|
+
const data2 = Buffer.from(file2.data);
|
|
108066
|
+
fileBuffers.push({ path, size: data2.length, data: data2 });
|
|
108067
|
+
}
|
|
108068
|
+
return buildTarball(fileBuffers);
|
|
108069
|
+
}
|
|
108070
|
+
|
|
108071
|
+
// ../core/dist/watcher.js
|
|
108072
|
+
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
108073
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, watch } from "node:fs";
|
|
108074
|
+
import { join as join11, sep as sep3 } from "node:path";
|
|
108075
|
+
var DEFAULT_IGNORE2 = [
|
|
108076
|
+
"node_modules",
|
|
108077
|
+
".git",
|
|
108078
|
+
"dist",
|
|
108079
|
+
".next",
|
|
108080
|
+
".svelte-kit",
|
|
108081
|
+
"build",
|
|
108082
|
+
"coverage",
|
|
108083
|
+
".env",
|
|
108084
|
+
".env.*",
|
|
108085
|
+
".bun",
|
|
108086
|
+
"*.log",
|
|
108087
|
+
".DS_Store",
|
|
108088
|
+
"__pycache__"
|
|
108089
|
+
];
|
|
108090
|
+
function createWatcher(options) {
|
|
108091
|
+
const debounceMs = options.debounceMs ?? 500;
|
|
108092
|
+
const ig = import_ignore2.default();
|
|
108093
|
+
ig.add(DEFAULT_IGNORE2);
|
|
108094
|
+
const gitignorePath = join11(options.cwd, ".gitignore");
|
|
108095
|
+
if (existsSync7(gitignorePath)) {
|
|
108096
|
+
ig.add(readFileSync7(gitignorePath, "utf8"));
|
|
108097
|
+
}
|
|
108098
|
+
if (options.extraIgnore)
|
|
108099
|
+
ig.add(options.extraIgnore);
|
|
108100
|
+
let timer = null;
|
|
108101
|
+
let pending = new Set;
|
|
108102
|
+
const fsWatcher = watch(options.cwd, { recursive: true }, (_event, filename) => {
|
|
108103
|
+
if (!filename)
|
|
108104
|
+
return;
|
|
108105
|
+
const rel = filename.split(sep3).join("/");
|
|
108106
|
+
if (ig.ignores(rel))
|
|
108107
|
+
return;
|
|
108108
|
+
pending.add(rel);
|
|
108109
|
+
if (timer)
|
|
108110
|
+
clearTimeout(timer);
|
|
108111
|
+
timer = setTimeout(() => {
|
|
108112
|
+
const files = [...pending];
|
|
108113
|
+
pending = new Set;
|
|
108114
|
+
timer = null;
|
|
108115
|
+
if (files.length > 0) {
|
|
108116
|
+
options.onChange(files);
|
|
108117
|
+
}
|
|
108118
|
+
}, debounceMs);
|
|
108119
|
+
});
|
|
108015
108120
|
return {
|
|
108016
|
-
|
|
108017
|
-
|
|
108018
|
-
|
|
108019
|
-
|
|
108020
|
-
|
|
108021
|
-
cause: mapping.cause,
|
|
108022
|
-
relevantFiles: [],
|
|
108023
|
-
missingEnvVars: []
|
|
108121
|
+
close() {
|
|
108122
|
+
if (timer)
|
|
108123
|
+
clearTimeout(timer);
|
|
108124
|
+
fsWatcher.close();
|
|
108125
|
+
}
|
|
108024
108126
|
};
|
|
108025
108127
|
}
|
|
108026
|
-
function trimMessage(raw) {
|
|
108027
|
-
const firstLine = raw.split(`
|
|
108028
|
-
`)[0] ?? "";
|
|
108029
|
-
return firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine;
|
|
108030
|
-
}
|
|
108031
108128
|
|
|
108032
|
-
// ../core/dist/commands/
|
|
108033
|
-
|
|
108034
|
-
|
|
108129
|
+
// ../core/dist/commands/dev.js
|
|
108130
|
+
var devInputSchema = exports_external.object({
|
|
108131
|
+
app: exports_external.string().optional()
|
|
108132
|
+
});
|
|
108133
|
+
async function loadConfig2(ctx, appOverride) {
|
|
108134
|
+
const tomlPath = join12(ctx.cwd, "percher.toml");
|
|
108135
|
+
if (!existsSync8(tomlPath)) {
|
|
108136
|
+
throw new PercherCoreError("No percher.toml found. Run: percher init", {
|
|
108137
|
+
code: "dev.no-app",
|
|
108138
|
+
hint: "Create a percher.toml first with `percher init`."
|
|
108139
|
+
});
|
|
108140
|
+
}
|
|
108141
|
+
const config2 = await parseFile(tomlPath);
|
|
108142
|
+
if (appOverride)
|
|
108143
|
+
return { ...config2, app: { ...config2.app, name: appOverride } };
|
|
108144
|
+
return config2;
|
|
108035
108145
|
}
|
|
108036
|
-
async function
|
|
108037
|
-
let buildLogTail;
|
|
108038
|
-
let buildLogFetchFailed = false;
|
|
108039
|
-
let fullLog = "";
|
|
108146
|
+
async function ensureApp(ctx, config2) {
|
|
108040
108147
|
try {
|
|
108041
|
-
|
|
108042
|
-
|
|
108043
|
-
|
|
108044
|
-
|
|
108045
|
-
|
|
108148
|
+
return await ctx.client.apps.get(config2.app.name);
|
|
108149
|
+
} catch (err) {
|
|
108150
|
+
if (err.code === "APP_NOT_FOUND") {
|
|
108151
|
+
return ctx.client.apps.create({
|
|
108152
|
+
name: config2.app.name,
|
|
108153
|
+
runtime: config2.app.runtime,
|
|
108154
|
+
framework: config2.app.framework
|
|
108155
|
+
});
|
|
108156
|
+
}
|
|
108157
|
+
throw err;
|
|
108158
|
+
}
|
|
108159
|
+
}
|
|
108160
|
+
async function doPush(ctx, config2, callbacks) {
|
|
108161
|
+
const tarball = await createTarball({ cwd: ctx.cwd, config: config2 });
|
|
108162
|
+
const app = await ensureApp(ctx, config2);
|
|
108163
|
+
await callbacks.writeLine(`Pushing ${tarball.fileCount} files (${(tarball.bytes / 1024).toFixed(0)} KB)...`);
|
|
108164
|
+
const deployResponse = await ctx.client.apps.deploy(app.id, { tarball: tarball.stream });
|
|
108165
|
+
if (deployResponse.status === "already_in_progress") {
|
|
108166
|
+
const active = deployResponse;
|
|
108167
|
+
await callbacks.writeLine(` Skipped — deploy already in progress (${active.deployId}, ${active.deployStatus}).`);
|
|
108168
|
+
return { success: false, appId: app.id };
|
|
108169
|
+
}
|
|
108170
|
+
let deployment = deployResponse;
|
|
108171
|
+
const startedAt = Date.now();
|
|
108172
|
+
const timeoutMs = TIMEOUTS.clientPublishPoll;
|
|
108173
|
+
const pollInterval = 2000;
|
|
108174
|
+
let lastStatus = "";
|
|
108175
|
+
while (deployment.status !== "live" && deployment.status !== "failed") {
|
|
108176
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
108177
|
+
await callbacks.writeLine("Deploy timed out");
|
|
108178
|
+
return { success: false, appId: app.id };
|
|
108179
|
+
}
|
|
108180
|
+
if (callbacks.signal.aborted)
|
|
108181
|
+
return { success: false, appId: app.id };
|
|
108182
|
+
if (deployment.status !== lastStatus) {
|
|
108183
|
+
lastStatus = deployment.status;
|
|
108184
|
+
await callbacks.writeLine(` ${deployment.status}...`);
|
|
108185
|
+
}
|
|
108186
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
108187
|
+
deployment = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
108188
|
+
}
|
|
108189
|
+
if (deployment.status === "failed") {
|
|
108190
|
+
try {
|
|
108191
|
+
const insp = await ctx.client.apps.getDeployment(app.id, deployment.id);
|
|
108192
|
+
if (insp.errorMessage) {
|
|
108193
|
+
await callbacks.writeLine("Deploy failed:");
|
|
108194
|
+
for (const line of insp.errorMessage.split(`
|
|
108195
|
+
`).slice(0, 30)) {
|
|
108196
|
+
await callbacks.writeLine(` ${line}`);
|
|
108197
|
+
}
|
|
108198
|
+
} else {
|
|
108199
|
+
await callbacks.writeLine("Deploy failed.");
|
|
108046
108200
|
}
|
|
108201
|
+
} catch {
|
|
108202
|
+
await callbacks.writeLine("Deploy failed.");
|
|
108047
108203
|
}
|
|
108048
|
-
|
|
108049
|
-
|
|
108050
|
-
`);
|
|
108051
|
-
|
|
108052
|
-
|
|
108204
|
+
return { success: false, appId: app.id };
|
|
108205
|
+
}
|
|
108206
|
+
await callbacks.writeLine(`Live at ${deployment.url ?? ""}`);
|
|
108207
|
+
return { success: true, appId: app.id };
|
|
108208
|
+
}
|
|
108209
|
+
function formatLogLine(l) {
|
|
108210
|
+
const level = l.level.toUpperCase().padEnd(5);
|
|
108211
|
+
const src = l.source === "runtime" ? "" : `[${l.source}] `;
|
|
108212
|
+
return ` ${src}${level} ${l.message}`;
|
|
108213
|
+
}
|
|
108214
|
+
async function streamLogs(ctx, appId, callbacks, signal) {
|
|
108215
|
+
try {
|
|
108216
|
+
for await (const line of ctx.client.logs.stream(appId, { signal })) {
|
|
108217
|
+
if (signal.aborted)
|
|
108218
|
+
return;
|
|
108219
|
+
await callbacks.writeLine(formatLogLine(line));
|
|
108053
108220
|
}
|
|
108054
|
-
} catch {
|
|
108055
|
-
|
|
108221
|
+
} catch (err) {
|
|
108222
|
+
if (signal.aborted)
|
|
108223
|
+
return;
|
|
108224
|
+
await callbacks.writeLine(`Log stream error: ${err.message}`);
|
|
108056
108225
|
}
|
|
108057
|
-
|
|
108058
|
-
|
|
108059
|
-
const
|
|
108060
|
-
const
|
|
108061
|
-
|
|
108062
|
-
|
|
108063
|
-
|
|
108064
|
-
|
|
108065
|
-
|
|
108066
|
-
|
|
108067
|
-
|
|
108068
|
-
|
|
108069
|
-
|
|
108070
|
-
|
|
108071
|
-
|
|
108072
|
-
|
|
108073
|
-
|
|
108074
|
-
|
|
108226
|
+
}
|
|
108227
|
+
async function dev(ctx, input, callbacks) {
|
|
108228
|
+
const config2 = await loadConfig2(ctx, input.app);
|
|
108229
|
+
const app = await ensureApp(ctx, config2);
|
|
108230
|
+
await callbacks.writeLine(`App: ${app.url}`);
|
|
108231
|
+
await callbacks.writeLine("Watching for changes (logs streaming below)...");
|
|
108232
|
+
await callbacks.writeLine("");
|
|
108233
|
+
const logStream = { ctrl: null };
|
|
108234
|
+
function startLogStream(appId) {
|
|
108235
|
+
logStream.ctrl?.abort();
|
|
108236
|
+
logStream.ctrl = new AbortController;
|
|
108237
|
+
const linkedSignal = logStream.ctrl.signal;
|
|
108238
|
+
streamLogs(ctx, appId, callbacks, linkedSignal);
|
|
108239
|
+
}
|
|
108240
|
+
startLogStream(app.id);
|
|
108241
|
+
let busy = false;
|
|
108242
|
+
let queued = null;
|
|
108243
|
+
async function handleChange(files) {
|
|
108244
|
+
if (busy) {
|
|
108245
|
+
queued = files;
|
|
108246
|
+
return;
|
|
108247
|
+
}
|
|
108248
|
+
busy = true;
|
|
108249
|
+
try {
|
|
108250
|
+
const shortList = files.length <= 3 ? files.join(", ") : `${files.slice(0, 3).join(", ")} +${files.length - 3} more`;
|
|
108251
|
+
await callbacks.writeLine(`Change detected: ${shortList}`);
|
|
108252
|
+
logStream.ctrl?.abort();
|
|
108253
|
+
const shouldPush = await callbacks.onFilesChanged(files);
|
|
108254
|
+
if (shouldPush) {
|
|
108255
|
+
try {
|
|
108256
|
+
const freshConfig = await loadConfig2(ctx, input.app);
|
|
108257
|
+
const result = await doPush(ctx, freshConfig, callbacks);
|
|
108258
|
+
if (result.success) {
|
|
108259
|
+
startLogStream(result.appId);
|
|
108260
|
+
} else {
|
|
108261
|
+
startLogStream(app.id);
|
|
108262
|
+
}
|
|
108263
|
+
} catch (err) {
|
|
108264
|
+
await callbacks.writeLine(`Push error: ${err.message}`);
|
|
108265
|
+
startLogStream(app.id);
|
|
108266
|
+
}
|
|
108267
|
+
} else {
|
|
108268
|
+
startLogStream(app.id);
|
|
108269
|
+
}
|
|
108270
|
+
} finally {
|
|
108271
|
+
busy = false;
|
|
108272
|
+
if (queued) {
|
|
108273
|
+
const next = queued;
|
|
108274
|
+
queued = null;
|
|
108275
|
+
await handleChange(next);
|
|
108276
|
+
}
|
|
108277
|
+
}
|
|
108075
108278
|
}
|
|
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"
|
|
108279
|
+
const devConfig = config2.dev;
|
|
108280
|
+
const watcher = createWatcher({
|
|
108281
|
+
cwd: ctx.cwd,
|
|
108282
|
+
extraIgnore: devConfig?.ignore,
|
|
108283
|
+
debounceMs: devConfig?.debounce,
|
|
108284
|
+
onChange(files) {
|
|
108285
|
+
handleChange(files);
|
|
108286
|
+
}
|
|
108102
108287
|
});
|
|
108103
|
-
|
|
108104
|
-
|
|
108105
|
-
|
|
108106
|
-
|
|
108107
|
-
|
|
108288
|
+
try {
|
|
108289
|
+
await new Promise((resolve7) => {
|
|
108290
|
+
if (callbacks.signal.aborted) {
|
|
108291
|
+
resolve7();
|
|
108292
|
+
return;
|
|
108293
|
+
}
|
|
108294
|
+
callbacks.signal.addEventListener("abort", () => resolve7(), { once: true });
|
|
108295
|
+
});
|
|
108296
|
+
} finally {
|
|
108297
|
+
logStream.ctrl?.abort();
|
|
108298
|
+
watcher.close();
|
|
108299
|
+
}
|
|
108108
108300
|
}
|
|
108109
|
-
|
|
108110
|
-
|
|
108111
|
-
|
|
108112
|
-
|
|
108113
|
-
|
|
108114
|
-
|
|
108115
|
-
|
|
108301
|
+
// ../core/dist/commands/diagnose.js
|
|
108302
|
+
var diagnoseInputSchema = exports_external.object({
|
|
108303
|
+
app: exports_external.string().optional(),
|
|
108304
|
+
logLines: exports_external.number().int().min(0).max(200).optional(),
|
|
108305
|
+
recencyHours: exports_external.number().positive().max(24 * 30).optional()
|
|
108306
|
+
});
|
|
108307
|
+
// ../core/dist/commands/diff.js
|
|
108308
|
+
var diffInputSchema = exports_external.object({
|
|
108309
|
+
app: exports_external.string().optional(),
|
|
108310
|
+
base: exports_external.string().optional(),
|
|
108311
|
+
head: exports_external.string().optional(),
|
|
108312
|
+
patch: exports_external.boolean().optional()
|
|
108313
|
+
});
|
|
108314
|
+
async function diff(ctx, input = {}) {
|
|
108315
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
108316
|
+
if (!name) {
|
|
108317
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
108318
|
+
code: "diff.no-app",
|
|
108319
|
+
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
108320
|
+
});
|
|
108321
|
+
}
|
|
108322
|
+
return ctx.client.apps.getChanges(name, {
|
|
108323
|
+
base: input.base,
|
|
108324
|
+
head: input.head,
|
|
108325
|
+
patch: input.patch
|
|
108116
108326
|
});
|
|
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
108327
|
}
|
|
108328
|
+
// ../core/dist/commands/doctor.js
|
|
108329
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
108330
|
+
import { join as join13 } from "node:path";
|
|
108130
108331
|
|
|
108131
108332
|
// ../core/dist/commands/wait-deploy.js
|
|
108132
108333
|
var waitForDeployInputSchema = exports_external.object({
|
|
@@ -109790,12 +109991,12 @@ import { basename as basename3, join as join17 } from "node:path";
|
|
|
109790
109991
|
// ../core/dist/detect.js
|
|
109791
109992
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
109792
109993
|
import { join as join16 } from "node:path";
|
|
109793
|
-
function
|
|
109794
|
-
|
|
109795
|
-
|
|
109796
|
-
|
|
109994
|
+
function readPackageJson(src) {
|
|
109995
|
+
const raw = src.text("package.json");
|
|
109996
|
+
if (raw === null)
|
|
109997
|
+
return null;
|
|
109797
109998
|
try {
|
|
109798
|
-
return JSON.parse(
|
|
109999
|
+
return JSON.parse(raw);
|
|
109799
110000
|
} catch {
|
|
109800
110001
|
return null;
|
|
109801
110002
|
}
|
|
@@ -109807,8 +110008,8 @@ function hasDependency(pkg, name) {
|
|
|
109807
110008
|
const dev2 = pkg.devDependencies ?? {};
|
|
109808
110009
|
return name in deps || name in dev2;
|
|
109809
110010
|
}
|
|
109810
|
-
function detectDataMode(pkg,
|
|
109811
|
-
if (
|
|
110011
|
+
function detectDataMode(pkg, src) {
|
|
110012
|
+
if (src.has("convex.json", "convex/_generated")) {
|
|
109812
110013
|
return { mode: "convex" };
|
|
109813
110014
|
}
|
|
109814
110015
|
if (hasDependency(pkg, "convex")) {
|
|
@@ -109819,36 +110020,35 @@ function detectDataMode(pkg, cwd) {
|
|
|
109819
110020
|
}
|
|
109820
110021
|
return { mode: "none" };
|
|
109821
110022
|
}
|
|
109822
|
-
|
|
109823
|
-
const
|
|
109824
|
-
|
|
109825
|
-
if (exists(cwd, "Dockerfile")) {
|
|
110023
|
+
function detectFromSource(src) {
|
|
110024
|
+
const pkg = readPackageJson(src);
|
|
110025
|
+
if (src.has("Dockerfile")) {
|
|
109826
110026
|
return {
|
|
109827
110027
|
runtime: "docker",
|
|
109828
110028
|
framework: "docker",
|
|
109829
110029
|
web: { port: 8080, health: "/health" },
|
|
109830
|
-
data: detectDataMode(pkg,
|
|
110030
|
+
data: detectDataMode(pkg, src)
|
|
109831
110031
|
};
|
|
109832
110032
|
}
|
|
109833
|
-
if (
|
|
110033
|
+
if (src.has("next.config.js", "next.config.ts", "next.config.mjs")) {
|
|
109834
110034
|
return {
|
|
109835
110035
|
runtime: "node",
|
|
109836
110036
|
framework: "nextjs",
|
|
109837
110037
|
build: { command: "bun run build", output: ".next" },
|
|
109838
110038
|
web: { port: 3000, health: "/api/health" },
|
|
109839
|
-
data: detectDataMode(pkg,
|
|
110039
|
+
data: detectDataMode(pkg, src)
|
|
109840
110040
|
};
|
|
109841
110041
|
}
|
|
109842
|
-
if (
|
|
110042
|
+
if (src.has("svelte.config.js", "svelte.config.ts")) {
|
|
109843
110043
|
return {
|
|
109844
110044
|
runtime: "node",
|
|
109845
110045
|
framework: "sveltekit",
|
|
109846
110046
|
build: { command: "bun run build", output: "build" },
|
|
109847
110047
|
web: { port: 3000, health: "/health" },
|
|
109848
|
-
data: detectDataMode(pkg,
|
|
110048
|
+
data: detectDataMode(pkg, src)
|
|
109849
110049
|
};
|
|
109850
110050
|
}
|
|
109851
|
-
if (
|
|
110051
|
+
if (src.has("astro.config.mjs", "astro.config.ts", "astro.config.js")) {
|
|
109852
110052
|
const ssr = hasDependency(pkg, "@astrojs/node");
|
|
109853
110053
|
if (ssr) {
|
|
109854
110054
|
return {
|
|
@@ -109856,7 +110056,7 @@ async function detectFramework(cwd) {
|
|
|
109856
110056
|
framework: "astro",
|
|
109857
110057
|
build: { command: "bun run build", output: "dist" },
|
|
109858
110058
|
web: { port: 4321, health: "/health" },
|
|
109859
|
-
data: detectDataMode(pkg,
|
|
110059
|
+
data: detectDataMode(pkg, src)
|
|
109860
110060
|
};
|
|
109861
110061
|
}
|
|
109862
110062
|
return {
|
|
@@ -109864,25 +110064,25 @@ async function detectFramework(cwd) {
|
|
|
109864
110064
|
framework: "astro",
|
|
109865
110065
|
web: { port: STATIC_DOCKER_PORT, health: "/" },
|
|
109866
110066
|
staticBuild: { outputDir: "dist", spa: false },
|
|
109867
|
-
data: detectDataMode(pkg,
|
|
110067
|
+
data: detectDataMode(pkg, src)
|
|
109868
110068
|
};
|
|
109869
110069
|
}
|
|
109870
|
-
if (
|
|
110070
|
+
if (src.has("remix.config.js", "remix.config.ts") || hasDependency(pkg, "@remix-run/node")) {
|
|
109871
110071
|
return {
|
|
109872
110072
|
runtime: "node",
|
|
109873
110073
|
framework: "remix",
|
|
109874
110074
|
build: { command: "bun run build", output: "build" },
|
|
109875
110075
|
web: { port: 3000, health: "/health" },
|
|
109876
|
-
data: detectDataMode(pkg,
|
|
110076
|
+
data: detectDataMode(pkg, src)
|
|
109877
110077
|
};
|
|
109878
110078
|
}
|
|
109879
|
-
if (
|
|
110079
|
+
if (src.has("vite.config.js", "vite.config.ts")) {
|
|
109880
110080
|
return {
|
|
109881
110081
|
runtime: "docker",
|
|
109882
110082
|
framework: "vite",
|
|
109883
110083
|
web: { port: STATIC_DOCKER_PORT, health: "/" },
|
|
109884
110084
|
staticBuild: { outputDir: "dist", spa: true },
|
|
109885
|
-
data: detectDataMode(pkg,
|
|
110085
|
+
data: detectDataMode(pkg, src)
|
|
109886
110086
|
};
|
|
109887
110087
|
}
|
|
109888
110088
|
if (hasDependency(pkg, "express")) {
|
|
@@ -109890,7 +110090,7 @@ async function detectFramework(cwd) {
|
|
|
109890
110090
|
runtime: "node",
|
|
109891
110091
|
framework: "express",
|
|
109892
110092
|
web: { port: 3000, health: "/health" },
|
|
109893
|
-
data: detectDataMode(pkg,
|
|
110093
|
+
data: detectDataMode(pkg, src)
|
|
109894
110094
|
};
|
|
109895
110095
|
}
|
|
109896
110096
|
if (hasDependency(pkg, "fastify")) {
|
|
@@ -109898,7 +110098,7 @@ async function detectFramework(cwd) {
|
|
|
109898
110098
|
runtime: "node",
|
|
109899
110099
|
framework: "fastify",
|
|
109900
110100
|
web: { port: 3000, health: "/health" },
|
|
109901
|
-
data: detectDataMode(pkg,
|
|
110101
|
+
data: detectDataMode(pkg, src)
|
|
109902
110102
|
};
|
|
109903
110103
|
}
|
|
109904
110104
|
if (hasDependency(pkg, "hono")) {
|
|
@@ -109906,14 +110106,14 @@ async function detectFramework(cwd) {
|
|
|
109906
110106
|
runtime: "node",
|
|
109907
110107
|
framework: "hono",
|
|
109908
110108
|
web: { port: 3000, health: "/health" },
|
|
109909
|
-
data: detectDataMode(pkg,
|
|
110109
|
+
data: detectDataMode(pkg, src)
|
|
109910
110110
|
};
|
|
109911
110111
|
}
|
|
109912
110112
|
if (pkg) {
|
|
109913
110113
|
const detected = {
|
|
109914
110114
|
runtime: "node",
|
|
109915
110115
|
web: { port: 3000, health: "/health" },
|
|
109916
|
-
data: detectDataMode(pkg,
|
|
110116
|
+
data: detectDataMode(pkg, src)
|
|
109917
110117
|
};
|
|
109918
110118
|
const scripts = pkg.scripts ?? {};
|
|
109919
110119
|
if (scripts.build) {
|
|
@@ -109921,65 +110121,64 @@ async function detectFramework(cwd) {
|
|
|
109921
110121
|
}
|
|
109922
110122
|
return detected;
|
|
109923
110123
|
}
|
|
109924
|
-
const hasPython =
|
|
110124
|
+
const hasPython = src.has("requirements.txt", "pyproject.toml");
|
|
109925
110125
|
if (hasPython) {
|
|
109926
|
-
if (
|
|
110126
|
+
if (src.has("manage.py")) {
|
|
109927
110127
|
return {
|
|
109928
110128
|
runtime: "python",
|
|
109929
110129
|
framework: "django",
|
|
109930
110130
|
web: { port: 8000, health: "/health" },
|
|
109931
|
-
data: detectDataMode(pkg,
|
|
110131
|
+
data: detectDataMode(pkg, src)
|
|
109932
110132
|
};
|
|
109933
110133
|
}
|
|
109934
|
-
const mainPy =
|
|
110134
|
+
const mainPy = src.text("main.py") ?? "";
|
|
109935
110135
|
if (/FastAPI\s*\(/.test(mainPy) || /from\s+fastapi/.test(mainPy)) {
|
|
109936
110136
|
return {
|
|
109937
110137
|
runtime: "python",
|
|
109938
110138
|
framework: "fastapi",
|
|
109939
110139
|
web: { port: 8000, health: "/health" },
|
|
109940
|
-
data: detectDataMode(pkg,
|
|
110140
|
+
data: detectDataMode(pkg, src)
|
|
109941
110141
|
};
|
|
109942
110142
|
}
|
|
109943
|
-
const appPy =
|
|
110143
|
+
const appPy = src.text("app.py") ?? "";
|
|
109944
110144
|
if (/Flask\s*\(/.test(appPy) || /from\s+flask/.test(appPy)) {
|
|
109945
110145
|
return {
|
|
109946
110146
|
runtime: "python",
|
|
109947
110147
|
framework: "flask",
|
|
109948
110148
|
web: { port: 5000, health: "/health" },
|
|
109949
|
-
data: detectDataMode(pkg,
|
|
110149
|
+
data: detectDataMode(pkg, src)
|
|
109950
110150
|
};
|
|
109951
110151
|
}
|
|
109952
110152
|
return {
|
|
109953
110153
|
runtime: "python",
|
|
109954
110154
|
web: { port: 8000, health: "/health" },
|
|
109955
|
-
data: detectDataMode(pkg,
|
|
110155
|
+
data: detectDataMode(pkg, src)
|
|
109956
110156
|
};
|
|
109957
110157
|
}
|
|
109958
|
-
if (
|
|
110158
|
+
if (src.has("index.html")) {
|
|
109959
110159
|
return { runtime: "static" };
|
|
109960
110160
|
}
|
|
109961
110161
|
return null;
|
|
109962
110162
|
}
|
|
110163
|
+
async function detectFramework(cwd) {
|
|
110164
|
+
const src = {
|
|
110165
|
+
has: (...names) => names.some((n) => existsSync12(join16(cwd, n))),
|
|
110166
|
+
text: (name) => {
|
|
110167
|
+
try {
|
|
110168
|
+
return readFileSync9(join16(cwd, name), "utf8");
|
|
110169
|
+
} catch {
|
|
110170
|
+
return null;
|
|
110171
|
+
}
|
|
110172
|
+
}
|
|
110173
|
+
};
|
|
110174
|
+
return detectFromSource(src);
|
|
110175
|
+
}
|
|
109963
110176
|
function refineBunRuntime(detected, cwd) {
|
|
109964
|
-
if (detected.runtime === "node" &&
|
|
110177
|
+
if (detected.runtime === "node" && (existsSync12(join16(cwd, "bun.lockb")) || existsSync12(join16(cwd, "bun.lock")))) {
|
|
109965
110178
|
return { ...detected, runtime: "bun" };
|
|
109966
110179
|
}
|
|
109967
110180
|
return detected;
|
|
109968
110181
|
}
|
|
109969
|
-
|
|
109970
|
-
// ../core/dist/commands/init.js
|
|
109971
|
-
function isSuspiciousAutoName(name) {
|
|
109972
|
-
if (/-\d{8,}$/.test(name))
|
|
109973
|
-
return true;
|
|
109974
|
-
if (/\d{4}-?\d{2}-?\d{2}/.test(name))
|
|
109975
|
-
return true;
|
|
109976
|
-
if (name.length >= 30)
|
|
109977
|
-
return true;
|
|
109978
|
-
return false;
|
|
109979
|
-
}
|
|
109980
|
-
function detectInteractive() {
|
|
109981
|
-
return process.stdin?.isTTY === true && process.stdout?.isTTY === true;
|
|
109982
|
-
}
|
|
109983
110182
|
function buildDataConfig(mode) {
|
|
109984
110183
|
if (mode === "convex") {
|
|
109985
110184
|
return {
|
|
@@ -109995,6 +110194,32 @@ function buildDataConfig(mode) {
|
|
|
109995
110194
|
}
|
|
109996
110195
|
return { mode };
|
|
109997
110196
|
}
|
|
110197
|
+
function configFromDetection(name, detected, dataMode) {
|
|
110198
|
+
return {
|
|
110199
|
+
app: {
|
|
110200
|
+
name,
|
|
110201
|
+
runtime: detected?.runtime ?? "node",
|
|
110202
|
+
...detected?.framework ? { framework: detected.framework } : {}
|
|
110203
|
+
},
|
|
110204
|
+
...detected?.build ? { build: detected.build } : {},
|
|
110205
|
+
...detected?.web ? { web: detected.web } : {},
|
|
110206
|
+
...detected?.data || dataMode ? { data: buildDataConfig(dataMode ?? detected?.data?.mode ?? "none") } : {}
|
|
110207
|
+
};
|
|
110208
|
+
}
|
|
110209
|
+
|
|
110210
|
+
// ../core/dist/commands/init.js
|
|
110211
|
+
function isSuspiciousAutoName(name) {
|
|
110212
|
+
if (/-\d{8,}$/.test(name))
|
|
110213
|
+
return true;
|
|
110214
|
+
if (/\d{4}-?\d{2}-?\d{2}/.test(name))
|
|
110215
|
+
return true;
|
|
110216
|
+
if (name.length >= 30)
|
|
110217
|
+
return true;
|
|
110218
|
+
return false;
|
|
110219
|
+
}
|
|
110220
|
+
function detectInteractive() {
|
|
110221
|
+
return process.stdin?.isTTY === true && process.stdout?.isTTY === true;
|
|
110222
|
+
}
|
|
109998
110223
|
var initInputSchema = exports_external.object({
|
|
109999
110224
|
force: exports_external.boolean().optional(),
|
|
110000
110225
|
name: exports_external.string().optional(),
|
|
@@ -110006,8 +110231,8 @@ function scanEnvVars(_cwd) {
|
|
|
110006
110231
|
}
|
|
110007
110232
|
async function init(ctx, input = {}) {
|
|
110008
110233
|
const path = join17(ctx.cwd, "percher.toml");
|
|
110009
|
-
const
|
|
110010
|
-
if (
|
|
110234
|
+
const exists = existsSync13(path);
|
|
110235
|
+
if (exists && !input.force) {
|
|
110011
110236
|
throw new PercherCoreError("percher.toml already exists", {
|
|
110012
110237
|
code: "init.exists",
|
|
110013
110238
|
hint: "Run with --force to overwrite."
|
|
@@ -110037,18 +110262,7 @@ async function init(ctx, input = {}) {
|
|
|
110037
110262
|
name = suggestion;
|
|
110038
110263
|
}
|
|
110039
110264
|
}
|
|
110040
|
-
const config2 =
|
|
110041
|
-
app: {
|
|
110042
|
-
name,
|
|
110043
|
-
runtime: detected?.runtime ?? "node",
|
|
110044
|
-
...detected?.framework ? { framework: detected.framework } : {}
|
|
110045
|
-
},
|
|
110046
|
-
...detected?.build ? { build: detected.build } : {},
|
|
110047
|
-
...detected?.web ? { web: detected.web } : {},
|
|
110048
|
-
...detected?.data || input.dataMode ? {
|
|
110049
|
-
data: buildDataConfig(input.dataMode ?? detected?.data?.mode ?? "none")
|
|
110050
|
-
} : {}
|
|
110051
|
-
};
|
|
110265
|
+
const config2 = configFromDetection(name, detected, input.dataMode);
|
|
110052
110266
|
const envVars = scanEnvVars(ctx.cwd);
|
|
110053
110267
|
if (Object.keys(envVars).length > 0) {
|
|
110054
110268
|
config2.env = envVars;
|
|
@@ -110080,7 +110294,7 @@ async function init(ctx, input = {}) {
|
|
|
110080
110294
|
path,
|
|
110081
110295
|
config: config2,
|
|
110082
110296
|
detected,
|
|
110083
|
-
overwritten:
|
|
110297
|
+
overwritten: exists,
|
|
110084
110298
|
scaffoldedFiles
|
|
110085
110299
|
};
|
|
110086
110300
|
}
|
|
@@ -110225,6 +110439,7 @@ async function logs(ctx, input = {}) {
|
|
|
110225
110439
|
}
|
|
110226
110440
|
// ../core/dist/commands/mcp.js
|
|
110227
110441
|
var mcpInputSchema = exports_external.object({});
|
|
110442
|
+
var HOSTED_MCP_URL = "https://mcp.percher.app/mcp";
|
|
110228
110443
|
function mcp(_ctx, _input = {}) {
|
|
110229
110444
|
return {
|
|
110230
110445
|
config: {
|
|
@@ -110235,19 +110450,43 @@ function mcp(_ctx, _input = {}) {
|
|
|
110235
110450
|
}
|
|
110236
110451
|
}
|
|
110237
110452
|
},
|
|
110453
|
+
hosted: {
|
|
110454
|
+
config: {
|
|
110455
|
+
mcpServers: {
|
|
110456
|
+
percher: {
|
|
110457
|
+
transport: "http",
|
|
110458
|
+
url: HOSTED_MCP_URL
|
|
110459
|
+
}
|
|
110460
|
+
}
|
|
110461
|
+
},
|
|
110462
|
+
url: HOSTED_MCP_URL,
|
|
110463
|
+
auth: "oauth"
|
|
110464
|
+
},
|
|
110238
110465
|
installLocations: [
|
|
110239
110466
|
{
|
|
110240
110467
|
client: "Claude Code",
|
|
110241
|
-
path: "~/.claude.json (under mcpServers, or a per-project .mcp.json)"
|
|
110468
|
+
path: "~/.claude.json (under mcpServers, or a per-project .mcp.json)",
|
|
110469
|
+
note: "Local stdio — recommended for Claude Code (full tool set, incl. publish from local files)."
|
|
110470
|
+
},
|
|
110471
|
+
{
|
|
110472
|
+
client: "Claude.ai (web/desktop)",
|
|
110473
|
+
path: `Settings → Connectors → Add custom connector → ${HOSTED_MCP_URL}`,
|
|
110474
|
+
note: "Hosted — OAuth, no token to paste. Remote-safe subset (operate existing apps + deploy connected repos)."
|
|
110242
110475
|
},
|
|
110243
110476
|
{
|
|
110244
110477
|
client: "Cursor / Windsurf",
|
|
110245
|
-
path: "settings → MCP → add server (
|
|
110478
|
+
path: "settings → MCP → add server (local: the same npx command; hosted: add the URL as a connector)"
|
|
110246
110479
|
},
|
|
110247
110480
|
{
|
|
110248
110481
|
client: "VS Code (Continue / Cline)",
|
|
110249
|
-
path: "extension settings → MCP servers"
|
|
110482
|
+
path: "extension settings → MCP servers (local npx command)"
|
|
110250
110483
|
}
|
|
110484
|
+
],
|
|
110485
|
+
notes: [
|
|
110486
|
+
"Two ways to connect:",
|
|
110487
|
+
" • Local (npx): full tool set, reads your project files — required for first publish from local files. Recommended for Claude Code.",
|
|
110488
|
+
` • 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.`,
|
|
110489
|
+
"Manage or revoke hosted connections from the Percher dashboard."
|
|
110251
110490
|
]
|
|
110252
110491
|
};
|
|
110253
110492
|
}
|
|
@@ -114209,66 +114448,6 @@ function withDuration(label, durationMs) {
|
|
|
114209
114448
|
return `${label} (${seconds}s)`;
|
|
114210
114449
|
}
|
|
114211
114450
|
|
|
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
114451
|
// ../core/dist/publish-retry.js
|
|
114273
114452
|
var NON_RETRYABLE_CODES = new Set([
|
|
114274
114453
|
"WORKER_UNAVAILABLE_PERSISTENT",
|
|
@@ -115282,11 +115461,29 @@ async function executeDeploy(opts) {
|
|
|
115282
115461
|
throw err;
|
|
115283
115462
|
}
|
|
115284
115463
|
}
|
|
115464
|
+
const uploadMs = Date.now() - uploadStart;
|
|
115465
|
+
return finalizeDeploy({
|
|
115466
|
+
ctx,
|
|
115467
|
+
config: config2,
|
|
115468
|
+
input,
|
|
115469
|
+
app,
|
|
115470
|
+
firstDeploy,
|
|
115471
|
+
tarball: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
115472
|
+
deployment,
|
|
115473
|
+
t0,
|
|
115474
|
+
packageMs,
|
|
115475
|
+
uploadMs,
|
|
115476
|
+
missingBuildEnvKeys,
|
|
115477
|
+
cacheReused: cacheReusedDeployment !== null
|
|
115478
|
+
});
|
|
115479
|
+
}
|
|
115480
|
+
async function finalizeDeploy(opts) {
|
|
115481
|
+
const { ctx, config: config2, input, app, firstDeploy, tarball, t0, packageMs, uploadMs, missingBuildEnvKeys, cacheReused } = opts;
|
|
115482
|
+
let deployment = opts.deployment;
|
|
115285
115483
|
const replacedPreview = deployment.replacedPreview === true;
|
|
115286
115484
|
if (replacedPreview) {
|
|
115287
115485
|
ctx.status("Replaced previous preview...");
|
|
115288
115486
|
}
|
|
115289
|
-
const uploadMs = Date.now() - uploadStart;
|
|
115290
115487
|
if (input.waitForLive === false) {
|
|
115291
115488
|
ctx.status(`Queued (${deployment.id}) — resume with percher_wait_for_deploy.`);
|
|
115292
115489
|
return {
|
|
@@ -115426,7 +115623,7 @@ async function executeDeploy(opts) {
|
|
|
115426
115623
|
firstDeploy: firstDeploy || undefined,
|
|
115427
115624
|
cacheHit,
|
|
115428
115625
|
cacheStatus,
|
|
115429
|
-
cacheReused:
|
|
115626
|
+
cacheReused: cacheReused || undefined,
|
|
115430
115627
|
missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
|
|
115431
115628
|
traceId: deployment.id,
|
|
115432
115629
|
recovery: RECOVERY_NONE,
|
|
@@ -115665,6 +115862,27 @@ function emitPreflight(ctx, config2, tarball) {
|
|
|
115665
115862
|
ctx.status(line);
|
|
115666
115863
|
}
|
|
115667
115864
|
}
|
|
115865
|
+
// ../core/dist/commands/publish-from-repo.js
|
|
115866
|
+
var publishFromRepoInputSchema = exports_external.object({
|
|
115867
|
+
url: exports_external.string().min(1).describe("Public https git repo URL to clone and deploy (one-shot — no webhook is created)."),
|
|
115868
|
+
name: exports_external.string().optional().describe("App name. Overrides the repo's percher.toml [app].name; required if the repo omits it."),
|
|
115869
|
+
branch: exports_external.string().optional().describe("Branch to deploy. Defaults to main."),
|
|
115870
|
+
note: exports_external.string().optional().describe("Optional note recorded against the deploy."),
|
|
115871
|
+
waitForLive: exports_external.boolean().optional().describe("If false (default on the hosted connector), returns once the deploy is queued — resume with percher_wait_for_deploy. If true, block until live or failed.")
|
|
115872
|
+
});
|
|
115873
|
+
// ../core/dist/commands/publish-inline.js
|
|
115874
|
+
var publishInlineInputSchema = exports_external.object({
|
|
115875
|
+
name: exports_external.string().optional().describe("App name. Overrides [app].name when percher.toml is present; REQUIRED when no percher.toml is provided (so the auto-detected app can be named)."),
|
|
115876
|
+
files: exports_external.array(exports_external.object({
|
|
115877
|
+
path: exports_external.string().min(1).describe("Repo-relative path (no leading / and no ..)."),
|
|
115878
|
+
content: exports_external.string().describe("File contents — UTF-8 text, or base64-encoded bytes when encoding is 'base64'."),
|
|
115879
|
+
encoding: exports_external.enum(["utf8", "base64"]).optional().describe("Content encoding. Default 'utf8' (text). Use 'base64' for binary assets (images, fonts).")
|
|
115880
|
+
})).min(1).describe("Inline source files. Include a percher.toml ([app].name + runtime), or omit it and Percher auto-detects the runtime from the files (then `name` is required)."),
|
|
115881
|
+
preview: exports_external.boolean().optional().describe("Deploy as a preview (does not replace the live version)."),
|
|
115882
|
+
message: exports_external.string().optional().describe("Deploy note (visible in deploy history)."),
|
|
115883
|
+
noCache: exports_external.boolean().optional().describe("Skip the image cache and force a fresh build."),
|
|
115884
|
+
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.")
|
|
115885
|
+
});
|
|
115668
115886
|
// ../core/dist/commands/push.js
|
|
115669
115887
|
import { existsSync as existsSync16 } from "node:fs";
|
|
115670
115888
|
import { join as join25 } from "node:path";
|
|
@@ -117314,13 +117532,23 @@ function registerAccountCommands(cli, ctx) {
|
|
|
117314
117532
|
return;
|
|
117315
117533
|
}
|
|
117316
117534
|
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
117535
|
await writeLine(ctx, "");
|
|
117536
|
+
await writeLine(ctx, import_picocolors2.default.bold("Local (npx) — full tool set, reads your project files:"));
|
|
117319
117537
|
await writeLine(ctx, JSON.stringify(result.config, null, 2));
|
|
117320
117538
|
await writeLine(ctx, "");
|
|
117539
|
+
await writeLine(ctx, import_picocolors2.default.bold("Hosted connector — add this URL, OAuth login in the browser (no token):"));
|
|
117540
|
+
await writeLine(ctx, ` ${result.hosted.url}`);
|
|
117541
|
+
await writeLine(ctx, "");
|
|
117321
117542
|
await writeLine(ctx, import_picocolors2.default.bold("Where to paste:"));
|
|
117322
117543
|
for (const loc of result.installLocations) {
|
|
117323
117544
|
await writeLine(ctx, ` ${loc.client.padEnd(28)} ${import_picocolors2.default.dim(loc.path)}`);
|
|
117545
|
+
if (loc.note) {
|
|
117546
|
+
await writeLine(ctx, ` ${"".padEnd(28)} ${import_picocolors2.default.dim(loc.note)}`);
|
|
117547
|
+
}
|
|
117548
|
+
}
|
|
117549
|
+
await writeLine(ctx, "");
|
|
117550
|
+
for (const note of result.notes) {
|
|
117551
|
+
await writeLine(ctx, import_picocolors2.default.dim(note));
|
|
117324
117552
|
}
|
|
117325
117553
|
await writeLine(ctx, "");
|
|
117326
117554
|
await writeLine(ctx, import_picocolors2.default.dim("Once installed, your assistant gets percher_publish + 28 other tools."));
|
|
@@ -118044,7 +118272,7 @@ function registerAuthCommands(cli, ctx) {
|
|
|
118044
118272
|
// src/commands/data.ts
|
|
118045
118273
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
118046
118274
|
function registerDataCommands(cli, ctx, argv) {
|
|
118047
|
-
cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").action(async (app, _rest, options) => {
|
|
118275
|
+
cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "(oauth only) target app (default: read percher.toml)").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret").option("--remove", "(oauth only) remove the provider instead of setting it").action(async (app, _rest, options) => {
|
|
118048
118276
|
if (app === "reset-superuser") {
|
|
118049
118277
|
const idx = argv.indexOf("reset-superuser");
|
|
118050
118278
|
const realApp = idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined;
|
|
@@ -118065,6 +118293,37 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
118065
118293
|
await writeLine(ctx, ` Size: ${mb} MB`);
|
|
118066
118294
|
return;
|
|
118067
118295
|
}
|
|
118296
|
+
if (app === "oauth") {
|
|
118297
|
+
const idx = argv.indexOf("oauth");
|
|
118298
|
+
const provider = (idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined) ?? options.provider ?? "google";
|
|
118299
|
+
const result2 = await dataOauth(ctx, {
|
|
118300
|
+
app: options.app,
|
|
118301
|
+
provider,
|
|
118302
|
+
clientId: options.clientId,
|
|
118303
|
+
clientSecret: options.clientSecret,
|
|
118304
|
+
remove: options.remove
|
|
118305
|
+
});
|
|
118306
|
+
if (result2.action === "removed") {
|
|
118307
|
+
await writeLine(ctx, `${import_picocolors6.default.green("✓")} Removed ${result2.provider} sign-in.`);
|
|
118308
|
+
} else if (result2.action === "set") {
|
|
118309
|
+
await writeLine(ctx, `${import_picocolors6.default.green("✓")} ${result2.provider} sign-in enabled.`);
|
|
118310
|
+
await writeLine(ctx, "");
|
|
118311
|
+
await writeLine(ctx, import_picocolors6.default.bold("One more step — add this redirect URI in the provider console:"));
|
|
118312
|
+
await writeLine(ctx, ` ${result2.redirectUri}`);
|
|
118313
|
+
} else {
|
|
118314
|
+
if (!result2.providers?.length) {
|
|
118315
|
+
await writeLine(ctx, import_picocolors6.default.dim("No OAuth providers configured."));
|
|
118316
|
+
} else {
|
|
118317
|
+
await writeLine(ctx, import_picocolors6.default.bold("Configured providers"));
|
|
118318
|
+
for (const p of result2.providers) {
|
|
118319
|
+
await writeLine(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors6.default.dim(p.clientId)}`);
|
|
118320
|
+
}
|
|
118321
|
+
}
|
|
118322
|
+
await writeLine(ctx, "");
|
|
118323
|
+
await writeLine(ctx, `Redirect URI: ${result2.redirectUri}`);
|
|
118324
|
+
}
|
|
118325
|
+
return;
|
|
118326
|
+
}
|
|
118068
118327
|
const result = await data(ctx, { app, json: options.json });
|
|
118069
118328
|
if (options.json) {
|
|
118070
118329
|
await writeLine(ctx, JSON.stringify(result, null, 2));
|
|
@@ -118094,6 +118353,7 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
118094
118353
|
const result = await resetSuperuser(ctx, { app, reveal: options.reveal });
|
|
118095
118354
|
await printSuperuserResetResult(ctx, result);
|
|
118096
118355
|
});
|
|
118356
|
+
cli.command("data oauth [provider]", "Configure capsule OAuth sign-in (e.g. `data oauth google --client-id … --client-secret …`)").action(async () => {});
|
|
118097
118357
|
cli.command("data export [app]", "Download a snapshot of your app's PocketBase data as a .tar.gz").action(async () => {});
|
|
118098
118358
|
cli.command("backups [app]", "List available backups for an app").action(async (app) => {
|
|
118099
118359
|
const { backups, skipped } = await listBackups(ctx, { app });
|
|
@@ -119048,7 +119308,7 @@ async function renderError(stderr, error48) {
|
|
|
119048
119308
|
// package.json
|
|
119049
119309
|
var package_default = {
|
|
119050
119310
|
name: "percher",
|
|
119051
|
-
version: "0.5.
|
|
119311
|
+
version: "0.5.12",
|
|
119052
119312
|
description: "CLI for the Percher hosting platform — publish apps with one command",
|
|
119053
119313
|
type: "module",
|
|
119054
119314
|
bin: {
|
|
@@ -119072,8 +119332,8 @@ var package_default = {
|
|
|
119072
119332
|
typecheck: "tsc --noEmit"
|
|
119073
119333
|
},
|
|
119074
119334
|
dependencies: {
|
|
119075
|
-
"@percher/client": "^0.2.
|
|
119076
|
-
"@percher/core": "^0.4.
|
|
119335
|
+
"@percher/client": "^0.2.16",
|
|
119336
|
+
"@percher/core": "^0.4.16",
|
|
119077
119337
|
"@percher/toml": "^0.1.5",
|
|
119078
119338
|
cac: "^7.0.0",
|
|
119079
119339
|
picocolors: "^1.1.1"
|