percher 0.5.20 → 0.5.21
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 +1001 -554
- package/dist/package.json +4 -4
- package/dist/src/cli-utils.d.ts +18 -0
- package/dist/src/cli-utils.d.ts.map +1 -1
- package/dist/src/cli-utils.js +80 -1
- package/dist/src/cli-utils.js.map +1 -1
- package/dist/src/commands/apps.d.ts.map +1 -1
- package/dist/src/commands/apps.js +14 -0
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/deploys.d.ts.map +1 -1
- package/dist/src/commands/deploys.js +42 -6
- package/dist/src/commands/deploys.js.map +1 -1
- package/dist/src/commands/env.d.ts.map +1 -1
- package/dist/src/commands/env.js +28 -7
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/hooks.d.ts +4 -0
- package/dist/src/commands/hooks.d.ts.map +1 -0
- package/dist/src/commands/hooks.js +80 -0
- package/dist/src/commands/hooks.js.map +1 -0
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +1 -0
- package/dist/src/context.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -4
package/dist/bin/percher.js
CHANGED
|
@@ -2511,7 +2511,9 @@ class AppsResource {
|
|
|
2511
2511
|
}
|
|
2512
2512
|
async deploy(id, input) {
|
|
2513
2513
|
const params = new URLSearchParams;
|
|
2514
|
-
if (input.
|
|
2514
|
+
if (input.check)
|
|
2515
|
+
params.set("check", "1");
|
|
2516
|
+
else if (input.type === "preview")
|
|
2515
2517
|
params.set("type", "preview");
|
|
2516
2518
|
if (input.note)
|
|
2517
2519
|
params.set("note", input.note);
|
|
@@ -2536,7 +2538,9 @@ class AppsResource {
|
|
|
2536
2538
|
params.set("runtime", input.runtime);
|
|
2537
2539
|
if (input.framework)
|
|
2538
2540
|
params.set("framework", input.framework);
|
|
2539
|
-
if (input.
|
|
2541
|
+
if (input.check)
|
|
2542
|
+
params.set("check", "1");
|
|
2543
|
+
else if (input.type === "preview")
|
|
2540
2544
|
params.set("type", "preview");
|
|
2541
2545
|
if (input.note)
|
|
2542
2546
|
params.set("note", input.note);
|
|
@@ -2550,8 +2554,8 @@ class AppsResource {
|
|
|
2550
2554
|
promotePreview(appRef, deployId) {
|
|
2551
2555
|
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/${encodeURIComponent(deployId)}/promote`, {});
|
|
2552
2556
|
}
|
|
2553
|
-
rerunDeploy(appRef, deployId) {
|
|
2554
|
-
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/${encodeURIComponent(deployId)}/rerun`, {});
|
|
2557
|
+
rerunDeploy(appRef, deployId, opts) {
|
|
2558
|
+
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/${encodeURIComponent(deployId)}/rerun`, opts?.noCache ? { noCache: true } : {});
|
|
2555
2559
|
}
|
|
2556
2560
|
deployLatestCommit(appRef, input) {
|
|
2557
2561
|
return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/from-latest-commit`, input ?? {});
|
|
@@ -2681,6 +2685,15 @@ class AppsResource {
|
|
|
2681
2685
|
commitSha
|
|
2682
2686
|
});
|
|
2683
2687
|
}
|
|
2688
|
+
async lastGoodVersion(appRef) {
|
|
2689
|
+
try {
|
|
2690
|
+
return await this.http.get(`/apps/${encodeURIComponent(appRef)}/rollback/last-good`);
|
|
2691
|
+
} catch (err) {
|
|
2692
|
+
if (err instanceof PercherApiError && err.status === 404)
|
|
2693
|
+
return null;
|
|
2694
|
+
throw err;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2684
2697
|
getChanges(appRef, opts = {}) {
|
|
2685
2698
|
const params = new URLSearchParams;
|
|
2686
2699
|
if (opts.base)
|
|
@@ -2893,6 +2906,10 @@ class DataResource {
|
|
|
2893
2906
|
}
|
|
2894
2907
|
|
|
2895
2908
|
// ../client/dist/resources/env.js
|
|
2909
|
+
function scopeQuery(opts) {
|
|
2910
|
+
return opts?.scope && opts.scope !== "all" ? `?scope=${opts.scope}` : "";
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2896
2913
|
class EnvResource {
|
|
2897
2914
|
http;
|
|
2898
2915
|
constructor(http) {
|
|
@@ -2901,11 +2918,15 @@ class EnvResource {
|
|
|
2901
2918
|
list(appId) {
|
|
2902
2919
|
return this.http.get(`/apps/${encodeURIComponent(appId)}/env`);
|
|
2903
2920
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2921
|
+
async listDetailed(appId) {
|
|
2922
|
+
const res = await this.http.get(`/apps/${encodeURIComponent(appId)}/env?detail=1`);
|
|
2923
|
+
return res.detailed;
|
|
2906
2924
|
}
|
|
2907
|
-
|
|
2908
|
-
return this.http.
|
|
2925
|
+
set(appId, values, opts) {
|
|
2926
|
+
return this.http.put(`/apps/${encodeURIComponent(appId)}/env${scopeQuery(opts)}`, values);
|
|
2927
|
+
}
|
|
2928
|
+
unset(appId, key, opts) {
|
|
2929
|
+
return this.http.delete(`/apps/${encodeURIComponent(appId)}/env/${encodeURIComponent(key)}${scopeQuery(opts)}`);
|
|
2909
2930
|
}
|
|
2910
2931
|
}
|
|
2911
2932
|
|
|
@@ -2949,6 +2970,24 @@ class ExportResource {
|
|
|
2949
2970
|
}
|
|
2950
2971
|
}
|
|
2951
2972
|
|
|
2973
|
+
// ../client/dist/resources/hooks.js
|
|
2974
|
+
class HooksResource {
|
|
2975
|
+
http;
|
|
2976
|
+
constructor(http) {
|
|
2977
|
+
this.http = http;
|
|
2978
|
+
}
|
|
2979
|
+
create(appId, name) {
|
|
2980
|
+
return this.http.post(`/apps/${encodeURIComponent(appId)}/hooks`, { name });
|
|
2981
|
+
}
|
|
2982
|
+
async list(appId) {
|
|
2983
|
+
const res = await this.http.get(`/apps/${encodeURIComponent(appId)}/hooks`);
|
|
2984
|
+
return res.hooks;
|
|
2985
|
+
}
|
|
2986
|
+
revoke(appId, hookId) {
|
|
2987
|
+
return this.http.delete(`/apps/${encodeURIComponent(appId)}/hooks/${encodeURIComponent(hookId)}`);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2952
2991
|
// ../client/dist/sse.js
|
|
2953
2992
|
async function* parseSseStream(stream, signal) {
|
|
2954
2993
|
const reader = stream.getReader();
|
|
@@ -3213,6 +3252,7 @@ class PercherClient {
|
|
|
3213
3252
|
env;
|
|
3214
3253
|
envScan;
|
|
3215
3254
|
export;
|
|
3255
|
+
hooks;
|
|
3216
3256
|
logs;
|
|
3217
3257
|
auth;
|
|
3218
3258
|
billing;
|
|
@@ -3243,6 +3283,7 @@ class PercherClient {
|
|
|
3243
3283
|
this.env = new EnvResource(http);
|
|
3244
3284
|
this.envScan = new EnvScanResource(http);
|
|
3245
3285
|
this.export = new ExportResource(http);
|
|
3286
|
+
this.hooks = new HooksResource(http);
|
|
3246
3287
|
this.logs = new LogsResource(http);
|
|
3247
3288
|
this.auth = new AuthResource(http);
|
|
3248
3289
|
this.billing = new BillingResource(http);
|
|
@@ -105906,9 +105947,39 @@ var webSchema = exports_external.object({
|
|
|
105906
105947
|
port: exports_external.number().int().min(1024).max(65535),
|
|
105907
105948
|
health: exports_external.string().optional(),
|
|
105908
105949
|
command: exports_external.string().min(1).max(1000).optional(),
|
|
105950
|
+
visibility: exports_external.enum(["public", "password", "private"]).optional(),
|
|
105909
105951
|
password: exports_external.boolean().optional(),
|
|
105910
|
-
spa: exports_external.boolean().optional()
|
|
105911
|
-
|
|
105952
|
+
spa: exports_external.boolean().optional(),
|
|
105953
|
+
rate_limit: exports_external.object({
|
|
105954
|
+
rps: exports_external.number().int().min(1).max(100),
|
|
105955
|
+
burst: exports_external.number().int().min(1).max(1000)
|
|
105956
|
+
}).strict().optional()
|
|
105957
|
+
}).strict().superRefine((web, ctx) => {
|
|
105958
|
+
if (web.visibility === "public" && web.password === true) {
|
|
105959
|
+
ctx.addIssue({
|
|
105960
|
+
code: exports_external.ZodIssueCode.custom,
|
|
105961
|
+
path: ["visibility"],
|
|
105962
|
+
message: `password = true implies visibility "password" — don't set visibility = "public" alongside it`
|
|
105963
|
+
});
|
|
105964
|
+
}
|
|
105965
|
+
if (web.rate_limit) {
|
|
105966
|
+
const { rps, burst } = web.rate_limit;
|
|
105967
|
+
if (burst < rps) {
|
|
105968
|
+
ctx.addIssue({
|
|
105969
|
+
code: exports_external.ZodIssueCode.custom,
|
|
105970
|
+
path: ["rate_limit", "burst"],
|
|
105971
|
+
message: `rate_limit.burst (${burst}) must be >= rate_limit.rps (${rps}) — a burst below the sustained rate is nonsense`
|
|
105972
|
+
});
|
|
105973
|
+
}
|
|
105974
|
+
if (burst > rps * 10) {
|
|
105975
|
+
ctx.addIssue({
|
|
105976
|
+
code: exports_external.ZodIssueCode.custom,
|
|
105977
|
+
path: ["rate_limit", "burst"],
|
|
105978
|
+
message: `rate_limit.burst (${burst}) must be <= 10× rate_limit.rps (${rps * 10})`
|
|
105979
|
+
});
|
|
105980
|
+
}
|
|
105981
|
+
}
|
|
105982
|
+
});
|
|
105912
105983
|
var convexConfigSchema = exports_external.object({
|
|
105913
105984
|
deployment_url: exports_external.string().url()
|
|
105914
105985
|
}).strict();
|
|
@@ -108168,6 +108239,11 @@ async function buildFailureError(ctx, app, deployment, signal) {
|
|
|
108168
108239
|
async function classifyDeploymentFailure(opts) {
|
|
108169
108240
|
const { ctx, app, deployment, publishInput } = opts;
|
|
108170
108241
|
const error48 = await buildFailureError(ctx, app, deployment, opts.signal);
|
|
108242
|
+
const isLiveDeploy = deployment.type !== "preview";
|
|
108243
|
+
if (isLiveDeploy && (app.status === "live" || app.status === "sleeping")) {
|
|
108244
|
+
error48.suggestion = `${error48.suggestion}
|
|
108245
|
+
To return to the last version that ran cleanly: \`bunx percher rollback --last-good\`.`;
|
|
108246
|
+
}
|
|
108171
108247
|
const recovery = error48.errorClass ? recoveryFromErrorClass(error48.errorClass, {
|
|
108172
108248
|
missingEnvVars: error48.missingEnvVars,
|
|
108173
108249
|
deployId: deployment.id,
|
|
@@ -108275,7 +108351,7 @@ async function deploysInspect(ctx, input) {
|
|
|
108275
108351
|
throw err;
|
|
108276
108352
|
}
|
|
108277
108353
|
}
|
|
108278
|
-
return { deploy, buildLog };
|
|
108354
|
+
return { deploy, buildLog, events: deploy.events ?? [] };
|
|
108279
108355
|
}
|
|
108280
108356
|
// ../core/dist/commands/dev.js
|
|
108281
108357
|
import { existsSync as existsSync8 } from "node:fs";
|
|
@@ -108776,6 +108852,17 @@ async function waitForDeploy(ctx, input, opts) {
|
|
|
108776
108852
|
summary
|
|
108777
108853
|
};
|
|
108778
108854
|
}
|
|
108855
|
+
if (deployment.isCheck && deployment.checkPassed) {
|
|
108856
|
+
return {
|
|
108857
|
+
status: "check_passed",
|
|
108858
|
+
app: { id: app.id, name: app.name, url: app.url },
|
|
108859
|
+
deployment,
|
|
108860
|
+
elapsedSeconds: Math.round((Date.now() - startedAt) / 1000),
|
|
108861
|
+
totalElapsedSeconds: secondsSinceCreated(deployment),
|
|
108862
|
+
recovery: RECOVERY_NONE,
|
|
108863
|
+
summary: `${app.name} check ${deployment.id} passed — would build and boot. The live app was not changed.`
|
|
108864
|
+
};
|
|
108865
|
+
}
|
|
108779
108866
|
if (deployment.status === "replaced") {
|
|
108780
108867
|
return await buildReplacedResult({ ctx, app, deployment, startedAt, signal: opts?.signal });
|
|
108781
108868
|
}
|
|
@@ -109676,13 +109763,16 @@ async function domainRemove(ctx, input) {
|
|
|
109676
109763
|
await ctx.client.apps.removeDomain(appRef, input.domain);
|
|
109677
109764
|
}
|
|
109678
109765
|
// ../core/dist/commands/env.js
|
|
109766
|
+
var envScopeEnum = exports_external.enum(["all", "live", "preview"]);
|
|
109679
109767
|
var envSetInputSchema = exports_external.object({
|
|
109680
109768
|
app: exports_external.string().optional(),
|
|
109681
|
-
values: exports_external.record(exports_external.string(), exports_external.string())
|
|
109769
|
+
values: exports_external.record(exports_external.string(), exports_external.string()),
|
|
109770
|
+
scope: envScopeEnum.optional().describe("Env scope: 'all' (default) applies to the live app and every preview; 'preview' applies only to preview deploys and overrides 'all' there; 'live' applies only to the live app and overrides 'all' there.")
|
|
109682
109771
|
});
|
|
109683
109772
|
var envUnsetInputSchema = exports_external.object({
|
|
109684
109773
|
app: exports_external.string().optional(),
|
|
109685
|
-
key: exports_external.string().min(1)
|
|
109774
|
+
key: exports_external.string().min(1),
|
|
109775
|
+
scope: envScopeEnum.optional().describe("Which scoped value to remove: 'all' (default), 'live', or 'preview'. Use 'preview' to drop a preview-only override without touching the value live deploys see.")
|
|
109686
109776
|
});
|
|
109687
109777
|
var envListInputSchema = exports_external.object({
|
|
109688
109778
|
app: exports_external.string().optional()
|
|
@@ -109705,21 +109795,30 @@ function mask(value) {
|
|
|
109705
109795
|
async function envSet(ctx, input) {
|
|
109706
109796
|
const name = resolveAppName2(ctx, input.app);
|
|
109707
109797
|
const app = await ctx.client.apps.get(name);
|
|
109708
|
-
await ctx.client.env.set(app.id, input.values);
|
|
109798
|
+
await ctx.client.env.set(app.id, input.values, { scope: input.scope });
|
|
109709
109799
|
}
|
|
109710
109800
|
async function envUnset(ctx, input) {
|
|
109711
109801
|
const name = resolveAppName2(ctx, input.app);
|
|
109712
109802
|
const app = await ctx.client.apps.get(name);
|
|
109713
|
-
await ctx.client.env.unset(app.id, input.key);
|
|
109803
|
+
await ctx.client.env.unset(app.id, input.key, { scope: input.scope });
|
|
109714
109804
|
}
|
|
109715
109805
|
async function envList(ctx, input = {}) {
|
|
109716
109806
|
const name = resolveAppName2(ctx, input.app);
|
|
109717
109807
|
const app = await ctx.client.apps.get(name);
|
|
109718
|
-
const
|
|
109719
|
-
|
|
109720
|
-
|
|
109721
|
-
|
|
109722
|
-
|
|
109808
|
+
const [serverValues, rows] = await Promise.all([
|
|
109809
|
+
ctx.client.env.list(app.id),
|
|
109810
|
+
ctx.client.env.listDetailed(app.id)
|
|
109811
|
+
]);
|
|
109812
|
+
const detailed = rows.map((r) => ({
|
|
109813
|
+
key: r.key,
|
|
109814
|
+
scope: r.scope,
|
|
109815
|
+
maskedValue: mask(r.maskedValue)
|
|
109816
|
+
}));
|
|
109817
|
+
const values = {};
|
|
109818
|
+
for (const [key, masked] of Object.entries(serverValues)) {
|
|
109819
|
+
values[key] = mask(masked);
|
|
109820
|
+
}
|
|
109821
|
+
return { values, detailed };
|
|
109723
109822
|
}
|
|
109724
109823
|
// ../core/dist/commands/export.js
|
|
109725
109824
|
import { randomUUID } from "node:crypto";
|
|
@@ -110104,6 +110203,46 @@ async function githubSetupWebhook(ctx, input) {
|
|
|
110104
110203
|
`)
|
|
110105
110204
|
};
|
|
110106
110205
|
}
|
|
110206
|
+
// ../core/dist/commands/hooks.js
|
|
110207
|
+
var hookCreateInputSchema = exports_external.object({
|
|
110208
|
+
app: exports_external.string().optional(),
|
|
110209
|
+
name: exports_external.string().trim().min(1).max(64).describe("Label for the hook, 1-64 chars — name it after the caller, e.g. 'sanity-publish' or 'nightly-cron' (shown in the list).")
|
|
110210
|
+
});
|
|
110211
|
+
var hookListInputSchema = exports_external.object({
|
|
110212
|
+
app: exports_external.string().optional()
|
|
110213
|
+
});
|
|
110214
|
+
var hookRevokeInputSchema = exports_external.object({
|
|
110215
|
+
app: exports_external.string().optional(),
|
|
110216
|
+
id: exports_external.string().min(1).describe("Hook id from `percher hook list`.")
|
|
110217
|
+
});
|
|
110218
|
+
function resolveAppName4(ctx, provided) {
|
|
110219
|
+
const name = provided ?? readPercherTomlAppName(ctx.cwd);
|
|
110220
|
+
if (!name) {
|
|
110221
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
110222
|
+
code: "hooks.no-app",
|
|
110223
|
+
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
110224
|
+
});
|
|
110225
|
+
}
|
|
110226
|
+
return name;
|
|
110227
|
+
}
|
|
110228
|
+
async function hookCreate(ctx, input) {
|
|
110229
|
+
const name = resolveAppName4(ctx, input.app);
|
|
110230
|
+
const app = await ctx.client.apps.get(name);
|
|
110231
|
+
ctx.status("Creating deploy hook…");
|
|
110232
|
+
return ctx.client.hooks.create(app.id, input.name);
|
|
110233
|
+
}
|
|
110234
|
+
async function hookList(ctx, input = {}) {
|
|
110235
|
+
const name = resolveAppName4(ctx, input.app);
|
|
110236
|
+
const app = await ctx.client.apps.get(name);
|
|
110237
|
+
ctx.status("Loading deploy hooks…");
|
|
110238
|
+
return ctx.client.hooks.list(app.id);
|
|
110239
|
+
}
|
|
110240
|
+
async function hookRevoke(ctx, input) {
|
|
110241
|
+
const name = resolveAppName4(ctx, input.app);
|
|
110242
|
+
const app = await ctx.client.apps.get(name);
|
|
110243
|
+
ctx.status("Revoking deploy hook…");
|
|
110244
|
+
await ctx.client.hooks.revoke(app.id, input.id);
|
|
110245
|
+
}
|
|
110107
110246
|
// ../core/dist/commands/import-project.js
|
|
110108
110247
|
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
110109
110248
|
import { join as join15 } from "node:path";
|
|
@@ -115168,6 +115307,11 @@ async function previewBranchShow(ctx, input = {}) {
|
|
|
115168
115307
|
status: app.previewStatus ?? "disabled"
|
|
115169
115308
|
};
|
|
115170
115309
|
}
|
|
115310
|
+
async function previewList(ctx, input = {}) {
|
|
115311
|
+
const appRef = resolveApp3(ctx, input.app);
|
|
115312
|
+
const app = await ctx.client.apps.get(appRef);
|
|
115313
|
+
return { app: app.name, previews: app.previews ?? [] };
|
|
115314
|
+
}
|
|
115171
115315
|
// ../core/dist/commands/publish.js
|
|
115172
115316
|
init_dist();
|
|
115173
115317
|
import { existsSync as existsSync15 } from "node:fs";
|
|
@@ -116090,6 +116234,7 @@ var publishInputSchema = exports_external.object({
|
|
|
116090
116234
|
preview: exports_external.boolean().optional().describe("Deploy as preview (does not replace the live version)"),
|
|
116091
116235
|
message: exports_external.string().optional().describe("Deploy note (visible in deploy history and version timeline)"),
|
|
116092
116236
|
dryRun: exports_external.boolean().optional().describe("Show what would be deployed (file count, bytes, top files) without uploading or building. Useful for verifying the bundle before a real deploy."),
|
|
116237
|
+
check: exports_external.boolean().optional().describe("Validation deploy: build → start → health, then tear the container down (no swap, no route). Proves the app builds and boots without touching the live version. Consumes the PREVIEW daily quota; the app is NOT changed. Beyond a manifest-only dry run — it actually builds and runs. On success: status='check_passed'."),
|
|
116093
116238
|
noCache: exports_external.boolean().optional().describe("Skip the image-cache lookup and force a fresh build. Use this when you suspect the cached image doesn't reflect your latest source — the new build's resulting image still gets cached, so future deploys benefit."),
|
|
116094
116239
|
waitForLive: exports_external.boolean().optional().describe("If true (default), publish blocks until the deploy is live or failed. If false, publish returns as soon as the deploy is queued and the agent can resume with percher_wait_for_deploy. Recommended for AI agents — gives back control quickly with a deployId so they can decide between waiting, asking the user, or working on something else."),
|
|
116095
116240
|
anonymous: exports_external.boolean().optional().describe("Publish WITHOUT an account. Creates a throwaway anonymous app that expires in 72h unless claimed (percher_claim_app). The app runs with no outbound network, no database, and no env vars until claimed. Only honored when the caller has no token; ignored once signed in. Server-gated — fails with ANON_DEPLOYS_DISABLED if the instance hasn't enabled it.")
|
|
@@ -116422,7 +116567,7 @@ async function executeDeploy(opts) {
|
|
|
116422
116567
|
const tarballBytes = new Uint8Array(await new Response(tarball.stream).arrayBuffer());
|
|
116423
116568
|
let cacheReusedDeployment = null;
|
|
116424
116569
|
const contentHash = tarball.contentHash;
|
|
116425
|
-
if (!input.noCache && !input.preview) {
|
|
116570
|
+
if (!input.noCache && !input.preview && !input.check) {
|
|
116426
116571
|
try {
|
|
116427
116572
|
const probe = await ctx.client.platform.cacheProbe({
|
|
116428
116573
|
appId: app.id,
|
|
@@ -116445,6 +116590,7 @@ async function executeDeploy(opts) {
|
|
|
116445
116590
|
call: (attempt) => ctx.client.apps.deploy(app.id, {
|
|
116446
116591
|
tarball: tarballBytes,
|
|
116447
116592
|
type: input.preview ? "preview" : undefined,
|
|
116593
|
+
check: input.check,
|
|
116448
116594
|
note: input.message,
|
|
116449
116595
|
noCache: input.noCache,
|
|
116450
116596
|
idempotencyKey,
|
|
@@ -116590,6 +116736,31 @@ async function finalizeDeploy(opts) {
|
|
|
116590
116736
|
};
|
|
116591
116737
|
}
|
|
116592
116738
|
const buildMs = Date.now() - buildStart;
|
|
116739
|
+
if (deployment.isCheck && deployment.checkPassed) {
|
|
116740
|
+
const totalSeconds2 = (Date.now() - t0) / 1000;
|
|
116741
|
+
const summary = `${app.name} check passed (${Math.round(totalSeconds2)}s) — would build and boot. The live app was not changed.`;
|
|
116742
|
+
ctx.status(summary);
|
|
116743
|
+
return {
|
|
116744
|
+
status: "check_passed",
|
|
116745
|
+
app,
|
|
116746
|
+
deployment,
|
|
116747
|
+
timing: {
|
|
116748
|
+
totalSeconds: Math.round(totalSeconds2),
|
|
116749
|
+
packageMs,
|
|
116750
|
+
uploadMs,
|
|
116751
|
+
buildMs
|
|
116752
|
+
},
|
|
116753
|
+
fileCount: tarball.fileCount,
|
|
116754
|
+
bytes: tarball.bytes,
|
|
116755
|
+
firstDeploy: firstDeploy || undefined,
|
|
116756
|
+
missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
|
|
116757
|
+
traceId: deployment.id,
|
|
116758
|
+
recovery: RECOVERY_NONE,
|
|
116759
|
+
summary,
|
|
116760
|
+
configPath: tomlPathFor2(ctx.cwd),
|
|
116761
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
116762
|
+
};
|
|
116763
|
+
}
|
|
116593
116764
|
if (deployment.status === "replaced") {
|
|
116594
116765
|
return await buildReplacedResult2({
|
|
116595
116766
|
ctx,
|
|
@@ -116624,6 +116795,16 @@ async function finalizeDeploy(opts) {
|
|
|
116624
116795
|
}
|
|
116625
116796
|
const cacheHit = deployment.cacheHit;
|
|
116626
116797
|
const cacheStatus = deployment.cacheStatus;
|
|
116798
|
+
const autoDeploys = resolveAutoDeploys(app);
|
|
116799
|
+
const liveSummary = buildLiveSummary({
|
|
116800
|
+
appName: app.name,
|
|
116801
|
+
url: url2 ?? app.url,
|
|
116802
|
+
totalSeconds: Math.round(totalSeconds),
|
|
116803
|
+
framework: config2.app.framework,
|
|
116804
|
+
preview: input.preview === true,
|
|
116805
|
+
replacedPreview,
|
|
116806
|
+
cacheHit: cacheHit === true
|
|
116807
|
+
});
|
|
116627
116808
|
return {
|
|
116628
116809
|
status: "live",
|
|
116629
116810
|
url: url2,
|
|
@@ -116646,19 +116827,30 @@ async function finalizeDeploy(opts) {
|
|
|
116646
116827
|
missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
|
|
116647
116828
|
traceId: deployment.id,
|
|
116648
116829
|
recovery: RECOVERY_NONE,
|
|
116649
|
-
|
|
116650
|
-
|
|
116651
|
-
|
|
116652
|
-
totalSeconds: Math.round(totalSeconds),
|
|
116653
|
-
framework: config2.app.framework,
|
|
116654
|
-
preview: input.preview === true,
|
|
116655
|
-
replacedPreview,
|
|
116656
|
-
cacheHit: cacheHit === true
|
|
116657
|
-
}),
|
|
116830
|
+
autoDeploys,
|
|
116831
|
+
summary: autoDeploys ? `${liveSummary}
|
|
116832
|
+
This app auto-deploys from ${autoDeploys.repo}@${autoDeploys.branch} — pushing there is the canonical path.` : liveSummary,
|
|
116658
116833
|
configPath: tomlPathFor2(ctx.cwd),
|
|
116659
116834
|
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
116660
116835
|
};
|
|
116661
116836
|
}
|
|
116837
|
+
function resolveAutoDeploys(app) {
|
|
116838
|
+
if (app.githubRepoUrl) {
|
|
116839
|
+
return {
|
|
116840
|
+
repo: app.githubRepoUrl,
|
|
116841
|
+
branch: app.githubBranch ?? "main",
|
|
116842
|
+
provider: "github"
|
|
116843
|
+
};
|
|
116844
|
+
}
|
|
116845
|
+
if (app.forgejoRepoUrl) {
|
|
116846
|
+
return {
|
|
116847
|
+
repo: app.forgejoRepoUrl,
|
|
116848
|
+
branch: app.forgejoBranch ?? "main",
|
|
116849
|
+
provider: "forgejo"
|
|
116850
|
+
};
|
|
116851
|
+
}
|
|
116852
|
+
return;
|
|
116853
|
+
}
|
|
116662
116854
|
async function handleUnauthorized(ctx, config2, tarball, input) {
|
|
116663
116855
|
if (input.anonymous) {
|
|
116664
116856
|
return handleAnonymousPublish(ctx, config2, input, tarball);
|
|
@@ -116901,6 +117093,7 @@ var publishInlineInputSchema = exports_external.object({
|
|
|
116901
117093
|
encoding: exports_external.enum(["utf8", "base64"]).optional().describe("Content encoding. Default 'utf8' (text). Use 'base64' for binary assets (images, fonts).")
|
|
116902
117094
|
})).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)."),
|
|
116903
117095
|
preview: exports_external.boolean().optional().describe("Deploy as a preview (does not replace the live version)."),
|
|
117096
|
+
check: exports_external.boolean().optional().describe("Validation deploy: build → start → health, then tear down (no swap, no route). Proves the app builds and boots without changing the live version. Consumes the preview quota. On success: status='check_passed'."),
|
|
116904
117097
|
message: exports_external.string().optional().describe("Deploy note (visible in deploy history)."),
|
|
116905
117098
|
noCache: exports_external.boolean().optional().describe("Skip the image cache and force a fresh build."),
|
|
116906
117099
|
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.")
|
|
@@ -117222,7 +117415,8 @@ ${deployment.errorMessage}` : "";
|
|
|
117222
117415
|
init_dist();
|
|
117223
117416
|
var redeployInputSchema = exports_external.object({
|
|
117224
117417
|
app: exports_external.string().optional().describe("App name. Defaults to the [app].name in the current directory's percher.toml."),
|
|
117225
|
-
deployId: exports_external.string().optional().describe("Specific deploy id (e.g. dep_abc123). Defaults to the latest live deploy for the app.")
|
|
117418
|
+
deployId: exports_external.string().optional().describe("Specific deploy id (e.g. dep_abc123). Defaults to the latest live deploy for the app."),
|
|
117419
|
+
noCache: exports_external.boolean().optional().describe("Force a fresh build — skip the image cache. The stale-image escape hatch when a re-run keeps serving old bytes.")
|
|
117226
117420
|
});
|
|
117227
117421
|
async function redeploy(ctx, input = {}) {
|
|
117228
117422
|
const t0 = Date.now();
|
|
@@ -117276,7 +117470,7 @@ async function redeploy(ctx, input = {}) {
|
|
|
117276
117470
|
ctx.status(`Re-running ${sourceDeployId}...`);
|
|
117277
117471
|
let initial;
|
|
117278
117472
|
try {
|
|
117279
|
-
initial = await ctx.client.apps.rerunDeploy(app.id, sourceDeployId);
|
|
117473
|
+
initial = await ctx.client.apps.rerunDeploy(app.id, sourceDeployId, input.noCache ? { noCache: true } : undefined);
|
|
117280
117474
|
} catch (err) {
|
|
117281
117475
|
if (err instanceof PercherApiError) {
|
|
117282
117476
|
switch (err.code) {
|
|
@@ -117875,8 +118069,90 @@ async function resume(ctx, input = {}) {
|
|
|
117875
118069
|
// ../core/dist/commands/rollback.js
|
|
117876
118070
|
var rollbackInputSchema = exports_external.object({
|
|
117877
118071
|
app: exports_external.string().optional(),
|
|
117878
|
-
commitSha: exports_external.string().min(1)
|
|
118072
|
+
commitSha: exports_external.string().min(1).optional().describe("Version SHA to roll back to (from percher_versions / `percher versions`). Omit to pick from the most recent versions, or set toLastGood."),
|
|
118073
|
+
toLastGood: exports_external.boolean().optional().describe("Resolves server-side to the most recent version that went live and didn't crash-loop.")
|
|
117879
118074
|
});
|
|
118075
|
+
var BARE_PICK_LIMIT = 5;
|
|
118076
|
+
function versionAge(iso, now = Date.now()) {
|
|
118077
|
+
const then = new Date(iso).getTime();
|
|
118078
|
+
if (Number.isNaN(then))
|
|
118079
|
+
return iso;
|
|
118080
|
+
const s = Math.max(0, Math.floor((now - then) / 1000));
|
|
118081
|
+
if (s < 60)
|
|
118082
|
+
return "just now";
|
|
118083
|
+
const m = Math.floor(s / 60);
|
|
118084
|
+
if (m < 60)
|
|
118085
|
+
return `${m}m ago`;
|
|
118086
|
+
const h = Math.floor(m / 60);
|
|
118087
|
+
if (h < 24)
|
|
118088
|
+
return `${h}h ago`;
|
|
118089
|
+
const d = Math.floor(h / 24);
|
|
118090
|
+
if (d < 30)
|
|
118091
|
+
return `${d}d ago`;
|
|
118092
|
+
return `${Math.floor(d / 30)}mo ago`;
|
|
118093
|
+
}
|
|
118094
|
+
async function writeLine(ctx, line) {
|
|
118095
|
+
const writer = ctx.stdout.getWriter();
|
|
118096
|
+
try {
|
|
118097
|
+
await writer.write(new TextEncoder().encode(`${line}
|
|
118098
|
+
`));
|
|
118099
|
+
} finally {
|
|
118100
|
+
writer.releaseLock();
|
|
118101
|
+
}
|
|
118102
|
+
}
|
|
118103
|
+
async function resolveCommitSha(ctx, name, input) {
|
|
118104
|
+
if (input.commitSha)
|
|
118105
|
+
return input.commitSha;
|
|
118106
|
+
if (input.toLastGood) {
|
|
118107
|
+
const lastGood = await ctx.client.apps.lastGoodVersion(name);
|
|
118108
|
+
if (!lastGood) {
|
|
118109
|
+
throw new PercherCoreError("No previous known-good version — deploy history has none that reached live without crash-looping.", {
|
|
118110
|
+
code: "rollback.no-last-good",
|
|
118111
|
+
hint: "Run `bunx percher versions` and pick a version explicitly: `bunx percher rollback <sha>`."
|
|
118112
|
+
});
|
|
118113
|
+
}
|
|
118114
|
+
return lastGood.commitSha;
|
|
118115
|
+
}
|
|
118116
|
+
const versions2 = await ctx.client.apps.listVersions(name);
|
|
118117
|
+
if (versions2.length === 0) {
|
|
118118
|
+
throw new PercherCoreError(`No versions to roll back to for "${name}"`, {
|
|
118119
|
+
code: "rollback.no-versions",
|
|
118120
|
+
hint: "Publish at least once before rolling back."
|
|
118121
|
+
});
|
|
118122
|
+
}
|
|
118123
|
+
const pick2 = versions2.slice(0, BARE_PICK_LIMIT);
|
|
118124
|
+
if (!ctx.stdinIsInteractive) {
|
|
118125
|
+
const appFlag = ` --app ${name}`;
|
|
118126
|
+
const lines = pick2.map((v) => {
|
|
118127
|
+
const note = v.message.split(`
|
|
118128
|
+
`)[0]?.trim() || "Published";
|
|
118129
|
+
return ` bunx percher rollback ${v.sha}${appFlag} # ${note} (${versionAge(v.timestamp)})`;
|
|
118130
|
+
});
|
|
118131
|
+
throw new PercherCoreError(`Rollback needs a version. The ${pick2.length} most recent for "${name}":
|
|
118132
|
+
${lines.join(`
|
|
118133
|
+
`)}`, {
|
|
118134
|
+
code: "rollback.sha-required",
|
|
118135
|
+
hint: "Re-run with an explicit sha, or pass --last-good to pick the most recent known-good version."
|
|
118136
|
+
});
|
|
118137
|
+
}
|
|
118138
|
+
await writeLine(ctx, `Recent versions of ${name}:`);
|
|
118139
|
+
for (const [i, v] of pick2.entries()) {
|
|
118140
|
+
const note = v.message.split(`
|
|
118141
|
+
`)[0]?.trim() || "Published";
|
|
118142
|
+
const live = i === 0 ? " (live now)" : "";
|
|
118143
|
+
await writeLine(ctx, ` ${i + 1}. ${v.sha.slice(0, 10)} ${versionAge(v.timestamp)} ${note}${live}`);
|
|
118144
|
+
}
|
|
118145
|
+
const answer = await ctx.prompt(`Roll back to which version? [1-${pick2.length}]`, {});
|
|
118146
|
+
const n = Number.parseInt(answer.trim(), 10);
|
|
118147
|
+
const chosen = Number.isInteger(n) && n >= 1 && n <= pick2.length ? pick2[n - 1] : undefined;
|
|
118148
|
+
if (!chosen) {
|
|
118149
|
+
throw new PercherCoreError(`"${answer.trim()}" is not a number between 1 and ${pick2.length}`, {
|
|
118150
|
+
code: "rollback.sha-required",
|
|
118151
|
+
hint: "Re-run `percher rollback` and pick a listed number, or pass the sha directly."
|
|
118152
|
+
});
|
|
118153
|
+
}
|
|
118154
|
+
return chosen.sha;
|
|
118155
|
+
}
|
|
117880
118156
|
async function rollback(ctx, input, internals = {}) {
|
|
117881
118157
|
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
117882
118158
|
if (!name) {
|
|
@@ -117885,7 +118161,9 @@ async function rollback(ctx, input, internals = {}) {
|
|
|
117885
118161
|
hint: "Pass --app <name> or run from a directory with percher.toml."
|
|
117886
118162
|
});
|
|
117887
118163
|
}
|
|
117888
|
-
|
|
118164
|
+
const commitSha = await resolveCommitSha(ctx, name, input);
|
|
118165
|
+
ctx.status(`Rolling back to ${commitSha.slice(0, 10)}...`);
|
|
118166
|
+
let deployment = await ctx.client.apps.rollback(name, commitSha);
|
|
117889
118167
|
const pollInterval = internals.pollIntervalMs ?? 2000;
|
|
117890
118168
|
const timeoutMs = internals.timeoutMs ?? TIMEOUTS.clientPublishPoll;
|
|
117891
118169
|
const startedAt = Date.now();
|
|
@@ -117906,7 +118184,7 @@ async function rollback(ctx, input, internals = {}) {
|
|
|
117906
118184
|
hint: "Run `percher logs` to see what went wrong."
|
|
117907
118185
|
});
|
|
117908
118186
|
}
|
|
117909
|
-
return deployment;
|
|
118187
|
+
return { ...deployment, rolledBackTo: commitSha };
|
|
117910
118188
|
}
|
|
117911
118189
|
// ../core/dist/commands/sharing.js
|
|
117912
118190
|
var ShareRoleSchema = exports_external.enum(["viewer", "admin"]);
|
|
@@ -118314,7 +118592,7 @@ class SilentExitError {
|
|
|
118314
118592
|
this.exitCode = exitCode;
|
|
118315
118593
|
}
|
|
118316
118594
|
}
|
|
118317
|
-
async function
|
|
118595
|
+
async function writeLine2(ctx, line) {
|
|
118318
118596
|
const writer = ctx.stdout.getWriter();
|
|
118319
118597
|
try {
|
|
118320
118598
|
await writer.write(new TextEncoder().encode(`${line}
|
|
@@ -118379,56 +118657,115 @@ function firstNonFlagAfter(argv, from, valueFlags = new Set) {
|
|
|
118379
118657
|
}
|
|
118380
118658
|
return;
|
|
118381
118659
|
}
|
|
118660
|
+
function foldStageTimeline(events) {
|
|
118661
|
+
const order = [];
|
|
118662
|
+
const info = new Map;
|
|
118663
|
+
for (const e of events) {
|
|
118664
|
+
if (e.subStage !== null)
|
|
118665
|
+
continue;
|
|
118666
|
+
let row = info.get(e.stage);
|
|
118667
|
+
if (!row) {
|
|
118668
|
+
row = { status: "running", durationMs: null };
|
|
118669
|
+
info.set(e.stage, row);
|
|
118670
|
+
order.push(e.stage);
|
|
118671
|
+
}
|
|
118672
|
+
const t = Date.parse(e.at);
|
|
118673
|
+
if (e.status === "started") {
|
|
118674
|
+
if (!Number.isNaN(t))
|
|
118675
|
+
row.startedAt = t;
|
|
118676
|
+
} else if (e.status === "ok" || e.status === "failed") {
|
|
118677
|
+
row.status = e.status;
|
|
118678
|
+
if (e.durationMs !== null)
|
|
118679
|
+
row.durationMs = e.durationMs;
|
|
118680
|
+
else if (!Number.isNaN(t))
|
|
118681
|
+
row.endedAt = t;
|
|
118682
|
+
}
|
|
118683
|
+
}
|
|
118684
|
+
return order.map((stage) => {
|
|
118685
|
+
const r = info.get(stage);
|
|
118686
|
+
let durationMs = r.durationMs;
|
|
118687
|
+
if (durationMs === null && r.startedAt !== undefined && r.endedAt !== undefined) {
|
|
118688
|
+
durationMs = Math.max(0, r.endedAt - r.startedAt);
|
|
118689
|
+
}
|
|
118690
|
+
return { stage, durationMs, status: r.status };
|
|
118691
|
+
});
|
|
118692
|
+
}
|
|
118693
|
+
function formatStageDuration(ms) {
|
|
118694
|
+
if (ms === null || !Number.isFinite(ms) || ms < 0)
|
|
118695
|
+
return "—";
|
|
118696
|
+
if (ms < 1000)
|
|
118697
|
+
return `${Math.round(ms)}ms`;
|
|
118698
|
+
if (ms < 60000)
|
|
118699
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
118700
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
118701
|
+
}
|
|
118702
|
+
async function writeStageTimeline(ctx, events) {
|
|
118703
|
+
if (events.length === 0)
|
|
118704
|
+
return;
|
|
118705
|
+
const rows = foldStageTimeline(events);
|
|
118706
|
+
if (rows.length === 0)
|
|
118707
|
+
return;
|
|
118708
|
+
const stageW = Math.max(...rows.map((r) => r.stage.length));
|
|
118709
|
+
const durW = Math.max(...rows.map((r) => formatStageDuration(r.durationMs).length));
|
|
118710
|
+
await writeLine2(ctx, "");
|
|
118711
|
+
await writeLine2(ctx, import_picocolors.default.bold("Stages:"));
|
|
118712
|
+
for (const r of rows) {
|
|
118713
|
+
const dur = formatStageDuration(r.durationMs).padStart(durW);
|
|
118714
|
+
const mark = r.status === "failed" ? import_picocolors.default.red("← failed") : r.status === "ok" ? import_picocolors.default.dim("ok") : import_picocolors.default.yellow("…");
|
|
118715
|
+
await writeLine2(ctx, ` ${r.stage.padEnd(stageW)} ${dur} ${mark}`);
|
|
118716
|
+
}
|
|
118717
|
+
}
|
|
118382
118718
|
async function printSuperuserResetResult(ctx, result) {
|
|
118383
|
-
await
|
|
118719
|
+
await writeLine2(ctx, `${import_picocolors.default.green("✓")} ${result.message}`);
|
|
118384
118720
|
if (result.password) {
|
|
118385
|
-
await
|
|
118386
|
-
await
|
|
118387
|
-
await
|
|
118388
|
-
await
|
|
118389
|
-
await
|
|
118721
|
+
await writeLine2(ctx, "");
|
|
118722
|
+
await writeLine2(ctx, ` ${import_picocolors.default.bold("Email:")} ${result.email}`);
|
|
118723
|
+
await writeLine2(ctx, ` ${import_picocolors.default.bold("Password:")} ${result.password}`);
|
|
118724
|
+
await writeLine2(ctx, "");
|
|
118725
|
+
await writeLine2(ctx, import_picocolors.default.yellow(" ⚠ This password is shown only once. Save it now if you need it."));
|
|
118390
118726
|
}
|
|
118391
118727
|
}
|
|
118392
118728
|
async function runDeploysInspect(ctx, input) {
|
|
118393
118729
|
const result = await deploysInspect(ctx, input);
|
|
118394
|
-
const { deploy, buildLog } = result;
|
|
118395
|
-
await
|
|
118396
|
-
await
|
|
118730
|
+
const { deploy, buildLog, events } = result;
|
|
118731
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Deploy:")} ${deploy.id}`);
|
|
118732
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Status:")} ${formatStatus(deploy.status)}`);
|
|
118397
118733
|
if (deploy.type) {
|
|
118398
|
-
await
|
|
118734
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Type:")} ${deploy.type}`);
|
|
118399
118735
|
}
|
|
118400
|
-
await
|
|
118736
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Created:")} ${formatDeployTimestamp(deploy.createdAt)}`);
|
|
118401
118737
|
if (typeof deploy.imageSizeBytes === "number") {
|
|
118402
|
-
await
|
|
118738
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Image:")} ${formatBytes(deploy.imageSizeBytes)}`);
|
|
118403
118739
|
}
|
|
118404
118740
|
if (deploy.url) {
|
|
118405
|
-
await
|
|
118741
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("URL:")} ${deploy.url}`);
|
|
118406
118742
|
}
|
|
118407
118743
|
if (deploy.previewUrl) {
|
|
118408
|
-
await
|
|
118744
|
+
await writeLine2(ctx, `${import_picocolors.default.bold("Preview:")} ${deploy.previewUrl}`);
|
|
118409
118745
|
}
|
|
118410
118746
|
if (deploy.errorMessage) {
|
|
118411
|
-
await
|
|
118412
|
-
await
|
|
118747
|
+
await writeLine2(ctx, "");
|
|
118748
|
+
await writeLine2(ctx, import_picocolors.default.bold(import_picocolors.default.red("Error:")));
|
|
118413
118749
|
for (const line of deploy.errorMessage.split(`
|
|
118414
118750
|
`)) {
|
|
118415
|
-
await
|
|
118751
|
+
await writeLine2(ctx, ` ${line}`);
|
|
118416
118752
|
}
|
|
118417
118753
|
}
|
|
118418
118754
|
if (buildLog) {
|
|
118419
|
-
await
|
|
118755
|
+
await writeLine2(ctx, "");
|
|
118420
118756
|
const lines = buildLog.split(`
|
|
118421
118757
|
`);
|
|
118422
118758
|
const tail = lines.slice(-200);
|
|
118423
118759
|
const header = lines.length > tail.length ? `${import_picocolors.default.bold("Build log")} ${import_picocolors.default.dim(`(last 200 of ${lines.length} lines)`)}` : import_picocolors.default.bold("Build log");
|
|
118424
|
-
await
|
|
118760
|
+
await writeLine2(ctx, header);
|
|
118425
118761
|
for (const line of tail) {
|
|
118426
|
-
await
|
|
118762
|
+
await writeLine2(ctx, ` ${line}`);
|
|
118427
118763
|
}
|
|
118428
118764
|
} else if (deploy.status === "failed" || deploy.status === "live") {
|
|
118429
|
-
await
|
|
118430
|
-
await
|
|
118765
|
+
await writeLine2(ctx, "");
|
|
118766
|
+
await writeLine2(ctx, import_picocolors.default.dim("(no build log available for this deploy)"));
|
|
118431
118767
|
}
|
|
118768
|
+
await writeStageTimeline(ctx, events);
|
|
118432
118769
|
}
|
|
118433
118770
|
|
|
118434
118771
|
// src/commands/account.ts
|
|
@@ -118446,7 +118783,7 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118446
118783
|
cli.command("account [action] [...rest]", "Account management: export (GDPR data portability) | preview-delete (inspect deletion blockers) | delete (GDPR erasure)").option("--out <file>", "(export only) Output file path").option("--include-app-data", "(export only) Also download per-app PocketBase tarballs (creates a directory)").option("--confirm <email>", "(delete only) Your email — must match the signed-in account").option("--yes", "(delete only) Skip the interactive confirmation prompt").action(async (action, _rest, options) => {
|
|
118447
118784
|
if (action === "export") {
|
|
118448
118785
|
if (!ctx.client.token) {
|
|
118449
|
-
await
|
|
118786
|
+
await writeLine2(ctx, `${import_picocolors2.default.red("✗")} Not logged in. Run: percher login`);
|
|
118450
118787
|
throw new SilentExitError;
|
|
118451
118788
|
}
|
|
118452
118789
|
const result = await accountExport(ctx, {
|
|
@@ -118454,69 +118791,69 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118454
118791
|
includeAppData: options.includeAppData
|
|
118455
118792
|
});
|
|
118456
118793
|
const kb = (result.bytes / 1024).toFixed(1);
|
|
118457
|
-
await
|
|
118794
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Exported ${kb} KB → ${result.path}`);
|
|
118458
118795
|
if (result.appTarballs && result.appTarballs.length > 0) {
|
|
118459
118796
|
for (const t of result.appTarballs) {
|
|
118460
118797
|
const mb = (t.bytes / 1024 / 1024).toFixed(1);
|
|
118461
|
-
await
|
|
118798
|
+
await writeLine2(ctx, ` ${import_picocolors2.default.green("✓")} ${t.app}: ${mb} MB → ${t.path}`);
|
|
118462
118799
|
}
|
|
118463
118800
|
}
|
|
118464
118801
|
if (result.appTarballsFailed && result.appTarballsFailed.length > 0) {
|
|
118465
118802
|
for (const f of result.appTarballsFailed) {
|
|
118466
|
-
await
|
|
118803
|
+
await writeLine2(ctx, ` ${import_picocolors2.default.yellow("⚠")} ${f.app}: ${f.error} (skipped)`);
|
|
118467
118804
|
}
|
|
118468
|
-
await
|
|
118805
|
+
await writeLine2(ctx, import_picocolors2.default.yellow(`Export is partial — ${result.appTarballsFailed.length} app(s) could not be downloaded. Re-run after the rate-limit window to get the rest.`));
|
|
118469
118806
|
}
|
|
118470
|
-
await
|
|
118807
|
+
await writeLine2(ctx, import_picocolors2.default.dim("Note: encrypted env-var values are not included (only the keys + metadata). Build logs are excluded too — request via `percher logs` if needed."));
|
|
118471
118808
|
return;
|
|
118472
118809
|
}
|
|
118473
118810
|
if (action === "preview-delete") {
|
|
118474
118811
|
if (!ctx.client.token) {
|
|
118475
|
-
await
|
|
118812
|
+
await writeLine2(ctx, `${import_picocolors2.default.red("✗")} Not logged in. Run: percher login`);
|
|
118476
118813
|
throw new SilentExitError;
|
|
118477
118814
|
}
|
|
118478
118815
|
const preview = await accountDeletionPreview(ctx, {});
|
|
118479
118816
|
if (preview.canDelete) {
|
|
118480
|
-
await
|
|
118817
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} No blockers — your account is ready to delete. Run: percher account delete --confirm <your-email>`);
|
|
118481
118818
|
return;
|
|
118482
118819
|
}
|
|
118483
|
-
await
|
|
118820
|
+
await writeLine2(ctx, `${import_picocolors2.default.yellow("!")} Account deletion is blocked by ${preview.blockers.length} ${preview.blockers.length === 1 ? "category" : "categories"} (${preview.ownedAppCount} owned ${preview.ownedAppCount === 1 ? "app" : "apps"}):`);
|
|
118484
118821
|
for (const blocker of preview.blockers) {
|
|
118485
118822
|
const action2 = BLOCKER_ACTIONS[blocker.kind] ?? "Clear via the dashboard or API.";
|
|
118486
|
-
await
|
|
118487
|
-
await
|
|
118488
|
-
await
|
|
118823
|
+
await writeLine2(ctx, "");
|
|
118824
|
+
await writeLine2(ctx, ` ${import_picocolors2.default.bold(blocker.kind)} (${blocker.count})`);
|
|
118825
|
+
await writeLine2(ctx, ` → ${action2}`);
|
|
118489
118826
|
const previewDetails = blocker.details.slice(0, 5);
|
|
118490
118827
|
for (const detail of previewDetails) {
|
|
118491
118828
|
const ref = detail.ref ? ` (${detail.ref})` : "";
|
|
118492
118829
|
const name = detail.appName ?? detail.appId ?? "—";
|
|
118493
|
-
await
|
|
118830
|
+
await writeLine2(ctx, ` · ${name}${ref}`);
|
|
118494
118831
|
}
|
|
118495
118832
|
if (blocker.details.length > previewDetails.length) {
|
|
118496
|
-
await
|
|
118833
|
+
await writeLine2(ctx, import_picocolors2.default.dim(` … ${blocker.details.length - previewDetails.length} more`));
|
|
118497
118834
|
}
|
|
118498
118835
|
}
|
|
118499
|
-
await
|
|
118836
|
+
await writeLine2(ctx, import_picocolors2.default.dim("\nResolve each category above, then re-run `percher account preview-delete` to confirm. Once canDelete=true, run `percher account delete --confirm <your-email>`."));
|
|
118500
118837
|
throw new SilentExitError(2);
|
|
118501
118838
|
}
|
|
118502
118839
|
if (action === "delete") {
|
|
118503
118840
|
if (!ctx.client.token) {
|
|
118504
|
-
await
|
|
118841
|
+
await writeLine2(ctx, `${import_picocolors2.default.red("✗")} Not logged in. Run: percher login`);
|
|
118505
118842
|
throw new SilentExitError;
|
|
118506
118843
|
}
|
|
118507
118844
|
if (!options.confirm) {
|
|
118508
|
-
await
|
|
118845
|
+
await writeLine2(ctx, `${import_picocolors2.default.red("✗")} Pass --confirm <your-email> to proceed. Account deletion is fail-closed — owned apps, accepted collaborators, pending invites, open PR previews, pending transfers, in-flight deploys, and active restores must all be cleared first.`);
|
|
118509
118846
|
throw new SilentExitError;
|
|
118510
118847
|
}
|
|
118511
118848
|
if (!options.yes) {
|
|
118512
118849
|
const answer = await ctx.prompt(`${import_picocolors2.default.red("!")} This will request deletion of your account. The server will reject the request if any apps, collaborators, pending invites, PR previews, pending transfers, in-flight deploys, or active restores remain — clear those first. On a clean account the request is accepted asynchronously and finalization completes within seconds; your API tokens are revoked immediately. Type "delete my account" to confirm:`);
|
|
118513
118850
|
if (answer.trim().toLowerCase() !== "delete my account") {
|
|
118514
|
-
await
|
|
118851
|
+
await writeLine2(ctx, "Cancelled.");
|
|
118515
118852
|
return;
|
|
118516
118853
|
}
|
|
118517
118854
|
}
|
|
118518
118855
|
const accepted = await accountDelete(ctx, { confirm: options.confirm });
|
|
118519
|
-
await
|
|
118856
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Account deletion started. Finalization runs asynchronously (job ${import_picocolors2.default.dim(accepted.finalizeJobId)}); usually completes within a few seconds. Your API tokens have already been revoked.`);
|
|
118520
118857
|
return;
|
|
118521
118858
|
}
|
|
118522
118859
|
throw new Error(`Unknown account action "${action ?? ""}". Use: export | preview-delete | delete.`);
|
|
@@ -118527,82 +118864,82 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118527
118864
|
cli.command("mcp", "Print the MCP server config for AI assistants (Claude Code, Cursor, Windsurf)").option("--json", "Output JSON only (paste-ready)").action(async (options) => {
|
|
118528
118865
|
const result = mcp(ctx, {});
|
|
118529
118866
|
if (options.json) {
|
|
118530
|
-
await
|
|
118867
|
+
await writeLine2(ctx, JSON.stringify(result.config, null, 2));
|
|
118531
118868
|
return;
|
|
118532
118869
|
}
|
|
118533
|
-
await
|
|
118534
|
-
await
|
|
118535
|
-
await
|
|
118536
|
-
await
|
|
118537
|
-
await
|
|
118538
|
-
await
|
|
118539
|
-
await
|
|
118540
|
-
await
|
|
118541
|
-
await
|
|
118870
|
+
await writeLine2(ctx, import_picocolors2.default.bold("Percher MCP server"));
|
|
118871
|
+
await writeLine2(ctx, "");
|
|
118872
|
+
await writeLine2(ctx, import_picocolors2.default.bold("Local (npx) — full tool set, reads your project files:"));
|
|
118873
|
+
await writeLine2(ctx, JSON.stringify(result.config, null, 2));
|
|
118874
|
+
await writeLine2(ctx, "");
|
|
118875
|
+
await writeLine2(ctx, import_picocolors2.default.bold("Hosted connector — add this URL, OAuth login in the browser (no token):"));
|
|
118876
|
+
await writeLine2(ctx, ` ${result.hosted.url}`);
|
|
118877
|
+
await writeLine2(ctx, "");
|
|
118878
|
+
await writeLine2(ctx, import_picocolors2.default.bold("Where to paste:"));
|
|
118542
118879
|
for (const loc of result.installLocations) {
|
|
118543
|
-
await
|
|
118880
|
+
await writeLine2(ctx, ` ${loc.client.padEnd(28)} ${import_picocolors2.default.dim(loc.path)}`);
|
|
118544
118881
|
if (loc.note) {
|
|
118545
|
-
await
|
|
118882
|
+
await writeLine2(ctx, ` ${"".padEnd(28)} ${import_picocolors2.default.dim(loc.note)}`);
|
|
118546
118883
|
}
|
|
118547
118884
|
}
|
|
118548
|
-
await
|
|
118885
|
+
await writeLine2(ctx, "");
|
|
118549
118886
|
for (const note of result.notes) {
|
|
118550
|
-
await
|
|
118887
|
+
await writeLine2(ctx, import_picocolors2.default.dim(note));
|
|
118551
118888
|
}
|
|
118552
|
-
await
|
|
118553
|
-
await
|
|
118889
|
+
await writeLine2(ctx, "");
|
|
118890
|
+
await writeLine2(ctx, import_picocolors2.default.dim("Once installed, your assistant gets percher_publish + 28 other tools."));
|
|
118554
118891
|
});
|
|
118555
118892
|
cli.command("ai-files <action>", "Manage AI-assistant rules files (install | update | status | remove)").option("--force", "Overwrite or remove user-modified / foreign files").option("--json", "Output JSON").action(async (action, options) => {
|
|
118556
118893
|
if (action === "install") {
|
|
118557
118894
|
const r = await aiFilesInstall(ctx, { force: options.force });
|
|
118558
118895
|
if (options.json) {
|
|
118559
|
-
await
|
|
118896
|
+
await writeLine2(ctx, JSON.stringify(r, null, 2));
|
|
118560
118897
|
return;
|
|
118561
118898
|
}
|
|
118562
118899
|
for (const i of r.installed) {
|
|
118563
|
-
await
|
|
118900
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} installed ${i.path} (v${i.templateVersion})`);
|
|
118564
118901
|
}
|
|
118565
118902
|
for (const s of r.skipped) {
|
|
118566
118903
|
const why = s.reason === "already-installed" ? "already installed" : s.reason === "modified" ? "user-modified — use --force to overwrite" : "foreign file — use --force to overwrite";
|
|
118567
|
-
await
|
|
118904
|
+
await writeLine2(ctx, `${import_picocolors2.default.yellow("!")} skipped ${s.path}: ${why}`);
|
|
118568
118905
|
}
|
|
118569
118906
|
} else if (action === "update") {
|
|
118570
118907
|
const r = await aiFilesUpdate(ctx, { force: options.force });
|
|
118571
118908
|
if (options.json) {
|
|
118572
|
-
await
|
|
118909
|
+
await writeLine2(ctx, JSON.stringify(r, null, 2));
|
|
118573
118910
|
return;
|
|
118574
118911
|
}
|
|
118575
118912
|
for (const u of r.updated) {
|
|
118576
118913
|
const tag = u.reason === "reinstalled" ? "reinstalled" : `${u.from ?? "new"} → ${u.to}`;
|
|
118577
|
-
await
|
|
118914
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} updated ${u.path} (${tag})`);
|
|
118578
118915
|
}
|
|
118579
118916
|
for (const s of r.skipped) {
|
|
118580
118917
|
const why = s.reason === "up-to-date" ? "up to date" : s.reason === "modified" ? "user-modified — use --force to overwrite" : s.reason === "foreign" ? "foreign file" : "no manifest entry";
|
|
118581
|
-
await
|
|
118918
|
+
await writeLine2(ctx, `${import_picocolors2.default.dim(`· ${s.path}: ${why}`)}`);
|
|
118582
118919
|
}
|
|
118583
118920
|
} else if (action === "status") {
|
|
118584
118921
|
const r = await aiFilesStatus(ctx, {});
|
|
118585
118922
|
if (options.json) {
|
|
118586
|
-
await
|
|
118923
|
+
await writeLine2(ctx, JSON.stringify(r, null, 2));
|
|
118587
118924
|
return;
|
|
118588
118925
|
}
|
|
118589
|
-
await
|
|
118926
|
+
await writeLine2(ctx, import_picocolors2.default.bold("AI files"));
|
|
118590
118927
|
for (const f of r.files) {
|
|
118591
118928
|
const icon = f.status === "managed" ? import_picocolors2.default.green("✓") : f.status === "modified" ? import_picocolors2.default.yellow("!") : f.status === "missing" ? import_picocolors2.default.dim("·") : import_picocolors2.default.red("✗");
|
|
118592
118929
|
const versionTag = f.installedVersion && f.installedVersion !== f.latestVersion ? import_picocolors2.default.yellow(` (v${f.installedVersion} → v${f.latestVersion})`) : f.installedVersion ? import_picocolors2.default.dim(` (v${f.installedVersion})`) : import_picocolors2.default.dim(` (latest v${f.latestVersion})`);
|
|
118593
|
-
await
|
|
118930
|
+
await writeLine2(ctx, ` ${icon} ${f.path.padEnd(40)} ${f.status}${versionTag}`);
|
|
118594
118931
|
}
|
|
118595
118932
|
} else if (action === "remove") {
|
|
118596
118933
|
const r = await aiFilesRemove(ctx, { force: options.force });
|
|
118597
118934
|
if (options.json) {
|
|
118598
|
-
await
|
|
118935
|
+
await writeLine2(ctx, JSON.stringify(r, null, 2));
|
|
118599
118936
|
return;
|
|
118600
118937
|
}
|
|
118601
118938
|
for (const x of r.removed) {
|
|
118602
|
-
await
|
|
118939
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} removed ${x.path}`);
|
|
118603
118940
|
}
|
|
118604
118941
|
for (const s of r.skipped) {
|
|
118605
|
-
await
|
|
118942
|
+
await writeLine2(ctx, `${import_picocolors2.default.yellow("!")} skipped ${s.path}: ${s.reason}${s.reason === "modified" ? " — use --force to delete" : ""}`);
|
|
118606
118943
|
}
|
|
118607
118944
|
} else {
|
|
118608
118945
|
throw new Error(`Unknown ai-files action "${action}". Use install | update | status | remove.`);
|
|
@@ -118613,7 +118950,7 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118613
118950
|
from: options.from
|
|
118614
118951
|
});
|
|
118615
118952
|
if (result.configGenerated) {
|
|
118616
|
-
await
|
|
118953
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Imported from ${result.provider} — percher.toml generated`);
|
|
118617
118954
|
}
|
|
118618
118955
|
const mappedParts = [];
|
|
118619
118956
|
if (result.mapped.redirects > 0) {
|
|
@@ -118623,25 +118960,25 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118623
118960
|
mappedParts.push(`headers for ${result.mapped.headerPaths} path${result.mapped.headerPaths === 1 ? "" : "s"}`);
|
|
118624
118961
|
}
|
|
118625
118962
|
if (mappedParts.length > 0) {
|
|
118626
|
-
await
|
|
118963
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Mapped ${mappedParts.join(" and ")} into percher.toml`);
|
|
118627
118964
|
}
|
|
118628
118965
|
for (const file2 of result.scaffoldedFiles) {
|
|
118629
|
-
await
|
|
118966
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} wrote ${file2}`);
|
|
118630
118967
|
}
|
|
118631
118968
|
for (const w of result.warnings) {
|
|
118632
|
-
await
|
|
118969
|
+
await writeLine2(ctx, `${import_picocolors2.default.yellow("!")} ${w}`);
|
|
118633
118970
|
}
|
|
118634
118971
|
if (result.envVars.length > 0) {
|
|
118635
|
-
await
|
|
118636
|
-
await
|
|
118972
|
+
await writeLine2(ctx, "");
|
|
118973
|
+
await writeLine2(ctx, "Set these env vars before publishing:");
|
|
118637
118974
|
for (const v of result.envVars) {
|
|
118638
|
-
await
|
|
118975
|
+
await writeLine2(ctx, ` bunx percher env set ${v}=your-value`);
|
|
118639
118976
|
}
|
|
118640
118977
|
}
|
|
118641
118978
|
if (result.configGenerated) {
|
|
118642
|
-
await
|
|
118643
|
-
await
|
|
118644
|
-
await
|
|
118979
|
+
await writeLine2(ctx, "");
|
|
118980
|
+
await writeLine2(ctx, "Next: review percher.toml, then run:");
|
|
118981
|
+
await writeLine2(ctx, " bunx percher publish");
|
|
118645
118982
|
}
|
|
118646
118983
|
});
|
|
118647
118984
|
cli.command("domains <action> [domain]", "Manage custom domains (add | verify | list | remove)").option("--app <name>", "App name (overrides percher.toml)").action(async (action, domain2, options) => {
|
|
@@ -118649,52 +118986,52 @@ function registerAccountCommands(cli, ctx) {
|
|
|
118649
118986
|
if (!domain2)
|
|
118650
118987
|
throw new Error("Domain is required. Usage: percher domains add <domain>");
|
|
118651
118988
|
const result = await domainAdd(ctx, { app: options.app, domain: domain2 });
|
|
118652
|
-
await
|
|
118653
|
-
await
|
|
118654
|
-
await
|
|
118655
|
-
await
|
|
118989
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Domain ${result.domain} added.`);
|
|
118990
|
+
await writeLine2(ctx, "");
|
|
118991
|
+
await writeLine2(ctx, `Add these DNS records ${result.dnsInstructions.kind === "apex" ? "(this is a root/apex domain — most providers reject CNAME on apex per RFC 1034, use A-record)" : "(this is a subdomain — CNAME works everywhere)"}:`);
|
|
118992
|
+
await writeLine2(ctx, ` TXT ${result.dnsInstructions.txt.name} ${result.dnsInstructions.txt.value}`);
|
|
118656
118993
|
const primary = result.dnsInstructions.primary;
|
|
118657
118994
|
if (primary.type === "A") {
|
|
118658
|
-
await
|
|
118995
|
+
await writeLine2(ctx, ` A ${primary.name} ${primary.values.join(", ")}`);
|
|
118659
118996
|
} else {
|
|
118660
|
-
await
|
|
118997
|
+
await writeLine2(ctx, ` CNAME ${primary.name} ${primary.value}`);
|
|
118661
118998
|
}
|
|
118662
118999
|
if (result.dnsInstructions.alternative) {
|
|
118663
119000
|
const alt = result.dnsInstructions.alternative;
|
|
118664
|
-
await
|
|
118665
|
-
await
|
|
119001
|
+
await writeLine2(ctx, "");
|
|
119002
|
+
await writeLine2(ctx, " Alternative (only for Cloudflare/Route53 with CNAME-flattening):");
|
|
118666
119003
|
if (alt.type === "A") {
|
|
118667
|
-
await
|
|
119004
|
+
await writeLine2(ctx, ` A ${alt.name} ${alt.values.join(", ")}`);
|
|
118668
119005
|
} else {
|
|
118669
|
-
await
|
|
119006
|
+
await writeLine2(ctx, ` CNAME ${alt.name} ${alt.value}`);
|
|
118670
119007
|
}
|
|
118671
119008
|
}
|
|
118672
|
-
await
|
|
118673
|
-
await
|
|
118674
|
-
await
|
|
119009
|
+
await writeLine2(ctx, "");
|
|
119010
|
+
await writeLine2(ctx, "Then run:");
|
|
119011
|
+
await writeLine2(ctx, ` percher domains verify ${result.domain}${options.app ? ` --app ${options.app}` : ""}`);
|
|
118675
119012
|
} else if (action === "verify") {
|
|
118676
119013
|
if (!domain2)
|
|
118677
119014
|
throw new Error("Domain is required. Usage: percher domains verify <domain>");
|
|
118678
119015
|
const result = await domainVerify(ctx, { app: options.app, domain: domain2 });
|
|
118679
119016
|
if (result.status === "verified") {
|
|
118680
|
-
await
|
|
119017
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Domain verified. ${result.domain} is now live.`);
|
|
118681
119018
|
} else {
|
|
118682
|
-
await
|
|
119019
|
+
await writeLine2(ctx, `${import_picocolors2.default.yellow("!")} ${result.domain}: ${result.failureReason}`);
|
|
118683
119020
|
}
|
|
118684
119021
|
} else if (action === "list") {
|
|
118685
119022
|
const list = await domainList(ctx, { app: options.app });
|
|
118686
119023
|
if (list.length === 0) {
|
|
118687
|
-
await
|
|
119024
|
+
await writeLine2(ctx, "No custom domains.");
|
|
118688
119025
|
} else {
|
|
118689
119026
|
for (const d of list) {
|
|
118690
|
-
await
|
|
119027
|
+
await writeLine2(ctx, `${d.domain.padEnd(40)} ${d.status}`);
|
|
118691
119028
|
}
|
|
118692
119029
|
}
|
|
118693
119030
|
} else if (action === "remove") {
|
|
118694
119031
|
if (!domain2)
|
|
118695
119032
|
throw new Error("Domain is required. Usage: percher domains remove <domain>");
|
|
118696
119033
|
await domainRemove(ctx, { app: options.app, domain: domain2 });
|
|
118697
|
-
await
|
|
119034
|
+
await writeLine2(ctx, `${import_picocolors2.default.green("✓")} Domain removed.`);
|
|
118698
119035
|
} else {
|
|
118699
119036
|
throw new Error(`Unknown domains action "${action}". Use add | verify | list | remove.`);
|
|
118700
119037
|
}
|
|
@@ -118707,7 +119044,7 @@ function registerAdminCommands(cli, ctx) {
|
|
|
118707
119044
|
cli.command("admin [action] [...rest]", "Operator commands (admin only): reconcile-routes").option("--app <name>", "(reconcile-routes) Limit to a single app by name").option("--app-id <id>", "(reconcile-routes) Limit to a single app by id").option("--dry-run", "(reconcile-routes) Read-only diff — no Caddy mutations, returns missing/extra/host drift").option("--json", "(reconcile-routes) Output JSON instead of formatted text").action(async (action, _rest, options) => {
|
|
118708
119045
|
if (action === "reconcile-routes") {
|
|
118709
119046
|
if (!ctx.client.token) {
|
|
118710
|
-
await
|
|
119047
|
+
await writeLine2(ctx, `${import_picocolors3.default.red("✗")} Not logged in. Run: percher login`);
|
|
118711
119048
|
throw new SilentExitError;
|
|
118712
119049
|
}
|
|
118713
119050
|
const result = await adminReconcileRoutes(ctx, {
|
|
@@ -118716,7 +119053,7 @@ function registerAdminCommands(cli, ctx) {
|
|
|
118716
119053
|
dryRun: Boolean(options.dryRun)
|
|
118717
119054
|
});
|
|
118718
119055
|
if (options.json) {
|
|
118719
|
-
await
|
|
119056
|
+
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
118720
119057
|
return;
|
|
118721
119058
|
}
|
|
118722
119059
|
if (options.dryRun) {
|
|
@@ -118725,25 +119062,25 @@ function registerAdminCommands(cli, ctx) {
|
|
|
118725
119062
|
const extra = result.extraRoute ?? 0;
|
|
118726
119063
|
const drift = result.dryRunDrift ?? [];
|
|
118727
119064
|
if (missing === 0 && hostDrift === 0 && extra === 0) {
|
|
118728
|
-
await
|
|
119065
|
+
await writeLine2(ctx, `${import_picocolors3.default.green("✓")} No drift — Caddy matches expected routes.`);
|
|
118729
119066
|
return;
|
|
118730
119067
|
}
|
|
118731
|
-
await
|
|
119068
|
+
await writeLine2(ctx, `${import_picocolors3.default.yellow("!")} drift: ${missing} missing route(s), ${hostDrift} missing host(s), ${extra} extra route(s)`);
|
|
118732
119069
|
for (const d of drift) {
|
|
118733
119070
|
const label = d.kind === "missing_route" ? import_picocolors3.default.red("MISSING") : d.kind === "missing_host" ? import_picocolors3.default.yellow("HOST_DRIFT") : import_picocolors3.default.dim("EXTRA");
|
|
118734
119071
|
const target = d.appName ? `[${d.appName}] ` : "";
|
|
118735
|
-
await
|
|
119072
|
+
await writeLine2(ctx, ` ${label} ${target}${d.routeId} (${d.routeType}) — ${d.hosts.join(", ")}`);
|
|
118736
119073
|
}
|
|
118737
|
-
await
|
|
119074
|
+
await writeLine2(ctx, import_picocolors3.default.dim(`Repair: percher admin reconcile-routes${options.app ? ` --app ${options.app}` : ""}`));
|
|
118738
119075
|
return;
|
|
118739
119076
|
}
|
|
118740
119077
|
if (result.errors === 0) {
|
|
118741
|
-
await
|
|
119078
|
+
await writeLine2(ctx, `${import_picocolors3.default.green("✓")} reconciled ${result.reconciled}, recovered ${result.recovered}, stale ${result.stale}, errors 0`);
|
|
118742
119079
|
return;
|
|
118743
119080
|
}
|
|
118744
|
-
await
|
|
119081
|
+
await writeLine2(ctx, `${import_picocolors3.default.yellow("!")} reconciled ${result.reconciled}, errors ${result.errors} (app=${result.appRouteErrors} pb=${result.pbRouteErrors} sleeping=${result.sleepingRouteErrors})`);
|
|
118745
119082
|
for (const e of result.perAppErrors) {
|
|
118746
|
-
await
|
|
119083
|
+
await writeLine2(ctx, ` ${import_picocolors3.default.red("✗")} [${e.appName}] ${e.routeId} (${e.routeType}) — ${e.errorCode}: ${e.cause}`);
|
|
118747
119084
|
}
|
|
118748
119085
|
throw new SilentExitError(1);
|
|
118749
119086
|
}
|
|
@@ -118760,156 +119097,162 @@ function assertNever2(status2, summary) {
|
|
|
118760
119097
|
async function handlePublishResult(ctx, result, options = {}) {
|
|
118761
119098
|
switch (result.status) {
|
|
118762
119099
|
case "dry_run": {
|
|
118763
|
-
await
|
|
119100
|
+
await writeLine2(ctx, `Dry run: ${result.fileCount} files, ${(result.bytes / 1024).toFixed(1)} KB`);
|
|
119101
|
+
return;
|
|
119102
|
+
}
|
|
119103
|
+
case "check_passed": {
|
|
119104
|
+
const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
|
|
119105
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Check passed${timing} — would build and boot`);
|
|
119106
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" The live app was not changed. Run `percher publish` to ship."));
|
|
118764
119107
|
return;
|
|
118765
119108
|
}
|
|
118766
119109
|
case "needs_login": {
|
|
118767
119110
|
if (result.loginUrl) {
|
|
118768
|
-
await
|
|
118769
|
-
await
|
|
119111
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Not logged in. Authorize here:`);
|
|
119112
|
+
await writeLine2(ctx, ` ${result.loginUrl}`);
|
|
118770
119113
|
} else {
|
|
118771
|
-
await
|
|
119114
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
|
|
118772
119115
|
}
|
|
118773
119116
|
throw new SilentExitError;
|
|
118774
119117
|
}
|
|
118775
119118
|
case "auth_pending": {
|
|
118776
119119
|
if (result.loginUrl) {
|
|
118777
|
-
await
|
|
118778
|
-
await
|
|
119120
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Not logged in. Authorize here:`);
|
|
119121
|
+
await writeLine2(ctx, ` ${result.loginUrl}`);
|
|
118779
119122
|
} else {
|
|
118780
|
-
await
|
|
119123
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
|
|
118781
119124
|
}
|
|
118782
119125
|
throw new SilentExitError;
|
|
118783
119126
|
}
|
|
118784
119127
|
case "queued": {
|
|
118785
|
-
await
|
|
119128
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
|
|
118786
119129
|
const deployId = result.deployment?.id;
|
|
118787
119130
|
const appName = result.app?.name;
|
|
118788
119131
|
if (deployId && appName) {
|
|
118789
|
-
await
|
|
119132
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${deployId} --app ${appName}`));
|
|
118790
119133
|
} else if (deployId) {
|
|
118791
|
-
await
|
|
119134
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${deployId}`));
|
|
118792
119135
|
}
|
|
118793
119136
|
return;
|
|
118794
119137
|
}
|
|
118795
119138
|
case "already_in_progress": {
|
|
118796
|
-
await
|
|
119139
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
|
|
118797
119140
|
if (result.recovery.nextAction === "wait_deploy") {
|
|
118798
119141
|
const args = result.recovery.args;
|
|
118799
119142
|
if (args.app && args.deployId) {
|
|
118800
|
-
await
|
|
119143
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${args.deployId} --app ${args.app}`));
|
|
118801
119144
|
}
|
|
118802
119145
|
}
|
|
118803
119146
|
return;
|
|
118804
119147
|
}
|
|
118805
119148
|
case "replaced": {
|
|
118806
|
-
await
|
|
119149
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
|
|
118807
119150
|
if (result.url) {
|
|
118808
|
-
await
|
|
119151
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Live URL: ${result.url}`));
|
|
118809
119152
|
}
|
|
118810
119153
|
if (result.recovery.nextAction === "wait_deploy") {
|
|
118811
119154
|
const args = result.recovery.args;
|
|
118812
119155
|
if (args.app && args.deployId) {
|
|
118813
|
-
await
|
|
119156
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher deploys inspect ${args.deployId} --app ${args.app}`));
|
|
118814
119157
|
}
|
|
118815
119158
|
} else if (result.recovery.nextAction === "ask_user" || result.recovery.nextAction === "run_doctor") {
|
|
118816
119159
|
const appName = result.app?.name;
|
|
118817
119160
|
if (appName) {
|
|
118818
|
-
await
|
|
119161
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher doctor --app ${appName}`));
|
|
118819
119162
|
} else {
|
|
118820
|
-
await
|
|
119163
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Next: percher doctor"));
|
|
118821
119164
|
}
|
|
118822
119165
|
}
|
|
118823
119166
|
return;
|
|
118824
119167
|
}
|
|
118825
119168
|
case "failed": {
|
|
118826
|
-
await
|
|
119169
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} ${result.error?.title ?? "Deployment failed"}`);
|
|
118827
119170
|
if (result.error?.explanation) {
|
|
118828
|
-
await
|
|
119171
|
+
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
118829
119172
|
}
|
|
118830
119173
|
if (result.error?.rawErrorMessage) {
|
|
118831
|
-
await
|
|
119174
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(`Details: ${result.error.rawErrorMessage}`)}`);
|
|
118832
119175
|
}
|
|
118833
119176
|
if (result.error?.suggestion) {
|
|
118834
|
-
await
|
|
119177
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
|
|
118835
119178
|
}
|
|
118836
119179
|
if (result.error?.missingEnvVars && result.error.missingEnvVars.length > 0) {
|
|
118837
|
-
await
|
|
118838
|
-
await
|
|
119180
|
+
await writeLine2(ctx, "");
|
|
119181
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Missing env vars:"));
|
|
118839
119182
|
for (const k of result.error.missingEnvVars) {
|
|
118840
|
-
await
|
|
119183
|
+
await writeLine2(ctx, ` ${k}`);
|
|
118841
119184
|
}
|
|
118842
|
-
await
|
|
119185
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Set with: percher env set ${result.error.missingEnvVars[0]}=...`));
|
|
118843
119186
|
}
|
|
118844
119187
|
if (result.error?.relevantFiles && result.error.relevantFiles.length > 0) {
|
|
118845
|
-
await
|
|
118846
|
-
await
|
|
119188
|
+
await writeLine2(ctx, "");
|
|
119189
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Check these files:"));
|
|
118847
119190
|
for (const f of result.error.relevantFiles) {
|
|
118848
|
-
await
|
|
119191
|
+
await writeLine2(ctx, ` ${f}`);
|
|
118849
119192
|
}
|
|
118850
119193
|
}
|
|
118851
119194
|
if (result.error?.buildLogTail) {
|
|
118852
|
-
await
|
|
119195
|
+
await writeLine2(ctx, "");
|
|
118853
119196
|
const lines = result.error.buildLogTail.split(`
|
|
118854
119197
|
`);
|
|
118855
|
-
await
|
|
119198
|
+
await writeLine2(ctx, `${import_picocolors4.default.bold("Build log")} ${import_picocolors4.default.dim(`(last ${lines.length} lines)`)}`);
|
|
118856
119199
|
for (const line of lines) {
|
|
118857
|
-
await
|
|
119200
|
+
await writeLine2(ctx, ` ${line}`);
|
|
118858
119201
|
}
|
|
118859
119202
|
if (result.app?.id && result.deployment?.id) {
|
|
118860
|
-
await
|
|
118861
|
-
await
|
|
119203
|
+
await writeLine2(ctx, "");
|
|
119204
|
+
await writeLine2(ctx, import_picocolors4.default.dim(`Full log: percher deploys inspect ${result.deployment.id} --app ${result.app.name}`));
|
|
118862
119205
|
}
|
|
118863
119206
|
}
|
|
118864
119207
|
throw new SilentExitError;
|
|
118865
119208
|
}
|
|
118866
119209
|
case "live": {
|
|
118867
119210
|
const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
|
|
118868
|
-
await
|
|
119211
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Published to ${result.url}${timing}`);
|
|
118869
119212
|
if (result.traceId) {
|
|
118870
|
-
await
|
|
119213
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Trace: ${result.traceId}`));
|
|
118871
119214
|
}
|
|
118872
119215
|
if (result.cacheReused) {
|
|
118873
|
-
await
|
|
119216
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: reused — identical bundle, redeployed without re-upload (--no-cache to force a fresh build)"));
|
|
118874
119217
|
} else {
|
|
118875
119218
|
const status2 = result.cacheStatus ?? (result.cacheHit === true ? "image_reused" : result.cacheHit === false ? "fresh" : null);
|
|
118876
119219
|
if (status2 === "image_reused") {
|
|
118877
|
-
await
|
|
119220
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
|
|
118878
119221
|
} else if (status2 === "layer_hit") {
|
|
118879
|
-
await
|
|
119222
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
|
|
118880
119223
|
} else if (status2 === "fresh") {
|
|
118881
|
-
await
|
|
119224
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: miss (fresh build)"));
|
|
118882
119225
|
}
|
|
118883
119226
|
}
|
|
118884
119227
|
if (typeof result.deployment?.imageSizeBytes === "number") {
|
|
118885
|
-
await
|
|
119228
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
|
|
118886
119229
|
}
|
|
118887
119230
|
if (result.replacedPreview) {
|
|
118888
|
-
await
|
|
119231
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" (replaced previous preview — free plan keeps 1 preview slot per app)"));
|
|
118889
119232
|
}
|
|
118890
119233
|
if (options.preview && result.url) {
|
|
118891
119234
|
try {
|
|
118892
119235
|
const qr = await qrToTerminal(result.url);
|
|
118893
|
-
await
|
|
118894
|
-
await
|
|
118895
|
-
await
|
|
119236
|
+
await writeLine2(ctx, "");
|
|
119237
|
+
await writeLine2(ctx, import_picocolors4.default.dim("Scan to preview on mobile:"));
|
|
119238
|
+
await writeLine2(ctx, qr);
|
|
118896
119239
|
} catch {}
|
|
118897
119240
|
}
|
|
118898
119241
|
if (result.anonymous && result.claim) {
|
|
118899
|
-
await
|
|
118900
|
-
await
|
|
118901
|
-
await
|
|
118902
|
-
await
|
|
118903
|
-
await
|
|
119242
|
+
await writeLine2(ctx, "");
|
|
119243
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Anonymous app — claim it to keep it:"));
|
|
119244
|
+
await writeLine2(ctx, ` Expires: ${new Date(result.claim.expiresAt).toLocaleString()}`);
|
|
119245
|
+
await writeLine2(ctx, ` Claim token: ${result.claim.claimToken}`);
|
|
119246
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
|
|
118904
119247
|
for (const w of result.warnings ?? []) {
|
|
118905
|
-
await
|
|
119248
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` ${w}`));
|
|
118906
119249
|
}
|
|
118907
119250
|
} else if (result.firstDeploy) {
|
|
118908
|
-
await
|
|
118909
|
-
await
|
|
118910
|
-
await
|
|
118911
|
-
await
|
|
118912
|
-
await
|
|
119251
|
+
await writeLine2(ctx, "");
|
|
119252
|
+
await writeLine2(ctx, import_picocolors4.default.bold("First deploy — next steps:"));
|
|
119253
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher dev")} watch for changes and re-deploy`);
|
|
119254
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher env set KEY")} add environment variables`);
|
|
119255
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher logs")} stream live logs`);
|
|
118913
119256
|
}
|
|
118914
119257
|
return;
|
|
118915
119258
|
}
|
|
@@ -118924,9 +119267,9 @@ function registerAppsCommands(cli, ctx) {
|
|
|
118924
119267
|
name: options.name,
|
|
118925
119268
|
interactive: true
|
|
118926
119269
|
});
|
|
118927
|
-
await
|
|
119270
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${result.path}`);
|
|
118928
119271
|
for (const file2 of result.scaffoldedFiles) {
|
|
118929
|
-
await
|
|
119272
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
|
|
118930
119273
|
}
|
|
118931
119274
|
});
|
|
118932
119275
|
cli.command("appstore-kit", "Write app-store starter files (PWA manifest, TWA/Capacitor, asset links)").option("--name <name>", "App name (defaults to percher.toml / directory)").option("--url <url>", "App URL (defaults to https://<name>.percher.run)").option("--package <pkg>", "Android package name for assetlinks.json").option("--fingerprint <sha256>", "Signing-cert SHA-256 for assetlinks.json").option("--team-id <id>", "Apple Team ID for apple-app-site-association").option("--bundle-id <id>", "Apple bundle ID for apple-app-site-association").option("--out <dir>", "Output directory (defaults to the current directory)").option("--force", "Overwrite files that already exist").action(async (options) => {
|
|
@@ -118941,32 +119284,32 @@ function registerAppsCommands(cli, ctx) {
|
|
|
118941
119284
|
force: options.force
|
|
118942
119285
|
});
|
|
118943
119286
|
for (const file2 of result.written) {
|
|
118944
|
-
await
|
|
119287
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
|
|
118945
119288
|
}
|
|
118946
119289
|
for (const file2 of result.skipped) {
|
|
118947
|
-
await
|
|
118948
|
-
}
|
|
118949
|
-
await
|
|
118950
|
-
await
|
|
118951
|
-
await
|
|
118952
|
-
await
|
|
118953
|
-
await
|
|
118954
|
-
await
|
|
118955
|
-
await
|
|
118956
|
-
await
|
|
118957
|
-
await
|
|
118958
|
-
await
|
|
118959
|
-
await
|
|
118960
|
-
await
|
|
119290
|
+
await writeLine2(ctx, `${import_picocolors4.default.dim("•")} skipped ${file2} (exists — pass --force to overwrite)`);
|
|
119291
|
+
}
|
|
119292
|
+
await writeLine2(ctx, "");
|
|
119293
|
+
await writeLine2(ctx, `${import_picocolors4.default.bold("Make it an installable PWA:")} link the manifest from your HTML`);
|
|
119294
|
+
await writeLine2(ctx, ` <link rel="manifest" href="/manifest.json">`);
|
|
119295
|
+
await writeLine2(ctx, "and register the service worker from your app entry:");
|
|
119296
|
+
await writeLine2(ctx, result.swRegistration);
|
|
119297
|
+
await writeLine2(ctx, "");
|
|
119298
|
+
await writeLine2(ctx, `${import_picocolors4.default.bold("Wrap for Google Play (TWA):")}`);
|
|
119299
|
+
await writeLine2(ctx, result.bubblewrapCommand);
|
|
119300
|
+
await writeLine2(ctx, "");
|
|
119301
|
+
await writeLine2(ctx, `The two ${import_picocolors4.default.dim(".well-known")} files are trust assertions for your origin — add real values`);
|
|
119302
|
+
await writeLine2(ctx, `after you sign the apps, then serve them yourself or let Percher serve them from the`);
|
|
119303
|
+
await writeLine2(ctx, "App store kit at https://percher.app. Full guide: /docs/app-stores");
|
|
118961
119304
|
});
|
|
118962
|
-
cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").option("--anonymous", "Publish without an account — a 72h sandbox you can claim later (where enabled)").action(async (options) => {
|
|
119305
|
+
cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--check", "Validation deploy: build → start → health, then tear down (no swap, no route). The live app is not changed. Consumes the preview quota.").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").option("--anonymous", "Publish without an account — a 72h sandbox you can claim later (where enabled)").action(async (options) => {
|
|
118963
119306
|
const { detectCiBranchMessage: detectCiBranchMessage2 } = await Promise.resolve().then(() => exports_ci_env);
|
|
118964
119307
|
let messageOverride = options.message;
|
|
118965
119308
|
if (options.preview && !options.message) {
|
|
118966
119309
|
const ci = detectCiBranchMessage2(process.env);
|
|
118967
119310
|
if (ci) {
|
|
118968
119311
|
messageOverride = ci.message;
|
|
118969
|
-
await
|
|
119312
|
+
await writeLine2(ctx, import_picocolors4.default.dim(`Auto-detected ${ci.source} branch — using deploy note "${ci.message}"`));
|
|
118970
119313
|
}
|
|
118971
119314
|
}
|
|
118972
119315
|
const result = await publish(ctx, {
|
|
@@ -118974,52 +119317,54 @@ function registerAppsCommands(cli, ctx) {
|
|
|
118974
119317
|
preview: options.preview,
|
|
118975
119318
|
message: messageOverride,
|
|
118976
119319
|
dryRun: options.dryRun,
|
|
119320
|
+
check: options.check,
|
|
118977
119321
|
noCache: options.cache === false,
|
|
118978
119322
|
anonymous: options.anonymous
|
|
118979
119323
|
});
|
|
118980
119324
|
await handlePublishResult(ctx, result, { preview: options.preview });
|
|
118981
119325
|
});
|
|
118982
|
-
cli.command("redeploy [deployId]", "Re-run an existing deploy without re-uploading source").option("--app <name>", "App name (defaults to percher.toml app.name)").action(async (deployId, options) => {
|
|
119326
|
+
cli.command("redeploy [deployId]", "Re-run an existing deploy without re-uploading source").option("--app <name>", "App name (defaults to percher.toml app.name)").option("--no-cache", "Force a fresh build of that deploy's source (skip the image cache — the stale-image escape hatch)").action(async (deployId, options) => {
|
|
118983
119327
|
const result = await redeploy(ctx, {
|
|
118984
119328
|
app: options.app,
|
|
118985
|
-
deployId
|
|
119329
|
+
deployId,
|
|
119330
|
+
noCache: options.cache === false
|
|
118986
119331
|
});
|
|
118987
119332
|
if (result.status === "needs_login") {
|
|
118988
|
-
await
|
|
119333
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
|
|
118989
119334
|
throw new SilentExitError;
|
|
118990
119335
|
}
|
|
118991
119336
|
if (result.status === "failed") {
|
|
118992
|
-
await
|
|
119337
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} ${result.error?.title ?? "Redeploy failed"}`);
|
|
118993
119338
|
if (result.error?.explanation) {
|
|
118994
|
-
await
|
|
119339
|
+
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
118995
119340
|
}
|
|
118996
119341
|
if (result.error?.suggestion) {
|
|
118997
|
-
await
|
|
119342
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
|
|
118998
119343
|
}
|
|
118999
119344
|
if (result.error?.problems && result.error.problems.length > 0) {
|
|
119000
|
-
await
|
|
119001
|
-
await
|
|
119345
|
+
await writeLine2(ctx, "");
|
|
119346
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Build problems:"));
|
|
119002
119347
|
for (const p of result.error.problems) {
|
|
119003
119348
|
const loc = p.file ? ` ${import_picocolors4.default.dim(`${p.file}${p.line ? `:${p.line}` : ""}${p.column ? `:${p.column}` : ""}`)}` : "";
|
|
119004
|
-
await
|
|
119349
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.red(p.code)}${loc} ${p.message}`);
|
|
119005
119350
|
if (p.hint)
|
|
119006
|
-
await
|
|
119351
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(`→ ${p.hint}`)}`);
|
|
119007
119352
|
}
|
|
119008
119353
|
}
|
|
119009
119354
|
throw new SilentExitError;
|
|
119010
119355
|
}
|
|
119011
119356
|
const timing = result.timing ? ` (${result.timing.totalSeconds.toFixed(0)}s)` : "";
|
|
119012
|
-
await
|
|
119357
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Redeployed to ${result.url}${timing}`);
|
|
119013
119358
|
});
|
|
119014
119359
|
cli.command("wait-for-deploy <deployId>", "Wait for an in-flight deploy to finish").option("--app <name>", "App name (defaults to percher.toml app.name)").option("--timeout <seconds>", "Max seconds to wait (default: 600)").action(async (deployId, options) => {
|
|
119015
119360
|
const appName = options.app ?? readPercherTomlAppName(ctx.cwd);
|
|
119016
119361
|
if (!appName) {
|
|
119017
|
-
await
|
|
119362
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} No app specified — pass --app <name> or run from a directory with percher.toml.`);
|
|
119018
119363
|
throw new SilentExitError(1);
|
|
119019
119364
|
}
|
|
119020
119365
|
const timeoutSec = Number.parseInt(options.timeout ?? "600", 10);
|
|
119021
119366
|
if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
|
|
119022
|
-
await
|
|
119367
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} --timeout must be a positive number of seconds.`);
|
|
119023
119368
|
throw new SilentExitError(1);
|
|
119024
119369
|
}
|
|
119025
119370
|
const deadline = Date.now() + timeoutSec * 1000;
|
|
@@ -119027,7 +119372,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119027
119372
|
for (;; ) {
|
|
119028
119373
|
const remainingMs = deadline - Date.now();
|
|
119029
119374
|
if (remainingMs <= 0) {
|
|
119030
|
-
await
|
|
119375
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
|
|
119031
119376
|
throw new SilentExitError(1);
|
|
119032
119377
|
}
|
|
119033
119378
|
let raceTimer;
|
|
@@ -119049,22 +119394,22 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119049
119394
|
}
|
|
119050
119395
|
if (result === null) {
|
|
119051
119396
|
callController.abort();
|
|
119052
|
-
await
|
|
119397
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
|
|
119053
119398
|
throw new SilentExitError(1);
|
|
119054
119399
|
}
|
|
119055
119400
|
if (result.deployment.status !== lastStatus) {
|
|
119056
119401
|
lastStatus = result.deployment.status;
|
|
119057
|
-
await
|
|
119402
|
+
await writeLine2(ctx, import_picocolors4.default.dim(`${lastStatus}...`));
|
|
119058
119403
|
}
|
|
119059
119404
|
if (result.status === "live") {
|
|
119060
|
-
await
|
|
119405
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
|
|
119061
119406
|
return;
|
|
119062
119407
|
}
|
|
119063
119408
|
if (result.status === "failed" || result.status === "replaced") {
|
|
119064
119409
|
const mark = result.status === "failed" ? import_picocolors4.default.red("✗") : import_picocolors4.default.yellow("!");
|
|
119065
|
-
await
|
|
119410
|
+
await writeLine2(ctx, `${mark} ${result.summary}`);
|
|
119066
119411
|
if (result.error?.suggestion) {
|
|
119067
|
-
await
|
|
119412
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
|
|
119068
119413
|
}
|
|
119069
119414
|
if (result.status === "failed")
|
|
119070
119415
|
throw new SilentExitError(1);
|
|
@@ -119080,49 +119425,49 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119080
119425
|
noCache: options.cache === false
|
|
119081
119426
|
});
|
|
119082
119427
|
if (result.dryRun) {
|
|
119083
|
-
await
|
|
119428
|
+
await writeLine2(ctx, `Dry run: ${result.fileCount} files, ${(result.bytes / 1024).toFixed(1)} KB`);
|
|
119084
119429
|
} else {
|
|
119085
119430
|
const url2 = result.deployment?.previewUrl ?? result.deployment?.url ?? result.app.url;
|
|
119086
119431
|
const label = result.deployment?.previewUrl ? "preview live at" : "deployed to";
|
|
119087
|
-
await
|
|
119432
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${label} ${url2}`);
|
|
119088
119433
|
const status2 = result.deployment?.cacheStatus ?? (result.deployment?.cacheHit === true ? "image_reused" : result.deployment?.cacheHit === false ? "fresh" : null);
|
|
119089
119434
|
if (status2 === "image_reused") {
|
|
119090
|
-
await
|
|
119435
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
|
|
119091
119436
|
} else if (status2 === "layer_hit") {
|
|
119092
|
-
await
|
|
119437
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
|
|
119093
119438
|
} else if (status2 === "fresh") {
|
|
119094
|
-
await
|
|
119439
|
+
await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: miss (fresh build)"));
|
|
119095
119440
|
}
|
|
119096
119441
|
if (typeof result.deployment?.imageSizeBytes === "number") {
|
|
119097
|
-
await
|
|
119442
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
|
|
119098
119443
|
}
|
|
119099
119444
|
}
|
|
119100
119445
|
});
|
|
119101
119446
|
cli.command("rename <name> [app]", "Rename an app (changes its subdomain)").action(async (name, app) => {
|
|
119102
119447
|
const result = await rename2(ctx, { app, name });
|
|
119103
|
-
await
|
|
119104
|
-
await
|
|
119448
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
|
|
119449
|
+
await writeLine2(ctx, ` Previous name: ${result.previousName} (reserved for 60 days)`);
|
|
119105
119450
|
if (result.tomlUpdated) {
|
|
119106
|
-
await
|
|
119451
|
+
await writeLine2(ctx, ` percher.toml updated: name = "${result.name}"`);
|
|
119107
119452
|
} else {
|
|
119108
|
-
await
|
|
119453
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.yellow("!")} Update percher.toml before the next publish: name = "${result.name}"`);
|
|
119109
119454
|
}
|
|
119110
|
-
await
|
|
119455
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(`Next rename available in ${result.cooldownDays} days`)}`);
|
|
119111
119456
|
});
|
|
119112
119457
|
cli.command("open [app]", "Open the app URL in your browser").action(async (app) => {
|
|
119113
119458
|
const url2 = await open(ctx, { app });
|
|
119114
|
-
await
|
|
119459
|
+
await writeLine2(ctx, url2);
|
|
119115
119460
|
});
|
|
119116
119461
|
cli.command("dashboard [tab]", "Open the dashboard for this app (optionally on a specific tab)").option("--app <name>", "App name (overrides percher.toml)").action(async (tab, options) => {
|
|
119117
119462
|
const url2 = await dashboard(ctx, { app: options.app, tab });
|
|
119118
|
-
await
|
|
119463
|
+
await writeLine2(ctx, url2);
|
|
119119
119464
|
});
|
|
119120
119465
|
cli.command("create [name]", "Create a new app from a template").option("--template <template>", "Template to use (node, python, static, nextjs, hono, express)").option("--force", "Overwrite existing files in target directory").option("--list-templates", "List available templates").action(async (name, options) => {
|
|
119121
119466
|
if (options.listTemplates) {
|
|
119122
119467
|
const templates = listTemplates();
|
|
119123
|
-
await
|
|
119468
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Available templates:"));
|
|
119124
119469
|
for (const t of templates) {
|
|
119125
|
-
await
|
|
119470
|
+
await writeLine2(ctx, ` ${t}`);
|
|
119126
119471
|
}
|
|
119127
119472
|
return;
|
|
119128
119473
|
}
|
|
@@ -119131,7 +119476,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119131
119476
|
}
|
|
119132
119477
|
if (!options.template) {
|
|
119133
119478
|
const templates = listTemplates();
|
|
119134
|
-
await
|
|
119479
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} --template is required. Available: ${templates.join(", ")}`);
|
|
119135
119480
|
return;
|
|
119136
119481
|
}
|
|
119137
119482
|
const result = await create(ctx, {
|
|
@@ -119139,41 +119484,41 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119139
119484
|
template: options.template,
|
|
119140
119485
|
force: options.force
|
|
119141
119486
|
});
|
|
119142
|
-
await
|
|
119487
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} created ${result.name} from ${result.template}`);
|
|
119143
119488
|
for (const f of result.files) {
|
|
119144
|
-
await
|
|
119489
|
+
await writeLine2(ctx, ` ${import_picocolors4.default.dim(f)}`);
|
|
119145
119490
|
}
|
|
119146
|
-
await
|
|
119147
|
-
await
|
|
119148
|
-
await
|
|
119149
|
-
await
|
|
119491
|
+
await writeLine2(ctx, "");
|
|
119492
|
+
await writeLine2(ctx, "Next steps:");
|
|
119493
|
+
await writeLine2(ctx, ` cd ${result.name}`);
|
|
119494
|
+
await writeLine2(ctx, " bunx percher publish");
|
|
119150
119495
|
});
|
|
119151
119496
|
cli.command("delete [app]", "Delete an app permanently").option("--yes", "Skip confirmation prompt").action(async (app, options) => {
|
|
119152
119497
|
const name = app ?? readPercherTomlAppName(ctx.cwd);
|
|
119153
119498
|
if (!name) {
|
|
119154
|
-
await
|
|
119499
|
+
await writeLine2(ctx, `${import_picocolors4.default.red("✗")} No app specified and no percher.toml found.`);
|
|
119155
119500
|
throw new SilentExitError;
|
|
119156
119501
|
}
|
|
119157
119502
|
if (!options.yes) {
|
|
119158
119503
|
const answer = await ctx.prompt(`${import_picocolors4.default.red("!")} This will permanently delete ${import_picocolors4.default.bold(name)} and all its data. Type the app name to confirm:`);
|
|
119159
119504
|
if (answer.trim() !== name) {
|
|
119160
|
-
await
|
|
119505
|
+
await writeLine2(ctx, "Cancelled.");
|
|
119161
119506
|
return;
|
|
119162
119507
|
}
|
|
119163
119508
|
}
|
|
119164
119509
|
await deleteApp(ctx, { app: name, confirm: true });
|
|
119165
|
-
await
|
|
119510
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Deleted ${name}`);
|
|
119166
119511
|
});
|
|
119167
119512
|
cli.command("resume [app]", "Resume an app suspended for inactivity").action(async (app) => {
|
|
119168
119513
|
const result = await resume(ctx, { app });
|
|
119169
|
-
await
|
|
119514
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
|
|
119170
119515
|
});
|
|
119171
119516
|
cli.command("claim", "Claim an anonymous app into your account").option("--app <name>", "App name (defaults to the persisted anon state or percher.toml)").option("--claim-token <token>", "Claim token from the anonymous publish").action(async (options) => {
|
|
119172
119517
|
const result = await claim(ctx, { app: options.app, claimToken: options.claimToken });
|
|
119173
119518
|
if (result.status === "claimed") {
|
|
119174
|
-
await
|
|
119519
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
|
|
119175
119520
|
} else {
|
|
119176
|
-
await
|
|
119521
|
+
await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
|
|
119177
119522
|
}
|
|
119178
119523
|
});
|
|
119179
119524
|
cli.command("inspect-link [app]", "Create a public read-only inspect link (or --revoke it)").option("--app <name>", "App name (defaults to percher.toml)").option("--revoke", "Revoke the app's inspect link instead of creating one").action(async (app, options) => {
|
|
@@ -119181,49 +119526,49 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119181
119526
|
app: app ?? options.app,
|
|
119182
119527
|
revoke: options.revoke
|
|
119183
119528
|
});
|
|
119184
|
-
await
|
|
119529
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
|
|
119185
119530
|
});
|
|
119186
119531
|
cli.command("cache <action> [app]", "Manage the app's build cache (reset)").action(async (action, app) => {
|
|
119187
119532
|
if (action !== "reset") {
|
|
119188
119533
|
throw new Error(`Unknown cache action "${action}". Use: percher cache reset [app]`);
|
|
119189
119534
|
}
|
|
119190
119535
|
const result = await cacheReset(ctx, { app });
|
|
119191
|
-
await
|
|
119536
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
|
|
119192
119537
|
});
|
|
119193
119538
|
cli.command("cache reset [app]", "Reset the app's build cache (next deploy runs cold)").action(async (app) => {
|
|
119194
119539
|
const result = await cacheReset(ctx, { app });
|
|
119195
|
-
await
|
|
119540
|
+
await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
|
|
119196
119541
|
});
|
|
119197
119542
|
cli.command("doctor [app]", "Check app health and connectivity").option("--json", "Output as JSON").action(async (app, options) => {
|
|
119198
119543
|
const result = await doctor(ctx, { app, json: options.json });
|
|
119199
119544
|
if (options.json) {
|
|
119200
|
-
await
|
|
119545
|
+
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
119201
119546
|
} else {
|
|
119202
|
-
await
|
|
119203
|
-
await
|
|
119204
|
-
await
|
|
119547
|
+
await writeLine2(ctx, `${import_picocolors4.default.bold("Percher Doctor")}`);
|
|
119548
|
+
await writeLine2(ctx, "──────────────");
|
|
119549
|
+
await writeLine2(ctx, "");
|
|
119205
119550
|
for (const check2 of result.checks) {
|
|
119206
119551
|
const icon = check2.status === "pass" ? import_picocolors4.default.green("✓") : check2.status === "fail" ? import_picocolors4.default.red("✗") : check2.status === "warn" ? import_picocolors4.default.yellow("!") : import_picocolors4.default.dim("—");
|
|
119207
|
-
await
|
|
119552
|
+
await writeLine2(ctx, `${icon} ${import_picocolors4.default.bold(check2.name.padEnd(20))} ${check2.message}`);
|
|
119208
119553
|
}
|
|
119209
|
-
await
|
|
119554
|
+
await writeLine2(ctx, "");
|
|
119210
119555
|
const counter = `${result.passed}/${result.total} checks passed`;
|
|
119211
119556
|
const counterColor = result.status === "ok" ? import_picocolors4.default.green : result.status === "in_progress" ? import_picocolors4.default.yellow : import_picocolors4.default.red;
|
|
119212
|
-
await
|
|
119213
|
-
await
|
|
119557
|
+
await writeLine2(ctx, counterColor(counter));
|
|
119558
|
+
await writeLine2(ctx, result.summary);
|
|
119214
119559
|
if (result.suspectDeploy) {
|
|
119215
119560
|
const s = result.suspectDeploy;
|
|
119216
119561
|
const head = (s.commitSha ?? s.deployId).slice(0, 10);
|
|
119217
|
-
await
|
|
119218
|
-
await
|
|
119219
|
-
await
|
|
119562
|
+
await writeLine2(ctx, "");
|
|
119563
|
+
await writeLine2(ctx, import_picocolors4.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
|
|
119564
|
+
await writeLine2(ctx, ` Suspect: ${head}`);
|
|
119220
119565
|
if (s.lastHealthyCommitSha) {
|
|
119221
119566
|
const base = s.lastHealthyCommitSha.slice(0, 10);
|
|
119222
|
-
await
|
|
119223
|
-
await
|
|
119567
|
+
await writeLine2(ctx, ` Last working: ${base}`);
|
|
119568
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
|
|
119224
119569
|
}
|
|
119225
119570
|
for (const e of s.evidence)
|
|
119226
|
-
await
|
|
119571
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` · ${e}`));
|
|
119227
119572
|
}
|
|
119228
119573
|
}
|
|
119229
119574
|
if (result.status === "blocked" || result.status === "needs_action") {
|
|
@@ -119235,15 +119580,15 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119235
119580
|
const onSignal = () => controller.abort();
|
|
119236
119581
|
process.on("SIGINT", onSignal);
|
|
119237
119582
|
process.on("SIGTERM", onSignal);
|
|
119238
|
-
await
|
|
119239
|
-
await
|
|
119240
|
-
await
|
|
119583
|
+
await writeLine2(ctx, import_picocolors4.default.bold("Percher Dev"));
|
|
119584
|
+
await writeLine2(ctx, "───────────");
|
|
119585
|
+
await writeLine2(ctx, "");
|
|
119241
119586
|
try {
|
|
119242
119587
|
await dev(ctx, { app }, {
|
|
119243
119588
|
signal: controller.signal,
|
|
119244
119589
|
async writeLine(line) {
|
|
119245
119590
|
const timestamp = new Date().toLocaleTimeString("sv-SE", { hour12: false });
|
|
119246
|
-
await
|
|
119591
|
+
await writeLine2(ctx, `${import_picocolors4.default.dim(`[${timestamp}]`)} ${line}`);
|
|
119247
119592
|
},
|
|
119248
119593
|
async onFilesChanged(_files) {
|
|
119249
119594
|
if (options.confirm) {
|
|
@@ -119257,8 +119602,8 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119257
119602
|
process.removeListener("SIGINT", onSignal);
|
|
119258
119603
|
process.removeListener("SIGTERM", onSignal);
|
|
119259
119604
|
}
|
|
119260
|
-
await
|
|
119261
|
-
await
|
|
119605
|
+
await writeLine2(ctx, "");
|
|
119606
|
+
await writeLine2(ctx, import_picocolors4.default.dim("Dev session ended."));
|
|
119262
119607
|
});
|
|
119263
119608
|
}
|
|
119264
119609
|
|
|
@@ -119270,11 +119615,11 @@ function registerAuthCommands(cli, ctx) {
|
|
|
119270
119615
|
});
|
|
119271
119616
|
cli.command("whoami", "Show the signed-in user").action(async () => {
|
|
119272
119617
|
if (!ctx.client.token) {
|
|
119273
|
-
await
|
|
119618
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
|
|
119274
119619
|
throw new SilentExitError;
|
|
119275
119620
|
}
|
|
119276
119621
|
const me = await whoami(ctx);
|
|
119277
|
-
await
|
|
119622
|
+
await writeLine2(ctx, `Signed in as ${me.email} (${me.userId})`);
|
|
119278
119623
|
});
|
|
119279
119624
|
}
|
|
119280
119625
|
|
|
@@ -119297,9 +119642,9 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119297
119642
|
const out = outIdx >= 0 ? argv[outIdx + 1] : undefined;
|
|
119298
119643
|
const result2 = await dataExport(ctx, { app: realApp, out });
|
|
119299
119644
|
const mb = (result2.bytes / 1024 / 1024).toFixed(1);
|
|
119300
|
-
await
|
|
119301
|
-
await
|
|
119302
|
-
await
|
|
119645
|
+
await writeLine2(ctx, `${import_picocolors6.default.green("✓")} Exported ${result2.app}`);
|
|
119646
|
+
await writeLine2(ctx, ` Path: ${result2.path}`);
|
|
119647
|
+
await writeLine2(ctx, ` Size: ${mb} MB`);
|
|
119303
119648
|
return;
|
|
119304
119649
|
}
|
|
119305
119650
|
if (app === "oauth") {
|
|
@@ -119313,48 +119658,48 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119313
119658
|
remove: options.remove
|
|
119314
119659
|
});
|
|
119315
119660
|
if (result2.action === "removed") {
|
|
119316
|
-
await
|
|
119661
|
+
await writeLine2(ctx, `${import_picocolors6.default.green("✓")} Removed ${result2.provider} sign-in.`);
|
|
119317
119662
|
} else if (result2.action === "set") {
|
|
119318
|
-
await
|
|
119319
|
-
await
|
|
119320
|
-
await
|
|
119321
|
-
await
|
|
119663
|
+
await writeLine2(ctx, `${import_picocolors6.default.green("✓")} ${result2.provider} sign-in enabled.`);
|
|
119664
|
+
await writeLine2(ctx, "");
|
|
119665
|
+
await writeLine2(ctx, import_picocolors6.default.bold("One more step — add this redirect URI in the provider console:"));
|
|
119666
|
+
await writeLine2(ctx, ` ${result2.redirectUri}`);
|
|
119322
119667
|
} else {
|
|
119323
119668
|
if (!result2.providers?.length) {
|
|
119324
|
-
await
|
|
119669
|
+
await writeLine2(ctx, import_picocolors6.default.dim("No OAuth providers configured."));
|
|
119325
119670
|
} else {
|
|
119326
|
-
await
|
|
119671
|
+
await writeLine2(ctx, import_picocolors6.default.bold("Configured providers"));
|
|
119327
119672
|
for (const p of result2.providers) {
|
|
119328
|
-
await
|
|
119673
|
+
await writeLine2(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors6.default.dim(p.clientId)}`);
|
|
119329
119674
|
}
|
|
119330
119675
|
}
|
|
119331
|
-
await
|
|
119332
|
-
await
|
|
119676
|
+
await writeLine2(ctx, "");
|
|
119677
|
+
await writeLine2(ctx, `Redirect URI: ${result2.redirectUri}`);
|
|
119333
119678
|
}
|
|
119334
119679
|
return;
|
|
119335
119680
|
}
|
|
119336
119681
|
const result = await data(ctx, { app, json: options.json });
|
|
119337
119682
|
if (options.json) {
|
|
119338
|
-
await
|
|
119683
|
+
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
119339
119684
|
} else {
|
|
119340
119685
|
const version2 = result.version ? ` Version: ${result.version}` : "";
|
|
119341
|
-
await
|
|
119342
|
-
await
|
|
119686
|
+
await writeLine2(ctx, `${import_picocolors6.default.bold("Data")} Mode: ${result.mode} Status: ${result.status}${version2}`);
|
|
119687
|
+
await writeLine2(ctx, "");
|
|
119343
119688
|
if (result.collections.length > 0) {
|
|
119344
|
-
await
|
|
119689
|
+
await writeLine2(ctx, import_picocolors6.default.bold("Collections"));
|
|
119345
119690
|
for (const col of result.collections) {
|
|
119346
|
-
await
|
|
119691
|
+
await writeLine2(ctx, ` ${col.name.padEnd(20)} ${import_picocolors6.default.dim(col.type)}`);
|
|
119347
119692
|
}
|
|
119348
|
-
await
|
|
119693
|
+
await writeLine2(ctx, "");
|
|
119349
119694
|
}
|
|
119350
|
-
await
|
|
119695
|
+
await writeLine2(ctx, `Collections: ${result.stats.collections} Records: ${result.stats.totalRecords}`);
|
|
119351
119696
|
if (result.adminUrl) {
|
|
119352
|
-
await
|
|
119353
|
-
await
|
|
119697
|
+
await writeLine2(ctx, "");
|
|
119698
|
+
await writeLine2(ctx, `Admin: ${result.adminUrl}`);
|
|
119354
119699
|
}
|
|
119355
119700
|
if (result.warning) {
|
|
119356
|
-
await
|
|
119357
|
-
await
|
|
119701
|
+
await writeLine2(ctx, "");
|
|
119702
|
+
await writeLine2(ctx, import_picocolors6.default.yellow(`⚠ ${result.warning}`));
|
|
119358
119703
|
}
|
|
119359
119704
|
}
|
|
119360
119705
|
});
|
|
@@ -119367,19 +119712,19 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119367
119712
|
cli.command("backups [app]", "List available backups for an app").action(async (app) => {
|
|
119368
119713
|
const { backups, skipped } = await listBackups(ctx, { app });
|
|
119369
119714
|
if (backups.length === 0) {
|
|
119370
|
-
await
|
|
119715
|
+
await writeLine2(ctx, `${import_picocolors6.default.dim("No backups found.")}`);
|
|
119371
119716
|
if (skipped.length > 0) {
|
|
119372
|
-
await
|
|
119717
|
+
await writeLine2(ctx, import_picocolors6.default.yellow(`(${skipped.length} sidecar(s) skipped — see ops logs)`));
|
|
119373
119718
|
}
|
|
119374
119719
|
return;
|
|
119375
119720
|
}
|
|
119376
119721
|
for (const b of backups) {
|
|
119377
119722
|
const components = b.apps[0]?.components.join(", ") ?? "—";
|
|
119378
119723
|
const size = b.tarball_size_bytes < 1024 * 1024 ? `${Math.round(b.tarball_size_bytes / 1024)} KB` : `${(b.tarball_size_bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
119379
|
-
await
|
|
119724
|
+
await writeLine2(ctx, `${import_picocolors6.default.green("●")} ${import_picocolors6.default.bold(b.backup_id)} ${import_picocolors6.default.dim(size.padStart(10))} ${import_picocolors6.default.dim(`[${components}]`)}`);
|
|
119380
119725
|
}
|
|
119381
119726
|
if (skipped.length > 0) {
|
|
119382
|
-
await
|
|
119727
|
+
await writeLine2(ctx, import_picocolors6.default.yellow(`
|
|
119383
119728
|
${skipped.length} sidecar(s) skipped — older backups predating the manifest schema.`));
|
|
119384
119729
|
}
|
|
119385
119730
|
});
|
|
@@ -119392,14 +119737,14 @@ ${skipped.length} sidecar(s) skipped — older backups predating the manifest sc
|
|
|
119392
119737
|
dryRun: options.dryRun ?? false
|
|
119393
119738
|
});
|
|
119394
119739
|
if ("kind" in result) {
|
|
119395
|
-
await
|
|
119396
|
-
await
|
|
119397
|
-
await
|
|
119398
|
-
await
|
|
119740
|
+
await writeLine2(ctx, `${import_picocolors6.default.cyan("dry-run")} would restore ${import_picocolors6.default.bold(result.app)} from ${import_picocolors6.default.bold(result.resolvedBackup.backup_id)}`);
|
|
119741
|
+
await writeLine2(ctx, ` components: ${import_picocolors6.default.dim(result.components.join(", "))}`);
|
|
119742
|
+
await writeLine2(ctx, ` sha: ${import_picocolors6.default.dim(`${result.resolvedBackup.tarball_sha256.slice(0, 16)}…`)}`);
|
|
119743
|
+
await writeLine2(ctx, import_picocolors6.default.dim(`
|
|
119399
119744
|
Run without --dry-run to actually restore. Expect 30–120s of downtime.`));
|
|
119400
119745
|
return;
|
|
119401
119746
|
}
|
|
119402
|
-
await
|
|
119747
|
+
await writeLine2(ctx, `${import_picocolors6.default.green("✓")} restored from ${result.sourceBackupId} (job ${result.id.slice(0, 12)})`);
|
|
119403
119748
|
});
|
|
119404
119749
|
}
|
|
119405
119750
|
|
|
@@ -119420,10 +119765,10 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119420
119765
|
const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
|
|
119421
119766
|
const list = await deploysList(ctx, { app, limit });
|
|
119422
119767
|
if (list.length === 0) {
|
|
119423
|
-
await
|
|
119768
|
+
await writeLine2(ctx, import_picocolors7.default.dim("No deploys yet."));
|
|
119424
119769
|
return;
|
|
119425
119770
|
}
|
|
119426
|
-
await
|
|
119771
|
+
await writeLine2(ctx, `${import_picocolors7.default.bold("ID".padEnd(22))} ${import_picocolors7.default.bold("STATUS".padEnd(10))} ${import_picocolors7.default.bold("CREATED".padEnd(20))} ${import_picocolors7.default.bold("ERROR")}`);
|
|
119427
119772
|
for (const d of list) {
|
|
119428
119773
|
const id = d.id.padEnd(22);
|
|
119429
119774
|
const status2 = d.status.padEnd(10);
|
|
@@ -119431,15 +119776,19 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119431
119776
|
const err = d.errorMessage ? d.errorMessage.split(`
|
|
119432
119777
|
`)[0]?.slice(0, 60) ?? "—" : import_picocolors7.default.dim("—");
|
|
119433
119778
|
const colored = d.status === "live" ? import_picocolors7.default.green(status2) : d.status === "failed" ? import_picocolors7.default.red(status2) : import_picocolors7.default.yellow(status2);
|
|
119434
|
-
await
|
|
119779
|
+
await writeLine2(ctx, `${import_picocolors7.default.dim(id)} ${colored} ${import_picocolors7.default.dim(created)} ${err}`);
|
|
119435
119780
|
}
|
|
119436
119781
|
});
|
|
119437
119782
|
cli.command("deploys inspect [id]", "Inspect a deploy: status, error, build log").option("--app <name>", "App name (defaults to percher.toml in cwd)").option("--latest-failed", "Resolve to the most recent failed deploy").action(async (id, options) => {
|
|
119438
119783
|
await runDeploysInspect(ctx, { id, app: options.app, latestFailed: options.latestFailed });
|
|
119439
119784
|
});
|
|
119440
|
-
cli.command("rollback
|
|
119441
|
-
const result = await rollback(ctx, {
|
|
119442
|
-
|
|
119785
|
+
cli.command("rollback [sha] [app]", "Roll back to a previous version (bare: pick from the 5 most recent)").option("--last-good", "Roll back to the most recent version that went live without crash-looping").option("--app <name>", "App name (defaults to percher.toml in cwd)").action(async (sha, app, options) => {
|
|
119786
|
+
const result = await rollback(ctx, {
|
|
119787
|
+
app: options.app ?? app,
|
|
119788
|
+
commitSha: sha,
|
|
119789
|
+
toLastGood: Boolean(options.lastGood)
|
|
119790
|
+
});
|
|
119791
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} rolled back to ${result.rolledBackTo.slice(0, 10)} (status: ${result.status})`);
|
|
119443
119792
|
});
|
|
119444
119793
|
cli.command("reproduce <deployId>", "Download a deploy's source and reproduce the build locally").option("--app <name>", "App name (defaults to percher.toml app.name)").action(async (deployId, options) => {
|
|
119445
119794
|
const result = await reproduce(ctx, {
|
|
@@ -119447,27 +119796,27 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119447
119796
|
deployId
|
|
119448
119797
|
});
|
|
119449
119798
|
if (result.status === "needs_login") {
|
|
119450
|
-
await
|
|
119799
|
+
await writeLine2(ctx, `${import_picocolors7.default.red("✗")} Not logged in. Run: percher login`);
|
|
119451
119800
|
throw new SilentExitError;
|
|
119452
119801
|
}
|
|
119453
119802
|
if (result.status === "failed") {
|
|
119454
|
-
await
|
|
119803
|
+
await writeLine2(ctx, `${import_picocolors7.default.red("✗")} ${result.error?.title ?? "Reproduce failed"}`);
|
|
119455
119804
|
if (result.error?.explanation) {
|
|
119456
|
-
await
|
|
119805
|
+
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
119457
119806
|
}
|
|
119458
119807
|
if (result.error?.suggestion) {
|
|
119459
|
-
await
|
|
119808
|
+
await writeLine2(ctx, ` ${import_picocolors7.default.dim(result.error.suggestion)}`);
|
|
119460
119809
|
}
|
|
119461
119810
|
throw new SilentExitError;
|
|
119462
119811
|
}
|
|
119463
|
-
await
|
|
119812
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} ${result.summary}`);
|
|
119464
119813
|
if (result.currentAppEnvKeys && result.currentAppEnvKeys.length > 0) {
|
|
119465
|
-
await
|
|
119466
|
-
await
|
|
119814
|
+
await writeLine2(ctx, "");
|
|
119815
|
+
await writeLine2(ctx, import_picocolors7.default.bold("Current app env keys:"));
|
|
119467
119816
|
for (const k of result.currentAppEnvKeys) {
|
|
119468
|
-
await
|
|
119817
|
+
await writeLine2(ctx, ` ${k}`);
|
|
119469
119818
|
}
|
|
119470
|
-
await
|
|
119819
|
+
await writeLine2(ctx, import_picocolors7.default.dim(" (values not exfiltrated — set them locally before building. Note: this is the CURRENT app env, not a deploy-time snapshot.)"));
|
|
119471
119820
|
}
|
|
119472
119821
|
});
|
|
119473
119822
|
cli.command("logs [app]", "Stream application logs").option("--tail <n>", "Return the last N lines and exit", { type: [Number] }).option("--no-follow", "Don't stream new lines").option("--build [deployId]", "Show the BUILD log instead of runtime logs (defaults to latest failed deploy)").action(async (app, options) => {
|
|
@@ -119479,30 +119828,30 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119479
119828
|
latestFailed: deployId ? undefined : true
|
|
119480
119829
|
});
|
|
119481
119830
|
const { deploy, buildLog } = result;
|
|
119482
|
-
await
|
|
119483
|
-
await
|
|
119484
|
-
await
|
|
119831
|
+
await writeLine2(ctx, `${import_picocolors7.default.bold("Deploy:")} ${deploy.id}`);
|
|
119832
|
+
await writeLine2(ctx, `${import_picocolors7.default.bold("Status:")} ${formatStatus(deploy.status)}`);
|
|
119833
|
+
await writeLine2(ctx, `${import_picocolors7.default.bold("Created:")} ${formatDeployTimestamp(deploy.createdAt)}`);
|
|
119485
119834
|
if (deploy.errorMessage) {
|
|
119486
|
-
await
|
|
119487
|
-
await
|
|
119835
|
+
await writeLine2(ctx, "");
|
|
119836
|
+
await writeLine2(ctx, import_picocolors7.default.bold(import_picocolors7.default.red("Error:")));
|
|
119488
119837
|
for (const line of deploy.errorMessage.split(`
|
|
119489
119838
|
`)) {
|
|
119490
|
-
await
|
|
119839
|
+
await writeLine2(ctx, ` ${line}`);
|
|
119491
119840
|
}
|
|
119492
119841
|
}
|
|
119493
119842
|
if (buildLog) {
|
|
119494
|
-
await
|
|
119843
|
+
await writeLine2(ctx, "");
|
|
119495
119844
|
const lines = buildLog.split(`
|
|
119496
119845
|
`);
|
|
119497
119846
|
const tailN = options.tail ? Number(options.tail) : 200;
|
|
119498
119847
|
const tail = lines.slice(-tailN);
|
|
119499
119848
|
const header = lines.length > tail.length ? `${import_picocolors7.default.bold("Build log")} ${import_picocolors7.default.dim(`(last ${tail.length} of ${lines.length} lines)`)}` : import_picocolors7.default.bold("Build log");
|
|
119500
|
-
await
|
|
119849
|
+
await writeLine2(ctx, header);
|
|
119501
119850
|
for (const line of tail) {
|
|
119502
|
-
await
|
|
119851
|
+
await writeLine2(ctx, ` ${line}`);
|
|
119503
119852
|
}
|
|
119504
119853
|
} else {
|
|
119505
|
-
await
|
|
119854
|
+
await writeLine2(ctx, import_picocolors7.default.dim("(no build log available for this deploy)"));
|
|
119506
119855
|
}
|
|
119507
119856
|
return;
|
|
119508
119857
|
}
|
|
@@ -119526,7 +119875,7 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119526
119875
|
};
|
|
119527
119876
|
const label = v.channel ? channelLabels[v.channel] ?? v.channel : null;
|
|
119528
119877
|
const suffix = label ? ` · via ${label}` : "";
|
|
119529
|
-
await
|
|
119878
|
+
await writeLine2(ctx, `${v.sha} ${v.timestamp} ${message}${suffix}`);
|
|
119530
119879
|
}
|
|
119531
119880
|
});
|
|
119532
119881
|
cli.command("diff [base] [head]", "Show what changed between two deploy versions").option("--app <name>", "App name (overrides percher.toml)").option("--patch", "Include the unified diff per file (owner-only)").action(async (base, head, options) => {
|
|
@@ -119537,23 +119886,27 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119537
119886
|
patch: options.patch
|
|
119538
119887
|
});
|
|
119539
119888
|
const { summary, files } = result;
|
|
119540
|
-
await
|
|
119889
|
+
await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors7.default.green(`+${summary.additions}`)} ${import_picocolors7.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
|
|
119541
119890
|
for (const f of files) {
|
|
119542
119891
|
const stat2 = f.binary ? import_picocolors7.default.dim("binary") : `${import_picocolors7.default.green(`+${f.additions}`)} ${import_picocolors7.default.red(`−${f.deletions}`)}`;
|
|
119543
|
-
await
|
|
119892
|
+
await writeLine2(ctx, ` ${f.status} ${f.path} ${stat2}${f.truncated ? import_picocolors7.default.dim(" (truncated)") : ""}`);
|
|
119544
119893
|
if (options.patch && f.patch) {
|
|
119545
|
-
await
|
|
119894
|
+
await writeLine2(ctx, f.patch.replace(/\n$/, ""));
|
|
119546
119895
|
}
|
|
119547
119896
|
}
|
|
119548
119897
|
if (result.truncated) {
|
|
119549
|
-
await
|
|
119898
|
+
await writeLine2(ctx, import_picocolors7.default.dim("… some patches omitted (response size limit)"));
|
|
119550
119899
|
}
|
|
119551
119900
|
});
|
|
119552
|
-
cli.command("preview <action> [arg] [arg2]", "Preview deploys: promote <id> | discard <id> | branch set <branch> | branch unset | branch show").option("--app <name>", "App name (overrides percher.toml)").action(async (action, arg, arg2, options) => {
|
|
119901
|
+
cli.command("preview <action> [arg] [arg2]", "Preview deploys: list | promote <id> | discard <id> | branch set <branch> | branch unset | branch show").option("--app <name>", "App name (overrides percher.toml)").action(async (action, arg, arg2, options) => {
|
|
119553
119902
|
if (action === "branch") {
|
|
119554
119903
|
await runPreviewBranch(arg, arg2, options.app);
|
|
119555
119904
|
return;
|
|
119556
119905
|
}
|
|
119906
|
+
if (action === "list") {
|
|
119907
|
+
await runPreviewList(options.app);
|
|
119908
|
+
return;
|
|
119909
|
+
}
|
|
119557
119910
|
const appRef = options.app ?? readPercherTomlAppName(ctx.cwd);
|
|
119558
119911
|
if (!appRef) {
|
|
119559
119912
|
throw new Error("App name required. Use --app or run from a project directory.");
|
|
@@ -119562,43 +119915,59 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119562
119915
|
if (!arg)
|
|
119563
119916
|
throw new Error("Deploy ID required. Usage: percher preview promote <id>");
|
|
119564
119917
|
const result = await ctx.client.apps.promotePreview(appRef, arg);
|
|
119565
|
-
await
|
|
119918
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview promoted to live. Status: ${result.status}`);
|
|
119566
119919
|
} else if (action === "discard") {
|
|
119567
119920
|
if (!arg)
|
|
119568
119921
|
throw new Error("Deploy ID required. Usage: percher preview discard <id>");
|
|
119569
119922
|
await ctx.client.apps.discardPreview(appRef, arg);
|
|
119570
|
-
await
|
|
119923
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview discarded.`);
|
|
119571
119924
|
} else {
|
|
119572
|
-
throw new Error(`Unknown preview action "${action}". Use promote | discard | branch.`);
|
|
119925
|
+
throw new Error(`Unknown preview action "${action}". Use list | promote | discard | branch.`);
|
|
119573
119926
|
}
|
|
119574
119927
|
});
|
|
119928
|
+
async function runPreviewList(app) {
|
|
119929
|
+
const { app: appName, previews } = await previewList(ctx, { app });
|
|
119930
|
+
await writeLine2(ctx, `Preview surfaces for ${import_picocolors7.default.bold(appName)}:`);
|
|
119931
|
+
if (previews.length === 0) {
|
|
119932
|
+
await writeLine2(ctx, import_picocolors7.default.dim(" (no preview surfaces reported by the server)"));
|
|
119933
|
+
return;
|
|
119934
|
+
}
|
|
119935
|
+
for (const p of previews) {
|
|
119936
|
+
const label = p.kind.padEnd(14);
|
|
119937
|
+
const statusColour = p.status === "ready" || p.status === "enabled" || p.status === "available" || p.status === "active" ? import_picocolors7.default.green(p.status) : p.status === "error" ? import_picocolors7.default.red(p.status) : import_picocolors7.default.dim(p.status);
|
|
119938
|
+
await writeLine2(ctx, ` ${import_picocolors7.default.bold(label)} ${statusColour}`);
|
|
119939
|
+
if (p.url)
|
|
119940
|
+
await writeLine2(ctx, ` url: ${p.url}`);
|
|
119941
|
+
await writeLine2(ctx, import_picocolors7.default.dim(` how: ${p.howTriggered}`));
|
|
119942
|
+
}
|
|
119943
|
+
}
|
|
119575
119944
|
async function runPreviewBranch(sub, branch, app) {
|
|
119576
119945
|
if (sub === "set") {
|
|
119577
119946
|
if (!branch)
|
|
119578
119947
|
throw new Error("Branch required. Usage: percher preview branch set <branch>");
|
|
119579
119948
|
const r = await previewBranchSet(ctx, { app, branch });
|
|
119580
|
-
await
|
|
119949
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview branch set to "${branch}".`);
|
|
119581
119950
|
if (r.previewUrl)
|
|
119582
|
-
await
|
|
119583
|
-
await
|
|
119951
|
+
await writeLine2(ctx, ` Your preview address: ${import_picocolors7.default.bold(r.previewUrl)}`);
|
|
119952
|
+
await writeLine2(ctx, r.status === "error" ? import_picocolors7.default.yellow(" ⚠ The initial deploy failed — push to the branch to retry.") : import_picocolors7.default.dim(" Building now — live within ~90s."));
|
|
119584
119953
|
return;
|
|
119585
119954
|
}
|
|
119586
119955
|
if (sub === "unset") {
|
|
119587
119956
|
await previewBranchUnset(ctx, { app });
|
|
119588
|
-
await
|
|
119957
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview branch cleared.`);
|
|
119589
119958
|
return;
|
|
119590
119959
|
}
|
|
119591
119960
|
if (sub === "show" || sub === undefined) {
|
|
119592
119961
|
const s = await previewBranchShow(ctx, { app });
|
|
119593
119962
|
if (!s.previewBranch) {
|
|
119594
|
-
await
|
|
119595
|
-
await
|
|
119963
|
+
await writeLine2(ctx, "No preview branch set.");
|
|
119964
|
+
await writeLine2(ctx, import_picocolors7.default.dim(" Set one: percher preview branch set <branch>"));
|
|
119596
119965
|
return;
|
|
119597
119966
|
}
|
|
119598
|
-
await
|
|
119967
|
+
await writeLine2(ctx, `Preview branch: ${import_picocolors7.default.bold(s.previewBranch)}`);
|
|
119599
119968
|
if (s.previewUrl)
|
|
119600
|
-
await
|
|
119601
|
-
await
|
|
119969
|
+
await writeLine2(ctx, ` Address: ${s.previewUrl}`);
|
|
119970
|
+
await writeLine2(ctx, ` Status: ${s.status}`);
|
|
119602
119971
|
return;
|
|
119603
119972
|
}
|
|
119604
119973
|
throw new Error(`Unknown preview branch action "${sub}". Use set <branch> | unset | show.`);
|
|
@@ -119607,8 +119976,25 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
119607
119976
|
|
|
119608
119977
|
// src/commands/env.ts
|
|
119609
119978
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
119979
|
+
var ENV_SCOPES = ["all", "live", "preview"];
|
|
119980
|
+
function isEnvScope(value) {
|
|
119981
|
+
return ENV_SCOPES.includes(value);
|
|
119982
|
+
}
|
|
119610
119983
|
function registerEnvCommands(cli, ctx) {
|
|
119611
|
-
cli.command("env <action> [...args]", "Manage env vars (set KEY=VALUE | set KEY --from-stdin | set KEY --from-file <path> | set --from-env-file <path> | set KEY (prompts) | unset KEY | list)").option("--app <name>", "App name (overrides percher.toml)").option("--from-stdin", "(set) Read value for the single KEY from stdin").option("--from-file <path>", "(set) Read value for the single KEY from file").option("--from-env-file <path>", "(set) Set every KEY=VALUE line from a .env-format file").action(async (action, args, options) => {
|
|
119984
|
+
cli.command("env <action> [...args]", "Manage env vars (set KEY=VALUE | set KEY --from-stdin | set KEY --from-file <path> | set --from-env-file <path> | set KEY (prompts) | unset KEY | list). set/unset take --scope all|live|preview").option("--app <name>", "App name (overrides percher.toml)").option("--from-stdin", "(set) Read value for the single KEY from stdin").option("--from-file <path>", "(set) Read value for the single KEY from file").option("--from-env-file <path>", "(set) Set every KEY=VALUE line from a .env-format file").option("--scope <scope>", "(set/unset) all (default) applies everywhere; live/preview apply only to that deploy type and override all").action(async (action, args, options) => {
|
|
119985
|
+
let scope;
|
|
119986
|
+
if (options.scope !== undefined) {
|
|
119987
|
+
const raw = String(options.scope);
|
|
119988
|
+
if (!isEnvScope(raw)) {
|
|
119989
|
+
await writeLine2(ctx, `${import_picocolors8.default.red("✗")} Invalid --scope "${raw}". Use all, live, or preview.`);
|
|
119990
|
+
throw new SilentExitError;
|
|
119991
|
+
}
|
|
119992
|
+
if (action === "list") {
|
|
119993
|
+
await writeLine2(ctx, `${import_picocolors8.default.red("✗")} --scope is not supported with "list" — env list already shows every scope ([live]/[preview] annotations).`);
|
|
119994
|
+
throw new SilentExitError;
|
|
119995
|
+
}
|
|
119996
|
+
scope = raw;
|
|
119997
|
+
}
|
|
119612
119998
|
if (action === "set") {
|
|
119613
119999
|
const { resolveEnvSetInputs: resolveEnvSetInputs2, EnvSetInputError: EnvSetInputError2 } = await Promise.resolve().then(() => (init_env_set_input(), exports_env_set_input));
|
|
119614
120000
|
const { promptHidden: promptHidden2 } = await Promise.resolve().then(() => (init_prompt_hidden(), exports_prompt_hidden));
|
|
@@ -119628,11 +120014,12 @@ function registerEnvCommands(cli, ctx) {
|
|
|
119628
120014
|
readFile: async (p) => readFile5(p, "utf8"),
|
|
119629
120015
|
promptHidden: promptHidden2
|
|
119630
120016
|
});
|
|
119631
|
-
await envSet(ctx, { app: options.app, values });
|
|
119632
|
-
|
|
120017
|
+
await envSet(ctx, { app: options.app, values, scope });
|
|
120018
|
+
const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
|
|
120019
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} env vars updated (${Object.keys(values).length})${scopeNote}`);
|
|
119633
120020
|
} catch (err) {
|
|
119634
120021
|
if (err instanceof EnvSetInputError2) {
|
|
119635
|
-
await
|
|
120022
|
+
await writeLine2(ctx, `${import_picocolors8.default.red("✗")} ${err.message}`);
|
|
119636
120023
|
throw new SilentExitError;
|
|
119637
120024
|
}
|
|
119638
120025
|
throw err;
|
|
@@ -119641,12 +120028,14 @@ function registerEnvCommands(cli, ctx) {
|
|
|
119641
120028
|
const key = args[0];
|
|
119642
120029
|
if (!key)
|
|
119643
120030
|
throw new Error("Expected key to unset");
|
|
119644
|
-
await envUnset(ctx, { app: options.app, key });
|
|
119645
|
-
|
|
120031
|
+
await envUnset(ctx, { app: options.app, key, scope });
|
|
120032
|
+
const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
|
|
120033
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} unset ${key}${scopeNote}`);
|
|
119646
120034
|
} else if (action === "list") {
|
|
119647
120035
|
const env = await envList(ctx, { app: options.app });
|
|
119648
|
-
for (const
|
|
119649
|
-
|
|
120036
|
+
for (const row of env.detailed) {
|
|
120037
|
+
const suffix = row.scope === "all" ? "" : ` [${row.scope}]`;
|
|
120038
|
+
await writeLine2(ctx, `${row.key}=${row.maskedValue}${suffix}`);
|
|
119650
120039
|
}
|
|
119651
120040
|
} else {
|
|
119652
120041
|
throw new Error(`Unknown env action "${action}". Use set | unset | list.`);
|
|
@@ -119655,7 +120044,7 @@ function registerEnvCommands(cli, ctx) {
|
|
|
119655
120044
|
cli.command("generate", "Generate .env.example and TypeScript types from percher.toml").action(async () => {
|
|
119656
120045
|
const result = await generate(ctx);
|
|
119657
120046
|
for (const f of result.files) {
|
|
119658
|
-
await
|
|
120047
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} ${f}`);
|
|
119659
120048
|
}
|
|
119660
120049
|
});
|
|
119661
120050
|
}
|
|
@@ -119666,8 +120055,8 @@ function registerExportCommands(cli, ctx) {
|
|
|
119666
120055
|
cli.command("export [app]", "Download an export bundle for an app (no secrets)").option("--out <path>", "Where to write the file (defaults to ./{name}-{date}.tar.gz)").option("--git", "Export the whole deploy history as a git bundle (.bundle) — re-clone with `git clone`").action(async (app, options) => {
|
|
119667
120056
|
if (options.git) {
|
|
119668
120057
|
const result2 = await exportGitBundle(ctx, { app, out: options.out });
|
|
119669
|
-
await
|
|
119670
|
-
await
|
|
120058
|
+
await writeLine2(ctx, `${import_picocolors9.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
|
|
120059
|
+
await writeLine2(ctx, import_picocolors9.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
|
|
119671
120060
|
return;
|
|
119672
120061
|
}
|
|
119673
120062
|
const result = await exportApp(ctx, {
|
|
@@ -119675,27 +120064,27 @@ function registerExportCommands(cli, ctx) {
|
|
|
119675
120064
|
out: options.out,
|
|
119676
120065
|
deliveryKind: "cli"
|
|
119677
120066
|
});
|
|
119678
|
-
await
|
|
119679
|
-
await
|
|
120067
|
+
await writeLine2(ctx, `${import_picocolors9.default.green("✓")} exported ${result.appName} → ${result.outPath} (${(result.bytes / 1024).toFixed(1)} KB)`);
|
|
120068
|
+
await writeLine2(ctx, import_picocolors9.default.dim("Env values are not included. To export encrypted env values, use the dashboard's Settings → Export and confirm your password."));
|
|
119680
120069
|
});
|
|
119681
120070
|
}
|
|
119682
120071
|
|
|
119683
120072
|
// src/commands/github.ts
|
|
119684
120073
|
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
119685
120074
|
async function printWebhookInstructions(ctx, instructions, branch) {
|
|
119686
|
-
await
|
|
119687
|
-
await
|
|
119688
|
-
await
|
|
119689
|
-
await
|
|
119690
|
-
await
|
|
119691
|
-
await
|
|
119692
|
-
await
|
|
119693
|
-
await
|
|
119694
|
-
await
|
|
119695
|
-
await
|
|
119696
|
-
await
|
|
119697
|
-
await
|
|
119698
|
-
await
|
|
120075
|
+
await writeLine2(ctx, import_picocolors10.default.bold("Add this webhook in GitHub to enable auto-deploy on push:"));
|
|
120076
|
+
await writeLine2(ctx, ` URL: ${instructions.settingsUrl}`);
|
|
120077
|
+
await writeLine2(ctx, "");
|
|
120078
|
+
await writeLine2(ctx, " Field Value");
|
|
120079
|
+
await writeLine2(ctx, " ─────────────────────────────────────────────────");
|
|
120080
|
+
await writeLine2(ctx, ` Payload URL ${instructions.payloadUrl}`);
|
|
120081
|
+
await writeLine2(ctx, " Content type application/json");
|
|
120082
|
+
await writeLine2(ctx, ` Secret ${instructions.secret}`);
|
|
120083
|
+
await writeLine2(ctx, " Which events? Just the push event");
|
|
120084
|
+
await writeLine2(ctx, "");
|
|
120085
|
+
await writeLine2(ctx, import_picocolors10.default.yellow(" ⚠ Treat this secret like a password — save it now, it won't be shown again."));
|
|
120086
|
+
await writeLine2(ctx, "");
|
|
120087
|
+
await writeLine2(ctx, import_picocolors10.default.dim(`After adding the webhook, every push to ${branch} will auto-deploy.`));
|
|
119699
120088
|
}
|
|
119700
120089
|
function registerGithubCommands(cli, ctx) {
|
|
119701
120090
|
cli.command("github [action] [repoUrl] [...rest]", "GitHub integration: connect | setup-webhook").option("--app <name>", "App name (overrides percher.toml)").option("--branch <branch>", "(connect only) Branch to track (default: main)").action(async (action, repoUrl, _rest, options) => {
|
|
@@ -119708,25 +120097,25 @@ function registerGithubCommands(cli, ctx) {
|
|
|
119708
120097
|
repoUrl,
|
|
119709
120098
|
branch: options.branch
|
|
119710
120099
|
});
|
|
119711
|
-
await
|
|
119712
|
-
await
|
|
119713
|
-
await
|
|
120100
|
+
await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Connected to ${result.repoUrl} (branch: ${result.branch})`);
|
|
120101
|
+
await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Deploy started: ${result.deployId}`);
|
|
120102
|
+
await writeLine2(ctx, "");
|
|
119714
120103
|
if (result.webhookSetupFailed) {
|
|
119715
|
-
await
|
|
119716
|
-
await
|
|
119717
|
-
await
|
|
120104
|
+
await writeLine2(ctx, import_picocolors10.default.yellow(`⚠ Webhook setup failed: ${result.webhookSetupError ?? "unknown error"}`));
|
|
120105
|
+
await writeLine2(ctx, import_picocolors10.default.yellow(" Auto-deploy will NOT work until you re-run webhook setup:"));
|
|
120106
|
+
await writeLine2(ctx, ` ${import_picocolors10.default.bold(`percher github setup-webhook${options.app ? ` --app ${options.app}` : ""}`)}`);
|
|
119718
120107
|
return;
|
|
119719
120108
|
}
|
|
119720
120109
|
if (result.webhookInstructions) {
|
|
119721
120110
|
await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
|
|
119722
120111
|
}
|
|
119723
|
-
await
|
|
120112
|
+
await writeLine2(ctx, import_picocolors10.default.dim(`Track the initial deploy: percher deploys inspect ${result.deployId}`));
|
|
119724
120113
|
return;
|
|
119725
120114
|
}
|
|
119726
120115
|
if (action === "setup-webhook") {
|
|
119727
120116
|
const result = await githubSetupWebhook(ctx, { app: options.app });
|
|
119728
|
-
await
|
|
119729
|
-
await
|
|
120117
|
+
await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Webhook regenerated for ${result.app}`);
|
|
120118
|
+
await writeLine2(ctx, "");
|
|
119730
120119
|
await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
|
|
119731
120120
|
return;
|
|
119732
120121
|
}
|
|
@@ -119736,8 +120125,64 @@ function registerGithubCommands(cli, ctx) {
|
|
|
119736
120125
|
cli.command("github setup-webhook", "Regenerate the GitHub webhook secret for an app (use after a failed `github connect`)").option("--app <name>", "App name (overrides percher.toml)").action(async () => {});
|
|
119737
120126
|
}
|
|
119738
120127
|
|
|
119739
|
-
// src/commands/
|
|
120128
|
+
// src/commands/hooks.ts
|
|
119740
120129
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
120130
|
+
function whenLabel(iso) {
|
|
120131
|
+
return iso ? new Date(iso).toISOString().slice(0, 10) : "never";
|
|
120132
|
+
}
|
|
120133
|
+
function registerHooksCommands(cli, ctx) {
|
|
120134
|
+
cli.command("hook [action] [arg]", "Manage deploy hooks: create | list | revoke").option("--app <name>", "App name (default: read from percher.toml)").action(async (action, arg, options) => {
|
|
120135
|
+
if (!ctx.client.token) {
|
|
120136
|
+
await writeLine2(ctx, `${import_picocolors11.default.red("✗")} Not logged in. Run: percher login`);
|
|
120137
|
+
throw new SilentExitError;
|
|
120138
|
+
}
|
|
120139
|
+
if (action === "create") {
|
|
120140
|
+
if (!arg) {
|
|
120141
|
+
await writeLine2(ctx, `${import_picocolors11.default.red("✗")} A name is required. Usage: percher hook create <name> [--app <name>]`);
|
|
120142
|
+
throw new SilentExitError;
|
|
120143
|
+
}
|
|
120144
|
+
const created = await hookCreate(ctx, { name: arg, app: options.app });
|
|
120145
|
+
await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy hook created: ${import_picocolors11.default.bold(created.name)}`);
|
|
120146
|
+
await writeLine2(ctx, "");
|
|
120147
|
+
await writeLine2(ctx, ` ${import_picocolors11.default.bold(created.hookUrl)}`);
|
|
120148
|
+
await writeLine2(ctx, import_picocolors11.default.yellow(" ↑ Shown once — store it in your CMS/CI secret store now. It can't be retrieved again."));
|
|
120149
|
+
await writeLine2(ctx, import_picocolors11.default.yellow(" The URL is a secret: anyone holding it can trigger builds (6/min)."));
|
|
120150
|
+
await writeLine2(ctx, "");
|
|
120151
|
+
await writeLine2(ctx, import_picocolors11.default.dim(" Trigger a rebuild with:"));
|
|
120152
|
+
await writeLine2(ctx, import_picocolors11.default.dim(` curl -X POST ${created.hookUrl}`));
|
|
120153
|
+
return;
|
|
120154
|
+
}
|
|
120155
|
+
if (action === "list") {
|
|
120156
|
+
const hooks = await hookList(ctx, { app: options.app });
|
|
120157
|
+
if (hooks.length === 0) {
|
|
120158
|
+
await writeLine2(ctx, import_picocolors11.default.dim("No deploy hooks. Create one with: percher hook create <name>"));
|
|
120159
|
+
return;
|
|
120160
|
+
}
|
|
120161
|
+
for (const h of hooks) {
|
|
120162
|
+
await writeLine2(ctx, `${import_picocolors11.default.bold(h.id)} ${h.name.padEnd(20)} ${import_picocolors11.default.dim(`created ${whenLabel(h.createdAt)}`)} ${import_picocolors11.default.dim(`last used ${whenLabel(h.lastUsedAt)}`)}`);
|
|
120163
|
+
}
|
|
120164
|
+
await writeLine2(ctx, "");
|
|
120165
|
+
await writeLine2(ctx, import_picocolors11.default.dim("Revoke one with: percher hook revoke <id>"));
|
|
120166
|
+
return;
|
|
120167
|
+
}
|
|
120168
|
+
if (action === "revoke") {
|
|
120169
|
+
if (!arg) {
|
|
120170
|
+
await writeLine2(ctx, `${import_picocolors11.default.red("✗")} A hook id is required. Usage: percher hook revoke <id> (list ids with \`percher hook list\`).`);
|
|
120171
|
+
throw new SilentExitError;
|
|
120172
|
+
}
|
|
120173
|
+
await hookRevoke(ctx, { id: arg, app: options.app });
|
|
120174
|
+
await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy hook revoked.`);
|
|
120175
|
+
return;
|
|
120176
|
+
}
|
|
120177
|
+
throw new Error(`Unknown hook action "${action ?? ""}". Use: create | list | revoke.`);
|
|
120178
|
+
});
|
|
120179
|
+
cli.command("hook create <name>", "Create a deploy hook (the URL is shown once)").option("--app <name>", "App name (default: read from percher.toml)").action(async () => {});
|
|
120180
|
+
cli.command("hook list", "List deploy hooks with created + last-used dates").option("--app <name>", "App name (default: read from percher.toml)").action(async () => {});
|
|
120181
|
+
cli.command("hook revoke <id>", "Revoke a deploy hook by id").option("--app <name>", "App name (default: read from percher.toml)").action(async () => {});
|
|
120182
|
+
}
|
|
120183
|
+
|
|
120184
|
+
// src/commands/migrate.ts
|
|
120185
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
119741
120186
|
function registerMigrateCommands(cli, ctx) {
|
|
119742
120187
|
cli.command("migrate-from-supabase", "Migrate a Supabase project to PocketBase: schema + data-import script, optionally rewrite SDK calls").option("--project <ref>", "Supabase project reference (the 20-char id from the project URL)").option("--token <token>", "Supabase personal access token (sbp_...). Read from SUPABASE_ACCESS_TOKEN if omitted.").option("--inspect-only", "Only print the schema that would migrate — read-only, no file writes").option("--output-dir <dir>", "Where to write the preview files (default: ./migration-preview/)").option("--rewrite-client", "Also convert your Supabase SDK calls to PocketBase across the project").option("--apply", "With --rewrite-client: overwrite files in place (originals backed up first). Default: preview only.").option("--root <dir>", "Project root to rewrite with --rewrite-client (default: current directory)").option("--no-scaffold-client", "With --rewrite-client: don't generate a PocketBase client module / insert pb imports (default: scaffold one)").option("--status", "Show a migration scoreboard for the local project — what's still on Supabase vs PocketBase. Local scan, no Supabase token needed.").option("--build", "With --status: also run the project build as a final gate").option("--pb-identifier <name>", "With --status: the PocketBase client identifier to check for (default: pb). Match what the rewrite used.").option("--json", "Output the structured result as JSON").action(async (options) => {
|
|
119743
120188
|
if (options.status) {
|
|
@@ -119747,7 +120192,7 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
119747
120192
|
pbIdentifier: options.pbIdentifier
|
|
119748
120193
|
});
|
|
119749
120194
|
if (options.json) {
|
|
119750
|
-
await
|
|
120195
|
+
await writeLine2(ctx, JSON.stringify(result2, null, 2));
|
|
119751
120196
|
if (result2.status === "failed")
|
|
119752
120197
|
throw new SilentExitError(1);
|
|
119753
120198
|
return;
|
|
@@ -119760,21 +120205,21 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
119760
120205
|
const projectRef = options.project;
|
|
119761
120206
|
const token = options.token ?? ctx.env.SUPABASE_ACCESS_TOKEN;
|
|
119762
120207
|
if (options.apply && !options.rewriteClient) {
|
|
119763
|
-
await
|
|
120208
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} --apply only applies to the client rewrite. Pass --rewrite-client --apply together.`);
|
|
119764
120209
|
throw new SilentExitError(1);
|
|
119765
120210
|
}
|
|
119766
120211
|
if (!projectRef) {
|
|
119767
|
-
await
|
|
120212
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Missing --project. Pass your Supabase project ref (e.g. --project abcdefghijklmnopqrst).`);
|
|
119768
120213
|
throw new SilentExitError(1);
|
|
119769
120214
|
}
|
|
119770
120215
|
if (!token) {
|
|
119771
|
-
await
|
|
120216
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Missing --token. Pass --token sbp_... or set SUPABASE_ACCESS_TOKEN. Generate one at https://supabase.com/dashboard/account/tokens`);
|
|
119772
120217
|
throw new SilentExitError(1);
|
|
119773
120218
|
}
|
|
119774
120219
|
if (options.inspectOnly) {
|
|
119775
120220
|
const result2 = await inspectSupabase(ctx, { projectRef, token });
|
|
119776
120221
|
if (options.json) {
|
|
119777
|
-
await
|
|
120222
|
+
await writeLine2(ctx, JSON.stringify(result2, null, 2));
|
|
119778
120223
|
if (result2.status === "failed")
|
|
119779
120224
|
throw new SilentExitError(1);
|
|
119780
120225
|
return;
|
|
@@ -119798,7 +120243,7 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
119798
120243
|
});
|
|
119799
120244
|
}
|
|
119800
120245
|
if (options.json) {
|
|
119801
|
-
await
|
|
120246
|
+
await writeLine2(ctx, JSON.stringify(rewrite ? { schema: result, rewrite } : result, null, 2));
|
|
119802
120247
|
if (result.status === "failed" || rewrite?.status === "failed") {
|
|
119803
120248
|
throw new SilentExitError(1);
|
|
119804
120249
|
}
|
|
@@ -119817,183 +120262,183 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
119817
120262
|
async function renderInspectHuman(ctx, result) {
|
|
119818
120263
|
if (result.status === "failed") {
|
|
119819
120264
|
const err = result.error;
|
|
119820
|
-
await
|
|
120265
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Inspection failed"}`);
|
|
119821
120266
|
if (err?.explanation)
|
|
119822
|
-
await
|
|
120267
|
+
await writeLine2(ctx, ` ${err.explanation}`);
|
|
119823
120268
|
if (err?.suggestion)
|
|
119824
|
-
await
|
|
120269
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
|
|
119825
120270
|
return;
|
|
119826
120271
|
}
|
|
119827
|
-
await
|
|
119828
|
-
await
|
|
119829
|
-
await
|
|
120272
|
+
await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
|
|
120273
|
+
await writeLine2(ctx, "");
|
|
120274
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Tables"));
|
|
119830
120275
|
if (result.tables.length === 0) {
|
|
119831
|
-
await
|
|
120276
|
+
await writeLine2(ctx, import_picocolors12.default.dim(" (none in user schemas)"));
|
|
119832
120277
|
}
|
|
119833
120278
|
for (const table of result.tables) {
|
|
119834
120279
|
const pkCount = table.columns.filter((c) => c.isPrimaryKey).length;
|
|
119835
120280
|
const fkCount = table.columns.filter((c) => c.foreignKey !== null).length;
|
|
119836
120281
|
const policyCount = table.rlsPolicies.length;
|
|
119837
120282
|
const rowEstimate = table.estimatedRowCount.toLocaleString();
|
|
119838
|
-
await
|
|
120283
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.cyan(`${table.schema}.${table.name}`)} ${import_picocolors12.default.dim(`cols=${table.columns.length} pk=${pkCount} fk=${fkCount} rls=${policyCount} rows≈${rowEstimate}`)}`);
|
|
119839
120284
|
}
|
|
119840
120285
|
if (result.storageBuckets.length > 0) {
|
|
119841
|
-
await
|
|
119842
|
-
await
|
|
120286
|
+
await writeLine2(ctx, "");
|
|
120287
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Storage buckets"));
|
|
119843
120288
|
for (const bucket of result.storageBuckets) {
|
|
119844
120289
|
const visibility = bucket.public ? "public" : "private";
|
|
119845
|
-
await
|
|
120290
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.cyan(bucket.name)} ${import_picocolors12.default.dim(`(${visibility})`)}`);
|
|
119846
120291
|
}
|
|
119847
120292
|
}
|
|
119848
120293
|
if (result.authUsersCount !== null) {
|
|
119849
|
-
await
|
|
119850
|
-
await
|
|
120294
|
+
await writeLine2(ctx, "");
|
|
120295
|
+
await writeLine2(ctx, `${import_picocolors12.default.bold("Auth users")}: ${result.authUsersCount}`);
|
|
119851
120296
|
}
|
|
119852
120297
|
if (result.warnings.length > 0) {
|
|
119853
|
-
await
|
|
119854
|
-
await
|
|
120298
|
+
await writeLine2(ctx, "");
|
|
120299
|
+
await writeLine2(ctx, import_picocolors12.default.bold(`Warnings (${result.warnings.length})`));
|
|
119855
120300
|
for (const warning of result.warnings) {
|
|
119856
|
-
await
|
|
120301
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${warning}`);
|
|
119857
120302
|
}
|
|
119858
120303
|
}
|
|
119859
|
-
await
|
|
119860
|
-
await
|
|
120304
|
+
await writeLine2(ctx, "");
|
|
120305
|
+
await writeLine2(ctx, import_picocolors12.default.dim("--inspect-only is read-only. Drop the flag to also write the PocketBase schema preview + pb_migrate.js data-import script to ./migration-preview/."));
|
|
119861
120306
|
}
|
|
119862
120307
|
async function renderSchemaHuman(ctx, result, opts) {
|
|
119863
120308
|
if (result.status === "failed") {
|
|
119864
120309
|
const err = result.error;
|
|
119865
|
-
await
|
|
120310
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Migration preview failed"}`);
|
|
119866
120311
|
if (err?.explanation)
|
|
119867
|
-
await
|
|
120312
|
+
await writeLine2(ctx, ` ${err.explanation}`);
|
|
119868
120313
|
if (err?.suggestion)
|
|
119869
|
-
await
|
|
120314
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
|
|
119870
120315
|
return;
|
|
119871
120316
|
}
|
|
119872
|
-
await
|
|
119873
|
-
await
|
|
119874
|
-
await
|
|
120317
|
+
await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
|
|
120318
|
+
await writeLine2(ctx, "");
|
|
120319
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Files written"));
|
|
119875
120320
|
for (const path of result.filesWritten) {
|
|
119876
|
-
await
|
|
120321
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.cyan(path)}`);
|
|
119877
120322
|
}
|
|
119878
120323
|
const preview = result.preview;
|
|
119879
120324
|
if (!preview)
|
|
119880
120325
|
return;
|
|
119881
120326
|
if (preview.collections.length > 0) {
|
|
119882
|
-
await
|
|
119883
|
-
await
|
|
120327
|
+
await writeLine2(ctx, "");
|
|
120328
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Collections"));
|
|
119884
120329
|
for (const collection of preview.collections) {
|
|
119885
120330
|
const fields = collection.schema.length;
|
|
119886
120331
|
const relations = collection.schema.filter((f) => f.type === "relation").length;
|
|
119887
|
-
await
|
|
120332
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.cyan(collection.name)} ${import_picocolors12.default.dim(`← ${collection.sourceTable} fields=${fields} relations=${relations}`)}`);
|
|
119888
120333
|
}
|
|
119889
120334
|
}
|
|
119890
120335
|
if (preview.warnings.length > 0) {
|
|
119891
|
-
await
|
|
119892
|
-
await
|
|
120336
|
+
await writeLine2(ctx, "");
|
|
120337
|
+
await writeLine2(ctx, import_picocolors12.default.bold(`Warnings (${preview.warnings.length})`));
|
|
119893
120338
|
for (const warning of preview.warnings.slice(0, 10)) {
|
|
119894
|
-
await
|
|
120339
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${warning}`);
|
|
119895
120340
|
}
|
|
119896
120341
|
if (preview.warnings.length > 10) {
|
|
119897
|
-
await
|
|
120342
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` …and ${preview.warnings.length - 10} more — see MIGRATION_NOTES.md`));
|
|
119898
120343
|
}
|
|
119899
120344
|
}
|
|
119900
|
-
await
|
|
119901
|
-
await
|
|
120345
|
+
await writeLine2(ctx, "");
|
|
120346
|
+
await writeLine2(ctx, import_picocolors12.default.dim("Preview only — nothing has touched your PocketBase yet. ./migration-preview/ includes pb_migrate.js, the data-import script: review MIGRATION_NOTES.md, then run it against your app's PocketBase to copy your rows over."));
|
|
119902
120347
|
if (!opts.clientRewriteRequested) {
|
|
119903
|
-
await
|
|
120348
|
+
await writeLine2(ctx, import_picocolors12.default.dim("Add --rewrite-client to also convert your Supabase SDK calls to PocketBase (preview first; --apply writes in place)."));
|
|
119904
120349
|
}
|
|
119905
120350
|
}
|
|
119906
120351
|
async function renderRewriteHuman(ctx, result) {
|
|
119907
|
-
await
|
|
120352
|
+
await writeLine2(ctx, "");
|
|
119908
120353
|
if (result.status === "failed") {
|
|
119909
120354
|
const err = result.error;
|
|
119910
|
-
await
|
|
120355
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Client rewrite failed"}`);
|
|
119911
120356
|
if (err?.explanation)
|
|
119912
|
-
await
|
|
120357
|
+
await writeLine2(ctx, ` ${err.explanation}`);
|
|
119913
120358
|
if (err?.suggestion)
|
|
119914
|
-
await
|
|
120359
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
|
|
119915
120360
|
return;
|
|
119916
120361
|
}
|
|
119917
|
-
await
|
|
120362
|
+
await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
|
|
119918
120363
|
if (result.filesWritten.length > 0) {
|
|
119919
|
-
await
|
|
119920
|
-
await
|
|
120364
|
+
await writeLine2(ctx, "");
|
|
120365
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Files written"));
|
|
119921
120366
|
for (const path of result.filesWritten.slice(0, 20)) {
|
|
119922
|
-
await
|
|
120367
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.cyan(path)}`);
|
|
119923
120368
|
}
|
|
119924
120369
|
if (result.filesWritten.length > 20) {
|
|
119925
|
-
await
|
|
120370
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` …and ${result.filesWritten.length - 20} more`));
|
|
119926
120371
|
}
|
|
119927
120372
|
}
|
|
119928
120373
|
if (result.flags.length > 0) {
|
|
119929
|
-
await
|
|
119930
|
-
await
|
|
120374
|
+
await writeLine2(ctx, "");
|
|
120375
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Needs review (see migration-preview/REWRITE_NOTES.md)"));
|
|
119931
120376
|
for (const flag of result.flags.slice(0, 15)) {
|
|
119932
120377
|
const loc = flag.line !== undefined ? `:${flag.line}` : "";
|
|
119933
120378
|
const tag = flag.kind === "manual_review" ? "rewritten, verify semantics" : "left untouched";
|
|
119934
|
-
await
|
|
120379
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${import_picocolors12.default.cyan(`${flag.file}${loc}`)} ${flag.pattern} ${import_picocolors12.default.dim(`(${tag}) — ${flag.note}`)}`);
|
|
119935
120380
|
}
|
|
119936
120381
|
if (result.flags.length > 15) {
|
|
119937
|
-
await
|
|
120382
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` …and ${result.flags.length - 15} more — see REWRITE_NOTES.md`));
|
|
119938
120383
|
}
|
|
119939
120384
|
}
|
|
119940
120385
|
if (result.skipped.length > 0) {
|
|
119941
|
-
await
|
|
120386
|
+
await writeLine2(ctx, import_picocolors12.default.dim(`
|
|
119942
120387
|
${result.skipped.length} file(s) couldn't be parsed and were skipped — listed in REWRITE_NOTES.md.`));
|
|
119943
120388
|
}
|
|
119944
|
-
await
|
|
120389
|
+
await writeLine2(ctx, "");
|
|
119945
120390
|
if (result.mode === "apply") {
|
|
119946
|
-
await
|
|
120391
|
+
await writeLine2(ctx, import_picocolors12.default.dim(result.changedCount > 0 ? "Files rewritten in place. Originals are in ./migration-preview/backup/ — diff against them (or git) before committing." : "No files were rewritten — the work left is the manual review above."));
|
|
119947
120392
|
} else {
|
|
119948
|
-
await
|
|
120393
|
+
await writeLine2(ctx, import_picocolors12.default.dim(result.changedCount > 0 ? "Preview only — diff ./migration-preview/rewritten/ against your project, then re-run with --rewrite-client --apply to write in place." : "Preview only — no files needed automatic rewriting; work through the manual review above."));
|
|
119949
120394
|
}
|
|
119950
120395
|
}
|
|
119951
120396
|
async function renderStatusHuman(ctx, result) {
|
|
119952
120397
|
if (result.status === "failed") {
|
|
119953
120398
|
const err = result.error;
|
|
119954
|
-
await
|
|
120399
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Migration status scan failed"}`);
|
|
119955
120400
|
if (err?.explanation)
|
|
119956
|
-
await
|
|
120401
|
+
await writeLine2(ctx, ` ${err.explanation}`);
|
|
119957
120402
|
if (err?.suggestion)
|
|
119958
|
-
await
|
|
120403
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
|
|
119959
120404
|
return;
|
|
119960
120405
|
}
|
|
119961
|
-
await
|
|
119962
|
-
await
|
|
120406
|
+
await writeLine2(ctx, import_picocolors12.default.bold("Migration status"));
|
|
120407
|
+
await writeLine2(ctx, "");
|
|
119963
120408
|
for (const check2 of result.checks) {
|
|
119964
|
-
const mark = check2.status === "pass" ?
|
|
119965
|
-
const count = check2.count > 0 ?
|
|
119966
|
-
await
|
|
120409
|
+
const mark = check2.status === "pass" ? import_picocolors12.default.green("✓") : check2.status === "fail" ? import_picocolors12.default.red("✗") : check2.status === "warn" ? import_picocolors12.default.yellow("⚠") : import_picocolors12.default.dim("•");
|
|
120410
|
+
const count = check2.count > 0 ? import_picocolors12.default.dim(` (${check2.count})`) : "";
|
|
120411
|
+
await writeLine2(ctx, ` ${mark} ${check2.label}${count} ${import_picocolors12.default.dim(`— ${check2.detail}`)}`);
|
|
119967
120412
|
for (const file2 of (check2.files ?? []).slice(0, 10)) {
|
|
119968
|
-
await
|
|
120413
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` - ${file2}`));
|
|
119969
120414
|
}
|
|
119970
120415
|
if ((check2.files?.length ?? 0) > 10) {
|
|
119971
|
-
await
|
|
120416
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` …and ${(check2.files?.length ?? 0) - 10} more`));
|
|
119972
120417
|
}
|
|
119973
120418
|
}
|
|
119974
120419
|
if (result.build) {
|
|
119975
|
-
await
|
|
119976
|
-
const mark = !result.build.ran ?
|
|
119977
|
-
await
|
|
120420
|
+
await writeLine2(ctx, "");
|
|
120421
|
+
const mark = !result.build.ran ? import_picocolors12.default.dim("•") : result.build.passed ? import_picocolors12.default.green("✓") : import_picocolors12.default.red("✗");
|
|
120422
|
+
await writeLine2(ctx, ` ${mark} Build ${import_picocolors12.default.dim(`— ${result.build.detail}`)}`);
|
|
119978
120423
|
}
|
|
119979
|
-
await
|
|
119980
|
-
await
|
|
120424
|
+
await writeLine2(ctx, "");
|
|
120425
|
+
await writeLine2(ctx, import_picocolors12.default.dim(result.summary));
|
|
119981
120426
|
}
|
|
119982
120427
|
|
|
119983
120428
|
// src/commands/sharing.ts
|
|
119984
|
-
var
|
|
120429
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
119985
120430
|
function registerSharingCommands(cli, ctx) {
|
|
119986
120431
|
cli.command("share [action] [arg] [valueOrRole]", "Per-app collaborator invites: list | invite <email> | role <shareId> <role> | revoke <shareId>").option("--app <name>", "App name (overrides percher.toml)").option("--role <role>", "(invite only) viewer | admin — viewer is read-only, admin can deploy + change config").action(async (action, arg, valueOrRole, options) => {
|
|
119987
120432
|
if (action === "list" || action === undefined) {
|
|
119988
120433
|
const rows = await shareList(ctx, { app: options.app });
|
|
119989
120434
|
if (rows.length === 0) {
|
|
119990
|
-
await
|
|
120435
|
+
await writeLine2(ctx, import_picocolors13.default.dim("No collaborators."));
|
|
119991
120436
|
return;
|
|
119992
120437
|
}
|
|
119993
120438
|
for (const r of rows) {
|
|
119994
|
-
const state = r.accepted ?
|
|
119995
|
-
const roleStr = r.role === "admin" ?
|
|
119996
|
-
await
|
|
120439
|
+
const state = r.accepted ? import_picocolors13.default.green("accepted") : import_picocolors13.default.yellow("pending");
|
|
120440
|
+
const roleStr = r.role === "admin" ? import_picocolors13.default.cyan("admin") : import_picocolors13.default.dim("viewer");
|
|
120441
|
+
await writeLine2(ctx, `${r.id} ${roleStr.padEnd(15)} ${state.padEnd(15)} ${r.email}`);
|
|
119997
120442
|
}
|
|
119998
120443
|
return;
|
|
119999
120444
|
}
|
|
@@ -120010,8 +120455,8 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120010
120455
|
email: arg,
|
|
120011
120456
|
role: roleRaw
|
|
120012
120457
|
});
|
|
120013
|
-
await
|
|
120014
|
-
await
|
|
120458
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Invited ${created.email} as ${created.role} (id ${created.id})`);
|
|
120459
|
+
await writeLine2(ctx, import_picocolors13.default.dim(`Pending until accepted — invite expires ${new Date(created.expiresAt).toLocaleDateString()}.`));
|
|
120015
120460
|
return;
|
|
120016
120461
|
}
|
|
120017
120462
|
if (action === "role") {
|
|
@@ -120026,7 +120471,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120026
120471
|
shareId: arg,
|
|
120027
120472
|
role: valueOrRole
|
|
120028
120473
|
});
|
|
120029
|
-
await
|
|
120474
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${updated.email} is now ${import_picocolors13.default.cyan(updated.role)}`);
|
|
120030
120475
|
return;
|
|
120031
120476
|
}
|
|
120032
120477
|
if (action === "revoke") {
|
|
@@ -120034,7 +120479,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120034
120479
|
throw new Error("Usage: percher share revoke <shareId> [--app <name>]");
|
|
120035
120480
|
}
|
|
120036
120481
|
await shareRevoke(ctx, { app: options.app, shareId: arg });
|
|
120037
|
-
await
|
|
120482
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Revoked share ${arg}`);
|
|
120038
120483
|
return;
|
|
120039
120484
|
}
|
|
120040
120485
|
throw new Error(`Unknown share action "${action}". Use list | invite <email> | role <shareId> <role> | revoke <shareId>.`);
|
|
@@ -120047,12 +120492,12 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120047
120492
|
if (action === undefined || action === "list") {
|
|
120048
120493
|
const pending = await invitesList(ctx, {});
|
|
120049
120494
|
if (pending.length === 0) {
|
|
120050
|
-
await
|
|
120495
|
+
await writeLine2(ctx, import_picocolors13.default.dim("No pending invites."));
|
|
120051
120496
|
return;
|
|
120052
120497
|
}
|
|
120053
120498
|
for (const inv of pending) {
|
|
120054
|
-
const roleStr = inv.role === "admin" ?
|
|
120055
|
-
await
|
|
120499
|
+
const roleStr = inv.role === "admin" ? import_picocolors13.default.cyan("admin") : import_picocolors13.default.dim("viewer");
|
|
120500
|
+
await writeLine2(ctx, `${inv.id} ${roleStr.padEnd(15)} ${inv.appName.padEnd(24)} from ${inv.invitedBy}`);
|
|
120056
120501
|
}
|
|
120057
120502
|
return;
|
|
120058
120503
|
}
|
|
@@ -120062,10 +120507,10 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120062
120507
|
}
|
|
120063
120508
|
const accepted = await invitesAccept(ctx, { shareId });
|
|
120064
120509
|
if (accepted.alreadyAccepted) {
|
|
120065
|
-
await
|
|
120510
|
+
await writeLine2(ctx, import_picocolors13.default.dim(`Invite ${shareId} was already accepted.`));
|
|
120066
120511
|
return;
|
|
120067
120512
|
}
|
|
120068
|
-
await
|
|
120513
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Accepted — you now have ${import_picocolors13.default.cyan(accepted.role)} access to the shared app.`);
|
|
120069
120514
|
return;
|
|
120070
120515
|
}
|
|
120071
120516
|
if (action === "decline") {
|
|
@@ -120073,7 +120518,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120073
120518
|
throw new Error("Usage: percher invites decline <shareId>");
|
|
120074
120519
|
}
|
|
120075
120520
|
await invitesDecline(ctx, { shareId });
|
|
120076
|
-
await
|
|
120521
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Declined invite ${shareId}.`);
|
|
120077
120522
|
return;
|
|
120078
120523
|
}
|
|
120079
120524
|
throw new Error(`Unknown invites action "${action}". Use list | accept <shareId> | decline <shareId>.`);
|
|
@@ -120084,12 +120529,12 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120084
120529
|
}
|
|
120085
120530
|
|
|
120086
120531
|
// src/commands/status.ts
|
|
120087
|
-
var
|
|
120532
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
120088
120533
|
function registerStatusCommands(cli, ctx) {
|
|
120089
120534
|
cli.command("status", "Show Percher platform status (API + worker health)").option("--json", "Output JSON instead of formatted text").action(async (options) => {
|
|
120090
120535
|
const result = await status(ctx, { json: Boolean(options.json) });
|
|
120091
120536
|
if (options.json) {
|
|
120092
|
-
await
|
|
120537
|
+
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
120093
120538
|
if (result.verdict === "unreachable")
|
|
120094
120539
|
throw new SilentExitError(1);
|
|
120095
120540
|
return;
|
|
@@ -120100,34 +120545,34 @@ function registerStatusCommands(cli, ctx) {
|
|
|
120100
120545
|
});
|
|
120101
120546
|
}
|
|
120102
120547
|
async function renderHumanStatus(ctx, result) {
|
|
120103
|
-
const symbol2 = result.verdict === "operational" ?
|
|
120104
|
-
await
|
|
120548
|
+
const symbol2 = result.verdict === "operational" ? import_picocolors14.default.green("✓") : result.verdict === "worker-degraded" ? import_picocolors14.default.yellow("⚠") : import_picocolors14.default.red("✗");
|
|
120549
|
+
await writeLine2(ctx, `${symbol2} ${result.summary}`);
|
|
120105
120550
|
if (!result.health)
|
|
120106
120551
|
return;
|
|
120107
120552
|
const worker = result.health.worker;
|
|
120108
120553
|
if (!worker) {
|
|
120109
|
-
await
|
|
120554
|
+
await writeLine2(ctx, import_picocolors14.default.dim(" worker: not configured (local Docker socket)"));
|
|
120110
120555
|
return;
|
|
120111
120556
|
}
|
|
120112
120557
|
if (worker.health) {
|
|
120113
120558
|
const ok = worker.health.ok;
|
|
120114
|
-
const symbol3 = ok ?
|
|
120559
|
+
const symbol3 = ok ? import_picocolors14.default.green("✓") : import_picocolors14.default.red("✗");
|
|
120115
120560
|
const lastOk = worker.health.lastOkAt ?? "never";
|
|
120116
|
-
await
|
|
120561
|
+
await writeLine2(ctx, ` ${symbol3} worker health: ${ok ? "ok" : `failing (${worker.health.consecutiveErrs} consecutive)`} last-ok=${import_picocolors14.default.dim(lastOk)}`);
|
|
120117
120562
|
}
|
|
120118
120563
|
if (worker.circuitBreaker) {
|
|
120119
120564
|
const breaker = worker.circuitBreaker;
|
|
120120
|
-
const symbol3 = breaker.open ?
|
|
120565
|
+
const symbol3 = breaker.open ? import_picocolors14.default.red("✗") : import_picocolors14.default.green("✓");
|
|
120121
120566
|
const state = breaker.open ? "open (deploys fail fast)" : "closed";
|
|
120122
|
-
await
|
|
120567
|
+
await writeLine2(ctx, ` ${symbol3} circuit breaker: ${state} ${breaker.failuresInWindow}/${breaker.threshold} failures in last ${Math.round(breaker.windowMs / 1000)}s`);
|
|
120123
120568
|
}
|
|
120124
120569
|
if (result.health.version) {
|
|
120125
|
-
await
|
|
120570
|
+
await writeLine2(ctx, import_picocolors14.default.dim(` api version: ${result.health.version}`));
|
|
120126
120571
|
}
|
|
120127
120572
|
}
|
|
120128
120573
|
|
|
120129
120574
|
// src/commands/token.ts
|
|
120130
|
-
var
|
|
120575
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
120131
120576
|
var VALID_SCOPES = ["deploy", "env", "read", "full"];
|
|
120132
120577
|
function scopeLabel(scopes, appName) {
|
|
120133
120578
|
const s = scopes && scopes.length > 0 ? scopes.join("+") : "full";
|
|
@@ -120136,22 +120581,22 @@ function scopeLabel(scopes, appName) {
|
|
|
120136
120581
|
function registerTokenCommands(cli, ctx) {
|
|
120137
120582
|
cli.command("token [action] [arg]", "Manage API tokens: create | list | revoke").option("--scope <scope>", "(create) deploy | env | read | full (default: full)").option("--app <name>", "(create) bind the token to a single app").option("--ttl-days <n>", "(create) token lifetime in days").action(async (action, arg, options) => {
|
|
120138
120583
|
if (!ctx.client.token) {
|
|
120139
|
-
await
|
|
120584
|
+
await writeLine2(ctx, `${import_picocolors15.default.red("✗")} Not logged in. Run: percher login`);
|
|
120140
120585
|
throw new SilentExitError;
|
|
120141
120586
|
}
|
|
120142
120587
|
if (action === "create") {
|
|
120143
120588
|
if (!arg) {
|
|
120144
|
-
await
|
|
120589
|
+
await writeLine2(ctx, `${import_picocolors15.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
|
|
120145
120590
|
throw new SilentExitError;
|
|
120146
120591
|
}
|
|
120147
120592
|
const scope = options.scope ?? "full";
|
|
120148
120593
|
if (!VALID_SCOPES.includes(scope)) {
|
|
120149
|
-
await
|
|
120594
|
+
await writeLine2(ctx, `${import_picocolors15.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
|
|
120150
120595
|
throw new SilentExitError;
|
|
120151
120596
|
}
|
|
120152
120597
|
const ttlDays = options.ttlDays !== undefined ? Number(options.ttlDays) : undefined;
|
|
120153
120598
|
if (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {
|
|
120154
|
-
await
|
|
120599
|
+
await writeLine2(ctx, `${import_picocolors15.default.red("✗")} --ttl-days must be a positive whole number.`);
|
|
120155
120600
|
throw new SilentExitError;
|
|
120156
120601
|
}
|
|
120157
120602
|
const created = await tokenCreate(ctx, {
|
|
@@ -120160,36 +120605,36 @@ function registerTokenCommands(cli, ctx) {
|
|
|
120160
120605
|
app: options.app,
|
|
120161
120606
|
ttlDays
|
|
120162
120607
|
});
|
|
120163
|
-
await
|
|
120164
|
-
await
|
|
120165
|
-
await
|
|
120166
|
-
await
|
|
120167
|
-
await
|
|
120168
|
-
await
|
|
120169
|
-
await
|
|
120608
|
+
await writeLine2(ctx, `${import_picocolors15.default.green("✓")} Token created: ${import_picocolors15.default.bold(created.name)} ${import_picocolors15.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
|
|
120609
|
+
await writeLine2(ctx, "");
|
|
120610
|
+
await writeLine2(ctx, ` ${import_picocolors15.default.bold(created.token)}`);
|
|
120611
|
+
await writeLine2(ctx, import_picocolors15.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
|
|
120612
|
+
await writeLine2(ctx, "");
|
|
120613
|
+
await writeLine2(ctx, import_picocolors15.default.dim(" Use it in CI / an agent:"));
|
|
120614
|
+
await writeLine2(ctx, import_picocolors15.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
|
|
120170
120615
|
return;
|
|
120171
120616
|
}
|
|
120172
120617
|
if (action === "list") {
|
|
120173
120618
|
const tokens = await tokenList(ctx);
|
|
120174
120619
|
if (tokens.length === 0) {
|
|
120175
|
-
await
|
|
120620
|
+
await writeLine2(ctx, import_picocolors15.default.dim("No API tokens."));
|
|
120176
120621
|
return;
|
|
120177
120622
|
}
|
|
120178
120623
|
for (const t of tokens) {
|
|
120179
120624
|
const expires = t.expiresAt ? `expires ${new Date(t.expiresAt).toISOString().slice(0, 10)}` : "no expiry";
|
|
120180
|
-
await
|
|
120625
|
+
await writeLine2(ctx, `${import_picocolors15.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors15.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors15.default.dim(expires)}`);
|
|
120181
120626
|
}
|
|
120182
|
-
await
|
|
120183
|
-
await
|
|
120627
|
+
await writeLine2(ctx, "");
|
|
120628
|
+
await writeLine2(ctx, import_picocolors15.default.dim("Revoke one with: percher token revoke <id>"));
|
|
120184
120629
|
return;
|
|
120185
120630
|
}
|
|
120186
120631
|
if (action === "revoke") {
|
|
120187
120632
|
if (!arg) {
|
|
120188
|
-
await
|
|
120633
|
+
await writeLine2(ctx, `${import_picocolors15.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
|
|
120189
120634
|
throw new SilentExitError;
|
|
120190
120635
|
}
|
|
120191
120636
|
await tokenRevoke(ctx, { id: arg });
|
|
120192
|
-
await
|
|
120637
|
+
await writeLine2(ctx, `${import_picocolors15.default.green("✓")} Token revoked.`);
|
|
120193
120638
|
return;
|
|
120194
120639
|
}
|
|
120195
120640
|
throw new Error(`Unknown token action "${action ?? ""}". Use: create | list | revoke.`);
|
|
@@ -120200,18 +120645,18 @@ function registerTokenCommands(cli, ctx) {
|
|
|
120200
120645
|
}
|
|
120201
120646
|
|
|
120202
120647
|
// src/commands/transfers.ts
|
|
120203
|
-
var
|
|
120648
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
120204
120649
|
function registerTransfersCommands(cli, ctx) {
|
|
120205
120650
|
cli.command("transfer [emailOrTransferId]", "Transfer ownership of an app: `transfer <email>` initiates; `transfer --cancel <id>` cancels; `transfer --list` shows history").option("--app <name>", "App name (overrides percher.toml)").option("--cancel", "Cancel a pending transfer by id (positional arg is the transferId)").option("--list", "List the transfer history for this app").action(async (emailOrTransferId, options) => {
|
|
120206
120651
|
if (options.list) {
|
|
120207
120652
|
const rows = await transferListForApp(ctx, { app: options.app });
|
|
120208
120653
|
if (rows.length === 0) {
|
|
120209
|
-
await
|
|
120654
|
+
await writeLine2(ctx, import_picocolors16.default.dim("No transfers for this app."));
|
|
120210
120655
|
return;
|
|
120211
120656
|
}
|
|
120212
120657
|
for (const r of rows) {
|
|
120213
|
-
const status2 = r.status === "pending" ?
|
|
120214
|
-
await
|
|
120658
|
+
const status2 = r.status === "pending" ? import_picocolors16.default.yellow("pending") : r.status === "accepted" ? import_picocolors16.default.green("accepted") : import_picocolors16.default.dim(r.status);
|
|
120659
|
+
await writeLine2(ctx, `${r.id} ${status2.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
|
|
120215
120660
|
}
|
|
120216
120661
|
return;
|
|
120217
120662
|
}
|
|
@@ -120223,7 +120668,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120223
120668
|
app: options.app,
|
|
120224
120669
|
transferId: emailOrTransferId
|
|
120225
120670
|
});
|
|
120226
|
-
await
|
|
120671
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
|
|
120227
120672
|
return;
|
|
120228
120673
|
}
|
|
120229
120674
|
if (!emailOrTransferId) {
|
|
@@ -120233,21 +120678,21 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120233
120678
|
app: options.app,
|
|
120234
120679
|
email: emailOrTransferId
|
|
120235
120680
|
});
|
|
120236
|
-
await
|
|
120237
|
-
await
|
|
120681
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
|
|
120682
|
+
await writeLine2(ctx, import_picocolors16.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
|
|
120238
120683
|
if (initiated.notes) {
|
|
120239
|
-
await
|
|
120684
|
+
await writeLine2(ctx, import_picocolors16.default.yellow(` ⚠ ${initiated.notes}`));
|
|
120240
120685
|
}
|
|
120241
120686
|
});
|
|
120242
120687
|
cli.command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>").action(async (action, transferId) => {
|
|
120243
120688
|
if (action === undefined || action === "list") {
|
|
120244
120689
|
const rows = await transfersIncoming(ctx, {});
|
|
120245
120690
|
if (rows.length === 0) {
|
|
120246
|
-
await
|
|
120691
|
+
await writeLine2(ctx, import_picocolors16.default.dim("No pending incoming transfers."));
|
|
120247
120692
|
return;
|
|
120248
120693
|
}
|
|
120249
120694
|
for (const r of rows) {
|
|
120250
|
-
await
|
|
120695
|
+
await writeLine2(ctx, `${r.id} ${r.appName.padEnd(24)} from ${r.fromEmail} (expires ${new Date(r.expiresAt).toLocaleDateString()})`);
|
|
120251
120696
|
}
|
|
120252
120697
|
return;
|
|
120253
120698
|
}
|
|
@@ -120256,7 +120701,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120256
120701
|
throw new Error("Usage: percher transfers accept <transferId>");
|
|
120257
120702
|
}
|
|
120258
120703
|
const accepted = await transfersAccept(ctx, { transferId });
|
|
120259
|
-
await
|
|
120704
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} You now own ${import_picocolors16.default.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
|
|
120260
120705
|
return;
|
|
120261
120706
|
}
|
|
120262
120707
|
if (action === "decline") {
|
|
@@ -120264,7 +120709,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120264
120709
|
throw new Error("Usage: percher transfers decline <transferId>");
|
|
120265
120710
|
}
|
|
120266
120711
|
await transfersDecline(ctx, { transferId });
|
|
120267
|
-
await
|
|
120712
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Declined transfer ${transferId}.`);
|
|
120268
120713
|
return;
|
|
120269
120714
|
}
|
|
120270
120715
|
throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
|
|
@@ -120328,6 +120773,7 @@ async function createCliContext() {
|
|
|
120328
120773
|
prompt,
|
|
120329
120774
|
openUrl,
|
|
120330
120775
|
interactiveLogin: true,
|
|
120776
|
+
stdinIsInteractive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
120331
120777
|
status(message) {
|
|
120332
120778
|
process.stderr.write(encoder.encode(`${message}
|
|
120333
120779
|
`));
|
|
@@ -120337,52 +120783,52 @@ async function createCliContext() {
|
|
|
120337
120783
|
|
|
120338
120784
|
// src/output.ts
|
|
120339
120785
|
init_dist();
|
|
120340
|
-
var
|
|
120786
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
120341
120787
|
async function renderError(stderr, error48) {
|
|
120342
120788
|
const writer = stderr.getWriter();
|
|
120343
120789
|
const encoder = new TextEncoder;
|
|
120344
120790
|
const lines = [];
|
|
120345
120791
|
if (error48 instanceof PercherTomlError) {
|
|
120346
|
-
lines.push(`${
|
|
120792
|
+
lines.push(`${import_picocolors17.default.red(`error (${error48.code}):`)} ${error48.message}`);
|
|
120347
120793
|
if (error48.tomlLine !== undefined) {
|
|
120348
|
-
lines.push(
|
|
120794
|
+
lines.push(import_picocolors17.default.dim(` at percher.toml line ${error48.tomlLine}`));
|
|
120349
120795
|
}
|
|
120350
120796
|
for (const issue2 of error48.issues ?? []) {
|
|
120351
|
-
lines.push(`${
|
|
120797
|
+
lines.push(`${import_picocolors17.default.dim(" · ") + import_picocolors17.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
|
|
120352
120798
|
}
|
|
120353
120799
|
} else if (error48 instanceof SunsetError) {
|
|
120354
120800
|
if (error48.gateCode === "platform_delete_gated") {
|
|
120355
|
-
lines.push(`${
|
|
120801
|
+
lines.push(`${import_picocolors17.default.yellow("Percher is shutting down — deletes are paused.")}`);
|
|
120356
120802
|
lines.push(error48.message);
|
|
120357
|
-
lines.push(
|
|
120358
|
-
lines.push(
|
|
120803
|
+
lines.push(import_picocolors17.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
|
|
120804
|
+
lines.push(import_picocolors17.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
|
|
120359
120805
|
} else {
|
|
120360
|
-
lines.push(`${
|
|
120806
|
+
lines.push(`${import_picocolors17.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
|
|
120361
120807
|
lines.push(error48.message);
|
|
120362
|
-
lines.push(
|
|
120808
|
+
lines.push(import_picocolors17.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
|
|
120363
120809
|
}
|
|
120364
|
-
lines.push(
|
|
120810
|
+
lines.push(import_picocolors17.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
|
|
120365
120811
|
} else if (error48 instanceof PercherApiError) {
|
|
120366
120812
|
if (error48.status === 502 || error48.status === 503) {
|
|
120367
|
-
lines.push(`${
|
|
120813
|
+
lines.push(`${import_picocolors17.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
|
|
120368
120814
|
} else {
|
|
120369
|
-
lines.push(`${
|
|
120370
|
-
lines.push(
|
|
120815
|
+
lines.push(`${import_picocolors17.default.red("error:")} ${error48.message}`);
|
|
120816
|
+
lines.push(import_picocolors17.default.dim(` ${error48.code} (HTTP ${error48.status})`));
|
|
120371
120817
|
}
|
|
120372
120818
|
if (error48.requestId)
|
|
120373
|
-
lines.push(
|
|
120819
|
+
lines.push(import_picocolors17.default.dim(` request: ${error48.requestId}`));
|
|
120374
120820
|
} else if (error48 instanceof PercherCoreError) {
|
|
120375
|
-
lines.push(`${
|
|
120821
|
+
lines.push(`${import_picocolors17.default.red(`error (${error48.code}):`)} ${error48.message}`);
|
|
120376
120822
|
if (error48.hint)
|
|
120377
|
-
lines.push(
|
|
120823
|
+
lines.push(import_picocolors17.default.dim(` ${error48.hint}`));
|
|
120378
120824
|
} else if (error48 instanceof Error) {
|
|
120379
|
-
lines.push(`${
|
|
120825
|
+
lines.push(`${import_picocolors17.default.red("unexpected error:")} ${error48.message}`);
|
|
120380
120826
|
if (error48.stack)
|
|
120381
|
-
lines.push(
|
|
120827
|
+
lines.push(import_picocolors17.default.dim(error48.stack.split(`
|
|
120382
120828
|
`).slice(1, 4).join(`
|
|
120383
120829
|
`)));
|
|
120384
120830
|
} else {
|
|
120385
|
-
lines.push(`${
|
|
120831
|
+
lines.push(`${import_picocolors17.default.red("unexpected error:")} ${String(error48)}`);
|
|
120386
120832
|
}
|
|
120387
120833
|
try {
|
|
120388
120834
|
await writer.write(encoder.encode(`${lines.join(`
|
|
@@ -120395,7 +120841,7 @@ async function renderError(stderr, error48) {
|
|
|
120395
120841
|
// package.json
|
|
120396
120842
|
var package_default = {
|
|
120397
120843
|
name: "percher",
|
|
120398
|
-
version: "0.5.
|
|
120844
|
+
version: "0.5.21",
|
|
120399
120845
|
description: "CLI for the Percher hosting platform — publish apps with one command",
|
|
120400
120846
|
type: "module",
|
|
120401
120847
|
bin: {
|
|
@@ -120422,9 +120868,9 @@ var package_default = {
|
|
|
120422
120868
|
typecheck: "tsc --noEmit"
|
|
120423
120869
|
},
|
|
120424
120870
|
dependencies: {
|
|
120425
|
-
"@percher/client": "^0.2.
|
|
120426
|
-
"@percher/core": "^0.4.
|
|
120427
|
-
"@percher/toml": "^0.1.
|
|
120871
|
+
"@percher/client": "^0.2.20",
|
|
120872
|
+
"@percher/core": "^0.4.24",
|
|
120873
|
+
"@percher/toml": "^0.1.9",
|
|
120428
120874
|
cac: "^7.0.0",
|
|
120429
120875
|
picocolors: "^1.1.1"
|
|
120430
120876
|
},
|
|
@@ -120495,7 +120941,7 @@ async function showTelemetryNotice() {
|
|
|
120495
120941
|
}
|
|
120496
120942
|
|
|
120497
120943
|
// src/version-check.ts
|
|
120498
|
-
var
|
|
120944
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
120499
120945
|
async function checkForUpdates() {
|
|
120500
120946
|
try {
|
|
120501
120947
|
const res = await fetch("https://registry.npmjs.org/percher/latest", {
|
|
@@ -120506,9 +120952,9 @@ async function checkForUpdates() {
|
|
|
120506
120952
|
const data2 = await res.json();
|
|
120507
120953
|
const latest = data2.version;
|
|
120508
120954
|
if (latest && latest !== VERSION) {
|
|
120509
|
-
console.error(
|
|
120955
|
+
console.error(import_picocolors18.default.yellow(`
|
|
120510
120956
|
Update available: ${VERSION} → ${latest}`));
|
|
120511
|
-
console.error(
|
|
120957
|
+
console.error(import_picocolors18.default.dim(`Run: bunx percher@latest
|
|
120512
120958
|
`));
|
|
120513
120959
|
}
|
|
120514
120960
|
} catch {}
|
|
@@ -120526,6 +120972,7 @@ async function run(argv, ctxOverride) {
|
|
|
120526
120972
|
registerAuthCommands(cli, ctx);
|
|
120527
120973
|
registerAccountCommands(cli, ctx);
|
|
120528
120974
|
registerGithubCommands(cli, ctx);
|
|
120975
|
+
registerHooksCommands(cli, ctx);
|
|
120529
120976
|
registerMigrateCommands(cli, ctx);
|
|
120530
120977
|
registerSharingCommands(cli, ctx);
|
|
120531
120978
|
registerStatusCommands(cli, ctx);
|