percher 0.5.22 → 0.5.23
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 +564 -439
- package/dist/package.json +3 -3
- package/dist/src/commands/analytics.d.ts +4 -0
- package/dist/src/commands/analytics.d.ts.map +1 -0
- package/dist/src/commands/analytics.js +66 -0
- package/dist/src/commands/analytics.js.map +1 -0
- 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/dist/templates/capsule/src/lib/ai-client.ts +193 -0
- package/dist/templates/capsule/src/lib/ai.ts +13 -89
- package/package.json +3 -3
package/dist/bin/percher.js
CHANGED
|
@@ -92369,6 +92369,8 @@ var TIMEOUTS = {
|
|
|
92369
92369
|
mcpWaitDeployDefault: 30000,
|
|
92370
92370
|
mcpWaitDeployMax: 12 * 60000
|
|
92371
92371
|
};
|
|
92372
|
+
// ../shared/dist/waitlist.js
|
|
92373
|
+
var WAITLIST_THROTTLE_WINDOW_MS = 60 * 60 * 1000;
|
|
92372
92374
|
// ../toml/dist/errors.js
|
|
92373
92375
|
class PercherTomlError extends Error {
|
|
92374
92376
|
code;
|
|
@@ -107009,6 +107011,28 @@ var webhookAddInputSchema = exports_external.object({
|
|
|
107009
107011
|
});
|
|
107010
107012
|
var webhookListInputSchema = exports_external.object({});
|
|
107011
107013
|
var webhookRemoveInputSchema = exports_external.object({});
|
|
107014
|
+
// ../core/dist/commands/analytics.js
|
|
107015
|
+
var analyticsInputSchema = exports_external.object({
|
|
107016
|
+
app: exports_external.string().optional(),
|
|
107017
|
+
days: exports_external.number().int().min(1).max(90).optional()
|
|
107018
|
+
});
|
|
107019
|
+
async function analytics(ctx, input = {}) {
|
|
107020
|
+
const parsed = analyticsInputSchema.safeParse(input);
|
|
107021
|
+
if (!parsed.success) {
|
|
107022
|
+
throw new PercherCoreError("Invalid analytics options", {
|
|
107023
|
+
code: "analytics.invalid-input",
|
|
107024
|
+
hint: "`days` must be a whole number between 1 and 90."
|
|
107025
|
+
});
|
|
107026
|
+
}
|
|
107027
|
+
const name = parsed.data.app ?? readPercherTomlAppName(ctx.cwd);
|
|
107028
|
+
if (!name) {
|
|
107029
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
107030
|
+
code: "analytics.no-app",
|
|
107031
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
107032
|
+
});
|
|
107033
|
+
}
|
|
107034
|
+
return ctx.client.apps.getAnalytics(name, parsed.data.days ?? 7);
|
|
107035
|
+
}
|
|
107012
107036
|
// ../core/dist/commands/app-resources.js
|
|
107013
107037
|
var appResourcesInputSchema = exports_external.object({
|
|
107014
107038
|
app: exports_external.string().optional(),
|
|
@@ -107196,7 +107220,7 @@ async function appstoreKit(ctx, input = {}) {
|
|
|
107196
107220
|
}
|
|
107197
107221
|
// ../core/dist/commands/billing.js
|
|
107198
107222
|
var billingUpgradeInputSchema = exports_external.object({
|
|
107199
|
-
plan: exports_external.enum(["starter", "maker", "
|
|
107223
|
+
plan: exports_external.enum(["starter", "maker", "max"])
|
|
107200
107224
|
});
|
|
107201
107225
|
var billingPortalInputSchema = exports_external.object({});
|
|
107202
107226
|
// ../core/dist/commands/cache.js
|
|
@@ -107645,8 +107669,30 @@ function envKeyList(value, max = 50) {
|
|
|
107645
107669
|
return [];
|
|
107646
107670
|
return [...new Set(value.filter(isEnvKey))].slice(0, max);
|
|
107647
107671
|
}
|
|
107672
|
+
var MINTED_ID = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
107648
107673
|
function idToken(value) {
|
|
107649
|
-
return typeof value === "string" &&
|
|
107674
|
+
return typeof value === "string" && MINTED_ID.test(value) ? value : null;
|
|
107675
|
+
}
|
|
107676
|
+
function appRef(value) {
|
|
107677
|
+
return idToken(value);
|
|
107678
|
+
}
|
|
107679
|
+
var CONFIG_KEY_SEGMENT = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
|
|
107680
|
+
function configKeyPath(segments, maxDepth = 8) {
|
|
107681
|
+
if (!Array.isArray(segments) || segments.length === 0)
|
|
107682
|
+
return null;
|
|
107683
|
+
const parts = [];
|
|
107684
|
+
for (const segment of segments.slice(0, maxDepth)) {
|
|
107685
|
+
if (typeof segment === "number") {
|
|
107686
|
+
if (!Number.isInteger(segment) || segment < 0 || segment > 9999)
|
|
107687
|
+
break;
|
|
107688
|
+
parts.push(String(segment));
|
|
107689
|
+
continue;
|
|
107690
|
+
}
|
|
107691
|
+
if (typeof segment !== "string" || !CONFIG_KEY_SEGMENT.test(segment))
|
|
107692
|
+
break;
|
|
107693
|
+
parts.push(segment);
|
|
107694
|
+
}
|
|
107695
|
+
return parts.length > 0 ? parts.join(".") : null;
|
|
107650
107696
|
}
|
|
107651
107697
|
function boundedLine(value, max = 1e7) {
|
|
107652
107698
|
if (typeof value !== "number" || !Number.isInteger(value))
|
|
@@ -107714,24 +107760,40 @@ function recoveryLogin(opts = {}) {
|
|
|
107714
107760
|
};
|
|
107715
107761
|
}
|
|
107716
107762
|
function recoveryWait(opts) {
|
|
107763
|
+
const app = appRef(opts.app);
|
|
107764
|
+
const deployId = idToken(opts.deployId);
|
|
107765
|
+
if (app === null || deployId === null) {
|
|
107766
|
+
return recoveryAsk({
|
|
107767
|
+
prompt: "This app already has a deploy in flight, so starting another would queue a duplicate. Percher can't hand you a `percher_wait_for_deploy` call for it — the app and deploy identifiers it came back with aren't well-formed. Check the app's recent deploys before publishing again. How would you like to proceed?",
|
|
107768
|
+
reasonCode: opts.reasonCode
|
|
107769
|
+
});
|
|
107770
|
+
}
|
|
107717
107771
|
return {
|
|
107718
107772
|
retryable: false,
|
|
107719
107773
|
nextAction: "wait_deploy",
|
|
107720
107774
|
suggestedTool: "percher_wait_for_deploy",
|
|
107721
107775
|
args: {
|
|
107722
|
-
app
|
|
107723
|
-
deployId
|
|
107776
|
+
app,
|
|
107777
|
+
deployId,
|
|
107724
107778
|
timeoutSeconds: opts.timeoutSeconds ?? TIMEOUTS.mcpWaitDeployDefault / 1000
|
|
107725
107779
|
},
|
|
107726
107780
|
reasonCode: opts.reasonCode
|
|
107727
107781
|
};
|
|
107728
107782
|
}
|
|
107729
107783
|
function recoveryDoctor(opts) {
|
|
107784
|
+
const app = opts.app === undefined ? undefined : appRef(opts.app);
|
|
107785
|
+
const deployId = opts.deployId === undefined ? undefined : idToken(opts.deployId);
|
|
107786
|
+
if (app === null || deployId === null) {
|
|
107787
|
+
return recoveryAsk({
|
|
107788
|
+
prompt: "Percher can't run diagnostics for this app automatically — the app or deploy identifier it came back with isn't well-formed, and guessing which app to inspect could point at the wrong one. Run `percher doctor --app <name>` with the app you meant, or tell me which app to look at.",
|
|
107789
|
+
reasonCode: opts.reasonCode
|
|
107790
|
+
});
|
|
107791
|
+
}
|
|
107730
107792
|
const args = {};
|
|
107731
|
-
if (
|
|
107732
|
-
args.app =
|
|
107733
|
-
if (
|
|
107734
|
-
args.deployId =
|
|
107793
|
+
if (app !== undefined)
|
|
107794
|
+
args.app = app;
|
|
107795
|
+
if (deployId !== undefined)
|
|
107796
|
+
args.deployId = deployId;
|
|
107735
107797
|
if (opts.mode !== undefined)
|
|
107736
107798
|
args.mode = opts.mode;
|
|
107737
107799
|
return {
|
|
@@ -107745,10 +107807,18 @@ function recoveryDoctor(opts) {
|
|
|
107745
107807
|
}
|
|
107746
107808
|
function recoveryEnv(opts) {
|
|
107747
107809
|
const args = {};
|
|
107748
|
-
|
|
107749
|
-
|
|
107750
|
-
|
|
107751
|
-
|
|
107810
|
+
const app = opts.app === undefined ? undefined : appRef(opts.app);
|
|
107811
|
+
if (app === null) {
|
|
107812
|
+
return recoveryAsk({
|
|
107813
|
+
prompt: "Percher needs environment variables set before this app can build, but the app identifier it came back with isn't well-formed — setting them against the wrong app would put values somewhere they don't belong. Which app should these be set on?",
|
|
107814
|
+
reasonCode: opts.reasonCode ?? "missing_env"
|
|
107815
|
+
});
|
|
107816
|
+
}
|
|
107817
|
+
const keys = envKeyList(opts.keys);
|
|
107818
|
+
if (app !== undefined)
|
|
107819
|
+
args.app = app;
|
|
107820
|
+
if (keys.length > 0)
|
|
107821
|
+
args.keys = keys;
|
|
107752
107822
|
return {
|
|
107753
107823
|
retryable: false,
|
|
107754
107824
|
nextAction: "set_env_vars",
|
|
@@ -107803,7 +107873,15 @@ function recoveryAsk(opts) {
|
|
|
107803
107873
|
};
|
|
107804
107874
|
}
|
|
107805
107875
|
function recoveryInspectBuildLog(opts) {
|
|
107806
|
-
const
|
|
107876
|
+
const app = opts.app === undefined ? undefined : appRef(opts.app);
|
|
107877
|
+
const deployId = opts.deployId === undefined ? undefined : idToken(opts.deployId);
|
|
107878
|
+
if (app === null || deployId === null) {
|
|
107879
|
+
return recoveryAsk({
|
|
107880
|
+
prompt: "Percher can't open the build log for this deploy automatically — the app or deploy identifier it came back with isn't well-formed, and falling back to the latest failed deploy could show you a different one. Which deploy did you want to inspect?",
|
|
107881
|
+
reasonCode: opts.reasonCode ?? "build_failed"
|
|
107882
|
+
});
|
|
107883
|
+
}
|
|
107884
|
+
const args = deployId ? { id: deployId, ...app !== undefined ? { app } : {} } : { latestFailed: true, ...app !== undefined ? { app } : {} };
|
|
107807
107885
|
return {
|
|
107808
107886
|
retryable: false,
|
|
107809
107887
|
nextAction: "inspect_build_log",
|
|
@@ -107874,10 +107952,13 @@ function recoveryFromErrorClass(errorClass, failure = {}) {
|
|
|
107874
107952
|
case "runtime_crash":
|
|
107875
107953
|
case "oom_killed": {
|
|
107876
107954
|
const fileProblems = (failure.problems ?? []).filter(hasEditableFile);
|
|
107877
|
-
const
|
|
107955
|
+
const altDeployId = idToken(failure.deployId);
|
|
107956
|
+
const altApp = appRef(failure.appName);
|
|
107957
|
+
const altAppArg = altApp !== null ? { app: altApp } : {};
|
|
107958
|
+
const reproduceAlt = altDeployId ? {
|
|
107878
107959
|
action: "reproduce_locally",
|
|
107879
107960
|
suggestedTool: "percher_reproduce",
|
|
107880
|
-
args: { deployId:
|
|
107961
|
+
args: { deployId: altDeployId, ...altAppArg },
|
|
107881
107962
|
when: "after the primary path didn't surface a clear fix and the user has nixpacks installed locally"
|
|
107882
107963
|
} : undefined;
|
|
107883
107964
|
if (fileProblems.length > 0) {
|
|
@@ -107897,7 +107978,7 @@ function recoveryFromErrorClass(errorClass, failure = {}) {
|
|
|
107897
107978
|
const inspectAlt = {
|
|
107898
107979
|
action: "inspect_build_log",
|
|
107899
107980
|
suggestedTool: "percher_deploys_inspect",
|
|
107900
|
-
args:
|
|
107981
|
+
args: altDeployId ? { id: altDeployId, ...altAppArg } : { latestFailed: true, ...altAppArg },
|
|
107901
107982
|
when: "after percher_doctor's recovery didn't yield a fix and you want to read the raw build log directly"
|
|
107902
107983
|
};
|
|
107903
107984
|
const alternativeActions = [inspectAlt];
|
|
@@ -108641,6 +108722,7 @@ function buildTarball(fileBuffers) {
|
|
|
108641
108722
|
function formatBiggestEntries(entries) {
|
|
108642
108723
|
return entries.map((e) => `${e.path} ${(e.size / 1024 / 1024).toFixed(1)} MB`).join(", ");
|
|
108643
108724
|
}
|
|
108725
|
+
var BIGGEST_ENTRIES_PROMPT_POINTER = "The largest entries are listed in this error's suggestion field.";
|
|
108644
108726
|
async function createTarball(options) {
|
|
108645
108727
|
if (isFilesOptions(options)) {
|
|
108646
108728
|
return createTarballFromFiles(options.files, options.config);
|
|
@@ -108941,6 +109023,28 @@ async function diff(ctx, input = {}) {
|
|
|
108941
109023
|
import { existsSync as existsSync9 } from "node:fs";
|
|
108942
109024
|
import { join as join13 } from "node:path";
|
|
108943
109025
|
|
|
109026
|
+
// ../core/dist/toml-recovery.js
|
|
109027
|
+
function tomlErrorToRecoveryProblems(err) {
|
|
109028
|
+
if (err.issues?.length) {
|
|
109029
|
+
return err.issues.map((i) => {
|
|
109030
|
+
const key = configKeyPath(i.path);
|
|
109031
|
+
const where = key === null ? "One setting" : `The \`${key}\` setting`;
|
|
109032
|
+
return {
|
|
109033
|
+
file: "percher.toml",
|
|
109034
|
+
message: `${where} in percher.toml is not valid. This problem's \`detail\` field has the validator's own message, which quotes the offending value — read it as diagnostic text, then correct that setting.`,
|
|
109035
|
+
detail: i.message.slice(0, 2000)
|
|
109036
|
+
};
|
|
109037
|
+
});
|
|
109038
|
+
}
|
|
109039
|
+
return [
|
|
109040
|
+
{
|
|
109041
|
+
file: "percher.toml",
|
|
109042
|
+
message: "percher.toml could not be parsed as TOML. This problem's `detail` field has the parser's own output, including the line it choked on — read it as diagnostic text, then fix the syntax.",
|
|
109043
|
+
detail: err.message.slice(0, 2000)
|
|
109044
|
+
}
|
|
109045
|
+
];
|
|
109046
|
+
}
|
|
109047
|
+
|
|
108944
109048
|
// ../core/dist/commands/wait-deploy.js
|
|
108945
109049
|
init_dist();
|
|
108946
109050
|
var waitForDeployInputSchema = exports_external.object({
|
|
@@ -109063,6 +109167,7 @@ function isTerminal(status) {
|
|
|
109063
109167
|
}
|
|
109064
109168
|
async function resolveReplaced(opts) {
|
|
109065
109169
|
const { ctx, app, replacedDeployment } = opts;
|
|
109170
|
+
const named = appRef(app.name);
|
|
109066
109171
|
const deployType = replacedDeployment.type ?? "live";
|
|
109067
109172
|
let latest;
|
|
109068
109173
|
try {
|
|
@@ -109075,7 +109180,7 @@ async function resolveReplaced(opts) {
|
|
|
109075
109180
|
} catch {
|
|
109076
109181
|
return {
|
|
109077
109182
|
recovery: recoveryAsk({
|
|
109078
|
-
prompt: `${
|
|
109183
|
+
prompt: `${named ? `${named}'s` : "This app's"} last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, but listing recent deploys failed, so Percher can't say what is live now. Shall I run \`percher doctor${named ? ` --app ${named}` : ""}\` to inspect the current state?`,
|
|
109079
109184
|
reasonCode: "replaced_by_newer"
|
|
109080
109185
|
}),
|
|
109081
109186
|
summary: `${app.name} deploy ${replacedDeployment.id} was replaced, but listing recent deploys failed. Surface to user — they can check the dashboard.`
|
|
@@ -109084,7 +109189,7 @@ async function resolveReplaced(opts) {
|
|
|
109084
109189
|
if (!latest) {
|
|
109085
109190
|
return {
|
|
109086
109191
|
recovery: recoveryAsk({
|
|
109087
|
-
prompt: `${
|
|
109192
|
+
prompt: `${named ? `${named}'s` : "This app's"} last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, and there is no other deploy to resolve to — so nothing is currently live from it. Do you want to publish again?`,
|
|
109088
109193
|
reasonCode: "replaced_by_newer"
|
|
109089
109194
|
}),
|
|
109090
109195
|
summary: `${app.name} deploy ${replacedDeployment.id} was replaced and there are no other deploys to resolve to. Surface to user.`
|
|
@@ -109102,7 +109207,7 @@ async function resolveReplaced(opts) {
|
|
|
109102
109207
|
if (idToken(latest.id) === null && (latest.status === "queued" || latest.status === "building" || latest.status === "deploying")) {
|
|
109103
109208
|
return {
|
|
109104
109209
|
recovery: recoveryAsk({
|
|
109105
|
-
prompt: `${
|
|
109210
|
+
prompt: `${named ? `${named}'s` : "This app's"} last deploy was replaced by a newer one that is still in progress, but Percher couldn't read that deploy's id from the response, so there is nothing to poll. Shall I check the app's status again in a moment?`,
|
|
109106
109211
|
reasonCode: "replaced_by_newer"
|
|
109107
109212
|
}),
|
|
109108
109213
|
resolvedDeployment: latest,
|
|
@@ -109119,7 +109224,7 @@ async function resolveReplaced(opts) {
|
|
|
109119
109224
|
}
|
|
109120
109225
|
return {
|
|
109121
109226
|
recovery: recoveryAsk({
|
|
109122
|
-
prompt: `${
|
|
109227
|
+
prompt: `${named ? `${named}'s` : "This app's"} last deploy was replaced, and the newest deploy${idToken(latest.id) ? ` (${latest.id})` : ""} is not live either — this result's summary has both deploys' state. Shall I run \`percher doctor${named ? ` --app ${named}` : ""}\` to diagnose?`,
|
|
109123
109228
|
reasonCode: "replaced_by_newer"
|
|
109124
109229
|
}),
|
|
109125
109230
|
resolvedDeployment: latest,
|
|
@@ -109220,6 +109325,7 @@ async function doctor(ctx, input = {}) {
|
|
|
109220
109325
|
const tomlPath = join13(ctx.cwd, "percher.toml");
|
|
109221
109326
|
const tomlExists = existsSync9(tomlPath);
|
|
109222
109327
|
let tomlValid = false;
|
|
109328
|
+
let tomlProblems;
|
|
109223
109329
|
if (!tomlExists) {
|
|
109224
109330
|
checks3.push({
|
|
109225
109331
|
name: "percher.toml",
|
|
@@ -109232,6 +109338,9 @@ async function doctor(ctx, input = {}) {
|
|
|
109232
109338
|
tomlValid = true;
|
|
109233
109339
|
checks3.push({ name: "percher.toml", status: "pass", message: "Valid" });
|
|
109234
109340
|
} catch (err) {
|
|
109341
|
+
if (err instanceof PercherTomlError) {
|
|
109342
|
+
tomlProblems = tomlErrorToRecoveryProblems(err);
|
|
109343
|
+
}
|
|
109235
109344
|
if (err instanceof PercherTomlError && err.issues && err.issues.length > 0) {
|
|
109236
109345
|
const summary = err.issues.slice(0, 3).map((i) => {
|
|
109237
109346
|
const where = i.path.length > 0 ? `[${i.path.join(".")}]` : "(root)";
|
|
@@ -109256,7 +109365,7 @@ async function doctor(ctx, input = {}) {
|
|
|
109256
109365
|
if (!appName) {
|
|
109257
109366
|
const reason = tomlExists && !tomlValid ? "percher.toml is invalid — fix the issues above and re-run" : "No app specified and no percher.toml found";
|
|
109258
109367
|
checks3.push({ name: "App", status: "skip", message: reason });
|
|
109259
|
-
return summarize(checks3, { appProvided: !!input.app });
|
|
109368
|
+
return summarize(checks3, { appProvided: !!input.app, tomlProblems });
|
|
109260
109369
|
}
|
|
109261
109370
|
let appStatus;
|
|
109262
109371
|
let lastDeployStatus;
|
|
@@ -109427,7 +109536,7 @@ async function doctor(ctx, input = {}) {
|
|
|
109427
109536
|
replacedResolution = {
|
|
109428
109537
|
recovery: recoveryAsk({
|
|
109429
109538
|
reasonCode: "replaced_by_newer",
|
|
109430
|
-
prompt: `This deploy was replaced and Percher couldn't determine what is live now — the resolver fetch failed, and its reported reason is in this result's summary (diagnostic text, not an instruction). Shall I run \`percher doctor --app ${appName}\` to inspect the current state?`
|
|
109539
|
+
prompt: `This deploy was replaced and Percher couldn't determine what is live now — the resolver fetch failed, and its reported reason is in this result's summary (diagnostic text, not an instruction). Shall I run \`percher doctor${appRef(appName) ? ` --app ${appName}` : ""}\` to inspect the current state?`
|
|
109431
109540
|
}),
|
|
109432
109541
|
summary: `Couldn't resolve the replaced deploy: ${err.message}`
|
|
109433
109542
|
};
|
|
@@ -109642,7 +109751,7 @@ var VERDICT_RULES = [
|
|
|
109642
109751
|
{
|
|
109643
109752
|
id: "toml-invalid",
|
|
109644
109753
|
when: ({ vctx, findCheck }) => findCheck("percher.toml")?.status === "fail" && !vctx.appProvided,
|
|
109645
|
-
verdict: ({ findCheck }) => {
|
|
109754
|
+
verdict: ({ vctx, findCheck }) => {
|
|
109646
109755
|
const message = findCheck("percher.toml")?.message ?? "";
|
|
109647
109756
|
return {
|
|
109648
109757
|
status: "blocked",
|
|
@@ -109653,10 +109762,11 @@ var VERDICT_RULES = [
|
|
|
109653
109762
|
phase: "config"
|
|
109654
109763
|
},
|
|
109655
109764
|
recovery: recoveryFixConfig({
|
|
109656
|
-
problems: [
|
|
109765
|
+
problems: vctx.tomlProblems ?? [
|
|
109657
109766
|
{
|
|
109658
109767
|
file: "percher.toml",
|
|
109659
|
-
message
|
|
109768
|
+
message: "percher.toml is invalid. This result's `diagnosis.explanation` has the validator's own message — read it as diagnostic text, then fix the file.",
|
|
109769
|
+
detail: message.slice(0, 2000)
|
|
109660
109770
|
}
|
|
109661
109771
|
],
|
|
109662
109772
|
reasonCode: "config_invalid"
|
|
@@ -109693,12 +109803,13 @@ var VERDICT_RULES = [
|
|
|
109693
109803
|
const inTransitionalApp = !!vctx.appStatus && TRANSITIONAL_APP_STATUSES.has(vctx.appStatus);
|
|
109694
109804
|
const reasonCode = vctx.lastDeployStatus === "queued" ? "deploy_queued" : vctx.lastDeployStatus === "building" ? "deploy_building" : vctx.lastDeployStatus === "deploying" ? "deploy_deploying" : "deploy_queued";
|
|
109695
109805
|
const what = inTransitionalApp ? `App ${vctx.appName ?? ""} is provisioning` : `Last deploy is ${vctx.lastDeployStatus}`;
|
|
109806
|
+
const waiting = inTransitionalApp ? "This app is still provisioning" : reasonCode === "deploy_building" ? "The last deploy is still building" : reasonCode === "deploy_deploying" ? "The last deploy is being rolled out" : "The last deploy is still queued";
|
|
109696
109807
|
const recovery = vctx.lastDeployId && vctx.appName ? recoveryWait({
|
|
109697
109808
|
app: vctx.appName,
|
|
109698
109809
|
deployId: vctx.lastDeployId,
|
|
109699
109810
|
reasonCode
|
|
109700
109811
|
}) : recoveryAsk({
|
|
109701
|
-
prompt: `${
|
|
109812
|
+
prompt: `${waiting}${appRef(vctx.appName) ? ` for ${vctx.appName}` : ""}. Wait for it to finish before retrying — there's no deployId to track yet.`,
|
|
109702
109813
|
reasonCode
|
|
109703
109814
|
});
|
|
109704
109815
|
return {
|
|
@@ -109825,17 +109936,12 @@ var VERDICT_RULES = [
|
|
|
109825
109936
|
reasonCode: "build_failed",
|
|
109826
109937
|
phase: "build"
|
|
109827
109938
|
},
|
|
109828
|
-
recovery: {
|
|
109829
|
-
|
|
109830
|
-
|
|
109831
|
-
|
|
109832
|
-
args: {
|
|
109833
|
-
app: vctx.appName,
|
|
109834
|
-
mode: "deploy",
|
|
109835
|
-
...vctx.lastDeployId ? { deployId: vctx.lastDeployId } : {}
|
|
109836
|
-
},
|
|
109939
|
+
recovery: recoveryDoctor({
|
|
109940
|
+
...vctx.appName !== undefined ? { app: vctx.appName } : {},
|
|
109941
|
+
...vctx.lastDeployId ? { deployId: vctx.lastDeployId } : {},
|
|
109942
|
+
mode: "deploy",
|
|
109837
109943
|
reasonCode: "build_failed"
|
|
109838
|
-
},
|
|
109944
|
+
}),
|
|
109839
109945
|
summary: vctx.lastDeployId ? `Deploy ${vctx.lastDeployId} failed — call percher_doctor with mode='deploy'.` : "Last deploy failed — call percher_doctor with mode='deploy'."
|
|
109840
109946
|
})
|
|
109841
109947
|
},
|
|
@@ -109933,13 +110039,11 @@ var VERDICT_RULES = [
|
|
|
109933
110039
|
reasonCode: "runtime_crashed",
|
|
109934
110040
|
phase: "runtime"
|
|
109935
110041
|
},
|
|
109936
|
-
recovery: {
|
|
109937
|
-
|
|
109938
|
-
|
|
109939
|
-
suggestedTool: "percher_doctor",
|
|
109940
|
-
args: { app: vctx.appName, mode: "runtime" },
|
|
110042
|
+
recovery: recoveryDoctor({
|
|
110043
|
+
...vctx.appName !== undefined ? { app: vctx.appName } : {},
|
|
110044
|
+
mode: "runtime",
|
|
109941
110045
|
reasonCode: "runtime_crashed"
|
|
109942
|
-
},
|
|
110046
|
+
}),
|
|
109943
110047
|
summary: vctx.appName ? `${vctx.appName} runtime needs investigation — call percher_doctor with mode='runtime'.` : "Runtime needs investigation — call percher_doctor with mode='runtime'."
|
|
109944
110048
|
};
|
|
109945
110049
|
}
|
|
@@ -109957,13 +110061,11 @@ var VERDICT_RULES = [
|
|
|
109957
110061
|
reasonCode: "infra_transient",
|
|
109958
110062
|
phase: "infra"
|
|
109959
110063
|
},
|
|
109960
|
-
recovery: {
|
|
109961
|
-
retryable: true,
|
|
109962
|
-
nextAction: "retry",
|
|
110064
|
+
recovery: recoveryRetry({
|
|
109963
110065
|
suggestedTool: "percher_doctor",
|
|
109964
|
-
args: { app: vctx.appName },
|
|
110066
|
+
args: appRef(vctx.appName) ? { app: vctx.appName } : {},
|
|
109965
110067
|
reasonCode: "infra_transient"
|
|
109966
|
-
},
|
|
110068
|
+
}),
|
|
109967
110069
|
summary: vctx.appName ? `${vctx.appName} public route is failing — likely transient, re-run doctor in a moment.` : "Public route is failing — likely transient, re-run doctor in a moment."
|
|
109968
110070
|
};
|
|
109969
110071
|
}
|
|
@@ -110002,20 +110104,20 @@ function resolveApp(ctx, app) {
|
|
|
110002
110104
|
return name;
|
|
110003
110105
|
}
|
|
110004
110106
|
async function domainAdd(ctx, input) {
|
|
110005
|
-
const
|
|
110006
|
-
return ctx.client.apps.addDomain(
|
|
110107
|
+
const appRef2 = resolveApp(ctx, input.app);
|
|
110108
|
+
return ctx.client.apps.addDomain(appRef2, input.domain);
|
|
110007
110109
|
}
|
|
110008
110110
|
async function domainVerify(ctx, input) {
|
|
110009
|
-
const
|
|
110010
|
-
return ctx.client.apps.verifyDomain(
|
|
110111
|
+
const appRef2 = resolveApp(ctx, input.app);
|
|
110112
|
+
return ctx.client.apps.verifyDomain(appRef2, input.domain);
|
|
110011
110113
|
}
|
|
110012
110114
|
async function domainList(ctx, input = {}) {
|
|
110013
|
-
const
|
|
110014
|
-
return ctx.client.apps.listDomains(
|
|
110115
|
+
const appRef2 = resolveApp(ctx, input.app);
|
|
110116
|
+
return ctx.client.apps.listDomains(appRef2);
|
|
110015
110117
|
}
|
|
110016
110118
|
async function domainRemove(ctx, input) {
|
|
110017
|
-
const
|
|
110018
|
-
await ctx.client.apps.removeDomain(
|
|
110119
|
+
const appRef2 = resolveApp(ctx, input.app);
|
|
110120
|
+
await ctx.client.apps.removeDomain(appRef2, input.domain);
|
|
110019
110121
|
}
|
|
110020
110122
|
// ../core/dist/commands/env.js
|
|
110021
110123
|
var envScopeEnum = exports_external.enum(["all", "live", "preview"]);
|
|
@@ -110371,12 +110473,12 @@ function resolveApp2(ctx, app) {
|
|
|
110371
110473
|
return name;
|
|
110372
110474
|
}
|
|
110373
110475
|
async function githubConnect(ctx, input) {
|
|
110374
|
-
const
|
|
110476
|
+
const appRef2 = resolveApp2(ctx, input.app);
|
|
110375
110477
|
const branch = input.branch ?? "main";
|
|
110376
110478
|
let deployment;
|
|
110377
110479
|
let created;
|
|
110378
110480
|
try {
|
|
110379
|
-
deployment = await ctx.client.apps.importFromGithub(
|
|
110481
|
+
deployment = await ctx.client.apps.importFromGithub(appRef2, {
|
|
110380
110482
|
repoUrl: input.repoUrl,
|
|
110381
110483
|
branch,
|
|
110382
110484
|
...input.autoPreviews !== undefined ? { autoPreviews: input.autoPreviews } : {}
|
|
@@ -110386,7 +110488,7 @@ async function githubConnect(ctx, input) {
|
|
|
110386
110488
|
throw err;
|
|
110387
110489
|
deployment = await ctx.client.apps.importGithub({
|
|
110388
110490
|
repoUrl: input.repoUrl,
|
|
110389
|
-
name:
|
|
110491
|
+
name: appRef2,
|
|
110390
110492
|
branch,
|
|
110391
110493
|
...input.autoPreviews !== undefined ? { autoPreviews: input.autoPreviews } : {}
|
|
110392
110494
|
});
|
|
@@ -110395,14 +110497,14 @@ async function githubConnect(ctx, input) {
|
|
|
110395
110497
|
let webhook = null;
|
|
110396
110498
|
let webhookError;
|
|
110397
110499
|
try {
|
|
110398
|
-
webhook = await ctx.client.apps.setupWebhook(
|
|
110500
|
+
webhook = await ctx.client.apps.setupWebhook(appRef2);
|
|
110399
110501
|
} catch (err) {
|
|
110400
110502
|
webhookError = err instanceof Error ? err.message : String(err);
|
|
110401
110503
|
}
|
|
110402
110504
|
const repoBase = input.repoUrl.replace(/\.git$/, "");
|
|
110403
110505
|
if (!webhook) {
|
|
110404
110506
|
return {
|
|
110405
|
-
app:
|
|
110507
|
+
app: appRef2,
|
|
110406
110508
|
repoUrl: input.repoUrl,
|
|
110407
110509
|
branch,
|
|
110408
110510
|
deployId: deployment.id,
|
|
@@ -110410,16 +110512,16 @@ async function githubConnect(ctx, input) {
|
|
|
110410
110512
|
webhookSetupError: webhookError,
|
|
110411
110513
|
...created ? { created } : {},
|
|
110412
110514
|
summary: [
|
|
110413
|
-
created ? `Created ${
|
|
110515
|
+
created ? `Created ${appRef2} from ${input.repoUrl} and started its first deploy: ${deployment.id}.` : `Repo imported and initial deploy started: ${deployment.id}.`,
|
|
110414
110516
|
`WARNING: Webhook setup failed (${webhookError ?? "unknown error"}).`,
|
|
110415
110517
|
"Auto-deploy will NOT work until you re-run webhook setup:",
|
|
110416
|
-
` bunx percher github setup-webhook --app ${
|
|
110518
|
+
` bunx percher github setup-webhook --app ${appRef2}`
|
|
110417
110519
|
].join(`
|
|
110418
110520
|
`)
|
|
110419
110521
|
};
|
|
110420
110522
|
}
|
|
110421
110523
|
return {
|
|
110422
|
-
app:
|
|
110524
|
+
app: appRef2,
|
|
110423
110525
|
repoUrl: input.repoUrl,
|
|
110424
110526
|
branch,
|
|
110425
110527
|
deployId: deployment.id,
|
|
@@ -110432,7 +110534,7 @@ async function githubConnect(ctx, input) {
|
|
|
110432
110534
|
},
|
|
110433
110535
|
...created ? { created } : {},
|
|
110434
110536
|
summary: [
|
|
110435
|
-
created ? `Created ${
|
|
110537
|
+
created ? `Created ${appRef2} from ${input.repoUrl} (branch: ${branch}).` : `Connected ${appRef2} to ${input.repoUrl} (branch: ${branch}).`,
|
|
110436
110538
|
`Initial deploy started: ${deployment.id}.`,
|
|
110437
110539
|
`Add the webhook in GitHub: ${repoBase}/settings/hooks/new`,
|
|
110438
110540
|
` Payload URL: ${webhook.webhookUrl}`,
|
|
@@ -110451,11 +110553,11 @@ var githubSetupWebhookInputSchema = exports_external.object({
|
|
|
110451
110553
|
app: exports_external.string().optional().describe("App name (overrides percher.toml)")
|
|
110452
110554
|
});
|
|
110453
110555
|
async function githubSetupWebhook(ctx, input) {
|
|
110454
|
-
const
|
|
110455
|
-
const webhook = await ctx.client.apps.setupWebhook(
|
|
110556
|
+
const appRef2 = resolveApp2(ctx, input.app);
|
|
110557
|
+
const webhook = await ctx.client.apps.setupWebhook(appRef2);
|
|
110456
110558
|
const repoBase = webhook.instructions.repo.replace(/\.git$/, "");
|
|
110457
110559
|
return {
|
|
110458
|
-
app:
|
|
110560
|
+
app: appRef2,
|
|
110459
110561
|
branch: webhook.branch,
|
|
110460
110562
|
webhookInstructions: {
|
|
110461
110563
|
settingsUrl: `${repoBase}/settings/hooks/new`,
|
|
@@ -110465,7 +110567,7 @@ async function githubSetupWebhook(ctx, input) {
|
|
|
110465
110567
|
events: ["push"]
|
|
110466
110568
|
},
|
|
110467
110569
|
summary: [
|
|
110468
|
-
`Webhook (re)generated for ${
|
|
110570
|
+
`Webhook (re)generated for ${appRef2} (branch: ${webhook.branch}).`,
|
|
110469
110571
|
`Add the webhook in GitHub: ${repoBase}/settings/hooks/new`,
|
|
110470
110572
|
` Payload URL: ${webhook.webhookUrl}`,
|
|
110471
110573
|
" Content type: application/json",
|
|
@@ -115588,20 +115690,20 @@ function resolveApp3(ctx, app) {
|
|
|
115588
115690
|
return name;
|
|
115589
115691
|
}
|
|
115590
115692
|
async function previewBranchSet(ctx, input) {
|
|
115591
|
-
const
|
|
115592
|
-
return ctx.client.apps.setPreviewBranch(
|
|
115693
|
+
const appRef2 = resolveApp3(ctx, input.app);
|
|
115694
|
+
return ctx.client.apps.setPreviewBranch(appRef2, input.branch);
|
|
115593
115695
|
}
|
|
115594
115696
|
async function previewBranchUnset(ctx, input = {}) {
|
|
115595
|
-
const
|
|
115596
|
-
return ctx.client.apps.unsetPreviewBranch(
|
|
115697
|
+
const appRef2 = resolveApp3(ctx, input.app);
|
|
115698
|
+
return ctx.client.apps.unsetPreviewBranch(appRef2);
|
|
115597
115699
|
}
|
|
115598
115700
|
var setPreviewBranchToolInputSchema = exports_external.object({
|
|
115599
115701
|
app: exports_external.string().optional(),
|
|
115600
115702
|
branch: exports_external.string().nullable().describe("Branch to pin as the preview address; pass null to clear it.")
|
|
115601
115703
|
});
|
|
115602
115704
|
async function previewBranchShow(ctx, input = {}) {
|
|
115603
|
-
const
|
|
115604
|
-
const app = await ctx.client.apps.get(
|
|
115705
|
+
const appRef2 = resolveApp3(ctx, input.app);
|
|
115706
|
+
const app = await ctx.client.apps.get(appRef2);
|
|
115605
115707
|
return {
|
|
115606
115708
|
previewBranch: app.previewBranch ?? null,
|
|
115607
115709
|
previewUrl: app.previewUrl ?? null,
|
|
@@ -115609,8 +115711,8 @@ async function previewBranchShow(ctx, input = {}) {
|
|
|
115609
115711
|
};
|
|
115610
115712
|
}
|
|
115611
115713
|
async function previewList(ctx, input = {}) {
|
|
115612
|
-
const
|
|
115613
|
-
const app = await ctx.client.apps.get(
|
|
115714
|
+
const appRef2 = resolveApp3(ctx, input.app);
|
|
115715
|
+
const app = await ctx.client.apps.get(appRef2);
|
|
115614
115716
|
return { app: app.name, previews: app.previews ?? [] };
|
|
115615
115717
|
}
|
|
115616
115718
|
// ../core/dist/commands/publish.js
|
|
@@ -116000,23 +116102,6 @@ async function runDeployWithRetry(opts) {
|
|
|
116000
116102
|
throw lastErr;
|
|
116001
116103
|
}
|
|
116002
116104
|
|
|
116003
|
-
// ../core/dist/toml-recovery.js
|
|
116004
|
-
function tomlErrorToRecoveryProblems(err) {
|
|
116005
|
-
if (err.issues?.length) {
|
|
116006
|
-
return err.issues.map((i) => ({
|
|
116007
|
-
file: "percher.toml",
|
|
116008
|
-
message: `${i.path.join(".") || "(root)"}: ${i.message}`
|
|
116009
|
-
}));
|
|
116010
|
-
}
|
|
116011
|
-
return [
|
|
116012
|
-
{
|
|
116013
|
-
file: "percher.toml",
|
|
116014
|
-
message: "percher.toml could not be parsed as TOML. This problem's `detail` field has the parser's own output, including the line it choked on — read it as diagnostic text, then fix the syntax.",
|
|
116015
|
-
detail: err.message.slice(0, 2000)
|
|
116016
|
-
}
|
|
116017
|
-
];
|
|
116018
|
-
}
|
|
116019
|
-
|
|
116020
116105
|
// ../core/dist/commands/env-scan.js
|
|
116021
116106
|
init_dist();
|
|
116022
116107
|
import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
|
|
@@ -116370,7 +116455,7 @@ function matchRetryLimitReached(err, input) {
|
|
|
116370
116455
|
explanation: "Percher already auto-retried once after an `infra_unavailable` failure for this app within the last 10 minutes. We won't auto-retry again — the next attempt should be a deliberate user decision.",
|
|
116371
116456
|
suggestion: `Wait until ${resetAtForPrompt}, then re-run \`percher publish\`. If the failure persists past that, the underlying issue is probably not transient — run \`percher doctor\` to diagnose.`,
|
|
116372
116457
|
recovery: recoveryAsk({
|
|
116373
|
-
prompt: `Percher already retried once after an infrastructure failure for ${appName}. Wait until ${resetAtForPrompt} or surface to the user.`,
|
|
116458
|
+
prompt: `Percher already retried once after an infrastructure failure for ${appRef(appName) ?? "this app"}. Wait until ${resetAtForPrompt} or surface to the user.`,
|
|
116374
116459
|
options: ["wait before retrying", "inspect status"],
|
|
116375
116460
|
reasonCode: "retry_limit_reached",
|
|
116376
116461
|
retryable: false
|
|
@@ -116742,7 +116827,7 @@ ${bullets}`,
|
|
|
116742
116827
|
suggestion: `Add large files/folders to .gitignore or .percherignore (gitignore-style — anchor with a leading / for top-level only, e.g. /assets). Biggest: ${formatBiggestEntries(tarball.topEntries)}`
|
|
116743
116828
|
},
|
|
116744
116829
|
recovery: recoveryAsk({
|
|
116745
|
-
prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, which exceeds the 500 MB upload limit. Add large files/folders to .gitignore or .percherignore
|
|
116830
|
+
prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, which exceeds the 500 MB upload limit. Add large files/folders to .gitignore or .percherignore, then try again. ${BIGGEST_ENTRIES_PROMPT_POINTER}`,
|
|
116746
116831
|
reasonCode: "unknown"
|
|
116747
116832
|
}),
|
|
116748
116833
|
summary: `Repository too large (${(tarball.bytes / 1024 / 1024).toFixed(1)} MB exceeds 500 MB limit).`,
|
|
@@ -117496,7 +117581,7 @@ async function push(ctx, input = {}, internals = {}) {
|
|
|
117496
117581
|
hint: `Biggest: ${formatBiggestEntries(tarball.topEntries)}`,
|
|
117497
117582
|
recovery: {
|
|
117498
117583
|
...recoveryAsk({
|
|
117499
|
-
prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, exceeding the 500 MB limit. Add large files/folders to .gitignore or .percherignore
|
|
117584
|
+
prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, exceeding the 500 MB limit. Add large files/folders to .gitignore or .percherignore, then re-run. The largest entries are listed in this error's hint field.`,
|
|
117500
117585
|
reasonCode: "unknown"
|
|
117501
117586
|
})
|
|
117502
117587
|
}
|
|
@@ -117601,7 +117686,7 @@ async function push(ctx, input = {}, internals = {}) {
|
|
|
117601
117686
|
hint: `Wait until ${resetAtForPrompt}. If the failure repeats afterwards, the issue is probably not transient — run \`percher doctor\` to diagnose.`,
|
|
117602
117687
|
recovery: {
|
|
117603
117688
|
...recoveryAsk({
|
|
117604
|
-
prompt: `Percher already retried once after an infrastructure failure for ${app.name}. Wait until ${resetAtForPrompt} or surface to the user.`,
|
|
117689
|
+
prompt: `Percher already retried once after an infrastructure failure for ${appRef(app.name) ?? "this app"}. Wait until ${resetAtForPrompt} or surface to the user.`,
|
|
117605
117690
|
options: ["wait before retrying", "inspect status"],
|
|
117606
117691
|
reasonCode: "retry_limit_reached",
|
|
117607
117692
|
retryable: false
|
|
@@ -117841,8 +117926,8 @@ async function redeploy(ctx, input = {}) {
|
|
|
117841
117926
|
recovery: recoveryRetry({
|
|
117842
117927
|
suggestedTool: "percher_redeploy",
|
|
117843
117928
|
args: {
|
|
117844
|
-
app: app.name,
|
|
117845
|
-
deployId: sourceDeployId,
|
|
117929
|
+
...appRef(app.name) !== null ? { app: app.name } : {},
|
|
117930
|
+
...idToken(sourceDeployId) !== null ? { deployId: sourceDeployId } : {},
|
|
117846
117931
|
retryAfterSec,
|
|
117847
117932
|
code: "DEPLOY_RATE_LIMITED"
|
|
117848
117933
|
},
|
|
@@ -117876,7 +117961,7 @@ async function redeploy(ctx, input = {}) {
|
|
|
117876
117961
|
suggestion: "Percher already retried once after an infrastructure failure for this app. Wait until the window resets or surface to the user."
|
|
117877
117962
|
},
|
|
117878
117963
|
recovery: recoveryAsk({
|
|
117879
|
-
prompt: `Percher already retried once after an infrastructure failure on ${app.name}. Wait until ${resetAt} or surface to the user.`,
|
|
117964
|
+
prompt: `Percher already retried once after an infrastructure failure on ${appRef(app.name) ?? "this app"}. Wait until ${resetAt} or surface to the user.`,
|
|
117880
117965
|
options: ["wait before retrying", "inspect status"],
|
|
117881
117966
|
reasonCode: "retry_limit_reached",
|
|
117882
117967
|
retryable: false
|
|
@@ -118055,14 +118140,14 @@ var renameInputSchema = exports_external.object({
|
|
|
118055
118140
|
name: exports_external.string().min(3).max(40)
|
|
118056
118141
|
});
|
|
118057
118142
|
async function rename2(ctx, input) {
|
|
118058
|
-
const
|
|
118059
|
-
if (!
|
|
118143
|
+
const appRef2 = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
118144
|
+
if (!appRef2) {
|
|
118060
118145
|
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
118061
118146
|
code: "open.no-app",
|
|
118062
118147
|
hint: "Pass an app name or run from a directory with percher.toml."
|
|
118063
118148
|
});
|
|
118064
118149
|
}
|
|
118065
|
-
const result = await ctx.client.apps.rename(
|
|
118150
|
+
const result = await ctx.client.apps.rename(appRef2, input.name);
|
|
118066
118151
|
return {
|
|
118067
118152
|
...result,
|
|
118068
118153
|
tomlUpdated: updateLocalTomlName(ctx.cwd, result.previousName, result.name)
|
|
@@ -118236,14 +118321,14 @@ var resetSuperuserInputSchema = exports_external.object({
|
|
|
118236
118321
|
reveal: exports_external.boolean().optional()
|
|
118237
118322
|
});
|
|
118238
118323
|
async function resetSuperuser(ctx, input = {}) {
|
|
118239
|
-
const
|
|
118240
|
-
if (!
|
|
118324
|
+
const appRef2 = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
118325
|
+
if (!appRef2) {
|
|
118241
118326
|
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
118242
118327
|
code: "data.no-app",
|
|
118243
118328
|
hint: "Pass an app name or run from a directory with percher.toml."
|
|
118244
118329
|
});
|
|
118245
118330
|
}
|
|
118246
|
-
return ctx.client.apps.resetSuperuser(
|
|
118331
|
+
return ctx.client.apps.resetSuperuser(appRef2, { reveal: input.reveal });
|
|
118247
118332
|
}
|
|
118248
118333
|
// ../core/dist/commands/restore.js
|
|
118249
118334
|
var listBackupsInputSchema = exports_external.object({
|
|
@@ -118526,20 +118611,20 @@ var shareRevokeInputSchema = exports_external.object({
|
|
|
118526
118611
|
shareId: exports_external.string().min(1, "shareId is required")
|
|
118527
118612
|
});
|
|
118528
118613
|
async function shareList(ctx, input = {}) {
|
|
118529
|
-
const
|
|
118530
|
-
return ctx.client.sharing.list(
|
|
118614
|
+
const appRef2 = resolveApp4(ctx, input.app);
|
|
118615
|
+
return ctx.client.sharing.list(appRef2);
|
|
118531
118616
|
}
|
|
118532
118617
|
async function shareInvite(ctx, input) {
|
|
118533
|
-
const
|
|
118534
|
-
return ctx.client.sharing.create(
|
|
118618
|
+
const appRef2 = resolveApp4(ctx, input.app);
|
|
118619
|
+
return ctx.client.sharing.create(appRef2, { email: input.email, role: input.role });
|
|
118535
118620
|
}
|
|
118536
118621
|
async function shareSetRole(ctx, input) {
|
|
118537
|
-
const
|
|
118538
|
-
return ctx.client.sharing.setRole(
|
|
118622
|
+
const appRef2 = resolveApp4(ctx, input.app);
|
|
118623
|
+
return ctx.client.sharing.setRole(appRef2, input.shareId, input.role);
|
|
118539
118624
|
}
|
|
118540
118625
|
async function shareRevoke(ctx, input) {
|
|
118541
|
-
const
|
|
118542
|
-
await ctx.client.sharing.revoke(
|
|
118626
|
+
const appRef2 = resolveApp4(ctx, input.app);
|
|
118627
|
+
await ctx.client.sharing.revoke(appRef2, input.shareId);
|
|
118543
118628
|
}
|
|
118544
118629
|
var invitesListInputSchema = exports_external.object({});
|
|
118545
118630
|
var invitesAcceptInputSchema = exports_external.object({
|
|
@@ -118654,16 +118739,16 @@ var transferListForAppInputSchema = exports_external.object({
|
|
|
118654
118739
|
app: exports_external.string().optional()
|
|
118655
118740
|
});
|
|
118656
118741
|
async function transferInitiate(ctx, input) {
|
|
118657
|
-
const
|
|
118658
|
-
return ctx.client.transfers.initiate(
|
|
118742
|
+
const appRef2 = resolveApp5(ctx, input.app);
|
|
118743
|
+
return ctx.client.transfers.initiate(appRef2, { email: input.email });
|
|
118659
118744
|
}
|
|
118660
118745
|
async function transferCancel(ctx, input) {
|
|
118661
|
-
const
|
|
118662
|
-
await ctx.client.transfers.cancel(
|
|
118746
|
+
const appRef2 = resolveApp5(ctx, input.app);
|
|
118747
|
+
await ctx.client.transfers.cancel(appRef2, input.transferId);
|
|
118663
118748
|
}
|
|
118664
118749
|
async function transferListForApp(ctx, input = {}) {
|
|
118665
|
-
const
|
|
118666
|
-
return ctx.client.transfers.listForApp(
|
|
118750
|
+
const appRef2 = resolveApp5(ctx, input.app);
|
|
118751
|
+
return ctx.client.transfers.listForApp(appRef2);
|
|
118667
118752
|
}
|
|
118668
118753
|
var transfersIncomingInputSchema = exports_external.object({});
|
|
118669
118754
|
var transfersAcceptInputSchema = exports_external.object({
|
|
@@ -118823,8 +118908,8 @@ var PLAN_CATALOG = {
|
|
|
118823
118908
|
diskPoolMb: 25000
|
|
118824
118909
|
}
|
|
118825
118910
|
},
|
|
118826
|
-
|
|
118827
|
-
label: "
|
|
118911
|
+
max: {
|
|
118912
|
+
label: "Max",
|
|
118828
118913
|
priceEur: 29,
|
|
118829
118914
|
limits: {
|
|
118830
118915
|
apps: 40,
|
|
@@ -119399,8 +119484,47 @@ function registerAdminCommands(cli, ctx) {
|
|
|
119399
119484
|
cli.command("admin reconcile-routes", "(admin only) Reconcile Caddy routes against DB state — fixes silent route loss").option("--app <name>", "Limit to a single app by name").option("--app-id <id>", "Limit to a single app by id").option("--dry-run", "Read-only diff — no Caddy mutations").option("--json", "Output JSON instead of formatted text").action(async () => {});
|
|
119400
119485
|
}
|
|
119401
119486
|
|
|
119402
|
-
// src/commands/
|
|
119487
|
+
// src/commands/analytics.ts
|
|
119403
119488
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
119489
|
+
function registerAnalyticsCommands(cli, ctx) {
|
|
119490
|
+
cli.command("analytics [app]", "Show visitor analytics for an app").option("--days <n>", "Window in days (1-90, default 7)").action(async (app, options) => {
|
|
119491
|
+
const days = options.days === undefined ? undefined : Number(options.days);
|
|
119492
|
+
const a = await analytics(ctx, { app, days });
|
|
119493
|
+
await writeLine2(ctx, import_picocolors4.default.bold(`Visitors — last ${a.period}`));
|
|
119494
|
+
await writeLine2(ctx, "");
|
|
119495
|
+
await writeLine2(ctx, ` Unique visitors ${a.totalVisitors.toLocaleString()} ${import_picocolors4.default.dim("(daily, summed)")}`);
|
|
119496
|
+
await writeLine2(ctx, ` Page views ${a.totalPageviews.toLocaleString()}`);
|
|
119497
|
+
await writeLine2(ctx, ` Requests ${a.totalRequests.toLocaleString()}`);
|
|
119498
|
+
await writeLine2(ctx, ` Sessions ${a.totalSessions.toLocaleString()}`);
|
|
119499
|
+
await writeLine2(ctx, ` Bounce rate ${a.totalSessions > 0 ? `${a.bounceRate}%` : "—"}`);
|
|
119500
|
+
await writeLine2(ctx, ` Error rate ${a.errorRate}%`);
|
|
119501
|
+
await writeLine2(ctx, import_picocolors4.default.dim(` Filtered ${(a.totalBotRequests + a.totalScannerRequests).toLocaleString()} bot/scanner requests`));
|
|
119502
|
+
await printList(ctx, "Top pages", a.topPages.map((p) => [p.path, p.views]));
|
|
119503
|
+
await printList(ctx, "Traffic sources", a.topReferrers.map((r) => [r.source, r.visits]));
|
|
119504
|
+
await printList(ctx, "Entry pages", a.topEntryPaths.map((p) => [p.path, p.sessions]));
|
|
119505
|
+
await printList(ctx, "Countries", a.byCountry.map((c) => [c.country, c.visitors]));
|
|
119506
|
+
await printList(ctx, "Devices", a.byDevice.map((d) => [d.key, d.visitors]));
|
|
119507
|
+
if (a.totalRequests === 0 && a.totalBotRequests === 0) {
|
|
119508
|
+
await writeLine2(ctx, "");
|
|
119509
|
+
await writeLine2(ctx, import_picocolors4.default.dim("No traffic recorded yet. Analytics update every 15 minutes."));
|
|
119510
|
+
}
|
|
119511
|
+
});
|
|
119512
|
+
}
|
|
119513
|
+
function safeLabel(label) {
|
|
119514
|
+
return label.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, "").replace(/[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "").slice(0, 120);
|
|
119515
|
+
}
|
|
119516
|
+
async function printList(ctx, title, rows) {
|
|
119517
|
+
if (rows.length === 0)
|
|
119518
|
+
return;
|
|
119519
|
+
await writeLine2(ctx, "");
|
|
119520
|
+
await writeLine2(ctx, import_picocolors4.default.bold(title));
|
|
119521
|
+
for (const [label, value] of rows.slice(0, 10)) {
|
|
119522
|
+
await writeLine2(ctx, ` ${String(value).padStart(7)} ${safeLabel(label)}`);
|
|
119523
|
+
}
|
|
119524
|
+
}
|
|
119525
|
+
|
|
119526
|
+
// src/commands/apps.ts
|
|
119527
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
119404
119528
|
function assertNever2(status2, summary) {
|
|
119405
119529
|
throw new Error(`Unhandled publish status: ${JSON.stringify(status2)}${summary ? ` (${summary})` : ""}`);
|
|
119406
119530
|
}
|
|
@@ -119412,91 +119536,91 @@ async function handlePublishResult(ctx, result, options = {}) {
|
|
|
119412
119536
|
}
|
|
119413
119537
|
case "check_passed": {
|
|
119414
119538
|
const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
|
|
119415
|
-
await writeLine2(ctx, `${
|
|
119416
|
-
await writeLine2(ctx,
|
|
119539
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Check passed${timing} — would build and boot`);
|
|
119540
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" The live app was not changed. Run `percher publish` to ship."));
|
|
119417
119541
|
return;
|
|
119418
119542
|
}
|
|
119419
119543
|
case "needs_login": {
|
|
119420
119544
|
if (result.loginUrl) {
|
|
119421
|
-
await writeLine2(ctx, `${
|
|
119545
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Not logged in. Authorize here:`);
|
|
119422
119546
|
await writeLine2(ctx, ` ${result.loginUrl}`);
|
|
119423
119547
|
} else {
|
|
119424
|
-
await writeLine2(ctx, `${
|
|
119548
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
|
|
119425
119549
|
}
|
|
119426
119550
|
throw new SilentExitError;
|
|
119427
119551
|
}
|
|
119428
119552
|
case "auth_pending": {
|
|
119429
119553
|
if (result.loginUrl) {
|
|
119430
|
-
await writeLine2(ctx, `${
|
|
119554
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Not logged in. Authorize here:`);
|
|
119431
119555
|
await writeLine2(ctx, ` ${result.loginUrl}`);
|
|
119432
119556
|
} else {
|
|
119433
|
-
await writeLine2(ctx, `${
|
|
119557
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
|
|
119434
119558
|
}
|
|
119435
119559
|
throw new SilentExitError;
|
|
119436
119560
|
}
|
|
119437
119561
|
case "queued": {
|
|
119438
|
-
await writeLine2(ctx, `${
|
|
119562
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
|
|
119439
119563
|
const deployId = result.deployment?.id;
|
|
119440
119564
|
const appName = result.app?.name;
|
|
119441
119565
|
if (deployId && appName) {
|
|
119442
|
-
await writeLine2(ctx,
|
|
119566
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${deployId} --app ${appName}`));
|
|
119443
119567
|
} else if (deployId) {
|
|
119444
|
-
await writeLine2(ctx,
|
|
119568
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${deployId}`));
|
|
119445
119569
|
}
|
|
119446
119570
|
return;
|
|
119447
119571
|
}
|
|
119448
119572
|
case "already_in_progress": {
|
|
119449
|
-
await writeLine2(ctx, `${
|
|
119573
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
|
|
119450
119574
|
if (result.recovery.nextAction === "wait_deploy") {
|
|
119451
119575
|
const args = result.recovery.args;
|
|
119452
119576
|
if (args.app && args.deployId) {
|
|
119453
|
-
await writeLine2(ctx,
|
|
119577
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${args.deployId} --app ${args.app}`));
|
|
119454
119578
|
}
|
|
119455
119579
|
}
|
|
119456
119580
|
return;
|
|
119457
119581
|
}
|
|
119458
119582
|
case "replaced": {
|
|
119459
|
-
await writeLine2(ctx, `${
|
|
119583
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
|
|
119460
119584
|
if (result.url) {
|
|
119461
|
-
await writeLine2(ctx,
|
|
119585
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Live URL: ${result.url}`));
|
|
119462
119586
|
}
|
|
119463
119587
|
if (result.recovery.nextAction === "wait_deploy") {
|
|
119464
119588
|
const args = result.recovery.args;
|
|
119465
119589
|
if (args.app && args.deployId) {
|
|
119466
|
-
await writeLine2(ctx,
|
|
119590
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher deploys inspect ${args.deployId} --app ${args.app}`));
|
|
119467
119591
|
}
|
|
119468
119592
|
} else if (result.recovery.nextAction === "ask_user" || result.recovery.nextAction === "run_doctor") {
|
|
119469
119593
|
const appName = result.app?.name;
|
|
119470
119594
|
if (appName) {
|
|
119471
|
-
await writeLine2(ctx,
|
|
119595
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher doctor --app ${appName}`));
|
|
119472
119596
|
} else {
|
|
119473
|
-
await writeLine2(ctx,
|
|
119597
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Next: percher doctor"));
|
|
119474
119598
|
}
|
|
119475
119599
|
}
|
|
119476
119600
|
return;
|
|
119477
119601
|
}
|
|
119478
119602
|
case "failed": {
|
|
119479
|
-
await writeLine2(ctx, `${
|
|
119603
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} ${result.error?.title ?? "Deployment failed"}`);
|
|
119480
119604
|
if (result.error?.explanation) {
|
|
119481
119605
|
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
119482
119606
|
}
|
|
119483
119607
|
if (result.error?.rawErrorMessage) {
|
|
119484
|
-
await writeLine2(ctx, ` ${
|
|
119608
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(`Details: ${result.error.rawErrorMessage}`)}`);
|
|
119485
119609
|
}
|
|
119486
119610
|
if (result.error?.suggestion) {
|
|
119487
|
-
await writeLine2(ctx, ` ${
|
|
119611
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
|
|
119488
119612
|
}
|
|
119489
119613
|
if (result.error?.missingEnvVars && result.error.missingEnvVars.length > 0) {
|
|
119490
119614
|
await writeLine2(ctx, "");
|
|
119491
|
-
await writeLine2(ctx,
|
|
119615
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Missing env vars:"));
|
|
119492
119616
|
for (const k of result.error.missingEnvVars) {
|
|
119493
119617
|
await writeLine2(ctx, ` ${k}`);
|
|
119494
119618
|
}
|
|
119495
|
-
await writeLine2(ctx,
|
|
119619
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Set with: percher env set ${result.error.missingEnvVars[0]}=...`));
|
|
119496
119620
|
}
|
|
119497
119621
|
if (result.error?.relevantFiles && result.error.relevantFiles.length > 0) {
|
|
119498
119622
|
await writeLine2(ctx, "");
|
|
119499
|
-
await writeLine2(ctx,
|
|
119623
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Check these files:"));
|
|
119500
119624
|
for (const f of result.error.relevantFiles) {
|
|
119501
119625
|
await writeLine2(ctx, ` ${f}`);
|
|
119502
119626
|
}
|
|
@@ -119505,64 +119629,64 @@ async function handlePublishResult(ctx, result, options = {}) {
|
|
|
119505
119629
|
await writeLine2(ctx, "");
|
|
119506
119630
|
const lines = result.error.buildLogTail.split(`
|
|
119507
119631
|
`);
|
|
119508
|
-
await writeLine2(ctx, `${
|
|
119632
|
+
await writeLine2(ctx, `${import_picocolors5.default.bold("Build log")} ${import_picocolors5.default.dim(`(last ${lines.length} lines)`)}`);
|
|
119509
119633
|
for (const line of lines) {
|
|
119510
119634
|
await writeLine2(ctx, ` ${line}`);
|
|
119511
119635
|
}
|
|
119512
119636
|
if (result.app?.id && result.deployment?.id) {
|
|
119513
119637
|
await writeLine2(ctx, "");
|
|
119514
|
-
await writeLine2(ctx,
|
|
119638
|
+
await writeLine2(ctx, import_picocolors5.default.dim(`Full log: percher deploys inspect ${result.deployment.id} --app ${result.app.name}`));
|
|
119515
119639
|
}
|
|
119516
119640
|
}
|
|
119517
119641
|
throw new SilentExitError;
|
|
119518
119642
|
}
|
|
119519
119643
|
case "live": {
|
|
119520
119644
|
const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
|
|
119521
|
-
await writeLine2(ctx, `${
|
|
119645
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Published to ${result.url}${timing}`);
|
|
119522
119646
|
if (result.traceId) {
|
|
119523
|
-
await writeLine2(ctx,
|
|
119647
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Trace: ${result.traceId}`));
|
|
119524
119648
|
}
|
|
119525
119649
|
if (result.cacheReused) {
|
|
119526
|
-
await writeLine2(ctx,
|
|
119650
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: reused — identical bundle, redeployed without re-upload (--no-cache to force a fresh build)"));
|
|
119527
119651
|
} else {
|
|
119528
119652
|
const status2 = result.cacheStatus ?? (result.cacheHit === true ? "image_reused" : result.cacheHit === false ? "fresh" : null);
|
|
119529
119653
|
if (status2 === "image_reused") {
|
|
119530
|
-
await writeLine2(ctx,
|
|
119654
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
|
|
119531
119655
|
} else if (status2 === "layer_hit") {
|
|
119532
|
-
await writeLine2(ctx,
|
|
119656
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
|
|
119533
119657
|
} else if (status2 === "fresh") {
|
|
119534
|
-
await writeLine2(ctx,
|
|
119658
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: miss (fresh build)"));
|
|
119535
119659
|
}
|
|
119536
119660
|
}
|
|
119537
119661
|
if (typeof result.deployment?.imageSizeBytes === "number") {
|
|
119538
|
-
await writeLine2(ctx,
|
|
119662
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
|
|
119539
119663
|
}
|
|
119540
119664
|
if (result.replacedPreview) {
|
|
119541
|
-
await writeLine2(ctx,
|
|
119665
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" (replaced previous preview — free plan keeps 1 preview slot per app)"));
|
|
119542
119666
|
}
|
|
119543
119667
|
if (options.preview && result.url) {
|
|
119544
119668
|
try {
|
|
119545
119669
|
const qr = await qrToTerminal(result.url);
|
|
119546
119670
|
await writeLine2(ctx, "");
|
|
119547
|
-
await writeLine2(ctx,
|
|
119671
|
+
await writeLine2(ctx, import_picocolors5.default.dim("Scan to preview on mobile:"));
|
|
119548
119672
|
await writeLine2(ctx, qr);
|
|
119549
119673
|
} catch {}
|
|
119550
119674
|
}
|
|
119551
119675
|
if (result.anonymous && result.claim) {
|
|
119552
119676
|
await writeLine2(ctx, "");
|
|
119553
|
-
await writeLine2(ctx,
|
|
119677
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Anonymous app — claim it to keep it:"));
|
|
119554
119678
|
await writeLine2(ctx, ` Expires: ${new Date(result.claim.expiresAt).toLocaleString()}`);
|
|
119555
119679
|
await writeLine2(ctx, ` Claim token: ${result.claim.claimToken}`);
|
|
119556
|
-
await writeLine2(ctx, ` ${
|
|
119680
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
|
|
119557
119681
|
for (const w of result.warnings ?? []) {
|
|
119558
|
-
await writeLine2(ctx,
|
|
119682
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` ${w}`));
|
|
119559
119683
|
}
|
|
119560
119684
|
} else if (result.firstDeploy) {
|
|
119561
119685
|
await writeLine2(ctx, "");
|
|
119562
|
-
await writeLine2(ctx,
|
|
119563
|
-
await writeLine2(ctx, ` ${
|
|
119564
|
-
await writeLine2(ctx, ` ${
|
|
119565
|
-
await writeLine2(ctx, ` ${
|
|
119686
|
+
await writeLine2(ctx, import_picocolors5.default.bold("First deploy — next steps:"));
|
|
119687
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher dev")} watch for changes and re-deploy`);
|
|
119688
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher env set KEY")} add environment variables`);
|
|
119689
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher logs")} stream live logs`);
|
|
119566
119690
|
}
|
|
119567
119691
|
return;
|
|
119568
119692
|
}
|
|
@@ -119577,9 +119701,9 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119577
119701
|
name: options.name,
|
|
119578
119702
|
interactive: true
|
|
119579
119703
|
});
|
|
119580
|
-
await writeLine2(ctx, `${
|
|
119704
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${result.path}`);
|
|
119581
119705
|
for (const file2 of result.scaffoldedFiles) {
|
|
119582
|
-
await writeLine2(ctx, `${
|
|
119706
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${file2}`);
|
|
119583
119707
|
}
|
|
119584
119708
|
});
|
|
119585
119709
|
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) => {
|
|
@@ -119594,21 +119718,21 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119594
119718
|
force: options.force
|
|
119595
119719
|
});
|
|
119596
119720
|
for (const file2 of result.written) {
|
|
119597
|
-
await writeLine2(ctx, `${
|
|
119721
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${file2}`);
|
|
119598
119722
|
}
|
|
119599
119723
|
for (const file2 of result.skipped) {
|
|
119600
|
-
await writeLine2(ctx, `${
|
|
119724
|
+
await writeLine2(ctx, `${import_picocolors5.default.dim("•")} skipped ${file2} (exists — pass --force to overwrite)`);
|
|
119601
119725
|
}
|
|
119602
119726
|
await writeLine2(ctx, "");
|
|
119603
|
-
await writeLine2(ctx, `${
|
|
119727
|
+
await writeLine2(ctx, `${import_picocolors5.default.bold("Make it an installable PWA:")} link the manifest from your HTML`);
|
|
119604
119728
|
await writeLine2(ctx, ` <link rel="manifest" href="/manifest.json">`);
|
|
119605
119729
|
await writeLine2(ctx, "and register the service worker from your app entry:");
|
|
119606
119730
|
await writeLine2(ctx, result.swRegistration);
|
|
119607
119731
|
await writeLine2(ctx, "");
|
|
119608
|
-
await writeLine2(ctx, `${
|
|
119732
|
+
await writeLine2(ctx, `${import_picocolors5.default.bold("Wrap for Google Play (TWA):")}`);
|
|
119609
119733
|
await writeLine2(ctx, result.bubblewrapCommand);
|
|
119610
119734
|
await writeLine2(ctx, "");
|
|
119611
|
-
await writeLine2(ctx, `The two ${
|
|
119735
|
+
await writeLine2(ctx, `The two ${import_picocolors5.default.dim(".well-known")} files are trust assertions for your origin — add real values`);
|
|
119612
119736
|
await writeLine2(ctx, `after you sign the apps, then serve them yourself or let Percher serve them from the`);
|
|
119613
119737
|
await writeLine2(ctx, "App store kit at https://percher.app. Full guide: /docs/app-stores");
|
|
119614
119738
|
});
|
|
@@ -119619,7 +119743,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119619
119743
|
const ci = detectCiBranchMessage2(process.env);
|
|
119620
119744
|
if (ci) {
|
|
119621
119745
|
messageOverride = ci.message;
|
|
119622
|
-
await writeLine2(ctx,
|
|
119746
|
+
await writeLine2(ctx, import_picocolors5.default.dim(`Auto-detected ${ci.source} branch — using deploy note "${ci.message}"`));
|
|
119623
119747
|
}
|
|
119624
119748
|
}
|
|
119625
119749
|
const result = await publish(ctx, {
|
|
@@ -119640,41 +119764,41 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119640
119764
|
noCache: options.cache === false
|
|
119641
119765
|
});
|
|
119642
119766
|
if (result.status === "needs_login") {
|
|
119643
|
-
await writeLine2(ctx, `${
|
|
119767
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
|
|
119644
119768
|
throw new SilentExitError;
|
|
119645
119769
|
}
|
|
119646
119770
|
if (result.status === "failed") {
|
|
119647
|
-
await writeLine2(ctx, `${
|
|
119771
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} ${result.error?.title ?? "Redeploy failed"}`);
|
|
119648
119772
|
if (result.error?.explanation) {
|
|
119649
119773
|
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
119650
119774
|
}
|
|
119651
119775
|
if (result.error?.suggestion) {
|
|
119652
|
-
await writeLine2(ctx, ` ${
|
|
119776
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
|
|
119653
119777
|
}
|
|
119654
119778
|
if (result.error?.problems && result.error.problems.length > 0) {
|
|
119655
119779
|
await writeLine2(ctx, "");
|
|
119656
|
-
await writeLine2(ctx,
|
|
119780
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Build problems:"));
|
|
119657
119781
|
for (const p of result.error.problems) {
|
|
119658
|
-
const loc = p.file ? ` ${
|
|
119659
|
-
await writeLine2(ctx, ` ${
|
|
119782
|
+
const loc = p.file ? ` ${import_picocolors5.default.dim(`${p.file}${p.line ? `:${p.line}` : ""}${p.column ? `:${p.column}` : ""}`)}` : "";
|
|
119783
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.red(p.code)}${loc} ${p.message}`);
|
|
119660
119784
|
if (p.hint)
|
|
119661
|
-
await writeLine2(ctx, ` ${
|
|
119785
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(`→ ${p.hint}`)}`);
|
|
119662
119786
|
}
|
|
119663
119787
|
}
|
|
119664
119788
|
throw new SilentExitError;
|
|
119665
119789
|
}
|
|
119666
119790
|
const timing = result.timing ? ` (${result.timing.totalSeconds.toFixed(0)}s)` : "";
|
|
119667
|
-
await writeLine2(ctx, `${
|
|
119791
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Redeployed to ${result.url}${timing}`);
|
|
119668
119792
|
});
|
|
119669
119793
|
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) => {
|
|
119670
119794
|
const appName = options.app ?? readPercherTomlAppName(ctx.cwd);
|
|
119671
119795
|
if (!appName) {
|
|
119672
|
-
await writeLine2(ctx, `${
|
|
119796
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} No app specified — pass --app <name> or run from a directory with percher.toml.`);
|
|
119673
119797
|
throw new SilentExitError(1);
|
|
119674
119798
|
}
|
|
119675
119799
|
const timeoutSec = Number.parseInt(options.timeout ?? "600", 10);
|
|
119676
119800
|
if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
|
|
119677
|
-
await writeLine2(ctx, `${
|
|
119801
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} --timeout must be a positive number of seconds.`);
|
|
119678
119802
|
throw new SilentExitError(1);
|
|
119679
119803
|
}
|
|
119680
119804
|
const deadline = Date.now() + timeoutSec * 1000;
|
|
@@ -119682,7 +119806,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119682
119806
|
for (;; ) {
|
|
119683
119807
|
const remainingMs = deadline - Date.now();
|
|
119684
119808
|
if (remainingMs <= 0) {
|
|
119685
|
-
await writeLine2(ctx, `${
|
|
119809
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
|
|
119686
119810
|
throw new SilentExitError(1);
|
|
119687
119811
|
}
|
|
119688
119812
|
let raceTimer;
|
|
@@ -119704,22 +119828,22 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119704
119828
|
}
|
|
119705
119829
|
if (result === null) {
|
|
119706
119830
|
callController.abort();
|
|
119707
|
-
await writeLine2(ctx, `${
|
|
119831
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
|
|
119708
119832
|
throw new SilentExitError(1);
|
|
119709
119833
|
}
|
|
119710
119834
|
if (result.deployment.status !== lastStatus) {
|
|
119711
119835
|
lastStatus = result.deployment.status;
|
|
119712
|
-
await writeLine2(ctx,
|
|
119836
|
+
await writeLine2(ctx, import_picocolors5.default.dim(`${lastStatus}...`));
|
|
119713
119837
|
}
|
|
119714
119838
|
if (result.status === "live") {
|
|
119715
|
-
await writeLine2(ctx, `${
|
|
119839
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
|
|
119716
119840
|
return;
|
|
119717
119841
|
}
|
|
119718
119842
|
if (result.status === "failed" || result.status === "replaced") {
|
|
119719
|
-
const mark = result.status === "failed" ?
|
|
119843
|
+
const mark = result.status === "failed" ? import_picocolors5.default.red("✗") : import_picocolors5.default.yellow("!");
|
|
119720
119844
|
await writeLine2(ctx, `${mark} ${result.summary}`);
|
|
119721
119845
|
if (result.error?.suggestion) {
|
|
119722
|
-
await writeLine2(ctx, ` ${
|
|
119846
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
|
|
119723
119847
|
}
|
|
119724
119848
|
if (result.status === "failed")
|
|
119725
119849
|
throw new SilentExitError(1);
|
|
@@ -119739,30 +119863,30 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119739
119863
|
} else {
|
|
119740
119864
|
const url2 = result.deployment?.previewUrl ?? result.deployment?.url ?? result.app.url;
|
|
119741
119865
|
const label = result.deployment?.previewUrl ? "preview live at" : "deployed to";
|
|
119742
|
-
await writeLine2(ctx, `${
|
|
119866
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${label} ${url2}`);
|
|
119743
119867
|
const status2 = result.deployment?.cacheStatus ?? (result.deployment?.cacheHit === true ? "image_reused" : result.deployment?.cacheHit === false ? "fresh" : null);
|
|
119744
119868
|
if (status2 === "image_reused") {
|
|
119745
|
-
await writeLine2(ctx,
|
|
119869
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
|
|
119746
119870
|
} else if (status2 === "layer_hit") {
|
|
119747
|
-
await writeLine2(ctx,
|
|
119871
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
|
|
119748
119872
|
} else if (status2 === "fresh") {
|
|
119749
|
-
await writeLine2(ctx,
|
|
119873
|
+
await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: miss (fresh build)"));
|
|
119750
119874
|
}
|
|
119751
119875
|
if (typeof result.deployment?.imageSizeBytes === "number") {
|
|
119752
|
-
await writeLine2(ctx,
|
|
119876
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
|
|
119753
119877
|
}
|
|
119754
119878
|
}
|
|
119755
119879
|
});
|
|
119756
119880
|
cli.command("rename <name> [app]", "Rename an app (changes its subdomain)").action(async (name, app) => {
|
|
119757
119881
|
const result = await rename2(ctx, { app, name });
|
|
119758
|
-
await writeLine2(ctx, `${
|
|
119882
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
|
|
119759
119883
|
await writeLine2(ctx, ` Previous name: ${result.previousName} (reserved for 60 days)`);
|
|
119760
119884
|
if (result.tomlUpdated) {
|
|
119761
119885
|
await writeLine2(ctx, ` percher.toml updated: name = "${result.name}"`);
|
|
119762
119886
|
} else {
|
|
119763
|
-
await writeLine2(ctx, ` ${
|
|
119887
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.yellow("!")} Update percher.toml before the next publish: name = "${result.name}"`);
|
|
119764
119888
|
}
|
|
119765
|
-
await writeLine2(ctx, ` ${
|
|
119889
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(`Next rename available in ${result.cooldownDays} days`)}`);
|
|
119766
119890
|
});
|
|
119767
119891
|
cli.command("open [app]", "Open the app URL in your browser").action(async (app) => {
|
|
119768
119892
|
const url2 = await open(ctx, { app });
|
|
@@ -119775,7 +119899,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119775
119899
|
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) => {
|
|
119776
119900
|
if (options.listTemplates) {
|
|
119777
119901
|
const templates = listTemplates();
|
|
119778
|
-
await writeLine2(ctx,
|
|
119902
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Available templates:"));
|
|
119779
119903
|
for (const t of templates) {
|
|
119780
119904
|
await writeLine2(ctx, ` ${t}`);
|
|
119781
119905
|
}
|
|
@@ -119786,7 +119910,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119786
119910
|
}
|
|
119787
119911
|
if (!options.template) {
|
|
119788
119912
|
const templates = listTemplates();
|
|
119789
|
-
await writeLine2(ctx, `${
|
|
119913
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} --template is required. Available: ${templates.join(", ")}`);
|
|
119790
119914
|
return;
|
|
119791
119915
|
}
|
|
119792
119916
|
const result = await create(ctx, {
|
|
@@ -119794,9 +119918,9 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119794
119918
|
template: options.template,
|
|
119795
119919
|
force: options.force
|
|
119796
119920
|
});
|
|
119797
|
-
await writeLine2(ctx, `${
|
|
119921
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} created ${result.name} from ${result.template}`);
|
|
119798
119922
|
for (const f of result.files) {
|
|
119799
|
-
await writeLine2(ctx, ` ${
|
|
119923
|
+
await writeLine2(ctx, ` ${import_picocolors5.default.dim(f)}`);
|
|
119800
119924
|
}
|
|
119801
119925
|
await writeLine2(ctx, "");
|
|
119802
119926
|
await writeLine2(ctx, "Next steps:");
|
|
@@ -119806,29 +119930,29 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119806
119930
|
cli.command("delete [app]", "Delete an app permanently").option("--yes", "Skip confirmation prompt").action(async (app, options) => {
|
|
119807
119931
|
const name = app ?? readPercherTomlAppName(ctx.cwd);
|
|
119808
119932
|
if (!name) {
|
|
119809
|
-
await writeLine2(ctx, `${
|
|
119933
|
+
await writeLine2(ctx, `${import_picocolors5.default.red("✗")} No app specified and no percher.toml found.`);
|
|
119810
119934
|
throw new SilentExitError;
|
|
119811
119935
|
}
|
|
119812
119936
|
if (!options.yes) {
|
|
119813
|
-
const answer = await ctx.prompt(`${
|
|
119937
|
+
const answer = await ctx.prompt(`${import_picocolors5.default.red("!")} This will permanently delete ${import_picocolors5.default.bold(name)} and all its data. Type the app name to confirm:`);
|
|
119814
119938
|
if (answer.trim() !== name) {
|
|
119815
119939
|
await writeLine2(ctx, "Cancelled.");
|
|
119816
119940
|
return;
|
|
119817
119941
|
}
|
|
119818
119942
|
}
|
|
119819
119943
|
await deleteApp(ctx, { app: name, confirm: true });
|
|
119820
|
-
await writeLine2(ctx, `${
|
|
119944
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Deleted ${name}`);
|
|
119821
119945
|
});
|
|
119822
119946
|
cli.command("resume [app]", "Resume an app suspended for inactivity").action(async (app) => {
|
|
119823
119947
|
const result = await resume(ctx, { app });
|
|
119824
|
-
await writeLine2(ctx, `${
|
|
119948
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
|
|
119825
119949
|
});
|
|
119826
119950
|
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) => {
|
|
119827
119951
|
const result = await claim(ctx, { app: options.app, claimToken: options.claimToken });
|
|
119828
119952
|
if (result.status === "claimed") {
|
|
119829
|
-
await writeLine2(ctx, `${
|
|
119953
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
|
|
119830
119954
|
} else {
|
|
119831
|
-
await writeLine2(ctx, `${
|
|
119955
|
+
await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
|
|
119832
119956
|
}
|
|
119833
119957
|
});
|
|
119834
119958
|
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) => {
|
|
@@ -119836,49 +119960,49 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119836
119960
|
app: app ?? options.app,
|
|
119837
119961
|
revoke: options.revoke
|
|
119838
119962
|
});
|
|
119839
|
-
await writeLine2(ctx, `${
|
|
119963
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
|
|
119840
119964
|
});
|
|
119841
119965
|
cli.command("cache <action> [app]", "Manage the app's build cache (reset)").action(async (action, app) => {
|
|
119842
119966
|
if (action !== "reset") {
|
|
119843
119967
|
throw new Error(`Unknown cache action "${action}". Use: percher cache reset [app]`);
|
|
119844
119968
|
}
|
|
119845
119969
|
const result = await cacheReset(ctx, { app });
|
|
119846
|
-
await writeLine2(ctx, `${
|
|
119970
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
|
|
119847
119971
|
});
|
|
119848
119972
|
cli.command("cache reset [app]", "Reset the app's build cache (next deploy runs cold)").action(async (app) => {
|
|
119849
119973
|
const result = await cacheReset(ctx, { app });
|
|
119850
|
-
await writeLine2(ctx, `${
|
|
119974
|
+
await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
|
|
119851
119975
|
});
|
|
119852
119976
|
cli.command("doctor [app]", "Check app health and connectivity").option("--json", "Output as JSON").action(async (app, options) => {
|
|
119853
119977
|
const result = await doctor(ctx, { app, json: options.json });
|
|
119854
119978
|
if (options.json) {
|
|
119855
119979
|
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
119856
119980
|
} else {
|
|
119857
|
-
await writeLine2(ctx, `${
|
|
119981
|
+
await writeLine2(ctx, `${import_picocolors5.default.bold("Percher Doctor")}`);
|
|
119858
119982
|
await writeLine2(ctx, "──────────────");
|
|
119859
119983
|
await writeLine2(ctx, "");
|
|
119860
119984
|
for (const check2 of result.checks) {
|
|
119861
|
-
const icon = check2.status === "pass" ?
|
|
119862
|
-
await writeLine2(ctx, `${icon} ${
|
|
119985
|
+
const icon = check2.status === "pass" ? import_picocolors5.default.green("✓") : check2.status === "fail" ? import_picocolors5.default.red("✗") : check2.status === "warn" ? import_picocolors5.default.yellow("!") : import_picocolors5.default.dim("—");
|
|
119986
|
+
await writeLine2(ctx, `${icon} ${import_picocolors5.default.bold(check2.name.padEnd(20))} ${check2.message}`);
|
|
119863
119987
|
}
|
|
119864
119988
|
await writeLine2(ctx, "");
|
|
119865
119989
|
const counter = `${result.passed}/${result.total} checks passed`;
|
|
119866
|
-
const counterColor = result.status === "ok" ?
|
|
119990
|
+
const counterColor = result.status === "ok" ? import_picocolors5.default.green : result.status === "in_progress" ? import_picocolors5.default.yellow : import_picocolors5.default.red;
|
|
119867
119991
|
await writeLine2(ctx, counterColor(counter));
|
|
119868
119992
|
await writeLine2(ctx, result.summary);
|
|
119869
119993
|
if (result.suspectDeploy) {
|
|
119870
119994
|
const s = result.suspectDeploy;
|
|
119871
119995
|
const head = (s.commitSha ?? s.deployId).slice(0, 10);
|
|
119872
119996
|
await writeLine2(ctx, "");
|
|
119873
|
-
await writeLine2(ctx,
|
|
119997
|
+
await writeLine2(ctx, import_picocolors5.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
|
|
119874
119998
|
await writeLine2(ctx, ` Suspect: ${head}`);
|
|
119875
119999
|
if (s.lastHealthyCommitSha) {
|
|
119876
120000
|
const base = s.lastHealthyCommitSha.slice(0, 10);
|
|
119877
120001
|
await writeLine2(ctx, ` Last working: ${base}`);
|
|
119878
|
-
await writeLine2(ctx,
|
|
120002
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
|
|
119879
120003
|
}
|
|
119880
120004
|
for (const e of s.evidence)
|
|
119881
|
-
await writeLine2(ctx,
|
|
120005
|
+
await writeLine2(ctx, import_picocolors5.default.dim(` · ${e}`));
|
|
119882
120006
|
}
|
|
119883
120007
|
}
|
|
119884
120008
|
if (result.status === "blocked" || result.status === "needs_action") {
|
|
@@ -119890,7 +120014,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119890
120014
|
const onSignal = () => controller.abort();
|
|
119891
120015
|
process.on("SIGINT", onSignal);
|
|
119892
120016
|
process.on("SIGTERM", onSignal);
|
|
119893
|
-
await writeLine2(ctx,
|
|
120017
|
+
await writeLine2(ctx, import_picocolors5.default.bold("Percher Dev"));
|
|
119894
120018
|
await writeLine2(ctx, "───────────");
|
|
119895
120019
|
await writeLine2(ctx, "");
|
|
119896
120020
|
try {
|
|
@@ -119898,7 +120022,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119898
120022
|
signal: controller.signal,
|
|
119899
120023
|
async writeLine(line) {
|
|
119900
120024
|
const timestamp = new Date().toLocaleTimeString("sv-SE", { hour12: false });
|
|
119901
|
-
await writeLine2(ctx, `${
|
|
120025
|
+
await writeLine2(ctx, `${import_picocolors5.default.dim(`[${timestamp}]`)} ${line}`);
|
|
119902
120026
|
},
|
|
119903
120027
|
async onFilesChanged(_files) {
|
|
119904
120028
|
if (options.confirm) {
|
|
@@ -119913,12 +120037,12 @@ function registerAppsCommands(cli, ctx) {
|
|
|
119913
120037
|
process.removeListener("SIGTERM", onSignal);
|
|
119914
120038
|
}
|
|
119915
120039
|
await writeLine2(ctx, "");
|
|
119916
|
-
await writeLine2(ctx,
|
|
120040
|
+
await writeLine2(ctx, import_picocolors5.default.dim("Dev session ended."));
|
|
119917
120041
|
});
|
|
119918
120042
|
}
|
|
119919
120043
|
|
|
119920
120044
|
// src/commands/auth.ts
|
|
119921
|
-
var
|
|
120045
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
119922
120046
|
function registerAuthCommands(cli, ctx) {
|
|
119923
120047
|
cli.command("login", "Authenticate with Percher").option("--token <token>", "Dev: skip browser, use a token").option("--api-url <url>", "Override API URL").option("--force", "Pair again even if already signed in (switch accounts)").action(async (options) => {
|
|
119924
120048
|
await login(ctx, {
|
|
@@ -119929,7 +120053,7 @@ function registerAuthCommands(cli, ctx) {
|
|
|
119929
120053
|
});
|
|
119930
120054
|
cli.command("whoami", "Show the signed-in user").action(async () => {
|
|
119931
120055
|
if (!ctx.client.token) {
|
|
119932
|
-
await writeLine2(ctx, `${
|
|
120056
|
+
await writeLine2(ctx, `${import_picocolors6.default.red("✗")} Not logged in. Run: percher login`);
|
|
119933
120057
|
throw new SilentExitError;
|
|
119934
120058
|
}
|
|
119935
120059
|
const me = await whoami(ctx);
|
|
@@ -119938,7 +120062,7 @@ function registerAuthCommands(cli, ctx) {
|
|
|
119938
120062
|
}
|
|
119939
120063
|
|
|
119940
120064
|
// src/commands/data.ts
|
|
119941
|
-
var
|
|
120065
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
119942
120066
|
function registerDataCommands(cli, ctx, argv) {
|
|
119943
120067
|
cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "(oauth only) target app (default: read percher.toml)").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret").option("--remove", "(oauth only) remove the provider instead of setting it").action(async (app, _rest, options) => {
|
|
119944
120068
|
if (app === "reset-superuser") {
|
|
@@ -119956,7 +120080,7 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119956
120080
|
const out = outIdx >= 0 ? argv[outIdx + 1] : undefined;
|
|
119957
120081
|
const result2 = await dataExport(ctx, { app: realApp, out });
|
|
119958
120082
|
const mb = (result2.bytes / 1024 / 1024).toFixed(1);
|
|
119959
|
-
await writeLine2(ctx, `${
|
|
120083
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Exported ${result2.app}`);
|
|
119960
120084
|
await writeLine2(ctx, ` Path: ${result2.path}`);
|
|
119961
120085
|
await writeLine2(ctx, ` Size: ${mb} MB`);
|
|
119962
120086
|
return;
|
|
@@ -119972,19 +120096,19 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119972
120096
|
remove: options.remove
|
|
119973
120097
|
});
|
|
119974
120098
|
if (result2.action === "removed") {
|
|
119975
|
-
await writeLine2(ctx, `${
|
|
120099
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Removed ${result2.provider} sign-in.`);
|
|
119976
120100
|
} else if (result2.action === "set") {
|
|
119977
|
-
await writeLine2(ctx, `${
|
|
120101
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} ${result2.provider} sign-in enabled.`);
|
|
119978
120102
|
await writeLine2(ctx, "");
|
|
119979
|
-
await writeLine2(ctx,
|
|
120103
|
+
await writeLine2(ctx, import_picocolors7.default.bold("One more step — add this redirect URI in the provider console:"));
|
|
119980
120104
|
await writeLine2(ctx, ` ${result2.redirectUri}`);
|
|
119981
120105
|
} else {
|
|
119982
120106
|
if (!result2.providers?.length) {
|
|
119983
|
-
await writeLine2(ctx,
|
|
120107
|
+
await writeLine2(ctx, import_picocolors7.default.dim("No OAuth providers configured."));
|
|
119984
120108
|
} else {
|
|
119985
|
-
await writeLine2(ctx,
|
|
120109
|
+
await writeLine2(ctx, import_picocolors7.default.bold("Configured providers"));
|
|
119986
120110
|
for (const p of result2.providers) {
|
|
119987
|
-
await writeLine2(ctx, ` ${p.provider.padEnd(12)} ${
|
|
120111
|
+
await writeLine2(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors7.default.dim(p.clientId)}`);
|
|
119988
120112
|
}
|
|
119989
120113
|
}
|
|
119990
120114
|
await writeLine2(ctx, "");
|
|
@@ -119997,12 +120121,12 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
119997
120121
|
await writeLine2(ctx, JSON.stringify(result, null, 2));
|
|
119998
120122
|
} else {
|
|
119999
120123
|
const version2 = result.version ? ` Version: ${result.version}` : "";
|
|
120000
|
-
await writeLine2(ctx, `${
|
|
120124
|
+
await writeLine2(ctx, `${import_picocolors7.default.bold("Data")} Mode: ${result.mode} Status: ${result.status}${version2}`);
|
|
120001
120125
|
await writeLine2(ctx, "");
|
|
120002
120126
|
if (result.collections.length > 0) {
|
|
120003
|
-
await writeLine2(ctx,
|
|
120127
|
+
await writeLine2(ctx, import_picocolors7.default.bold("Collections"));
|
|
120004
120128
|
for (const col of result.collections) {
|
|
120005
|
-
await writeLine2(ctx, ` ${col.name.padEnd(20)} ${
|
|
120129
|
+
await writeLine2(ctx, ` ${col.name.padEnd(20)} ${import_picocolors7.default.dim(col.type)}`);
|
|
120006
120130
|
}
|
|
120007
120131
|
await writeLine2(ctx, "");
|
|
120008
120132
|
}
|
|
@@ -120013,7 +120137,7 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
120013
120137
|
}
|
|
120014
120138
|
if (result.warning) {
|
|
120015
120139
|
await writeLine2(ctx, "");
|
|
120016
|
-
await writeLine2(ctx,
|
|
120140
|
+
await writeLine2(ctx, import_picocolors7.default.yellow(`⚠ ${result.warning}`));
|
|
120017
120141
|
}
|
|
120018
120142
|
}
|
|
120019
120143
|
});
|
|
@@ -120026,19 +120150,19 @@ function registerDataCommands(cli, ctx, argv) {
|
|
|
120026
120150
|
cli.command("backups [app]", "List available backups for an app").action(async (app) => {
|
|
120027
120151
|
const { backups, skipped } = await listBackups(ctx, { app });
|
|
120028
120152
|
if (backups.length === 0) {
|
|
120029
|
-
await writeLine2(ctx, `${
|
|
120153
|
+
await writeLine2(ctx, `${import_picocolors7.default.dim("No backups found.")}`);
|
|
120030
120154
|
if (skipped.length > 0) {
|
|
120031
|
-
await writeLine2(ctx,
|
|
120155
|
+
await writeLine2(ctx, import_picocolors7.default.yellow(`(${skipped.length} sidecar(s) skipped — see ops logs)`));
|
|
120032
120156
|
}
|
|
120033
120157
|
return;
|
|
120034
120158
|
}
|
|
120035
120159
|
for (const b of backups) {
|
|
120036
120160
|
const components = b.apps[0]?.components.join(", ") ?? "—";
|
|
120037
120161
|
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`;
|
|
120038
|
-
await writeLine2(ctx, `${
|
|
120162
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("●")} ${import_picocolors7.default.bold(b.backup_id)} ${import_picocolors7.default.dim(size.padStart(10))} ${import_picocolors7.default.dim(`[${components}]`)}`);
|
|
120039
120163
|
}
|
|
120040
120164
|
if (skipped.length > 0) {
|
|
120041
|
-
await writeLine2(ctx,
|
|
120165
|
+
await writeLine2(ctx, import_picocolors7.default.yellow(`
|
|
120042
120166
|
${skipped.length} sidecar(s) skipped — older backups predating the manifest schema.`));
|
|
120043
120167
|
}
|
|
120044
120168
|
});
|
|
@@ -120051,19 +120175,19 @@ ${skipped.length} sidecar(s) skipped — older backups predating the manifest sc
|
|
|
120051
120175
|
dryRun: options.dryRun ?? false
|
|
120052
120176
|
});
|
|
120053
120177
|
if ("kind" in result) {
|
|
120054
|
-
await writeLine2(ctx, `${
|
|
120055
|
-
await writeLine2(ctx, ` components: ${
|
|
120056
|
-
await writeLine2(ctx, ` sha: ${
|
|
120057
|
-
await writeLine2(ctx,
|
|
120178
|
+
await writeLine2(ctx, `${import_picocolors7.default.cyan("dry-run")} would restore ${import_picocolors7.default.bold(result.app)} from ${import_picocolors7.default.bold(result.resolvedBackup.backup_id)}`);
|
|
120179
|
+
await writeLine2(ctx, ` components: ${import_picocolors7.default.dim(result.components.join(", "))}`);
|
|
120180
|
+
await writeLine2(ctx, ` sha: ${import_picocolors7.default.dim(`${result.resolvedBackup.tarball_sha256.slice(0, 16)}…`)}`);
|
|
120181
|
+
await writeLine2(ctx, import_picocolors7.default.dim(`
|
|
120058
120182
|
Run without --dry-run to actually restore. Expect 30–120s of downtime.`));
|
|
120059
120183
|
return;
|
|
120060
120184
|
}
|
|
120061
|
-
await writeLine2(ctx, `${
|
|
120185
|
+
await writeLine2(ctx, `${import_picocolors7.default.green("✓")} restored from ${result.sourceBackupId} (job ${result.id.slice(0, 12)})`);
|
|
120062
120186
|
});
|
|
120063
120187
|
}
|
|
120064
120188
|
|
|
120065
120189
|
// src/commands/deploys.ts
|
|
120066
|
-
var
|
|
120190
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
120067
120191
|
function registerDeploysCommands(cli, ctx, argv) {
|
|
120068
120192
|
cli.command("deploys [app] [...rest]", "List recent deploys (id, status, created, error) for an app").option("--limit <n>", "Number of deploys to show (default: 10, max: 100)").option("--app <name>", "(inspect only) App name (defaults to percher.toml in cwd)").option("--latest-failed", "(inspect only) Resolve to the most recent failed deploy").action(async (app, _rest, options) => {
|
|
120069
120193
|
if (app === "inspect") {
|
|
@@ -120079,18 +120203,18 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120079
120203
|
const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
|
|
120080
120204
|
const list = await deploysList(ctx, { app, limit });
|
|
120081
120205
|
if (list.length === 0) {
|
|
120082
|
-
await writeLine2(ctx,
|
|
120206
|
+
await writeLine2(ctx, import_picocolors8.default.dim("No deploys yet."));
|
|
120083
120207
|
return;
|
|
120084
120208
|
}
|
|
120085
|
-
await writeLine2(ctx, `${
|
|
120209
|
+
await writeLine2(ctx, `${import_picocolors8.default.bold("ID".padEnd(22))} ${import_picocolors8.default.bold("STATUS".padEnd(10))} ${import_picocolors8.default.bold("CREATED".padEnd(20))} ${import_picocolors8.default.bold("ERROR")}`);
|
|
120086
120210
|
for (const d of list) {
|
|
120087
120211
|
const id = d.id.padEnd(22);
|
|
120088
120212
|
const status2 = d.status.padEnd(10);
|
|
120089
120213
|
const created = formatDeployTimestamp(d.createdAt).padEnd(20);
|
|
120090
120214
|
const err = d.errorMessage ? d.errorMessage.split(`
|
|
120091
|
-
`)[0]?.slice(0, 60) ?? "—" :
|
|
120092
|
-
const colored = d.status === "live" ?
|
|
120093
|
-
await writeLine2(ctx, `${
|
|
120215
|
+
`)[0]?.slice(0, 60) ?? "—" : import_picocolors8.default.dim("—");
|
|
120216
|
+
const colored = d.status === "live" ? import_picocolors8.default.green(status2) : d.status === "failed" ? import_picocolors8.default.red(status2) : import_picocolors8.default.yellow(status2);
|
|
120217
|
+
await writeLine2(ctx, `${import_picocolors8.default.dim(id)} ${colored} ${import_picocolors8.default.dim(created)} ${err}`);
|
|
120094
120218
|
}
|
|
120095
120219
|
});
|
|
120096
120220
|
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) => {
|
|
@@ -120102,7 +120226,7 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120102
120226
|
commitSha: sha,
|
|
120103
120227
|
toLastGood: Boolean(options.lastGood)
|
|
120104
120228
|
});
|
|
120105
|
-
await writeLine2(ctx, `${
|
|
120229
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} rolled back to ${result.rolledBackTo.slice(0, 10)} (status: ${result.status})`);
|
|
120106
120230
|
});
|
|
120107
120231
|
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) => {
|
|
120108
120232
|
const result = await reproduce(ctx, {
|
|
@@ -120110,27 +120234,27 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120110
120234
|
deployId
|
|
120111
120235
|
});
|
|
120112
120236
|
if (result.status === "needs_login") {
|
|
120113
|
-
await writeLine2(ctx, `${
|
|
120237
|
+
await writeLine2(ctx, `${import_picocolors8.default.red("✗")} Not logged in. Run: percher login`);
|
|
120114
120238
|
throw new SilentExitError;
|
|
120115
120239
|
}
|
|
120116
120240
|
if (result.status === "failed") {
|
|
120117
|
-
await writeLine2(ctx, `${
|
|
120241
|
+
await writeLine2(ctx, `${import_picocolors8.default.red("✗")} ${result.error?.title ?? "Reproduce failed"}`);
|
|
120118
120242
|
if (result.error?.explanation) {
|
|
120119
120243
|
await writeLine2(ctx, ` ${result.error.explanation}`);
|
|
120120
120244
|
}
|
|
120121
120245
|
if (result.error?.suggestion) {
|
|
120122
|
-
await writeLine2(ctx, ` ${
|
|
120246
|
+
await writeLine2(ctx, ` ${import_picocolors8.default.dim(result.error.suggestion)}`);
|
|
120123
120247
|
}
|
|
120124
120248
|
throw new SilentExitError;
|
|
120125
120249
|
}
|
|
120126
|
-
await writeLine2(ctx, `${
|
|
120250
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} ${result.summary}`);
|
|
120127
120251
|
if (result.currentAppEnvKeys && result.currentAppEnvKeys.length > 0) {
|
|
120128
120252
|
await writeLine2(ctx, "");
|
|
120129
|
-
await writeLine2(ctx,
|
|
120253
|
+
await writeLine2(ctx, import_picocolors8.default.bold("Current app env keys:"));
|
|
120130
120254
|
for (const k of result.currentAppEnvKeys) {
|
|
120131
120255
|
await writeLine2(ctx, ` ${k}`);
|
|
120132
120256
|
}
|
|
120133
|
-
await writeLine2(ctx,
|
|
120257
|
+
await writeLine2(ctx, import_picocolors8.default.dim(" (values not exfiltrated — set them locally before building. Note: this is the CURRENT app env, not a deploy-time snapshot.)"));
|
|
120134
120258
|
}
|
|
120135
120259
|
});
|
|
120136
120260
|
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) => {
|
|
@@ -120142,12 +120266,12 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120142
120266
|
latestFailed: deployId ? undefined : true
|
|
120143
120267
|
});
|
|
120144
120268
|
const { deploy, buildLog } = result;
|
|
120145
|
-
await writeLine2(ctx, `${
|
|
120146
|
-
await writeLine2(ctx, `${
|
|
120147
|
-
await writeLine2(ctx, `${
|
|
120269
|
+
await writeLine2(ctx, `${import_picocolors8.default.bold("Deploy:")} ${deploy.id}`);
|
|
120270
|
+
await writeLine2(ctx, `${import_picocolors8.default.bold("Status:")} ${formatStatus(deploy.status)}`);
|
|
120271
|
+
await writeLine2(ctx, `${import_picocolors8.default.bold("Created:")} ${formatDeployTimestamp(deploy.createdAt)}`);
|
|
120148
120272
|
if (deploy.errorMessage) {
|
|
120149
120273
|
await writeLine2(ctx, "");
|
|
120150
|
-
await writeLine2(ctx,
|
|
120274
|
+
await writeLine2(ctx, import_picocolors8.default.bold(import_picocolors8.default.red("Error:")));
|
|
120151
120275
|
for (const line of deploy.errorMessage.split(`
|
|
120152
120276
|
`)) {
|
|
120153
120277
|
await writeLine2(ctx, ` ${line}`);
|
|
@@ -120159,13 +120283,13 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120159
120283
|
`);
|
|
120160
120284
|
const tailN = options.tail ? Number(options.tail) : 200;
|
|
120161
120285
|
const tail = lines.slice(-tailN);
|
|
120162
|
-
const header = lines.length > tail.length ? `${
|
|
120286
|
+
const header = lines.length > tail.length ? `${import_picocolors8.default.bold("Build log")} ${import_picocolors8.default.dim(`(last ${tail.length} of ${lines.length} lines)`)}` : import_picocolors8.default.bold("Build log");
|
|
120163
120287
|
await writeLine2(ctx, header);
|
|
120164
120288
|
for (const line of tail) {
|
|
120165
120289
|
await writeLine2(ctx, ` ${line}`);
|
|
120166
120290
|
}
|
|
120167
120291
|
} else {
|
|
120168
|
-
await writeLine2(ctx,
|
|
120292
|
+
await writeLine2(ctx, import_picocolors8.default.dim("(no build log available for this deploy)"));
|
|
120169
120293
|
}
|
|
120170
120294
|
return;
|
|
120171
120295
|
}
|
|
@@ -120200,16 +120324,16 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120200
120324
|
patch: options.patch
|
|
120201
120325
|
});
|
|
120202
120326
|
const { summary, files } = result;
|
|
120203
|
-
await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${
|
|
120327
|
+
await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors8.default.green(`+${summary.additions}`)} ${import_picocolors8.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
|
|
120204
120328
|
for (const f of files) {
|
|
120205
|
-
const stat2 = f.binary ?
|
|
120206
|
-
await writeLine2(ctx, ` ${f.status} ${f.path} ${stat2}${f.truncated ?
|
|
120329
|
+
const stat2 = f.binary ? import_picocolors8.default.dim("binary") : `${import_picocolors8.default.green(`+${f.additions}`)} ${import_picocolors8.default.red(`−${f.deletions}`)}`;
|
|
120330
|
+
await writeLine2(ctx, ` ${f.status} ${f.path} ${stat2}${f.truncated ? import_picocolors8.default.dim(" (truncated)") : ""}`);
|
|
120207
120331
|
if (options.patch && f.patch) {
|
|
120208
120332
|
await writeLine2(ctx, f.patch.replace(/\n$/, ""));
|
|
120209
120333
|
}
|
|
120210
120334
|
}
|
|
120211
120335
|
if (result.truncated) {
|
|
120212
|
-
await writeLine2(ctx,
|
|
120336
|
+
await writeLine2(ctx, import_picocolors8.default.dim("… some patches omitted (response size limit)"));
|
|
120213
120337
|
}
|
|
120214
120338
|
});
|
|
120215
120339
|
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) => {
|
|
@@ -120221,38 +120345,38 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120221
120345
|
await runPreviewList(options.app);
|
|
120222
120346
|
return;
|
|
120223
120347
|
}
|
|
120224
|
-
const
|
|
120225
|
-
if (!
|
|
120348
|
+
const appRef2 = options.app ?? readPercherTomlAppName(ctx.cwd);
|
|
120349
|
+
if (!appRef2) {
|
|
120226
120350
|
throw new Error("App name required. Use --app or run from a project directory.");
|
|
120227
120351
|
}
|
|
120228
120352
|
if (action === "promote") {
|
|
120229
120353
|
if (!arg)
|
|
120230
120354
|
throw new Error("Deploy ID required. Usage: percher preview promote <id>");
|
|
120231
|
-
const result = await ctx.client.apps.promotePreview(
|
|
120232
|
-
await writeLine2(ctx, `${
|
|
120355
|
+
const result = await ctx.client.apps.promotePreview(appRef2, arg);
|
|
120356
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview promoted to live. Status: ${result.status}`);
|
|
120233
120357
|
} else if (action === "discard") {
|
|
120234
120358
|
if (!arg)
|
|
120235
120359
|
throw new Error("Deploy ID required. Usage: percher preview discard <id>");
|
|
120236
|
-
await ctx.client.apps.discardPreview(
|
|
120237
|
-
await writeLine2(ctx, `${
|
|
120360
|
+
await ctx.client.apps.discardPreview(appRef2, arg);
|
|
120361
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview discarded.`);
|
|
120238
120362
|
} else {
|
|
120239
120363
|
throw new Error(`Unknown preview action "${action}". Use list | promote | discard | branch.`);
|
|
120240
120364
|
}
|
|
120241
120365
|
});
|
|
120242
120366
|
async function runPreviewList(app) {
|
|
120243
120367
|
const { app: appName, previews } = await previewList(ctx, { app });
|
|
120244
|
-
await writeLine2(ctx, `Preview surfaces for ${
|
|
120368
|
+
await writeLine2(ctx, `Preview surfaces for ${import_picocolors8.default.bold(appName)}:`);
|
|
120245
120369
|
if (previews.length === 0) {
|
|
120246
|
-
await writeLine2(ctx,
|
|
120370
|
+
await writeLine2(ctx, import_picocolors8.default.dim(" (no preview surfaces reported by the server)"));
|
|
120247
120371
|
return;
|
|
120248
120372
|
}
|
|
120249
120373
|
for (const p of previews) {
|
|
120250
120374
|
const label = p.kind.padEnd(14);
|
|
120251
|
-
const statusColour = p.status === "ready" || p.status === "enabled" || p.status === "available" || p.status === "active" ?
|
|
120252
|
-
await writeLine2(ctx, ` ${
|
|
120375
|
+
const statusColour = p.status === "ready" || p.status === "enabled" || p.status === "available" || p.status === "active" ? import_picocolors8.default.green(p.status) : p.status === "error" ? import_picocolors8.default.red(p.status) : import_picocolors8.default.dim(p.status);
|
|
120376
|
+
await writeLine2(ctx, ` ${import_picocolors8.default.bold(label)} ${statusColour}`);
|
|
120253
120377
|
if (p.url)
|
|
120254
120378
|
await writeLine2(ctx, ` url: ${p.url}`);
|
|
120255
|
-
await writeLine2(ctx,
|
|
120379
|
+
await writeLine2(ctx, import_picocolors8.default.dim(` how: ${p.howTriggered}`));
|
|
120256
120380
|
}
|
|
120257
120381
|
}
|
|
120258
120382
|
async function runPreviewBranch(sub, branch, app) {
|
|
@@ -120260,25 +120384,25 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120260
120384
|
if (!branch)
|
|
120261
120385
|
throw new Error("Branch required. Usage: percher preview branch set <branch>");
|
|
120262
120386
|
const r = await previewBranchSet(ctx, { app, branch });
|
|
120263
|
-
await writeLine2(ctx, `${
|
|
120387
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview branch set to "${branch}".`);
|
|
120264
120388
|
if (r.previewUrl)
|
|
120265
|
-
await writeLine2(ctx, ` Your preview address: ${
|
|
120266
|
-
await writeLine2(ctx, r.status === "error" ?
|
|
120389
|
+
await writeLine2(ctx, ` Your preview address: ${import_picocolors8.default.bold(r.previewUrl)}`);
|
|
120390
|
+
await writeLine2(ctx, r.status === "error" ? import_picocolors8.default.yellow(" ⚠ The initial deploy failed — push to the branch to retry.") : import_picocolors8.default.dim(" Building now — live within ~90s."));
|
|
120267
120391
|
return;
|
|
120268
120392
|
}
|
|
120269
120393
|
if (sub === "unset") {
|
|
120270
120394
|
await previewBranchUnset(ctx, { app });
|
|
120271
|
-
await writeLine2(ctx, `${
|
|
120395
|
+
await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview branch cleared.`);
|
|
120272
120396
|
return;
|
|
120273
120397
|
}
|
|
120274
120398
|
if (sub === "show" || sub === undefined) {
|
|
120275
120399
|
const s = await previewBranchShow(ctx, { app });
|
|
120276
120400
|
if (!s.previewBranch) {
|
|
120277
120401
|
await writeLine2(ctx, "No preview branch set.");
|
|
120278
|
-
await writeLine2(ctx,
|
|
120402
|
+
await writeLine2(ctx, import_picocolors8.default.dim(" Set one: percher preview branch set <branch>"));
|
|
120279
120403
|
return;
|
|
120280
120404
|
}
|
|
120281
|
-
await writeLine2(ctx, `Preview branch: ${
|
|
120405
|
+
await writeLine2(ctx, `Preview branch: ${import_picocolors8.default.bold(s.previewBranch)}`);
|
|
120282
120406
|
if (s.previewUrl)
|
|
120283
120407
|
await writeLine2(ctx, ` Address: ${s.previewUrl}`);
|
|
120284
120408
|
await writeLine2(ctx, ` Status: ${s.status}`);
|
|
@@ -120289,7 +120413,7 @@ function registerDeploysCommands(cli, ctx, argv) {
|
|
|
120289
120413
|
}
|
|
120290
120414
|
|
|
120291
120415
|
// src/commands/env.ts
|
|
120292
|
-
var
|
|
120416
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
120293
120417
|
var ENV_SCOPES = ["all", "live", "preview"];
|
|
120294
120418
|
function isEnvScope(value) {
|
|
120295
120419
|
return ENV_SCOPES.includes(value);
|
|
@@ -120300,11 +120424,11 @@ function registerEnvCommands(cli, ctx) {
|
|
|
120300
120424
|
if (options.scope !== undefined) {
|
|
120301
120425
|
const raw = String(options.scope);
|
|
120302
120426
|
if (!isEnvScope(raw)) {
|
|
120303
|
-
await writeLine2(ctx, `${
|
|
120427
|
+
await writeLine2(ctx, `${import_picocolors9.default.red("✗")} Invalid --scope "${raw}". Use all, live, or preview.`);
|
|
120304
120428
|
throw new SilentExitError;
|
|
120305
120429
|
}
|
|
120306
120430
|
if (action === "list") {
|
|
120307
|
-
await writeLine2(ctx, `${
|
|
120431
|
+
await writeLine2(ctx, `${import_picocolors9.default.red("✗")} --scope is not supported with "list" — env list already shows every scope ([live]/[preview] annotations).`);
|
|
120308
120432
|
throw new SilentExitError;
|
|
120309
120433
|
}
|
|
120310
120434
|
scope = raw;
|
|
@@ -120330,10 +120454,10 @@ function registerEnvCommands(cli, ctx) {
|
|
|
120330
120454
|
});
|
|
120331
120455
|
await envSet(ctx, { app: options.app, values, scope });
|
|
120332
120456
|
const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
|
|
120333
|
-
await writeLine2(ctx, `${
|
|
120457
|
+
await writeLine2(ctx, `${import_picocolors9.default.green("✓")} env vars updated (${Object.keys(values).length})${scopeNote}`);
|
|
120334
120458
|
} catch (err) {
|
|
120335
120459
|
if (err instanceof EnvSetInputError2) {
|
|
120336
|
-
await writeLine2(ctx, `${
|
|
120460
|
+
await writeLine2(ctx, `${import_picocolors9.default.red("✗")} ${err.message}`);
|
|
120337
120461
|
throw new SilentExitError;
|
|
120338
120462
|
}
|
|
120339
120463
|
throw err;
|
|
@@ -120344,7 +120468,7 @@ function registerEnvCommands(cli, ctx) {
|
|
|
120344
120468
|
throw new Error("Expected key to unset");
|
|
120345
120469
|
await envUnset(ctx, { app: options.app, key, scope });
|
|
120346
120470
|
const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
|
|
120347
|
-
await writeLine2(ctx, `${
|
|
120471
|
+
await writeLine2(ctx, `${import_picocolors9.default.green("✓")} unset ${key}${scopeNote}`);
|
|
120348
120472
|
} else if (action === "list") {
|
|
120349
120473
|
const env = await envList(ctx, { app: options.app });
|
|
120350
120474
|
for (const row of env.detailed) {
|
|
@@ -120358,19 +120482,19 @@ function registerEnvCommands(cli, ctx) {
|
|
|
120358
120482
|
cli.command("generate", "Generate .env.example and TypeScript types from percher.toml").action(async () => {
|
|
120359
120483
|
const result = await generate(ctx);
|
|
120360
120484
|
for (const f of result.files) {
|
|
120361
|
-
await writeLine2(ctx, `${
|
|
120485
|
+
await writeLine2(ctx, `${import_picocolors9.default.green("✓")} ${f}`);
|
|
120362
120486
|
}
|
|
120363
120487
|
});
|
|
120364
120488
|
}
|
|
120365
120489
|
|
|
120366
120490
|
// src/commands/export.ts
|
|
120367
|
-
var
|
|
120491
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
120368
120492
|
function registerExportCommands(cli, ctx) {
|
|
120369
120493
|
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) => {
|
|
120370
120494
|
if (options.git) {
|
|
120371
120495
|
const result2 = await exportGitBundle(ctx, { app, out: options.out });
|
|
120372
|
-
await writeLine2(ctx, `${
|
|
120373
|
-
await writeLine2(ctx,
|
|
120496
|
+
await writeLine2(ctx, `${import_picocolors10.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
|
|
120497
|
+
await writeLine2(ctx, import_picocolors10.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
|
|
120374
120498
|
return;
|
|
120375
120499
|
}
|
|
120376
120500
|
const result = await exportApp(ctx, {
|
|
@@ -120378,15 +120502,15 @@ function registerExportCommands(cli, ctx) {
|
|
|
120378
120502
|
out: options.out,
|
|
120379
120503
|
deliveryKind: "cli"
|
|
120380
120504
|
});
|
|
120381
|
-
await writeLine2(ctx, `${
|
|
120382
|
-
await writeLine2(ctx,
|
|
120505
|
+
await writeLine2(ctx, `${import_picocolors10.default.green("✓")} exported ${result.appName} → ${result.outPath} (${(result.bytes / 1024).toFixed(1)} KB)`);
|
|
120506
|
+
await writeLine2(ctx, import_picocolors10.default.dim("Env values are not included. To export encrypted env values, use the dashboard's Settings → Export and confirm your password."));
|
|
120383
120507
|
});
|
|
120384
120508
|
}
|
|
120385
120509
|
|
|
120386
120510
|
// src/commands/github.ts
|
|
120387
|
-
var
|
|
120511
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
120388
120512
|
async function printWebhookInstructions(ctx, instructions, branch) {
|
|
120389
|
-
await writeLine2(ctx,
|
|
120513
|
+
await writeLine2(ctx, import_picocolors11.default.bold("Add this webhook in GitHub to enable auto-deploy on push:"));
|
|
120390
120514
|
await writeLine2(ctx, ` URL: ${instructions.settingsUrl}`);
|
|
120391
120515
|
await writeLine2(ctx, "");
|
|
120392
120516
|
await writeLine2(ctx, " Field Value");
|
|
@@ -120396,9 +120520,9 @@ async function printWebhookInstructions(ctx, instructions, branch) {
|
|
|
120396
120520
|
await writeLine2(ctx, ` Secret ${instructions.secret}`);
|
|
120397
120521
|
await writeLine2(ctx, " Which events? Just the push event");
|
|
120398
120522
|
await writeLine2(ctx, "");
|
|
120399
|
-
await writeLine2(ctx,
|
|
120523
|
+
await writeLine2(ctx, import_picocolors11.default.yellow(" ⚠ Treat this secret like a password — save it now, it won't be shown again."));
|
|
120400
120524
|
await writeLine2(ctx, "");
|
|
120401
|
-
await writeLine2(ctx,
|
|
120525
|
+
await writeLine2(ctx, import_picocolors11.default.dim(`After adding the webhook, every push to ${branch} will auto-deploy.`));
|
|
120402
120526
|
}
|
|
120403
120527
|
function registerGithubCommands(cli, ctx) {
|
|
120404
120528
|
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) => {
|
|
@@ -120411,24 +120535,24 @@ function registerGithubCommands(cli, ctx) {
|
|
|
120411
120535
|
repoUrl,
|
|
120412
120536
|
branch: options.branch
|
|
120413
120537
|
});
|
|
120414
|
-
await writeLine2(ctx, `${
|
|
120415
|
-
await writeLine2(ctx, `${
|
|
120538
|
+
await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Connected to ${result.repoUrl} (branch: ${result.branch})`);
|
|
120539
|
+
await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy started: ${result.deployId}`);
|
|
120416
120540
|
await writeLine2(ctx, "");
|
|
120417
120541
|
if (result.webhookSetupFailed) {
|
|
120418
|
-
await writeLine2(ctx,
|
|
120419
|
-
await writeLine2(ctx,
|
|
120420
|
-
await writeLine2(ctx, ` ${
|
|
120542
|
+
await writeLine2(ctx, import_picocolors11.default.yellow(`⚠ Webhook setup failed: ${result.webhookSetupError ?? "unknown error"}`));
|
|
120543
|
+
await writeLine2(ctx, import_picocolors11.default.yellow(" Auto-deploy will NOT work until you re-run webhook setup:"));
|
|
120544
|
+
await writeLine2(ctx, ` ${import_picocolors11.default.bold(`percher github setup-webhook${options.app ? ` --app ${options.app}` : ""}`)}`);
|
|
120421
120545
|
return;
|
|
120422
120546
|
}
|
|
120423
120547
|
if (result.webhookInstructions) {
|
|
120424
120548
|
await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
|
|
120425
120549
|
}
|
|
120426
|
-
await writeLine2(ctx,
|
|
120550
|
+
await writeLine2(ctx, import_picocolors11.default.dim(`Track the initial deploy: percher deploys inspect ${result.deployId}`));
|
|
120427
120551
|
return;
|
|
120428
120552
|
}
|
|
120429
120553
|
if (action === "setup-webhook") {
|
|
120430
120554
|
const result = await githubSetupWebhook(ctx, { app: options.app });
|
|
120431
|
-
await writeLine2(ctx, `${
|
|
120555
|
+
await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Webhook regenerated for ${result.app}`);
|
|
120432
120556
|
await writeLine2(ctx, "");
|
|
120433
120557
|
await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
|
|
120434
120558
|
return;
|
|
@@ -120440,52 +120564,52 @@ function registerGithubCommands(cli, ctx) {
|
|
|
120440
120564
|
}
|
|
120441
120565
|
|
|
120442
120566
|
// src/commands/hooks.ts
|
|
120443
|
-
var
|
|
120567
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
120444
120568
|
function whenLabel(iso) {
|
|
120445
120569
|
return iso ? new Date(iso).toISOString().slice(0, 10) : "never";
|
|
120446
120570
|
}
|
|
120447
120571
|
function registerHooksCommands(cli, ctx) {
|
|
120448
120572
|
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) => {
|
|
120449
120573
|
if (!ctx.client.token) {
|
|
120450
|
-
await writeLine2(ctx, `${
|
|
120574
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Not logged in. Run: percher login`);
|
|
120451
120575
|
throw new SilentExitError;
|
|
120452
120576
|
}
|
|
120453
120577
|
if (action === "create") {
|
|
120454
120578
|
if (!arg) {
|
|
120455
|
-
await writeLine2(ctx, `${
|
|
120579
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} A name is required. Usage: percher hook create <name> [--app <name>]`);
|
|
120456
120580
|
throw new SilentExitError;
|
|
120457
120581
|
}
|
|
120458
120582
|
const created = await hookCreate(ctx, { name: arg, app: options.app });
|
|
120459
|
-
await writeLine2(ctx, `${
|
|
120583
|
+
await writeLine2(ctx, `${import_picocolors12.default.green("✓")} Deploy hook created: ${import_picocolors12.default.bold(created.name)}`);
|
|
120460
120584
|
await writeLine2(ctx, "");
|
|
120461
|
-
await writeLine2(ctx, ` ${
|
|
120462
|
-
await writeLine2(ctx,
|
|
120463
|
-
await writeLine2(ctx,
|
|
120585
|
+
await writeLine2(ctx, ` ${import_picocolors12.default.bold(created.hookUrl)}`);
|
|
120586
|
+
await writeLine2(ctx, import_picocolors12.default.yellow(" ↑ Shown once — store it in your CMS/CI secret store now. It can't be retrieved again."));
|
|
120587
|
+
await writeLine2(ctx, import_picocolors12.default.yellow(" The URL is a secret: anyone holding it can trigger builds (6/min)."));
|
|
120464
120588
|
await writeLine2(ctx, "");
|
|
120465
|
-
await writeLine2(ctx,
|
|
120466
|
-
await writeLine2(ctx,
|
|
120589
|
+
await writeLine2(ctx, import_picocolors12.default.dim(" Trigger a rebuild with:"));
|
|
120590
|
+
await writeLine2(ctx, import_picocolors12.default.dim(` curl -X POST ${created.hookUrl}`));
|
|
120467
120591
|
return;
|
|
120468
120592
|
}
|
|
120469
120593
|
if (action === "list") {
|
|
120470
120594
|
const hooks = await hookList(ctx, { app: options.app });
|
|
120471
120595
|
if (hooks.length === 0) {
|
|
120472
|
-
await writeLine2(ctx,
|
|
120596
|
+
await writeLine2(ctx, import_picocolors12.default.dim("No deploy hooks. Create one with: percher hook create <name>"));
|
|
120473
120597
|
return;
|
|
120474
120598
|
}
|
|
120475
120599
|
for (const h of hooks) {
|
|
120476
|
-
await writeLine2(ctx, `${
|
|
120600
|
+
await writeLine2(ctx, `${import_picocolors12.default.bold(h.id)} ${h.name.padEnd(20)} ${import_picocolors12.default.dim(`created ${whenLabel(h.createdAt)}`)} ${import_picocolors12.default.dim(`last used ${whenLabel(h.lastUsedAt)}`)}`);
|
|
120477
120601
|
}
|
|
120478
120602
|
await writeLine2(ctx, "");
|
|
120479
|
-
await writeLine2(ctx,
|
|
120603
|
+
await writeLine2(ctx, import_picocolors12.default.dim("Revoke one with: percher hook revoke <id>"));
|
|
120480
120604
|
return;
|
|
120481
120605
|
}
|
|
120482
120606
|
if (action === "revoke") {
|
|
120483
120607
|
if (!arg) {
|
|
120484
|
-
await writeLine2(ctx, `${
|
|
120608
|
+
await writeLine2(ctx, `${import_picocolors12.default.red("✗")} A hook id is required. Usage: percher hook revoke <id> (list ids with \`percher hook list\`).`);
|
|
120485
120609
|
throw new SilentExitError;
|
|
120486
120610
|
}
|
|
120487
120611
|
await hookRevoke(ctx, { id: arg, app: options.app });
|
|
120488
|
-
await writeLine2(ctx, `${
|
|
120612
|
+
await writeLine2(ctx, `${import_picocolors12.default.green("✓")} Deploy hook revoked.`);
|
|
120489
120613
|
return;
|
|
120490
120614
|
}
|
|
120491
120615
|
throw new Error(`Unknown hook action "${action ?? ""}". Use: create | list | revoke.`);
|
|
@@ -120496,7 +120620,7 @@ function registerHooksCommands(cli, ctx) {
|
|
|
120496
120620
|
}
|
|
120497
120621
|
|
|
120498
120622
|
// src/commands/migrate.ts
|
|
120499
|
-
var
|
|
120623
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
120500
120624
|
function registerMigrateCommands(cli, ctx) {
|
|
120501
120625
|
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) => {
|
|
120502
120626
|
if (options.status) {
|
|
@@ -120519,15 +120643,15 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
120519
120643
|
const projectRef = options.project;
|
|
120520
120644
|
const token = options.token ?? ctx.env.SUPABASE_ACCESS_TOKEN;
|
|
120521
120645
|
if (options.apply && !options.rewriteClient) {
|
|
120522
|
-
await writeLine2(ctx, `${
|
|
120646
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} --apply only applies to the client rewrite. Pass --rewrite-client --apply together.`);
|
|
120523
120647
|
throw new SilentExitError(1);
|
|
120524
120648
|
}
|
|
120525
120649
|
if (!projectRef) {
|
|
120526
|
-
await writeLine2(ctx, `${
|
|
120650
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} Missing --project. Pass your Supabase project ref (e.g. --project abcdefghijklmnopqrst).`);
|
|
120527
120651
|
throw new SilentExitError(1);
|
|
120528
120652
|
}
|
|
120529
120653
|
if (!token) {
|
|
120530
|
-
await writeLine2(ctx, `${
|
|
120654
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} Missing --token. Pass --token sbp_... or set SUPABASE_ACCESS_TOKEN. Generate one at https://supabase.com/dashboard/account/tokens`);
|
|
120531
120655
|
throw new SilentExitError(1);
|
|
120532
120656
|
}
|
|
120533
120657
|
if (options.inspectOnly) {
|
|
@@ -120576,182 +120700,182 @@ function registerMigrateCommands(cli, ctx) {
|
|
|
120576
120700
|
async function renderInspectHuman(ctx, result) {
|
|
120577
120701
|
if (result.status === "failed") {
|
|
120578
120702
|
const err = result.error;
|
|
120579
|
-
await writeLine2(ctx, `${
|
|
120703
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Inspection failed"}`);
|
|
120580
120704
|
if (err?.explanation)
|
|
120581
120705
|
await writeLine2(ctx, ` ${err.explanation}`);
|
|
120582
120706
|
if (err?.suggestion)
|
|
120583
|
-
await writeLine2(ctx,
|
|
120707
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
|
|
120584
120708
|
return;
|
|
120585
120709
|
}
|
|
120586
|
-
await writeLine2(ctx, `${
|
|
120710
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
|
|
120587
120711
|
await writeLine2(ctx, "");
|
|
120588
|
-
await writeLine2(ctx,
|
|
120712
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Tables"));
|
|
120589
120713
|
if (result.tables.length === 0) {
|
|
120590
|
-
await writeLine2(ctx,
|
|
120714
|
+
await writeLine2(ctx, import_picocolors13.default.dim(" (none in user schemas)"));
|
|
120591
120715
|
}
|
|
120592
120716
|
for (const table of result.tables) {
|
|
120593
120717
|
const pkCount = table.columns.filter((c) => c.isPrimaryKey).length;
|
|
120594
120718
|
const fkCount = table.columns.filter((c) => c.foreignKey !== null).length;
|
|
120595
120719
|
const policyCount = table.rlsPolicies.length;
|
|
120596
120720
|
const rowEstimate = table.estimatedRowCount.toLocaleString();
|
|
120597
|
-
await writeLine2(ctx, ` ${
|
|
120721
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.cyan(`${table.schema}.${table.name}`)} ${import_picocolors13.default.dim(`cols=${table.columns.length} pk=${pkCount} fk=${fkCount} rls=${policyCount} rows≈${rowEstimate}`)}`);
|
|
120598
120722
|
}
|
|
120599
120723
|
if (result.storageBuckets.length > 0) {
|
|
120600
120724
|
await writeLine2(ctx, "");
|
|
120601
|
-
await writeLine2(ctx,
|
|
120725
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Storage buckets"));
|
|
120602
120726
|
for (const bucket of result.storageBuckets) {
|
|
120603
120727
|
const visibility = bucket.public ? "public" : "private";
|
|
120604
|
-
await writeLine2(ctx, ` ${
|
|
120728
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.cyan(bucket.name)} ${import_picocolors13.default.dim(`(${visibility})`)}`);
|
|
120605
120729
|
}
|
|
120606
120730
|
}
|
|
120607
120731
|
if (result.authUsersCount !== null) {
|
|
120608
120732
|
await writeLine2(ctx, "");
|
|
120609
|
-
await writeLine2(ctx, `${
|
|
120733
|
+
await writeLine2(ctx, `${import_picocolors13.default.bold("Auth users")}: ${result.authUsersCount}`);
|
|
120610
120734
|
}
|
|
120611
120735
|
if (result.warnings.length > 0) {
|
|
120612
120736
|
await writeLine2(ctx, "");
|
|
120613
|
-
await writeLine2(ctx,
|
|
120737
|
+
await writeLine2(ctx, import_picocolors13.default.bold(`Warnings (${result.warnings.length})`));
|
|
120614
120738
|
for (const warning of result.warnings) {
|
|
120615
|
-
await writeLine2(ctx, ` ${
|
|
120739
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${warning}`);
|
|
120616
120740
|
}
|
|
120617
120741
|
}
|
|
120618
120742
|
await writeLine2(ctx, "");
|
|
120619
|
-
await writeLine2(ctx,
|
|
120743
|
+
await writeLine2(ctx, import_picocolors13.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/."));
|
|
120620
120744
|
}
|
|
120621
120745
|
async function renderSchemaHuman(ctx, result, opts) {
|
|
120622
120746
|
if (result.status === "failed") {
|
|
120623
120747
|
const err = result.error;
|
|
120624
|
-
await writeLine2(ctx, `${
|
|
120748
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Migration preview failed"}`);
|
|
120625
120749
|
if (err?.explanation)
|
|
120626
120750
|
await writeLine2(ctx, ` ${err.explanation}`);
|
|
120627
120751
|
if (err?.suggestion)
|
|
120628
|
-
await writeLine2(ctx,
|
|
120752
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
|
|
120629
120753
|
return;
|
|
120630
120754
|
}
|
|
120631
|
-
await writeLine2(ctx, `${
|
|
120755
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
|
|
120632
120756
|
await writeLine2(ctx, "");
|
|
120633
|
-
await writeLine2(ctx,
|
|
120757
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Files written"));
|
|
120634
120758
|
for (const path of result.filesWritten) {
|
|
120635
|
-
await writeLine2(ctx, ` ${
|
|
120759
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.cyan(path)}`);
|
|
120636
120760
|
}
|
|
120637
120761
|
const preview = result.preview;
|
|
120638
120762
|
if (!preview)
|
|
120639
120763
|
return;
|
|
120640
120764
|
if (preview.collections.length > 0) {
|
|
120641
120765
|
await writeLine2(ctx, "");
|
|
120642
|
-
await writeLine2(ctx,
|
|
120766
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Collections"));
|
|
120643
120767
|
for (const collection of preview.collections) {
|
|
120644
120768
|
const fields = collection.schema.length;
|
|
120645
120769
|
const relations = collection.schema.filter((f) => f.type === "relation").length;
|
|
120646
|
-
await writeLine2(ctx, ` ${
|
|
120770
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.cyan(collection.name)} ${import_picocolors13.default.dim(`← ${collection.sourceTable} fields=${fields} relations=${relations}`)}`);
|
|
120647
120771
|
}
|
|
120648
120772
|
}
|
|
120649
120773
|
if (preview.warnings.length > 0) {
|
|
120650
120774
|
await writeLine2(ctx, "");
|
|
120651
|
-
await writeLine2(ctx,
|
|
120775
|
+
await writeLine2(ctx, import_picocolors13.default.bold(`Warnings (${preview.warnings.length})`));
|
|
120652
120776
|
for (const warning of preview.warnings.slice(0, 10)) {
|
|
120653
|
-
await writeLine2(ctx, ` ${
|
|
120777
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${warning}`);
|
|
120654
120778
|
}
|
|
120655
120779
|
if (preview.warnings.length > 10) {
|
|
120656
|
-
await writeLine2(ctx,
|
|
120780
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` …and ${preview.warnings.length - 10} more — see MIGRATION_NOTES.md`));
|
|
120657
120781
|
}
|
|
120658
120782
|
}
|
|
120659
120783
|
await writeLine2(ctx, "");
|
|
120660
|
-
await writeLine2(ctx,
|
|
120784
|
+
await writeLine2(ctx, import_picocolors13.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."));
|
|
120661
120785
|
if (!opts.clientRewriteRequested) {
|
|
120662
|
-
await writeLine2(ctx,
|
|
120786
|
+
await writeLine2(ctx, import_picocolors13.default.dim("Add --rewrite-client to also convert your Supabase SDK calls to PocketBase (preview first; --apply writes in place)."));
|
|
120663
120787
|
}
|
|
120664
120788
|
}
|
|
120665
120789
|
async function renderRewriteHuman(ctx, result) {
|
|
120666
120790
|
await writeLine2(ctx, "");
|
|
120667
120791
|
if (result.status === "failed") {
|
|
120668
120792
|
const err = result.error;
|
|
120669
|
-
await writeLine2(ctx, `${
|
|
120793
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Client rewrite failed"}`);
|
|
120670
120794
|
if (err?.explanation)
|
|
120671
120795
|
await writeLine2(ctx, ` ${err.explanation}`);
|
|
120672
120796
|
if (err?.suggestion)
|
|
120673
|
-
await writeLine2(ctx,
|
|
120797
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
|
|
120674
120798
|
return;
|
|
120675
120799
|
}
|
|
120676
|
-
await writeLine2(ctx, `${
|
|
120800
|
+
await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
|
|
120677
120801
|
if (result.filesWritten.length > 0) {
|
|
120678
120802
|
await writeLine2(ctx, "");
|
|
120679
|
-
await writeLine2(ctx,
|
|
120803
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Files written"));
|
|
120680
120804
|
for (const path of result.filesWritten.slice(0, 20)) {
|
|
120681
|
-
await writeLine2(ctx, ` ${
|
|
120805
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.cyan(path)}`);
|
|
120682
120806
|
}
|
|
120683
120807
|
if (result.filesWritten.length > 20) {
|
|
120684
|
-
await writeLine2(ctx,
|
|
120808
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` …and ${result.filesWritten.length - 20} more`));
|
|
120685
120809
|
}
|
|
120686
120810
|
}
|
|
120687
120811
|
if (result.flags.length > 0) {
|
|
120688
120812
|
await writeLine2(ctx, "");
|
|
120689
|
-
await writeLine2(ctx,
|
|
120813
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Needs review (see migration-preview/REWRITE_NOTES.md)"));
|
|
120690
120814
|
for (const flag of result.flags.slice(0, 15)) {
|
|
120691
120815
|
const loc = flag.line !== undefined ? `:${flag.line}` : "";
|
|
120692
120816
|
const tag = flag.kind === "manual_review" ? "rewritten, verify semantics" : "left untouched";
|
|
120693
|
-
await writeLine2(ctx, ` ${
|
|
120817
|
+
await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${import_picocolors13.default.cyan(`${flag.file}${loc}`)} ${flag.pattern} ${import_picocolors13.default.dim(`(${tag}) — ${flag.note}`)}`);
|
|
120694
120818
|
}
|
|
120695
120819
|
if (result.flags.length > 15) {
|
|
120696
|
-
await writeLine2(ctx,
|
|
120820
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` …and ${result.flags.length - 15} more — see REWRITE_NOTES.md`));
|
|
120697
120821
|
}
|
|
120698
120822
|
}
|
|
120699
120823
|
if (result.skipped.length > 0) {
|
|
120700
|
-
await writeLine2(ctx,
|
|
120824
|
+
await writeLine2(ctx, import_picocolors13.default.dim(`
|
|
120701
120825
|
${result.skipped.length} file(s) couldn't be parsed and were skipped — listed in REWRITE_NOTES.md.`));
|
|
120702
120826
|
}
|
|
120703
120827
|
await writeLine2(ctx, "");
|
|
120704
120828
|
if (result.mode === "apply") {
|
|
120705
|
-
await writeLine2(ctx,
|
|
120829
|
+
await writeLine2(ctx, import_picocolors13.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."));
|
|
120706
120830
|
} else {
|
|
120707
|
-
await writeLine2(ctx,
|
|
120831
|
+
await writeLine2(ctx, import_picocolors13.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."));
|
|
120708
120832
|
}
|
|
120709
120833
|
}
|
|
120710
120834
|
async function renderStatusHuman(ctx, result) {
|
|
120711
120835
|
if (result.status === "failed") {
|
|
120712
120836
|
const err = result.error;
|
|
120713
|
-
await writeLine2(ctx, `${
|
|
120837
|
+
await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Migration status scan failed"}`);
|
|
120714
120838
|
if (err?.explanation)
|
|
120715
120839
|
await writeLine2(ctx, ` ${err.explanation}`);
|
|
120716
120840
|
if (err?.suggestion)
|
|
120717
|
-
await writeLine2(ctx,
|
|
120841
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
|
|
120718
120842
|
return;
|
|
120719
120843
|
}
|
|
120720
|
-
await writeLine2(ctx,
|
|
120844
|
+
await writeLine2(ctx, import_picocolors13.default.bold("Migration status"));
|
|
120721
120845
|
await writeLine2(ctx, "");
|
|
120722
120846
|
for (const check2 of result.checks) {
|
|
120723
|
-
const mark = check2.status === "pass" ?
|
|
120724
|
-
const count = check2.count > 0 ?
|
|
120725
|
-
await writeLine2(ctx, ` ${mark} ${check2.label}${count} ${
|
|
120847
|
+
const mark = check2.status === "pass" ? import_picocolors13.default.green("✓") : check2.status === "fail" ? import_picocolors13.default.red("✗") : check2.status === "warn" ? import_picocolors13.default.yellow("⚠") : import_picocolors13.default.dim("•");
|
|
120848
|
+
const count = check2.count > 0 ? import_picocolors13.default.dim(` (${check2.count})`) : "";
|
|
120849
|
+
await writeLine2(ctx, ` ${mark} ${check2.label}${count} ${import_picocolors13.default.dim(`— ${check2.detail}`)}`);
|
|
120726
120850
|
for (const file2 of (check2.files ?? []).slice(0, 10)) {
|
|
120727
|
-
await writeLine2(ctx,
|
|
120851
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` - ${file2}`));
|
|
120728
120852
|
}
|
|
120729
120853
|
if ((check2.files?.length ?? 0) > 10) {
|
|
120730
|
-
await writeLine2(ctx,
|
|
120854
|
+
await writeLine2(ctx, import_picocolors13.default.dim(` …and ${(check2.files?.length ?? 0) - 10} more`));
|
|
120731
120855
|
}
|
|
120732
120856
|
}
|
|
120733
120857
|
if (result.build) {
|
|
120734
120858
|
await writeLine2(ctx, "");
|
|
120735
|
-
const mark = !result.build.ran ?
|
|
120736
|
-
await writeLine2(ctx, ` ${mark} Build ${
|
|
120859
|
+
const mark = !result.build.ran ? import_picocolors13.default.dim("•") : result.build.passed ? import_picocolors13.default.green("✓") : import_picocolors13.default.red("✗");
|
|
120860
|
+
await writeLine2(ctx, ` ${mark} Build ${import_picocolors13.default.dim(`— ${result.build.detail}`)}`);
|
|
120737
120861
|
}
|
|
120738
120862
|
await writeLine2(ctx, "");
|
|
120739
|
-
await writeLine2(ctx,
|
|
120863
|
+
await writeLine2(ctx, import_picocolors13.default.dim(result.summary));
|
|
120740
120864
|
}
|
|
120741
120865
|
|
|
120742
120866
|
// src/commands/sharing.ts
|
|
120743
|
-
var
|
|
120867
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
120744
120868
|
function registerSharingCommands(cli, ctx) {
|
|
120745
120869
|
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) => {
|
|
120746
120870
|
if (action === "list" || action === undefined) {
|
|
120747
120871
|
const rows = await shareList(ctx, { app: options.app });
|
|
120748
120872
|
if (rows.length === 0) {
|
|
120749
|
-
await writeLine2(ctx,
|
|
120873
|
+
await writeLine2(ctx, import_picocolors14.default.dim("No collaborators."));
|
|
120750
120874
|
return;
|
|
120751
120875
|
}
|
|
120752
120876
|
for (const r of rows) {
|
|
120753
|
-
const state = r.accepted ?
|
|
120754
|
-
const roleStr = r.role === "admin" ?
|
|
120877
|
+
const state = r.accepted ? import_picocolors14.default.green("accepted") : import_picocolors14.default.yellow("pending");
|
|
120878
|
+
const roleStr = r.role === "admin" ? import_picocolors14.default.cyan("admin") : import_picocolors14.default.dim("viewer");
|
|
120755
120879
|
await writeLine2(ctx, `${r.id} ${roleStr.padEnd(15)} ${state.padEnd(15)} ${r.email}`);
|
|
120756
120880
|
}
|
|
120757
120881
|
return;
|
|
@@ -120769,8 +120893,8 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120769
120893
|
email: arg,
|
|
120770
120894
|
role: roleRaw
|
|
120771
120895
|
});
|
|
120772
|
-
await writeLine2(ctx, `${
|
|
120773
|
-
await writeLine2(ctx,
|
|
120896
|
+
await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Invited ${created.email} as ${created.role} (id ${created.id})`);
|
|
120897
|
+
await writeLine2(ctx, import_picocolors14.default.dim(`Pending until accepted — invite expires ${new Date(created.expiresAt).toLocaleDateString()}.`));
|
|
120774
120898
|
return;
|
|
120775
120899
|
}
|
|
120776
120900
|
if (action === "role") {
|
|
@@ -120785,7 +120909,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120785
120909
|
shareId: arg,
|
|
120786
120910
|
role: valueOrRole
|
|
120787
120911
|
});
|
|
120788
|
-
await writeLine2(ctx, `${
|
|
120912
|
+
await writeLine2(ctx, `${import_picocolors14.default.green("✓")} ${updated.email} is now ${import_picocolors14.default.cyan(updated.role)}`);
|
|
120789
120913
|
return;
|
|
120790
120914
|
}
|
|
120791
120915
|
if (action === "revoke") {
|
|
@@ -120793,7 +120917,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120793
120917
|
throw new Error("Usage: percher share revoke <shareId> [--app <name>]");
|
|
120794
120918
|
}
|
|
120795
120919
|
await shareRevoke(ctx, { app: options.app, shareId: arg });
|
|
120796
|
-
await writeLine2(ctx, `${
|
|
120920
|
+
await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Revoked share ${arg}`);
|
|
120797
120921
|
return;
|
|
120798
120922
|
}
|
|
120799
120923
|
throw new Error(`Unknown share action "${action}". Use list | invite <email> | role <shareId> <role> | revoke <shareId>.`);
|
|
@@ -120806,11 +120930,11 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120806
120930
|
if (action === undefined || action === "list") {
|
|
120807
120931
|
const pending = await invitesList(ctx, {});
|
|
120808
120932
|
if (pending.length === 0) {
|
|
120809
|
-
await writeLine2(ctx,
|
|
120933
|
+
await writeLine2(ctx, import_picocolors14.default.dim("No pending invites."));
|
|
120810
120934
|
return;
|
|
120811
120935
|
}
|
|
120812
120936
|
for (const inv of pending) {
|
|
120813
|
-
const roleStr = inv.role === "admin" ?
|
|
120937
|
+
const roleStr = inv.role === "admin" ? import_picocolors14.default.cyan("admin") : import_picocolors14.default.dim("viewer");
|
|
120814
120938
|
await writeLine2(ctx, `${inv.id} ${roleStr.padEnd(15)} ${inv.appName.padEnd(24)} from ${inv.invitedBy}`);
|
|
120815
120939
|
}
|
|
120816
120940
|
return;
|
|
@@ -120821,10 +120945,10 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120821
120945
|
}
|
|
120822
120946
|
const accepted = await invitesAccept(ctx, { shareId });
|
|
120823
120947
|
if (accepted.alreadyAccepted) {
|
|
120824
|
-
await writeLine2(ctx,
|
|
120948
|
+
await writeLine2(ctx, import_picocolors14.default.dim(`Invite ${shareId} was already accepted.`));
|
|
120825
120949
|
return;
|
|
120826
120950
|
}
|
|
120827
|
-
await writeLine2(ctx, `${
|
|
120951
|
+
await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Accepted — you now have ${import_picocolors14.default.cyan(accepted.role)} access to the shared app.`);
|
|
120828
120952
|
return;
|
|
120829
120953
|
}
|
|
120830
120954
|
if (action === "decline") {
|
|
@@ -120832,7 +120956,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120832
120956
|
throw new Error("Usage: percher invites decline <shareId>");
|
|
120833
120957
|
}
|
|
120834
120958
|
await invitesDecline(ctx, { shareId });
|
|
120835
|
-
await writeLine2(ctx, `${
|
|
120959
|
+
await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Declined invite ${shareId}.`);
|
|
120836
120960
|
return;
|
|
120837
120961
|
}
|
|
120838
120962
|
throw new Error(`Unknown invites action "${action}". Use list | accept <shareId> | decline <shareId>.`);
|
|
@@ -120843,7 +120967,7 @@ function registerSharingCommands(cli, ctx) {
|
|
|
120843
120967
|
}
|
|
120844
120968
|
|
|
120845
120969
|
// src/commands/status.ts
|
|
120846
|
-
var
|
|
120970
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
120847
120971
|
function registerStatusCommands(cli, ctx) {
|
|
120848
120972
|
cli.command("status", "Show Percher platform status (API + worker health)").option("--json", "Output JSON instead of formatted text").action(async (options) => {
|
|
120849
120973
|
const result = await status(ctx, { json: Boolean(options.json) });
|
|
@@ -120859,34 +120983,34 @@ function registerStatusCommands(cli, ctx) {
|
|
|
120859
120983
|
});
|
|
120860
120984
|
}
|
|
120861
120985
|
async function renderHumanStatus(ctx, result) {
|
|
120862
|
-
const symbol2 = result.verdict === "operational" ?
|
|
120986
|
+
const symbol2 = result.verdict === "operational" ? import_picocolors15.default.green("✓") : result.verdict === "worker-degraded" ? import_picocolors15.default.yellow("⚠") : import_picocolors15.default.red("✗");
|
|
120863
120987
|
await writeLine2(ctx, `${symbol2} ${result.summary}`);
|
|
120864
120988
|
if (!result.health)
|
|
120865
120989
|
return;
|
|
120866
120990
|
const worker = result.health.worker;
|
|
120867
120991
|
if (!worker) {
|
|
120868
|
-
await writeLine2(ctx,
|
|
120992
|
+
await writeLine2(ctx, import_picocolors15.default.dim(" worker: not configured (local Docker socket)"));
|
|
120869
120993
|
return;
|
|
120870
120994
|
}
|
|
120871
120995
|
if (worker.health) {
|
|
120872
120996
|
const ok = worker.health.ok;
|
|
120873
|
-
const symbol3 = ok ?
|
|
120997
|
+
const symbol3 = ok ? import_picocolors15.default.green("✓") : import_picocolors15.default.red("✗");
|
|
120874
120998
|
const lastOk = worker.health.lastOkAt ?? "never";
|
|
120875
|
-
await writeLine2(ctx, ` ${symbol3} worker health: ${ok ? "ok" : `failing (${worker.health.consecutiveErrs} consecutive)`} last-ok=${
|
|
120999
|
+
await writeLine2(ctx, ` ${symbol3} worker health: ${ok ? "ok" : `failing (${worker.health.consecutiveErrs} consecutive)`} last-ok=${import_picocolors15.default.dim(lastOk)}`);
|
|
120876
121000
|
}
|
|
120877
121001
|
if (worker.circuitBreaker) {
|
|
120878
121002
|
const breaker = worker.circuitBreaker;
|
|
120879
|
-
const symbol3 = breaker.open ?
|
|
121003
|
+
const symbol3 = breaker.open ? import_picocolors15.default.red("✗") : import_picocolors15.default.green("✓");
|
|
120880
121004
|
const state = breaker.open ? "open (deploys fail fast)" : "closed";
|
|
120881
121005
|
await writeLine2(ctx, ` ${symbol3} circuit breaker: ${state} ${breaker.failuresInWindow}/${breaker.threshold} failures in last ${Math.round(breaker.windowMs / 1000)}s`);
|
|
120882
121006
|
}
|
|
120883
121007
|
if (result.health.version) {
|
|
120884
|
-
await writeLine2(ctx,
|
|
121008
|
+
await writeLine2(ctx, import_picocolors15.default.dim(` api version: ${result.health.version}`));
|
|
120885
121009
|
}
|
|
120886
121010
|
}
|
|
120887
121011
|
|
|
120888
121012
|
// src/commands/token.ts
|
|
120889
|
-
var
|
|
121013
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
120890
121014
|
var VALID_SCOPES = ["deploy", "env", "read", "full"];
|
|
120891
121015
|
function scopeLabel(scopes, appName) {
|
|
120892
121016
|
const s = scopes && scopes.length > 0 ? scopes.join("+") : "full";
|
|
@@ -120895,22 +121019,22 @@ function scopeLabel(scopes, appName) {
|
|
|
120895
121019
|
function registerTokenCommands(cli, ctx) {
|
|
120896
121020
|
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) => {
|
|
120897
121021
|
if (!ctx.client.token) {
|
|
120898
|
-
await writeLine2(ctx, `${
|
|
121022
|
+
await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Not logged in. Run: percher login`);
|
|
120899
121023
|
throw new SilentExitError;
|
|
120900
121024
|
}
|
|
120901
121025
|
if (action === "create") {
|
|
120902
121026
|
if (!arg) {
|
|
120903
|
-
await writeLine2(ctx, `${
|
|
121027
|
+
await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
|
|
120904
121028
|
throw new SilentExitError;
|
|
120905
121029
|
}
|
|
120906
121030
|
const scope = options.scope ?? "full";
|
|
120907
121031
|
if (!VALID_SCOPES.includes(scope)) {
|
|
120908
|
-
await writeLine2(ctx, `${
|
|
121032
|
+
await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
|
|
120909
121033
|
throw new SilentExitError;
|
|
120910
121034
|
}
|
|
120911
121035
|
const ttlDays = options.ttlDays !== undefined ? Number(options.ttlDays) : undefined;
|
|
120912
121036
|
if (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {
|
|
120913
|
-
await writeLine2(ctx, `${
|
|
121037
|
+
await writeLine2(ctx, `${import_picocolors16.default.red("✗")} --ttl-days must be a positive whole number.`);
|
|
120914
121038
|
throw new SilentExitError;
|
|
120915
121039
|
}
|
|
120916
121040
|
const created = await tokenCreate(ctx, {
|
|
@@ -120919,36 +121043,36 @@ function registerTokenCommands(cli, ctx) {
|
|
|
120919
121043
|
app: options.app,
|
|
120920
121044
|
ttlDays
|
|
120921
121045
|
});
|
|
120922
|
-
await writeLine2(ctx, `${
|
|
121046
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token created: ${import_picocolors16.default.bold(created.name)} ${import_picocolors16.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
|
|
120923
121047
|
await writeLine2(ctx, "");
|
|
120924
|
-
await writeLine2(ctx, ` ${
|
|
120925
|
-
await writeLine2(ctx,
|
|
121048
|
+
await writeLine2(ctx, ` ${import_picocolors16.default.bold(created.token)}`);
|
|
121049
|
+
await writeLine2(ctx, import_picocolors16.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
|
|
120926
121050
|
await writeLine2(ctx, "");
|
|
120927
|
-
await writeLine2(ctx,
|
|
120928
|
-
await writeLine2(ctx,
|
|
121051
|
+
await writeLine2(ctx, import_picocolors16.default.dim(" Use it in CI / an agent:"));
|
|
121052
|
+
await writeLine2(ctx, import_picocolors16.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
|
|
120929
121053
|
return;
|
|
120930
121054
|
}
|
|
120931
121055
|
if (action === "list") {
|
|
120932
121056
|
const tokens = await tokenList(ctx);
|
|
120933
121057
|
if (tokens.length === 0) {
|
|
120934
|
-
await writeLine2(ctx,
|
|
121058
|
+
await writeLine2(ctx, import_picocolors16.default.dim("No API tokens."));
|
|
120935
121059
|
return;
|
|
120936
121060
|
}
|
|
120937
121061
|
for (const t of tokens) {
|
|
120938
121062
|
const expires = t.expiresAt ? `expires ${new Date(t.expiresAt).toISOString().slice(0, 10)}` : "no expiry";
|
|
120939
|
-
await writeLine2(ctx, `${
|
|
121063
|
+
await writeLine2(ctx, `${import_picocolors16.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors16.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors16.default.dim(expires)}`);
|
|
120940
121064
|
}
|
|
120941
121065
|
await writeLine2(ctx, "");
|
|
120942
|
-
await writeLine2(ctx,
|
|
121066
|
+
await writeLine2(ctx, import_picocolors16.default.dim("Revoke one with: percher token revoke <id>"));
|
|
120943
121067
|
return;
|
|
120944
121068
|
}
|
|
120945
121069
|
if (action === "revoke") {
|
|
120946
121070
|
if (!arg) {
|
|
120947
|
-
await writeLine2(ctx, `${
|
|
121071
|
+
await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
|
|
120948
121072
|
throw new SilentExitError;
|
|
120949
121073
|
}
|
|
120950
121074
|
await tokenRevoke(ctx, { id: arg });
|
|
120951
|
-
await writeLine2(ctx, `${
|
|
121075
|
+
await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token revoked.`);
|
|
120952
121076
|
return;
|
|
120953
121077
|
}
|
|
120954
121078
|
throw new Error(`Unknown token action "${action ?? ""}". Use: create | list | revoke.`);
|
|
@@ -120959,17 +121083,17 @@ function registerTokenCommands(cli, ctx) {
|
|
|
120959
121083
|
}
|
|
120960
121084
|
|
|
120961
121085
|
// src/commands/transfers.ts
|
|
120962
|
-
var
|
|
121086
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
120963
121087
|
function registerTransfersCommands(cli, ctx) {
|
|
120964
121088
|
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) => {
|
|
120965
121089
|
if (options.list) {
|
|
120966
121090
|
const rows = await transferListForApp(ctx, { app: options.app });
|
|
120967
121091
|
if (rows.length === 0) {
|
|
120968
|
-
await writeLine2(ctx,
|
|
121092
|
+
await writeLine2(ctx, import_picocolors17.default.dim("No transfers for this app."));
|
|
120969
121093
|
return;
|
|
120970
121094
|
}
|
|
120971
121095
|
for (const r of rows) {
|
|
120972
|
-
const status2 = r.status === "pending" ?
|
|
121096
|
+
const status2 = r.status === "pending" ? import_picocolors17.default.yellow("pending") : r.status === "accepted" ? import_picocolors17.default.green("accepted") : import_picocolors17.default.dim(r.status);
|
|
120973
121097
|
await writeLine2(ctx, `${r.id} ${status2.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
|
|
120974
121098
|
}
|
|
120975
121099
|
return;
|
|
@@ -120982,7 +121106,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120982
121106
|
app: options.app,
|
|
120983
121107
|
transferId: emailOrTransferId
|
|
120984
121108
|
});
|
|
120985
|
-
await writeLine2(ctx, `${
|
|
121109
|
+
await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
|
|
120986
121110
|
return;
|
|
120987
121111
|
}
|
|
120988
121112
|
if (!emailOrTransferId) {
|
|
@@ -120992,17 +121116,17 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
120992
121116
|
app: options.app,
|
|
120993
121117
|
email: emailOrTransferId
|
|
120994
121118
|
});
|
|
120995
|
-
await writeLine2(ctx, `${
|
|
120996
|
-
await writeLine2(ctx,
|
|
121119
|
+
await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
|
|
121120
|
+
await writeLine2(ctx, import_picocolors17.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
|
|
120997
121121
|
if (initiated.notes) {
|
|
120998
|
-
await writeLine2(ctx,
|
|
121122
|
+
await writeLine2(ctx, import_picocolors17.default.yellow(` ⚠ ${initiated.notes}`));
|
|
120999
121123
|
}
|
|
121000
121124
|
});
|
|
121001
121125
|
cli.command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>").action(async (action, transferId) => {
|
|
121002
121126
|
if (action === undefined || action === "list") {
|
|
121003
121127
|
const rows = await transfersIncoming(ctx, {});
|
|
121004
121128
|
if (rows.length === 0) {
|
|
121005
|
-
await writeLine2(ctx,
|
|
121129
|
+
await writeLine2(ctx, import_picocolors17.default.dim("No pending incoming transfers."));
|
|
121006
121130
|
return;
|
|
121007
121131
|
}
|
|
121008
121132
|
for (const r of rows) {
|
|
@@ -121015,7 +121139,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
121015
121139
|
throw new Error("Usage: percher transfers accept <transferId>");
|
|
121016
121140
|
}
|
|
121017
121141
|
const accepted = await transfersAccept(ctx, { transferId });
|
|
121018
|
-
await writeLine2(ctx, `${
|
|
121142
|
+
await writeLine2(ctx, `${import_picocolors17.default.green("✓")} You now own ${import_picocolors17.default.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
|
|
121019
121143
|
return;
|
|
121020
121144
|
}
|
|
121021
121145
|
if (action === "decline") {
|
|
@@ -121023,7 +121147,7 @@ function registerTransfersCommands(cli, ctx) {
|
|
|
121023
121147
|
throw new Error("Usage: percher transfers decline <transferId>");
|
|
121024
121148
|
}
|
|
121025
121149
|
await transfersDecline(ctx, { transferId });
|
|
121026
|
-
await writeLine2(ctx, `${
|
|
121150
|
+
await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Declined transfer ${transferId}.`);
|
|
121027
121151
|
return;
|
|
121028
121152
|
}
|
|
121029
121153
|
throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
|
|
@@ -121097,52 +121221,52 @@ async function createCliContext() {
|
|
|
121097
121221
|
|
|
121098
121222
|
// src/output.ts
|
|
121099
121223
|
init_dist();
|
|
121100
|
-
var
|
|
121224
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
121101
121225
|
async function renderError(stderr, error48) {
|
|
121102
121226
|
const writer = stderr.getWriter();
|
|
121103
121227
|
const encoder = new TextEncoder;
|
|
121104
121228
|
const lines = [];
|
|
121105
121229
|
if (error48 instanceof PercherTomlError) {
|
|
121106
|
-
lines.push(`${
|
|
121230
|
+
lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
|
|
121107
121231
|
if (error48.tomlLine !== undefined) {
|
|
121108
|
-
lines.push(
|
|
121232
|
+
lines.push(import_picocolors18.default.dim(` at percher.toml line ${error48.tomlLine}`));
|
|
121109
121233
|
}
|
|
121110
121234
|
for (const issue2 of error48.issues ?? []) {
|
|
121111
|
-
lines.push(`${
|
|
121235
|
+
lines.push(`${import_picocolors18.default.dim(" · ") + import_picocolors18.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
|
|
121112
121236
|
}
|
|
121113
121237
|
} else if (error48 instanceof SunsetError) {
|
|
121114
121238
|
if (error48.gateCode === "platform_delete_gated") {
|
|
121115
|
-
lines.push(`${
|
|
121239
|
+
lines.push(`${import_picocolors18.default.yellow("Percher is shutting down — deletes are paused.")}`);
|
|
121116
121240
|
lines.push(error48.message);
|
|
121117
|
-
lines.push(
|
|
121118
|
-
lines.push(
|
|
121241
|
+
lines.push(import_picocolors18.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
|
|
121242
|
+
lines.push(import_picocolors18.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
|
|
121119
121243
|
} else {
|
|
121120
|
-
lines.push(`${
|
|
121244
|
+
lines.push(`${import_picocolors18.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
|
|
121121
121245
|
lines.push(error48.message);
|
|
121122
|
-
lines.push(
|
|
121246
|
+
lines.push(import_picocolors18.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
|
|
121123
121247
|
}
|
|
121124
|
-
lines.push(
|
|
121248
|
+
lines.push(import_picocolors18.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
|
|
121125
121249
|
} else if (error48 instanceof PercherApiError) {
|
|
121126
121250
|
if (error48.status === 502 || error48.status === 503) {
|
|
121127
|
-
lines.push(`${
|
|
121251
|
+
lines.push(`${import_picocolors18.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
|
|
121128
121252
|
} else {
|
|
121129
|
-
lines.push(`${
|
|
121130
|
-
lines.push(
|
|
121253
|
+
lines.push(`${import_picocolors18.default.red("error:")} ${error48.message}`);
|
|
121254
|
+
lines.push(import_picocolors18.default.dim(` ${error48.code} (HTTP ${error48.status})`));
|
|
121131
121255
|
}
|
|
121132
121256
|
if (error48.requestId)
|
|
121133
|
-
lines.push(
|
|
121257
|
+
lines.push(import_picocolors18.default.dim(` request: ${error48.requestId}`));
|
|
121134
121258
|
} else if (error48 instanceof PercherCoreError) {
|
|
121135
|
-
lines.push(`${
|
|
121259
|
+
lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
|
|
121136
121260
|
if (error48.hint)
|
|
121137
|
-
lines.push(
|
|
121261
|
+
lines.push(import_picocolors18.default.dim(` ${error48.hint}`));
|
|
121138
121262
|
} else if (error48 instanceof Error) {
|
|
121139
|
-
lines.push(`${
|
|
121263
|
+
lines.push(`${import_picocolors18.default.red("unexpected error:")} ${error48.message}`);
|
|
121140
121264
|
if (error48.stack)
|
|
121141
|
-
lines.push(
|
|
121265
|
+
lines.push(import_picocolors18.default.dim(error48.stack.split(`
|
|
121142
121266
|
`).slice(1, 4).join(`
|
|
121143
121267
|
`)));
|
|
121144
121268
|
} else {
|
|
121145
|
-
lines.push(`${
|
|
121269
|
+
lines.push(`${import_picocolors18.default.red("unexpected error:")} ${String(error48)}`);
|
|
121146
121270
|
}
|
|
121147
121271
|
try {
|
|
121148
121272
|
await writer.write(encoder.encode(`${lines.join(`
|
|
@@ -121155,7 +121279,7 @@ async function renderError(stderr, error48) {
|
|
|
121155
121279
|
// package.json
|
|
121156
121280
|
var package_default = {
|
|
121157
121281
|
name: "percher",
|
|
121158
|
-
version: "0.5.
|
|
121282
|
+
version: "0.5.23",
|
|
121159
121283
|
description: "CLI for the Percher hosting platform — publish apps with one command",
|
|
121160
121284
|
type: "module",
|
|
121161
121285
|
bin: {
|
|
@@ -121182,8 +121306,8 @@ var package_default = {
|
|
|
121182
121306
|
typecheck: "tsc --noEmit"
|
|
121183
121307
|
},
|
|
121184
121308
|
dependencies: {
|
|
121185
|
-
"@percher/client": "^0.2.
|
|
121186
|
-
"@percher/core": "^0.4.
|
|
121309
|
+
"@percher/client": "^0.2.22",
|
|
121310
|
+
"@percher/core": "^0.4.28",
|
|
121187
121311
|
"@percher/toml": "^0.1.10",
|
|
121188
121312
|
cac: "^7.0.0",
|
|
121189
121313
|
picocolors: "^1.1.1"
|
|
@@ -121255,7 +121379,7 @@ async function showTelemetryNotice() {
|
|
|
121255
121379
|
}
|
|
121256
121380
|
|
|
121257
121381
|
// src/version-check.ts
|
|
121258
|
-
var
|
|
121382
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
121259
121383
|
async function checkForUpdates() {
|
|
121260
121384
|
try {
|
|
121261
121385
|
const res = await fetch("https://registry.npmjs.org/percher/latest", {
|
|
@@ -121266,9 +121390,9 @@ async function checkForUpdates() {
|
|
|
121266
121390
|
const data2 = await res.json();
|
|
121267
121391
|
const latest = data2.version;
|
|
121268
121392
|
if (latest && latest !== VERSION) {
|
|
121269
|
-
console.error(
|
|
121393
|
+
console.error(import_picocolors19.default.yellow(`
|
|
121270
121394
|
Update available: ${VERSION} → ${latest}`));
|
|
121271
|
-
console.error(
|
|
121395
|
+
console.error(import_picocolors19.default.dim(`Run: bunx percher@latest
|
|
121272
121396
|
`));
|
|
121273
121397
|
}
|
|
121274
121398
|
} catch {}
|
|
@@ -121279,6 +121403,7 @@ async function run(argv, ctxOverride) {
|
|
|
121279
121403
|
const cli = cac("percher");
|
|
121280
121404
|
const ctx = ctxOverride ?? await createCliContext();
|
|
121281
121405
|
registerAppsCommands(cli, ctx);
|
|
121406
|
+
registerAnalyticsCommands(cli, ctx);
|
|
121282
121407
|
registerDeploysCommands(cli, ctx, argv);
|
|
121283
121408
|
registerEnvCommands(cli, ctx);
|
|
121284
121409
|
registerExportCommands(cli, ctx);
|