percher 0.5.0 → 0.5.2
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 +601 -313
- package/dist/package.json +4 -4
- package/dist/src/commands/apps.d.ts.map +1 -1
- package/dist/src/commands/apps.js +41 -1
- package/dist/src/commands/apps.js.map +1 -1
- package/package.json +3 -3
- package/dist/context.d.ts +0 -3
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -31
- package/dist/context.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -385
- package/dist/index.js.map +0 -1
- package/dist/open.d.ts +0 -2
- package/dist/open.d.ts.map +0 -1
- package/dist/open.js +0 -16
- package/dist/open.js.map +0 -1
- package/dist/output.d.ts +0 -2
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -44
- package/dist/output.js.map +0 -1
- package/dist/prompt.d.ts +0 -4
- package/dist/prompt.d.ts.map +0 -1
- package/dist/prompt.js +0 -14
- package/dist/prompt.js.map +0 -1
- package/dist/templates/node/index.ts +0 -12
package/dist/bin/percher.js
CHANGED
|
@@ -104962,249 +104962,26 @@ async function cacheReset(ctx, input = {}) {
|
|
|
104962
104962
|
}
|
|
104963
104963
|
return ctx.client.apps.resetCache(name);
|
|
104964
104964
|
}
|
|
104965
|
-
// ../core/dist/commands/
|
|
104966
|
-
|
|
104967
|
-
|
|
104968
|
-
// ../core/dist/templates.js
|
|
104969
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
104970
|
-
import { basename, join as join5, normalize, relative, resolve as resolve3 } from "node:path";
|
|
104971
|
-
var __dirname = "C:\\dev\\Percher\\packages\\core\\dist";
|
|
104972
|
-
var TEMPLATES_DIR = resolve3(import.meta.dirname ?? __dirname, "../templates");
|
|
104973
|
-
var PLACEHOLDER = "__APP_NAME__";
|
|
104974
|
-
function listTemplates() {
|
|
104975
|
-
return readdirSync(TEMPLATES_DIR).filter((name) => {
|
|
104976
|
-
const full = join5(TEMPLATES_DIR, name);
|
|
104977
|
-
return statSync(full).isDirectory();
|
|
104978
|
-
});
|
|
104979
|
-
}
|
|
104980
|
-
function scaffoldTemplate(input) {
|
|
104981
|
-
const { template, appName, targetDir, force } = input;
|
|
104982
|
-
const available = listTemplates();
|
|
104983
|
-
if (!available.includes(template)) {
|
|
104984
|
-
throw new Error(`Unknown template "${template}". Available: ${available.join(", ")}`);
|
|
104985
|
-
}
|
|
104986
|
-
const absTarget = resolve3(targetDir);
|
|
104987
|
-
if (existsSync3(absTarget)) {
|
|
104988
|
-
const entries = readdirSync(absTarget);
|
|
104989
|
-
if (entries.length > 0 && !force) {
|
|
104990
|
-
throw new Error(`Target directory "${absTarget}" is not empty. Use --force to overwrite.`);
|
|
104991
|
-
}
|
|
104992
|
-
}
|
|
104993
|
-
const srcDir = join5(TEMPLATES_DIR, template);
|
|
104994
|
-
const files = [];
|
|
104995
|
-
function copyDir(src, dest) {
|
|
104996
|
-
mkdirSync3(dest, { recursive: true });
|
|
104997
|
-
for (const entry of readdirSync(src)) {
|
|
104998
|
-
const srcPath = join5(src, entry);
|
|
104999
|
-
const destPath = join5(dest, entry);
|
|
105000
|
-
const rel = relative(absTarget, normalize(destPath));
|
|
105001
|
-
if (rel.startsWith("..") || resolve3(destPath) === resolve3(absTarget, "..", basename(absTarget))) {
|
|
105002
|
-
throw new Error(`Template contains path traversal: ${entry}`);
|
|
105003
|
-
}
|
|
105004
|
-
if (statSync(srcPath).isDirectory()) {
|
|
105005
|
-
copyDir(srcPath, destPath);
|
|
105006
|
-
} else {
|
|
105007
|
-
if (existsSync3(destPath) && !force) {
|
|
105008
|
-
continue;
|
|
105009
|
-
}
|
|
105010
|
-
let content = readFileSync4(srcPath, "utf-8");
|
|
105011
|
-
content = content.replaceAll(PLACEHOLDER, appName);
|
|
105012
|
-
content = content.replace(/\r\n/g, `
|
|
105013
|
-
`);
|
|
105014
|
-
writeFileSync3(destPath, content, "utf-8");
|
|
105015
|
-
files.push(relative(absTarget, destPath).replace(/\\/g, "/"));
|
|
105016
|
-
}
|
|
105017
|
-
}
|
|
105018
|
-
}
|
|
105019
|
-
copyDir(srcDir, absTarget);
|
|
105020
|
-
return { files };
|
|
105021
|
-
}
|
|
105022
|
-
|
|
105023
|
-
// ../core/dist/commands/create.js
|
|
105024
|
-
var createInputSchema = exports_external.object({
|
|
105025
|
-
name: exports_external.string().regex(APP_NAME_RE, "must be 3-40 chars, lowercase, start with a letter, [a-z0-9-] only"),
|
|
105026
|
-
template: exports_external.string().min(1, "Template is required"),
|
|
105027
|
-
directory: exports_external.string().optional(),
|
|
105028
|
-
force: exports_external.boolean().optional()
|
|
105029
|
-
});
|
|
105030
|
-
async function create(ctx, input) {
|
|
105031
|
-
const available = listTemplates();
|
|
105032
|
-
if (!available.includes(input.template)) {
|
|
105033
|
-
throw new PercherCoreError(`Unknown template "${input.template}". Available: ${available.join(", ")}`, {
|
|
105034
|
-
code: "create.invalid-template",
|
|
105035
|
-
hint: `Run "percher create --list-templates" to see available templates.`
|
|
105036
|
-
});
|
|
105037
|
-
}
|
|
105038
|
-
let targetDir;
|
|
105039
|
-
if (input.directory) {
|
|
105040
|
-
targetDir = resolve4(ctx.cwd, input.directory);
|
|
105041
|
-
if (!targetDir.startsWith(resolve4(ctx.cwd))) {
|
|
105042
|
-
throw new PercherCoreError("Directory must be within the current workspace", {
|
|
105043
|
-
code: "create.failed"
|
|
105044
|
-
});
|
|
105045
|
-
}
|
|
105046
|
-
} else {
|
|
105047
|
-
targetDir = join6(ctx.cwd, input.name);
|
|
105048
|
-
}
|
|
105049
|
-
try {
|
|
105050
|
-
const result = scaffoldTemplate({
|
|
105051
|
-
template: input.template,
|
|
105052
|
-
appName: input.name,
|
|
105053
|
-
targetDir,
|
|
105054
|
-
force: input.force
|
|
105055
|
-
});
|
|
105056
|
-
return {
|
|
105057
|
-
...result,
|
|
105058
|
-
name: input.name,
|
|
105059
|
-
template: input.template,
|
|
105060
|
-
directory: targetDir
|
|
105061
|
-
};
|
|
105062
|
-
} catch (err) {
|
|
105063
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
105064
|
-
if (msg.includes("not empty")) {
|
|
105065
|
-
throw new PercherCoreError(msg, {
|
|
105066
|
-
code: "create.dir-not-empty",
|
|
105067
|
-
hint: "Use --force to overwrite existing files."
|
|
105068
|
-
});
|
|
105069
|
-
}
|
|
105070
|
-
throw new PercherCoreError(msg, { code: "create.failed" });
|
|
105071
|
-
}
|
|
105072
|
-
}
|
|
105073
|
-
// ../core/dist/commands/dashboard.js
|
|
105074
|
-
var dashboardInputSchema = exports_external.object({
|
|
105075
|
-
app: exports_external.string().optional(),
|
|
105076
|
-
tab: exports_external.string().optional()
|
|
105077
|
-
});
|
|
105078
|
-
var DEFAULT_BASE = "https://percher.app";
|
|
105079
|
-
var TAB_SHAPE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
105080
|
-
var SETTINGS_ANCHOR_TABS = new Set(["github", "env", "domains", "crash-diagnosis"]);
|
|
105081
|
-
async function dashboard(ctx, input = {}) {
|
|
105082
|
-
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
105083
|
-
if (!name) {
|
|
105084
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
105085
|
-
code: "dashboard.no-app",
|
|
105086
|
-
hint: "Pass an app name or run from a directory with percher.toml."
|
|
105087
|
-
});
|
|
105088
|
-
}
|
|
105089
|
-
const tab = input.tab ?? "overview";
|
|
105090
|
-
if (!TAB_SHAPE.test(tab)) {
|
|
105091
|
-
throw new PercherCoreError(`Invalid tab "${tab}"`, {
|
|
105092
|
-
code: "dashboard.invalid-tab",
|
|
105093
|
-
hint: "Tab must be lowercase letters, digits, and hyphens (≤32 chars)."
|
|
105094
|
-
});
|
|
105095
|
-
}
|
|
105096
|
-
const base = ctx.env.PERCHER_DASHBOARD_URL ?? DEFAULT_BASE;
|
|
105097
|
-
const appBase = `${base}/apps/${encodeURIComponent(name)}`;
|
|
105098
|
-
const url2 = SETTINGS_ANCHOR_TABS.has(tab) ? `${appBase}/settings#${tab}` : `${appBase}?tab=${tab}`;
|
|
105099
|
-
await ctx.openUrl(url2);
|
|
105100
|
-
return url2;
|
|
105101
|
-
}
|
|
105102
|
-
// ../core/dist/commands/data.js
|
|
105103
|
-
var dataInputSchema = exports_external.object({
|
|
105104
|
-
app: exports_external.string().optional(),
|
|
105105
|
-
json: exports_external.boolean().optional()
|
|
105106
|
-
});
|
|
105107
|
-
async function data(ctx, input = {}) {
|
|
105108
|
-
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
105109
|
-
if (!name) {
|
|
105110
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
105111
|
-
code: "data.no-app",
|
|
105112
|
-
hint: "Pass an app name or run from a directory with percher.toml."
|
|
105113
|
-
});
|
|
105114
|
-
}
|
|
105115
|
-
const status = await ctx.client.data.getStatus(name);
|
|
105116
|
-
const adminLink = await ctx.client.data.getAdminLink(name);
|
|
105117
|
-
let collections = [];
|
|
105118
|
-
let stats = { collections: 0, totalRecords: 0 };
|
|
105119
|
-
let warning = null;
|
|
105120
|
-
try {
|
|
105121
|
-
const cols = await ctx.client.data.getCollections(name);
|
|
105122
|
-
collections = cols.map((c) => ({ name: c.name, type: c.type }));
|
|
105123
|
-
stats = await ctx.client.data.getStats(name);
|
|
105124
|
-
} catch (err) {
|
|
105125
|
-
const code = err.code;
|
|
105126
|
-
if (code === "PB_AUTH_FAILED") {
|
|
105127
|
-
warning = `Could not authenticate with PocketBase — collections + stats unavailable. Try \`percher data reset-superuser ${name}\` to rotate the credentials.`;
|
|
105128
|
-
} else {
|
|
105129
|
-
throw err;
|
|
105130
|
-
}
|
|
105131
|
-
}
|
|
105132
|
-
return {
|
|
105133
|
-
mode: status.mode,
|
|
105134
|
-
status: status.status,
|
|
105135
|
-
version: status.version ?? null,
|
|
105136
|
-
collections,
|
|
105137
|
-
stats,
|
|
105138
|
-
adminUrl: adminLink.adminUrl ?? null,
|
|
105139
|
-
warning
|
|
105140
|
-
};
|
|
105141
|
-
}
|
|
105142
|
-
// ../core/dist/commands/data-export.js
|
|
105143
|
-
import { resolve as resolve5 } from "node:path";
|
|
105144
|
-
var dataExportInputSchema = exports_external.object({
|
|
105145
|
-
app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
|
|
105146
|
-
out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
|
|
105147
|
-
});
|
|
105148
|
-
async function dataExport(ctx, input) {
|
|
105149
|
-
const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
105150
|
-
if (!appName) {
|
|
105151
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
105152
|
-
code: "data-export.no-app",
|
|
105153
|
-
hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
|
|
105154
|
-
});
|
|
105155
|
-
}
|
|
105156
|
-
ctx.status?.(`Exporting ${appName} data...`);
|
|
105157
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
105158
|
-
const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
|
|
105159
|
-
const path = resolve5(ctx.cwd, fileName);
|
|
105160
|
-
const res = await ctx.client.apps.streamDataExport(appName);
|
|
105161
|
-
const bytes = await writeResponseToFile(res, path);
|
|
105162
|
-
return {
|
|
105163
|
-
app: appName,
|
|
105164
|
-
path,
|
|
105165
|
-
bytes,
|
|
105166
|
-
exportedAt: new Date().toISOString()
|
|
105167
|
-
};
|
|
105168
|
-
}
|
|
105169
|
-
// ../core/dist/commands/delete.js
|
|
105170
|
-
var deleteInputSchema = exports_external.object({
|
|
105171
|
-
app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
|
|
105172
|
-
confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
|
|
105173
|
-
});
|
|
105174
|
-
async function deleteApp(ctx, input) {
|
|
105175
|
-
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
105176
|
-
if (!name) {
|
|
105177
|
-
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
105178
|
-
code: "env.no-app",
|
|
105179
|
-
hint: "Pass an app name or run from a directory with percher.toml."
|
|
105180
|
-
});
|
|
105181
|
-
}
|
|
105182
|
-
if (!input.confirm) {
|
|
105183
|
-
throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
|
|
105184
|
-
code: "delete.confirmation-required",
|
|
105185
|
-
hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
|
|
105186
|
-
});
|
|
105187
|
-
}
|
|
105188
|
-
await ctx.client.apps.delete(name);
|
|
105189
|
-
}
|
|
104965
|
+
// ../core/dist/commands/capabilities.js
|
|
104966
|
+
var capabilitiesInputSchema = exports_external.object({});
|
|
105190
104967
|
// ../client/dist/abortable-sleep.js
|
|
105191
104968
|
function abortableSleep(ms, signal) {
|
|
105192
|
-
return new Promise((
|
|
104969
|
+
return new Promise((resolve3) => {
|
|
105193
104970
|
if (!signal) {
|
|
105194
|
-
setTimeout(
|
|
104971
|
+
setTimeout(resolve3, ms);
|
|
105195
104972
|
return;
|
|
105196
104973
|
}
|
|
105197
104974
|
if (signal.aborted) {
|
|
105198
|
-
|
|
104975
|
+
resolve3();
|
|
105199
104976
|
return;
|
|
105200
104977
|
}
|
|
105201
104978
|
const onAbort = () => {
|
|
105202
104979
|
clearTimeout(t);
|
|
105203
|
-
|
|
104980
|
+
resolve3();
|
|
105204
104981
|
};
|
|
105205
104982
|
const t = setTimeout(() => {
|
|
105206
104983
|
signal.removeEventListener("abort", onAbort);
|
|
105207
|
-
|
|
104984
|
+
resolve3();
|
|
105208
104985
|
}, ms);
|
|
105209
104986
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
105210
104987
|
});
|
|
@@ -105223,9 +105000,9 @@ function isAutoForwardedBuildKey(name) {
|
|
|
105223
105000
|
return AUTO_FORWARD_PREFIXES.some((p) => name.startsWith(p));
|
|
105224
105001
|
}
|
|
105225
105002
|
// ../client/dist/config.js
|
|
105226
|
-
import { existsSync as
|
|
105003
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
105227
105004
|
import { homedir } from "node:os";
|
|
105228
|
-
import { dirname as dirname3, join as
|
|
105005
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
105229
105006
|
var DEFAULT_API_URL = "https://api.percher.run";
|
|
105230
105007
|
function home() {
|
|
105231
105008
|
return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
@@ -105233,15 +105010,15 @@ function home() {
|
|
|
105233
105010
|
function configPath() {
|
|
105234
105011
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
105235
105012
|
if (xdg)
|
|
105236
|
-
return
|
|
105237
|
-
return
|
|
105013
|
+
return join5(xdg, "percher", "config.json");
|
|
105014
|
+
return join5(home(), ".percher", "config.json");
|
|
105238
105015
|
}
|
|
105239
105016
|
function readFile2() {
|
|
105240
105017
|
const p = configPath();
|
|
105241
|
-
if (!
|
|
105018
|
+
if (!existsSync3(p))
|
|
105242
105019
|
return {};
|
|
105243
105020
|
try {
|
|
105244
|
-
const parsed = JSON.parse(
|
|
105021
|
+
const parsed = JSON.parse(readFileSync4(p, "utf8"));
|
|
105245
105022
|
return parsed ?? {};
|
|
105246
105023
|
} catch {
|
|
105247
105024
|
return {};
|
|
@@ -105253,16 +105030,17 @@ function loadConfig() {
|
|
|
105253
105030
|
const envToken = process.env.PERCHER_TOKEN;
|
|
105254
105031
|
return {
|
|
105255
105032
|
apiUrl: envUrl ?? file2.apiUrl ?? DEFAULT_API_URL,
|
|
105256
|
-
token: envToken ?? file2.token ?? null
|
|
105033
|
+
token: envToken ?? file2.token ?? null,
|
|
105034
|
+
anonClaim: file2.anonClaim ?? null
|
|
105257
105035
|
};
|
|
105258
105036
|
}
|
|
105259
105037
|
function saveConfig(partial2) {
|
|
105260
105038
|
const p = configPath();
|
|
105261
|
-
|
|
105039
|
+
mkdirSync3(dirname3(p), { recursive: true });
|
|
105262
105040
|
const current = readFile2();
|
|
105263
105041
|
const merged = { ...current, ...partial2 };
|
|
105264
105042
|
const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
|
|
105265
|
-
|
|
105043
|
+
writeFileSync3(tmp, JSON.stringify(merged, null, 2), "utf8");
|
|
105266
105044
|
renameSync(tmp, p);
|
|
105267
105045
|
}
|
|
105268
105046
|
|
|
@@ -105559,6 +105337,20 @@ class AlertsResource {
|
|
|
105559
105337
|
}
|
|
105560
105338
|
}
|
|
105561
105339
|
|
|
105340
|
+
// ../client/dist/resources/anon.js
|
|
105341
|
+
class AnonResource {
|
|
105342
|
+
http;
|
|
105343
|
+
constructor(http) {
|
|
105344
|
+
this.http = http;
|
|
105345
|
+
}
|
|
105346
|
+
requestChallenge() {
|
|
105347
|
+
return this.http.get("/anon/challenge");
|
|
105348
|
+
}
|
|
105349
|
+
createAnonApp(input) {
|
|
105350
|
+
return this.http.post("/anon/apps", input);
|
|
105351
|
+
}
|
|
105352
|
+
}
|
|
105353
|
+
|
|
105562
105354
|
// ../client/dist/resources/apps-types.js
|
|
105563
105355
|
function isDeployAlreadyInProgress(res) {
|
|
105564
105356
|
return res.status === "already_in_progress";
|
|
@@ -105582,6 +105374,18 @@ class AppsResource {
|
|
|
105582
105374
|
get(id, opts) {
|
|
105583
105375
|
return this.http.get(`/apps/${encodeURIComponent(id)}`, opts);
|
|
105584
105376
|
}
|
|
105377
|
+
claim(appRef, input) {
|
|
105378
|
+
return this.http.post(`/apps/${encodeURIComponent(appRef)}/claim`, input);
|
|
105379
|
+
}
|
|
105380
|
+
createInspectLink(appRef) {
|
|
105381
|
+
return this.http.post(`/apps/${encodeURIComponent(appRef)}/inspect-link`, {});
|
|
105382
|
+
}
|
|
105383
|
+
inspectLinkStatus(appRef) {
|
|
105384
|
+
return this.http.get(`/apps/${encodeURIComponent(appRef)}/inspect-link`);
|
|
105385
|
+
}
|
|
105386
|
+
revokeInspectLink(appRef) {
|
|
105387
|
+
return this.http.deleteJson(`/apps/${encodeURIComponent(appRef)}/inspect-link`);
|
|
105388
|
+
}
|
|
105585
105389
|
async deploy(id, input) {
|
|
105586
105390
|
const params = new URLSearchParams;
|
|
105587
105391
|
if (input.type === "preview")
|
|
@@ -106076,6 +105880,9 @@ class PlatformResource {
|
|
|
106076
105880
|
health() {
|
|
106077
105881
|
return this.http.get("/health");
|
|
106078
105882
|
}
|
|
105883
|
+
capabilities() {
|
|
105884
|
+
return this.http.get("/capabilities");
|
|
105885
|
+
}
|
|
106079
105886
|
preflight() {
|
|
106080
105887
|
return this.http.get("/publish/preflight");
|
|
106081
105888
|
}
|
|
@@ -106181,70 +105988,328 @@ class TransfersResource {
|
|
|
106181
105988
|
cancel(appRef, transferId) {
|
|
106182
105989
|
return this.http.delete(`/apps/${encodeURIComponent(appRef)}/transfer/${encodeURIComponent(transferId)}`);
|
|
106183
105990
|
}
|
|
106184
|
-
listForApp(appRef) {
|
|
106185
|
-
return this.http.get(`/apps/${encodeURIComponent(appRef)}/transfer`);
|
|
105991
|
+
listForApp(appRef) {
|
|
105992
|
+
return this.http.get(`/apps/${encodeURIComponent(appRef)}/transfer`);
|
|
105993
|
+
}
|
|
105994
|
+
listIncoming() {
|
|
105995
|
+
return this.http.get("/transfers/incoming");
|
|
105996
|
+
}
|
|
105997
|
+
accept(transferId) {
|
|
105998
|
+
return this.http.post(`/transfers/${encodeURIComponent(transferId)}/accept`, {});
|
|
105999
|
+
}
|
|
106000
|
+
decline(transferId) {
|
|
106001
|
+
return this.http.post(`/transfers/${encodeURIComponent(transferId)}/decline`, {});
|
|
106002
|
+
}
|
|
106003
|
+
}
|
|
106004
|
+
|
|
106005
|
+
// ../client/dist/client.js
|
|
106006
|
+
class PercherClient {
|
|
106007
|
+
apiUrl;
|
|
106008
|
+
token;
|
|
106009
|
+
sessionId;
|
|
106010
|
+
apps;
|
|
106011
|
+
anon;
|
|
106012
|
+
data;
|
|
106013
|
+
env;
|
|
106014
|
+
envScan;
|
|
106015
|
+
export;
|
|
106016
|
+
logs;
|
|
106017
|
+
auth;
|
|
106018
|
+
billing;
|
|
106019
|
+
account;
|
|
106020
|
+
alerts;
|
|
106021
|
+
platform;
|
|
106022
|
+
security;
|
|
106023
|
+
search;
|
|
106024
|
+
sharing;
|
|
106025
|
+
invites;
|
|
106026
|
+
transfers;
|
|
106027
|
+
constructor(options = {}) {
|
|
106028
|
+
const defaults = loadConfig();
|
|
106029
|
+
this.apiUrl = options.apiUrl ?? defaults.apiUrl;
|
|
106030
|
+
this.token = options.token !== undefined ? options.token : defaults.token;
|
|
106031
|
+
this.sessionId = options.sessionId;
|
|
106032
|
+
const http = new Http({
|
|
106033
|
+
apiUrl: this.apiUrl,
|
|
106034
|
+
token: this.token,
|
|
106035
|
+
fetchImpl: options.fetchImpl,
|
|
106036
|
+
clientVersion: options.clientVersion,
|
|
106037
|
+
sessionId: options.sessionId
|
|
106038
|
+
});
|
|
106039
|
+
this.apps = new AppsResource(http);
|
|
106040
|
+
this.anon = new AnonResource(http);
|
|
106041
|
+
this.data = new DataResource(http);
|
|
106042
|
+
this.env = new EnvResource(http);
|
|
106043
|
+
this.envScan = new EnvScanResource(http);
|
|
106044
|
+
this.export = new ExportResource(http);
|
|
106045
|
+
this.logs = new LogsResource(http);
|
|
106046
|
+
this.auth = new AuthResource(http);
|
|
106047
|
+
this.billing = new BillingResource(http);
|
|
106048
|
+
this.account = new AccountResource(http);
|
|
106049
|
+
this.alerts = new AlertsResource(http);
|
|
106050
|
+
this.platform = new PlatformResource(http);
|
|
106051
|
+
this.security = new SecurityResource(http);
|
|
106052
|
+
this.search = new SearchResource(http);
|
|
106053
|
+
this.sharing = new SharingResource(http);
|
|
106054
|
+
this.invites = new InvitesResource(http);
|
|
106055
|
+
this.transfers = new TransfersResource(http);
|
|
106056
|
+
}
|
|
106057
|
+
}
|
|
106058
|
+
// ../core/dist/commands/claim.js
|
|
106059
|
+
var claimInputSchema = exports_external.object({
|
|
106060
|
+
app: exports_external.string().optional().describe("App name to claim (defaults to the persisted anon state or percher.toml)"),
|
|
106061
|
+
claimToken: exports_external.string().optional().describe("The one-time claim token returned by the anonymous publish (pcl_…)")
|
|
106062
|
+
});
|
|
106063
|
+
async function claim(ctx, input = {}) {
|
|
106064
|
+
const state = loadConfig().anonClaim;
|
|
106065
|
+
const app = input.app ?? state?.app ?? readPercherTomlAppName(ctx.cwd) ?? undefined;
|
|
106066
|
+
const claimToken = input.claimToken ?? state?.claimToken;
|
|
106067
|
+
if (!app) {
|
|
106068
|
+
return {
|
|
106069
|
+
status: "needs_token",
|
|
106070
|
+
summary: "No app to claim. Pass --app (or run from the project that published it) and the claim token."
|
|
106071
|
+
};
|
|
106072
|
+
}
|
|
106073
|
+
if (!claimToken) {
|
|
106074
|
+
return {
|
|
106075
|
+
status: "needs_token",
|
|
106076
|
+
summary: "No claim token found. Pass claimToken explicitly — it was returned by the anonymous publish."
|
|
106077
|
+
};
|
|
106078
|
+
}
|
|
106079
|
+
const result = await ctx.client.apps.claim(app, { claimToken });
|
|
106080
|
+
if (state)
|
|
106081
|
+
saveConfig({ anonClaim: null });
|
|
106082
|
+
return {
|
|
106083
|
+
status: "claimed",
|
|
106084
|
+
app: result.name,
|
|
106085
|
+
url: result.url,
|
|
106086
|
+
summary: `Claimed ${result.name} — anonymous restrictions lift on the next deploy.`
|
|
106087
|
+
};
|
|
106088
|
+
}
|
|
106089
|
+
// ../core/dist/commands/create.js
|
|
106090
|
+
import { join as join7, resolve as resolve4 } from "node:path";
|
|
106091
|
+
|
|
106092
|
+
// ../core/dist/templates.js
|
|
106093
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
106094
|
+
import { basename, join as join6, normalize, relative, resolve as resolve3 } from "node:path";
|
|
106095
|
+
var __dirname = "C:\\dev\\Percher-lakebed-clean\\packages\\core\\dist";
|
|
106096
|
+
var TEMPLATES_DIR = resolve3(import.meta.dirname ?? __dirname, "../templates");
|
|
106097
|
+
var PLACEHOLDER = "__APP_NAME__";
|
|
106098
|
+
function listTemplates() {
|
|
106099
|
+
return readdirSync(TEMPLATES_DIR).filter((name) => {
|
|
106100
|
+
const full = join6(TEMPLATES_DIR, name);
|
|
106101
|
+
return statSync(full).isDirectory();
|
|
106102
|
+
});
|
|
106103
|
+
}
|
|
106104
|
+
function scaffoldTemplate(input) {
|
|
106105
|
+
const { template, appName, targetDir, force } = input;
|
|
106106
|
+
const available = listTemplates();
|
|
106107
|
+
if (!available.includes(template)) {
|
|
106108
|
+
throw new Error(`Unknown template "${template}". Available: ${available.join(", ")}`);
|
|
106109
|
+
}
|
|
106110
|
+
const absTarget = resolve3(targetDir);
|
|
106111
|
+
if (existsSync4(absTarget)) {
|
|
106112
|
+
const entries = readdirSync(absTarget);
|
|
106113
|
+
if (entries.length > 0 && !force) {
|
|
106114
|
+
throw new Error(`Target directory "${absTarget}" is not empty. Use --force to overwrite.`);
|
|
106115
|
+
}
|
|
106116
|
+
}
|
|
106117
|
+
const srcDir = join6(TEMPLATES_DIR, template);
|
|
106118
|
+
const files = [];
|
|
106119
|
+
function copyDir(src, dest) {
|
|
106120
|
+
mkdirSync4(dest, { recursive: true });
|
|
106121
|
+
for (const entry of readdirSync(src)) {
|
|
106122
|
+
const srcPath = join6(src, entry);
|
|
106123
|
+
const destPath = join6(dest, entry);
|
|
106124
|
+
const rel = relative(absTarget, normalize(destPath));
|
|
106125
|
+
if (rel.startsWith("..") || resolve3(destPath) === resolve3(absTarget, "..", basename(absTarget))) {
|
|
106126
|
+
throw new Error(`Template contains path traversal: ${entry}`);
|
|
106127
|
+
}
|
|
106128
|
+
if (statSync(srcPath).isDirectory()) {
|
|
106129
|
+
copyDir(srcPath, destPath);
|
|
106130
|
+
} else {
|
|
106131
|
+
if (existsSync4(destPath) && !force) {
|
|
106132
|
+
continue;
|
|
106133
|
+
}
|
|
106134
|
+
let content = readFileSync5(srcPath, "utf-8");
|
|
106135
|
+
content = content.replaceAll(PLACEHOLDER, appName);
|
|
106136
|
+
content = content.replace(/\r\n/g, `
|
|
106137
|
+
`);
|
|
106138
|
+
writeFileSync4(destPath, content, "utf-8");
|
|
106139
|
+
files.push(relative(absTarget, destPath).replace(/\\/g, "/"));
|
|
106140
|
+
}
|
|
106141
|
+
}
|
|
106142
|
+
}
|
|
106143
|
+
copyDir(srcDir, absTarget);
|
|
106144
|
+
return { files };
|
|
106145
|
+
}
|
|
106146
|
+
|
|
106147
|
+
// ../core/dist/commands/create.js
|
|
106148
|
+
var createInputSchema = exports_external.object({
|
|
106149
|
+
name: exports_external.string().regex(APP_NAME_RE, "must be 3-40 chars, lowercase, start with a letter, [a-z0-9-] only"),
|
|
106150
|
+
template: exports_external.string().min(1, "Template is required"),
|
|
106151
|
+
directory: exports_external.string().optional(),
|
|
106152
|
+
force: exports_external.boolean().optional()
|
|
106153
|
+
});
|
|
106154
|
+
async function create(ctx, input) {
|
|
106155
|
+
const available = listTemplates();
|
|
106156
|
+
if (!available.includes(input.template)) {
|
|
106157
|
+
throw new PercherCoreError(`Unknown template "${input.template}". Available: ${available.join(", ")}`, {
|
|
106158
|
+
code: "create.invalid-template",
|
|
106159
|
+
hint: `Run "percher create --list-templates" to see available templates.`
|
|
106160
|
+
});
|
|
106161
|
+
}
|
|
106162
|
+
let targetDir;
|
|
106163
|
+
if (input.directory) {
|
|
106164
|
+
targetDir = resolve4(ctx.cwd, input.directory);
|
|
106165
|
+
if (!targetDir.startsWith(resolve4(ctx.cwd))) {
|
|
106166
|
+
throw new PercherCoreError("Directory must be within the current workspace", {
|
|
106167
|
+
code: "create.failed"
|
|
106168
|
+
});
|
|
106169
|
+
}
|
|
106170
|
+
} else {
|
|
106171
|
+
targetDir = join7(ctx.cwd, input.name);
|
|
106172
|
+
}
|
|
106173
|
+
try {
|
|
106174
|
+
const result = scaffoldTemplate({
|
|
106175
|
+
template: input.template,
|
|
106176
|
+
appName: input.name,
|
|
106177
|
+
targetDir,
|
|
106178
|
+
force: input.force
|
|
106179
|
+
});
|
|
106180
|
+
return {
|
|
106181
|
+
...result,
|
|
106182
|
+
name: input.name,
|
|
106183
|
+
template: input.template,
|
|
106184
|
+
directory: targetDir
|
|
106185
|
+
};
|
|
106186
|
+
} catch (err) {
|
|
106187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106188
|
+
if (msg.includes("not empty")) {
|
|
106189
|
+
throw new PercherCoreError(msg, {
|
|
106190
|
+
code: "create.dir-not-empty",
|
|
106191
|
+
hint: "Use --force to overwrite existing files."
|
|
106192
|
+
});
|
|
106193
|
+
}
|
|
106194
|
+
throw new PercherCoreError(msg, { code: "create.failed" });
|
|
106195
|
+
}
|
|
106196
|
+
}
|
|
106197
|
+
// ../core/dist/commands/dashboard.js
|
|
106198
|
+
var dashboardInputSchema = exports_external.object({
|
|
106199
|
+
app: exports_external.string().optional(),
|
|
106200
|
+
tab: exports_external.string().optional()
|
|
106201
|
+
});
|
|
106202
|
+
var DEFAULT_BASE = "https://percher.app";
|
|
106203
|
+
var TAB_SHAPE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
106204
|
+
var SETTINGS_ANCHOR_TABS = new Set(["github", "env", "domains", "crash-diagnosis"]);
|
|
106205
|
+
async function dashboard(ctx, input = {}) {
|
|
106206
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
106207
|
+
if (!name) {
|
|
106208
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
106209
|
+
code: "dashboard.no-app",
|
|
106210
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
106211
|
+
});
|
|
106212
|
+
}
|
|
106213
|
+
const tab = input.tab ?? "overview";
|
|
106214
|
+
if (!TAB_SHAPE.test(tab)) {
|
|
106215
|
+
throw new PercherCoreError(`Invalid tab "${tab}"`, {
|
|
106216
|
+
code: "dashboard.invalid-tab",
|
|
106217
|
+
hint: "Tab must be lowercase letters, digits, and hyphens (≤32 chars)."
|
|
106218
|
+
});
|
|
106186
106219
|
}
|
|
106187
|
-
|
|
106188
|
-
|
|
106220
|
+
const base = ctx.env.PERCHER_DASHBOARD_URL ?? DEFAULT_BASE;
|
|
106221
|
+
const appBase = `${base}/apps/${encodeURIComponent(name)}`;
|
|
106222
|
+
const url2 = SETTINGS_ANCHOR_TABS.has(tab) ? `${appBase}/settings#${tab}` : `${appBase}?tab=${tab}`;
|
|
106223
|
+
await ctx.openUrl(url2);
|
|
106224
|
+
return url2;
|
|
106225
|
+
}
|
|
106226
|
+
// ../core/dist/commands/data.js
|
|
106227
|
+
var dataInputSchema = exports_external.object({
|
|
106228
|
+
app: exports_external.string().optional(),
|
|
106229
|
+
json: exports_external.boolean().optional()
|
|
106230
|
+
});
|
|
106231
|
+
async function data(ctx, input = {}) {
|
|
106232
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
106233
|
+
if (!name) {
|
|
106234
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
106235
|
+
code: "data.no-app",
|
|
106236
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
106237
|
+
});
|
|
106189
106238
|
}
|
|
106190
|
-
|
|
106191
|
-
|
|
106239
|
+
const status = await ctx.client.data.getStatus(name);
|
|
106240
|
+
const adminLink = await ctx.client.data.getAdminLink(name);
|
|
106241
|
+
let collections = [];
|
|
106242
|
+
let stats = { collections: 0, totalRecords: 0 };
|
|
106243
|
+
let warning = null;
|
|
106244
|
+
try {
|
|
106245
|
+
const cols = await ctx.client.data.getCollections(name);
|
|
106246
|
+
collections = cols.map((c) => ({ name: c.name, type: c.type }));
|
|
106247
|
+
stats = await ctx.client.data.getStats(name);
|
|
106248
|
+
} catch (err) {
|
|
106249
|
+
const code = err.code;
|
|
106250
|
+
if (code === "PB_AUTH_FAILED") {
|
|
106251
|
+
warning = `Could not authenticate with PocketBase — collections + stats unavailable. Try \`percher data reset-superuser ${name}\` to rotate the credentials.`;
|
|
106252
|
+
} else {
|
|
106253
|
+
throw err;
|
|
106254
|
+
}
|
|
106192
106255
|
}
|
|
106193
|
-
|
|
106194
|
-
|
|
106256
|
+
return {
|
|
106257
|
+
mode: status.mode,
|
|
106258
|
+
status: status.status,
|
|
106259
|
+
version: status.version ?? null,
|
|
106260
|
+
collections,
|
|
106261
|
+
stats,
|
|
106262
|
+
adminUrl: adminLink.adminUrl ?? null,
|
|
106263
|
+
warning
|
|
106264
|
+
};
|
|
106265
|
+
}
|
|
106266
|
+
// ../core/dist/commands/data-export.js
|
|
106267
|
+
import { resolve as resolve5 } from "node:path";
|
|
106268
|
+
var dataExportInputSchema = exports_external.object({
|
|
106269
|
+
app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
|
|
106270
|
+
out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
|
|
106271
|
+
});
|
|
106272
|
+
async function dataExport(ctx, input) {
|
|
106273
|
+
const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
106274
|
+
if (!appName) {
|
|
106275
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
106276
|
+
code: "data-export.no-app",
|
|
106277
|
+
hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
|
|
106278
|
+
});
|
|
106195
106279
|
}
|
|
106280
|
+
ctx.status?.(`Exporting ${appName} data...`);
|
|
106281
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
106282
|
+
const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
|
|
106283
|
+
const path = resolve5(ctx.cwd, fileName);
|
|
106284
|
+
const res = await ctx.client.apps.streamDataExport(appName);
|
|
106285
|
+
const bytes = await writeResponseToFile(res, path);
|
|
106286
|
+
return {
|
|
106287
|
+
app: appName,
|
|
106288
|
+
path,
|
|
106289
|
+
bytes,
|
|
106290
|
+
exportedAt: new Date().toISOString()
|
|
106291
|
+
};
|
|
106196
106292
|
}
|
|
106197
|
-
|
|
106198
|
-
|
|
106199
|
-
|
|
106200
|
-
|
|
106201
|
-
|
|
106202
|
-
|
|
106203
|
-
|
|
106204
|
-
|
|
106205
|
-
|
|
106206
|
-
|
|
106207
|
-
|
|
106208
|
-
|
|
106209
|
-
|
|
106210
|
-
|
|
106211
|
-
|
|
106212
|
-
|
|
106213
|
-
|
|
106214
|
-
security;
|
|
106215
|
-
search;
|
|
106216
|
-
sharing;
|
|
106217
|
-
invites;
|
|
106218
|
-
transfers;
|
|
106219
|
-
constructor(options = {}) {
|
|
106220
|
-
const defaults = loadConfig();
|
|
106221
|
-
this.apiUrl = options.apiUrl ?? defaults.apiUrl;
|
|
106222
|
-
this.token = options.token !== undefined ? options.token : defaults.token;
|
|
106223
|
-
this.sessionId = options.sessionId;
|
|
106224
|
-
const http = new Http({
|
|
106225
|
-
apiUrl: this.apiUrl,
|
|
106226
|
-
token: this.token,
|
|
106227
|
-
fetchImpl: options.fetchImpl,
|
|
106228
|
-
clientVersion: options.clientVersion,
|
|
106229
|
-
sessionId: options.sessionId
|
|
106293
|
+
// ../core/dist/commands/delete.js
|
|
106294
|
+
var deleteInputSchema = exports_external.object({
|
|
106295
|
+
app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
|
|
106296
|
+
confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
|
|
106297
|
+
});
|
|
106298
|
+
async function deleteApp(ctx, input) {
|
|
106299
|
+
const name = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
106300
|
+
if (!name) {
|
|
106301
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
106302
|
+
code: "env.no-app",
|
|
106303
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
106304
|
+
});
|
|
106305
|
+
}
|
|
106306
|
+
if (!input.confirm) {
|
|
106307
|
+
throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
|
|
106308
|
+
code: "delete.confirmation-required",
|
|
106309
|
+
hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
|
|
106230
106310
|
});
|
|
106231
|
-
this.apps = new AppsResource(http);
|
|
106232
|
-
this.data = new DataResource(http);
|
|
106233
|
-
this.env = new EnvResource(http);
|
|
106234
|
-
this.envScan = new EnvScanResource(http);
|
|
106235
|
-
this.export = new ExportResource(http);
|
|
106236
|
-
this.logs = new LogsResource(http);
|
|
106237
|
-
this.auth = new AuthResource(http);
|
|
106238
|
-
this.billing = new BillingResource(http);
|
|
106239
|
-
this.account = new AccountResource(http);
|
|
106240
|
-
this.alerts = new AlertsResource(http);
|
|
106241
|
-
this.platform = new PlatformResource(http);
|
|
106242
|
-
this.security = new SecurityResource(http);
|
|
106243
|
-
this.search = new SearchResource(http);
|
|
106244
|
-
this.sharing = new SharingResource(http);
|
|
106245
|
-
this.invites = new InvitesResource(http);
|
|
106246
|
-
this.transfers = new TransfersResource(http);
|
|
106247
106311
|
}
|
|
106312
|
+
await ctx.client.apps.delete(name);
|
|
106248
106313
|
}
|
|
106249
106314
|
// ../core/dist/commands/deploys.js
|
|
106250
106315
|
var deploysListInputSchema = exports_external.object({
|
|
@@ -109277,6 +109342,30 @@ async function init(ctx, input = {}) {
|
|
|
109277
109342
|
var insightsInputSchema = exports_external.object({
|
|
109278
109343
|
app: exports_external.string().optional()
|
|
109279
109344
|
});
|
|
109345
|
+
// ../core/dist/commands/inspect-link.js
|
|
109346
|
+
var inspectLinkInputSchema = exports_external.object({
|
|
109347
|
+
app: exports_external.string().optional().describe("App name (defaults to percher.toml)"),
|
|
109348
|
+
revoke: exports_external.boolean().optional().describe("Revoke the app's inspect link instead of creating one")
|
|
109349
|
+
});
|
|
109350
|
+
async function inspectLink(ctx, input = {}) {
|
|
109351
|
+
const app = input.app ?? readPercherTomlAppName(ctx.cwd);
|
|
109352
|
+
if (!app) {
|
|
109353
|
+
throw new PercherCoreError("No app specified and no percher.toml found", {
|
|
109354
|
+
code: "env.no-app",
|
|
109355
|
+
hint: "Pass an app name or run from a directory with percher.toml."
|
|
109356
|
+
});
|
|
109357
|
+
}
|
|
109358
|
+
if (input.revoke) {
|
|
109359
|
+
await ctx.client.apps.revokeInspectLink(app);
|
|
109360
|
+
return { revoked: true, app, summary: `Inspect link for ${app} revoked.` };
|
|
109361
|
+
}
|
|
109362
|
+
const { inspectUrl } = await ctx.client.apps.createInspectLink(app);
|
|
109363
|
+
return {
|
|
109364
|
+
inspectUrl,
|
|
109365
|
+
app,
|
|
109366
|
+
summary: `Share ${inspectUrl} — read-only status + deploy history, no login needed. Re-running rotates the link; --revoke kills it.`
|
|
109367
|
+
};
|
|
109368
|
+
}
|
|
109280
109369
|
// ../core/dist/commands/login.js
|
|
109281
109370
|
var loginInputSchema = exports_external.object({
|
|
109282
109371
|
token: exports_external.string().optional(),
|
|
@@ -110276,24 +110365,23 @@ function topologicalSort(collections) {
|
|
|
110276
110365
|
continue;
|
|
110277
110366
|
if (!byName.has(target))
|
|
110278
110367
|
continue;
|
|
110279
|
-
dependsOn.get(c.name)
|
|
110280
|
-
dependents.get(target)
|
|
110368
|
+
dependsOn.get(c.name)?.add(target);
|
|
110369
|
+
dependents.get(target)?.add(c.name);
|
|
110281
110370
|
}
|
|
110282
110371
|
}
|
|
110283
110372
|
const inputOrder = collections.map((c) => c.name);
|
|
110284
110373
|
const inDegree = new Map;
|
|
110285
110374
|
for (const name of inputOrder)
|
|
110286
|
-
inDegree.set(name, dependsOn.get(name)
|
|
110375
|
+
inDegree.set(name, dependsOn.get(name)?.size ?? 0);
|
|
110287
110376
|
const ready = [];
|
|
110288
110377
|
for (const name of inputOrder) {
|
|
110289
110378
|
if (inDegree.get(name) === 0)
|
|
110290
110379
|
ready.push(name);
|
|
110291
110380
|
}
|
|
110292
110381
|
const out = [];
|
|
110293
|
-
|
|
110294
|
-
const name = ready.shift();
|
|
110382
|
+
for (let name = ready.shift();name !== undefined; name = ready.shift()) {
|
|
110295
110383
|
out.push(name);
|
|
110296
|
-
for (const dependent of dependents.get(name)) {
|
|
110384
|
+
for (const dependent of dependents.get(name) ?? []) {
|
|
110297
110385
|
const next = (inDegree.get(dependent) ?? 0) - 1;
|
|
110298
110386
|
inDegree.set(dependent, next);
|
|
110299
110387
|
if (next === 0)
|
|
@@ -111013,6 +111101,35 @@ async function previewBranchShow(ctx, input = {}) {
|
|
|
111013
111101
|
import { existsSync as existsSync14 } from "node:fs";
|
|
111014
111102
|
import { join as join20 } from "node:path";
|
|
111015
111103
|
|
|
111104
|
+
// ../shared/dist/pow.js
|
|
111105
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
111106
|
+
function digest(nonce, solution) {
|
|
111107
|
+
return createHash3("sha256").update(`${nonce}:${solution}`).digest("hex");
|
|
111108
|
+
}
|
|
111109
|
+
function leadingZeroBits(hexDigest) {
|
|
111110
|
+
let bits = 0;
|
|
111111
|
+
for (const ch of hexDigest) {
|
|
111112
|
+
const nibble = Number.parseInt(ch, 16);
|
|
111113
|
+
if (nibble === 0) {
|
|
111114
|
+
bits += 4;
|
|
111115
|
+
continue;
|
|
111116
|
+
}
|
|
111117
|
+
bits += Math.clz32(nibble) - 28;
|
|
111118
|
+
break;
|
|
111119
|
+
}
|
|
111120
|
+
return bits;
|
|
111121
|
+
}
|
|
111122
|
+
function solvePow(nonce, difficulty, maxIterations = 50000000) {
|
|
111123
|
+
if (difficulty <= 0)
|
|
111124
|
+
return "0";
|
|
111125
|
+
for (let i = 0;i < maxIterations; i++) {
|
|
111126
|
+
const solution = i.toString(36);
|
|
111127
|
+
if (leadingZeroBits(digest(nonce, solution)) >= difficulty)
|
|
111128
|
+
return solution;
|
|
111129
|
+
}
|
|
111130
|
+
return null;
|
|
111131
|
+
}
|
|
111132
|
+
|
|
111016
111133
|
// ../core/dist/event-renderer.js
|
|
111017
111134
|
function renderDeployEvent(event) {
|
|
111018
111135
|
if (event.stage === "queue") {
|
|
@@ -111819,7 +111936,8 @@ var publishInputSchema = exports_external.object({
|
|
|
111819
111936
|
message: exports_external.string().optional().describe("Deploy note (visible in deploy history and version timeline)"),
|
|
111820
111937
|
dryRun: exports_external.boolean().optional().describe("Show what would be deployed (file count, bytes, top files) without uploading or building. Useful for verifying the bundle before a real deploy."),
|
|
111821
111938
|
noCache: exports_external.boolean().optional().describe("Skip the image-cache lookup and force a fresh build. Use this when you suspect the cached image doesn't reflect your latest source — the new build's resulting image still gets cached, so future deploys benefit."),
|
|
111822
|
-
waitForLive: exports_external.boolean().optional().describe("If true (default), publish blocks until the deploy is live or failed. If false, publish returns as soon as the deploy is queued and the agent can resume with percher_wait_for_deploy. Recommended for AI agents — gives back control quickly with a deployId so they can decide between waiting, asking the user, or working on something else.")
|
|
111939
|
+
waitForLive: exports_external.boolean().optional().describe("If true (default), publish blocks until the deploy is live or failed. If false, publish returns as soon as the deploy is queued and the agent can resume with percher_wait_for_deploy. Recommended for AI agents — gives back control quickly with a deployId so they can decide between waiting, asking the user, or working on something else."),
|
|
111940
|
+
anonymous: exports_external.boolean().optional().describe("Publish WITHOUT an account. Creates a throwaway anonymous app that expires in 72h unless claimed (percher_claim_app). The app runs with no outbound network, no database, and no env vars until claimed. Only honored when the caller has no token; ignored once signed in. Server-gated — fails with ANON_DEPLOYS_DISABLED if the instance hasn't enabled it.")
|
|
111823
111941
|
});
|
|
111824
111942
|
var SUSPICIOUS_BYTES = 50 * 1024 * 1024;
|
|
111825
111943
|
var SUSPICIOUS_FILES = 1e4;
|
|
@@ -112320,6 +112438,16 @@ async function executeDeploy(opts) {
|
|
|
112320
112438
|
};
|
|
112321
112439
|
}
|
|
112322
112440
|
async function handleUnauthorized(ctx, config2, tarball, input) {
|
|
112441
|
+
if (input.anonymous) {
|
|
112442
|
+
return handleAnonymousPublish(ctx, config2, input, tarball);
|
|
112443
|
+
}
|
|
112444
|
+
const anonAlternative = {
|
|
112445
|
+
action: "retry",
|
|
112446
|
+
suggestedTool: "percher_publish",
|
|
112447
|
+
args: { ...input, anonymous: true },
|
|
112448
|
+
when: "to publish without an account (expires in 72h, claim later with percher_claim_app)"
|
|
112449
|
+
};
|
|
112450
|
+
const needsLoginRecovery = { ...RECOVERY_NEEDS_LOGIN, alternativeActions: [anonAlternative] };
|
|
112323
112451
|
if (!ctx.interactiveLogin) {
|
|
112324
112452
|
try {
|
|
112325
112453
|
const deviceClient = new PercherClient({
|
|
@@ -112334,7 +112462,7 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
|
|
|
112334
112462
|
loginCode: userCode,
|
|
112335
112463
|
fileCount: tarball.fileCount,
|
|
112336
112464
|
bytes: tarball.bytes,
|
|
112337
|
-
recovery:
|
|
112465
|
+
recovery: needsLoginRecovery,
|
|
112338
112466
|
summary: `Login required: open ${loginUrl} (code: ${userCode}).`,
|
|
112339
112467
|
configPath: tomlPathFor2(ctx.cwd),
|
|
112340
112468
|
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
@@ -112349,7 +112477,7 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
|
|
|
112349
112477
|
explanation: "No valid Percher token found.",
|
|
112350
112478
|
suggestion: "Run the percher_login tool first, or set PERCHER_TOKEN environment variable."
|
|
112351
112479
|
},
|
|
112352
|
-
recovery:
|
|
112480
|
+
recovery: needsLoginRecovery,
|
|
112353
112481
|
summary: "Login required — run percher_login or set PERCHER_TOKEN.",
|
|
112354
112482
|
configPath: tomlPathFor2(ctx.cwd),
|
|
112355
112483
|
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
@@ -112379,6 +112507,107 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
|
|
|
112379
112507
|
missingBuildEnvKeys: []
|
|
112380
112508
|
});
|
|
112381
112509
|
}
|
|
112510
|
+
async function handleAnonymousPublish(ctx, config2, input, tarball) {
|
|
112511
|
+
const anonClient = new PercherClient({
|
|
112512
|
+
apiUrl: ctx.client.apiUrl,
|
|
112513
|
+
sessionId: ctx.client.sessionId
|
|
112514
|
+
});
|
|
112515
|
+
let created;
|
|
112516
|
+
try {
|
|
112517
|
+
const ch = await anonClient.anon.requestChallenge();
|
|
112518
|
+
const solution = solvePow(ch.nonce, ch.difficulty);
|
|
112519
|
+
if (solution === null) {
|
|
112520
|
+
throw new Error("proof-of-work unsolvable (difficulty too high)");
|
|
112521
|
+
}
|
|
112522
|
+
created = await anonClient.anon.createAnonApp({
|
|
112523
|
+
name: config2.app.name,
|
|
112524
|
+
powChallengeId: ch.challengeId,
|
|
112525
|
+
powSolution: solution
|
|
112526
|
+
});
|
|
112527
|
+
} catch (err) {
|
|
112528
|
+
const httpStatus = err.status;
|
|
112529
|
+
const code = err.code;
|
|
112530
|
+
if (httpStatus === 429) {
|
|
112531
|
+
return {
|
|
112532
|
+
status: "failed",
|
|
112533
|
+
fileCount: tarball.fileCount,
|
|
112534
|
+
bytes: tarball.bytes,
|
|
112535
|
+
error: {
|
|
112536
|
+
title: "Too many anonymous deploys",
|
|
112537
|
+
explanation: "Anonymous publishing is rate-limited per network to deter abuse. Wait a bit and try again, or log in to publish with an account.",
|
|
112538
|
+
suggestion: "Retry in a little while, or run percher login to publish with an account."
|
|
112539
|
+
},
|
|
112540
|
+
recovery: recoveryAsk({
|
|
112541
|
+
prompt: "Anonymous publishing is rate-limited per network. Wait and retry, or run `percher login` to publish with an account.",
|
|
112542
|
+
reasonCode: "quota_exceeded"
|
|
112543
|
+
}),
|
|
112544
|
+
summary: "Rate limited — too many anonymous deploys from your network. Try again later.",
|
|
112545
|
+
configPath: tomlPathFor2(ctx.cwd),
|
|
112546
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
112547
|
+
};
|
|
112548
|
+
}
|
|
112549
|
+
const detail = code === "ANON_CAPACITY" ? "Anonymous capacity is full right now." : "Anonymous deploys are not enabled on this instance.";
|
|
112550
|
+
return {
|
|
112551
|
+
status: "needs_login",
|
|
112552
|
+
fileCount: tarball.fileCount,
|
|
112553
|
+
bytes: tarball.bytes,
|
|
112554
|
+
error: {
|
|
112555
|
+
title: "Anonymous publish unavailable",
|
|
112556
|
+
explanation: detail,
|
|
112557
|
+
suggestion: "Run the percher_login tool to publish with an account instead."
|
|
112558
|
+
},
|
|
112559
|
+
recovery: RECOVERY_NEEDS_LOGIN,
|
|
112560
|
+
summary: `${detail} Log in to publish instead.`,
|
|
112561
|
+
configPath: tomlPathFor2(ctx.cwd),
|
|
112562
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
|
|
112563
|
+
};
|
|
112564
|
+
}
|
|
112565
|
+
saveConfig({
|
|
112566
|
+
token: created.token,
|
|
112567
|
+
anonClaim: {
|
|
112568
|
+
app: created.app.name,
|
|
112569
|
+
claimToken: created.claimToken,
|
|
112570
|
+
expiresAt: created.expiresAt
|
|
112571
|
+
}
|
|
112572
|
+
});
|
|
112573
|
+
const newClient = new PercherClient({
|
|
112574
|
+
apiUrl: ctx.client.apiUrl,
|
|
112575
|
+
token: created.token,
|
|
112576
|
+
sessionId: ctx.client.sessionId
|
|
112577
|
+
});
|
|
112578
|
+
ctx.client = newClient;
|
|
112579
|
+
ctx.setClient?.(newClient);
|
|
112580
|
+
config2.app.name = created.app.name;
|
|
112581
|
+
const t0 = Date.now();
|
|
112582
|
+
const freshTarball = await createTarball({ cwd: ctx.cwd, config: config2 });
|
|
112583
|
+
const packageMs = Date.now() - t0;
|
|
112584
|
+
const { app, firstDeploy } = await ensureApp2(ctx, config2);
|
|
112585
|
+
const result = await executeDeploy({
|
|
112586
|
+
ctx,
|
|
112587
|
+
config: config2,
|
|
112588
|
+
input,
|
|
112589
|
+
app,
|
|
112590
|
+
firstDeploy,
|
|
112591
|
+
tarball: freshTarball,
|
|
112592
|
+
t0,
|
|
112593
|
+
packageMs,
|
|
112594
|
+
missingBuildEnvKeys: []
|
|
112595
|
+
});
|
|
112596
|
+
return {
|
|
112597
|
+
...result,
|
|
112598
|
+
anonymous: true,
|
|
112599
|
+
claim: {
|
|
112600
|
+
claimUrl: created.claimUrl,
|
|
112601
|
+
claimToken: created.claimToken,
|
|
112602
|
+
expiresAt: created.expiresAt
|
|
112603
|
+
},
|
|
112604
|
+
warnings: [
|
|
112605
|
+
...result.warnings ?? [],
|
|
112606
|
+
`Anonymous app: expires ${created.expiresAt} unless claimed — run percher_claim_app (claim token is in claim.claimToken).`,
|
|
112607
|
+
"No outbound network, no database, and no env vars until claimed."
|
|
112608
|
+
]
|
|
112609
|
+
};
|
|
112610
|
+
}
|
|
112382
112611
|
async function loadOrGenerate(ctx) {
|
|
112383
112612
|
const tomlPath = join20(ctx.cwd, "percher.toml");
|
|
112384
112613
|
if (!existsSync14(tomlPath)) {
|
|
@@ -113761,9 +113990,43 @@ var PLAN_CATALOG = {
|
|
|
113761
113990
|
vcpuPool: 4,
|
|
113762
113991
|
diskPoolMb: 75000
|
|
113763
113992
|
}
|
|
113993
|
+
},
|
|
113994
|
+
anonymous: {
|
|
113995
|
+
label: "Anonymous",
|
|
113996
|
+
priceEur: 0,
|
|
113997
|
+
hidden: true,
|
|
113998
|
+
limits: {
|
|
113999
|
+
apps: 1,
|
|
114000
|
+
deploysPerMonth: 20,
|
|
114001
|
+
deploysPerHour: 6,
|
|
114002
|
+
memory: "128mb",
|
|
114003
|
+
cpu: 0.25,
|
|
114004
|
+
pids: 64,
|
|
114005
|
+
activePreviewsPerApp: 0,
|
|
114006
|
+
customDomainsPerApp: 0,
|
|
114007
|
+
removesPercherBadge: false,
|
|
114008
|
+
autoSleep: true,
|
|
114009
|
+
inactivityWarningDays: 1,
|
|
114010
|
+
inactivityGraceDays: 1,
|
|
114011
|
+
deployRetention: 2,
|
|
114012
|
+
logRetentionDays: 1,
|
|
114013
|
+
buildLogRetentionDays: 1,
|
|
114014
|
+
cronJobs: 0,
|
|
114015
|
+
storageMb: 0,
|
|
114016
|
+
instances: 1,
|
|
114017
|
+
kvStorageMb: 0,
|
|
114018
|
+
uptimeIntervalSec: 0,
|
|
114019
|
+
dailyLiveDeploys: 10,
|
|
114020
|
+
dailyPreviewDeploys: 0,
|
|
114021
|
+
userConcurrentDeploys: 1,
|
|
114022
|
+
ramPoolMb: 128,
|
|
114023
|
+
vcpuPool: 0.25,
|
|
114024
|
+
diskPoolMb: 200
|
|
114025
|
+
}
|
|
113764
114026
|
}
|
|
113765
114027
|
};
|
|
113766
|
-
var
|
|
114028
|
+
var ALL_PLAN_IDS = Object.keys(PLAN_CATALOG);
|
|
114029
|
+
var PLAN_IDS = Object.entries(PLAN_CATALOG).filter(([, info]) => !info.hidden).map(([id]) => id);
|
|
113767
114030
|
|
|
113768
114031
|
// ../core/dist/index.js
|
|
113769
114032
|
init_qr();
|
|
@@ -114392,7 +114655,16 @@ async function handlePublishResult(ctx, result, options = {}) {
|
|
|
114392
114655
|
await writeLine(ctx, qr);
|
|
114393
114656
|
} catch {}
|
|
114394
114657
|
}
|
|
114395
|
-
if (result.
|
|
114658
|
+
if (result.anonymous && result.claim) {
|
|
114659
|
+
await writeLine(ctx, "");
|
|
114660
|
+
await writeLine(ctx, import_picocolors4.default.bold("Anonymous app — claim it to keep it:"));
|
|
114661
|
+
await writeLine(ctx, ` Expires: ${new Date(result.claim.expiresAt).toLocaleString()}`);
|
|
114662
|
+
await writeLine(ctx, ` Claim token: ${result.claim.claimToken}`);
|
|
114663
|
+
await writeLine(ctx, ` ${import_picocolors4.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
|
|
114664
|
+
for (const w of result.warnings ?? []) {
|
|
114665
|
+
await writeLine(ctx, import_picocolors4.default.dim(` ${w}`));
|
|
114666
|
+
}
|
|
114667
|
+
} else if (result.firstDeploy) {
|
|
114396
114668
|
await writeLine(ctx, "");
|
|
114397
114669
|
await writeLine(ctx, import_picocolors4.default.bold("First deploy — next steps:"));
|
|
114398
114670
|
await writeLine(ctx, ` ${import_picocolors4.default.cyan("percher dev")} watch for changes and re-deploy`);
|
|
@@ -114417,7 +114689,7 @@ function registerAppsCommands(cli, ctx) {
|
|
|
114417
114689
|
await writeLine(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
|
|
114418
114690
|
}
|
|
114419
114691
|
});
|
|
114420
|
-
cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").action(async (options) => {
|
|
114692
|
+
cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").option("--anonymous", "Publish without an account — a 72h sandbox you can claim later (where enabled)").action(async (options) => {
|
|
114421
114693
|
const { detectCiBranchMessage: detectCiBranchMessage2 } = await Promise.resolve().then(() => exports_ci_env);
|
|
114422
114694
|
let messageOverride = options.message;
|
|
114423
114695
|
if (options.preview && !options.message) {
|
|
@@ -114432,7 +114704,8 @@ function registerAppsCommands(cli, ctx) {
|
|
|
114432
114704
|
preview: options.preview,
|
|
114433
114705
|
message: messageOverride,
|
|
114434
114706
|
dryRun: options.dryRun,
|
|
114435
|
-
noCache: options.cache === false
|
|
114707
|
+
noCache: options.cache === false,
|
|
114708
|
+
anonymous: options.anonymous
|
|
114436
114709
|
});
|
|
114437
114710
|
await handlePublishResult(ctx, result, { preview: options.preview });
|
|
114438
114711
|
});
|
|
@@ -114625,6 +114898,21 @@ function registerAppsCommands(cli, ctx) {
|
|
|
114625
114898
|
const result = await resume(ctx, { app });
|
|
114626
114899
|
await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
|
|
114627
114900
|
});
|
|
114901
|
+
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) => {
|
|
114902
|
+
const result = await claim(ctx, { app: options.app, claimToken: options.claimToken });
|
|
114903
|
+
if (result.status === "claimed") {
|
|
114904
|
+
await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
|
|
114905
|
+
} else {
|
|
114906
|
+
await writeLine(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
|
|
114907
|
+
}
|
|
114908
|
+
});
|
|
114909
|
+
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) => {
|
|
114910
|
+
const result = await inspectLink(ctx, {
|
|
114911
|
+
app: app ?? options.app,
|
|
114912
|
+
revoke: options.revoke
|
|
114913
|
+
});
|
|
114914
|
+
await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
|
|
114915
|
+
});
|
|
114628
114916
|
cli.command("cache <action> [app]", "Manage the app's build cache (reset)").action(async (action, app) => {
|
|
114629
114917
|
if (action !== "reset") {
|
|
114630
114918
|
throw new Error(`Unknown cache action "${action}". Use: percher cache reset [app]`);
|
|
@@ -115564,7 +115852,7 @@ async function renderError(stderr, error48) {
|
|
|
115564
115852
|
// package.json
|
|
115565
115853
|
var package_default = {
|
|
115566
115854
|
name: "percher",
|
|
115567
|
-
version: "0.5.
|
|
115855
|
+
version: "0.5.2",
|
|
115568
115856
|
description: "CLI for the Percher hosting platform — publish apps with one command",
|
|
115569
115857
|
type: "module",
|
|
115570
115858
|
bin: {
|
|
@@ -115588,9 +115876,9 @@ var package_default = {
|
|
|
115588
115876
|
typecheck: "tsc --noEmit"
|
|
115589
115877
|
},
|
|
115590
115878
|
dependencies: {
|
|
115591
|
-
"@percher/client": "^0.2.
|
|
115592
|
-
"@percher/core": "^0.4.
|
|
115593
|
-
"@percher/toml": "^0.1.
|
|
115879
|
+
"@percher/client": "^0.2.10",
|
|
115880
|
+
"@percher/core": "^0.4.5",
|
|
115881
|
+
"@percher/toml": "^0.1.5",
|
|
115594
115882
|
cac: "^7.0.0",
|
|
115595
115883
|
picocolors: "^1.1.1"
|
|
115596
115884
|
},
|