@percher/core 0.4.12 → 0.4.14
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/commands/appstore-kit.d.ts.map +1 -1
- package/dist/commands/appstore-kit.js +4 -1
- package/dist/commands/appstore-kit.js.map +1 -1
- package/dist/commands/deploy-repo.d.ts +28 -0
- package/dist/commands/deploy-repo.d.ts.map +1 -0
- package/dist/commands/deploy-repo.js +265 -0
- package/dist/commands/deploy-repo.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/mcp.d.ts +37 -5
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +39 -6
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/publish-inline.d.ts +28 -0
- package/dist/commands/publish-inline.d.ts.map +1 -0
- package/dist/commands/publish-inline.js +267 -0
- package/dist/commands/publish-inline.js.map +1 -0
- package/dist/commands/publish.d.ts +65 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +86 -16
- package/dist/commands/publish.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/static-docker.d.ts.map +1 -1
- package/dist/static-docker.js +2 -1
- package/dist/static-docker.js.map +1 -1
- package/dist/tarball.d.ts +39 -1
- package/dist/tarball.d.ts.map +1 -1
- package/dist/tarball.js +92 -16
- package/dist/tarball.js.map +1 -1
- package/dist/templates.d.ts +6 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +8 -0
- package/dist/templates.js.map +1 -1
- package/dist/toml-recovery.d.ts +11 -0
- package/dist/toml-recovery.d.ts.map +1 -0
- package/dist/toml-recovery.js +17 -0
- package/dist/toml-recovery.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { isDeployAlreadyInProgress, PercherApiError } from "@percher/client";
|
|
2
|
+
import { MAX_INLINE_PUBLISH_BYTES } from "@percher/shared";
|
|
3
|
+
import { PercherTomlError, parse } from "@percher/toml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { recoveryAsk, recoveryFixConfig, recoveryWait, } from "../recovery.js";
|
|
6
|
+
import { createTarballFromFiles } from "../tarball.js";
|
|
7
|
+
import { tomlErrorToRecoveryProblems } from "../toml-recovery.js";
|
|
8
|
+
import { finalizeDeploy } from "./publish.js";
|
|
9
|
+
import { classifyPublishApiError, DEPLOY_GATE_CODES } from "./publish-api-error.js";
|
|
10
|
+
export const publishInlineInputSchema = z.object({
|
|
11
|
+
name: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Override the app name from percher.toml. Optional when percher.toml sets [app].name."),
|
|
15
|
+
files: z
|
|
16
|
+
.array(z.object({
|
|
17
|
+
path: z.string().min(1).describe("Repo-relative path (no leading / and no ..)."),
|
|
18
|
+
content: z.string().describe("UTF-8 file contents."),
|
|
19
|
+
}))
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe("Inline source files. MUST include a percher.toml."),
|
|
22
|
+
preview: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Deploy as a preview (does not replace the live version)."),
|
|
26
|
+
message: z.string().optional().describe("Deploy note (visible in deploy history)."),
|
|
27
|
+
noCache: z.boolean().optional().describe("Skip the image cache and force a fresh build."),
|
|
28
|
+
waitForLive: z
|
|
29
|
+
.boolean()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("If true (default), block until the deploy is live or failed. If false, return as soon as it is queued so the agent can resume with percher_wait_for_deploy."),
|
|
32
|
+
});
|
|
33
|
+
const TOML_FILENAME = "percher.toml";
|
|
34
|
+
/** Total decoded `content` bytes across all inline files. */
|
|
35
|
+
function totalContentBytes(files) {
|
|
36
|
+
let total = 0;
|
|
37
|
+
for (const f of files)
|
|
38
|
+
total += Buffer.byteLength(f.content, "utf8");
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Hosted "publish from inline source" — the filesystem-free counterpart
|
|
43
|
+
* to {@link publish}. An assistant passes the source files it just
|
|
44
|
+
* generated (including a `percher.toml`) as a tool argument; this builds
|
|
45
|
+
* a tarball entirely in memory and ships it through the curated
|
|
46
|
+
* `POST /apps/publish` endpoint (the only publish surface an OAuth token
|
|
47
|
+
* may reach). It NEVER reads `ctx.cwd`.
|
|
48
|
+
*
|
|
49
|
+
* Returns the same {@link PublishResult} shape + recovery contract as
|
|
50
|
+
* `publish` so an agent's `wait_for_deploy` chain works identically.
|
|
51
|
+
*/
|
|
52
|
+
export async function publishInline(ctx, input) {
|
|
53
|
+
try {
|
|
54
|
+
return await publishInlineInner(ctx, input);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Business-rule 403s / gate codes (plan limits, rate limits,
|
|
58
|
+
// quota) classify into a structured failure with the right title +
|
|
59
|
+
// suggestion. Anything else becomes a generic structured failure so
|
|
60
|
+
// an agent always gets JSON, never a raw stack.
|
|
61
|
+
const classified = classifyPublishApiError(err, {
|
|
62
|
+
configPath: TOML_FILENAME,
|
|
63
|
+
bundle: { fileCount: 0, bytes: 0 },
|
|
64
|
+
});
|
|
65
|
+
if (classified)
|
|
66
|
+
return classified;
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
const hint = err instanceof PercherApiError ? `API returned ${err.status}: ${err.message}` : undefined;
|
|
69
|
+
return failure({
|
|
70
|
+
title: "Unexpected error",
|
|
71
|
+
explanation: hint ?? message,
|
|
72
|
+
suggestion: "This may be a network issue or a Percher bug. If it persists, run percher_doctor for diagnostics.",
|
|
73
|
+
recovery: recoveryAsk({
|
|
74
|
+
prompt: `Unexpected inline-publish failure: ${hint ?? message}. Surface this to the user or run percher_doctor.`,
|
|
75
|
+
reasonCode: "unknown",
|
|
76
|
+
}),
|
|
77
|
+
summary: `Unexpected error: ${hint ?? message}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function publishInlineInner(ctx, input) {
|
|
82
|
+
const t0 = Date.now();
|
|
83
|
+
// ── 0. Size guard (defense in depth; the tool + API also enforce) ──
|
|
84
|
+
const contentBytes = totalContentBytes(input.files);
|
|
85
|
+
if (contentBytes > MAX_INLINE_PUBLISH_BYTES) {
|
|
86
|
+
return failure({
|
|
87
|
+
title: "Inline source too large",
|
|
88
|
+
explanation: `The inline files total ${(contentBytes / 1024 / 1024).toFixed(1)} MB, which exceeds the ${MAX_INLINE_PUBLISH_BYTES / 1024 / 1024} MB inline-publish limit.`,
|
|
89
|
+
suggestion: "Inline publishing is for small apps generated in-session. For a larger app, connect a GitHub/Forgejo repo and deploy with percher_deploy.",
|
|
90
|
+
recovery: recoveryAsk({
|
|
91
|
+
prompt: `Inline source is ${(contentBytes / 1024 / 1024).toFixed(1)} MB (limit ${MAX_INLINE_PUBLISH_BYTES / 1024 / 1024} MB). Use the connected-repo deploy path (percher_deploy) for a large app.`,
|
|
92
|
+
reasonCode: "unknown",
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// ── 1. Config — read percher.toml out of the inline files ──────────
|
|
97
|
+
ctx.status("[1/3] Reading inline percher.toml...");
|
|
98
|
+
const tomlFile = input.files.find((f) => normalizeForMatch(f.path) === TOML_FILENAME);
|
|
99
|
+
if (!tomlFile) {
|
|
100
|
+
return failure({
|
|
101
|
+
title: "percher.toml missing from inline source",
|
|
102
|
+
explanation: "Inline publishing requires a percher.toml among the files — it declares the app's runtime/framework and is read from the uploaded bundle.",
|
|
103
|
+
suggestion: "Add a percher.toml file (with at least [app] name and runtime) to the files array. See the percher.toml docs.",
|
|
104
|
+
recovery: recoveryFixConfig({
|
|
105
|
+
problems: [
|
|
106
|
+
{
|
|
107
|
+
file: TOML_FILENAME,
|
|
108
|
+
message: "Include a percher.toml in the files array (with at least [app] name and runtime) — see the percher.toml docs.",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
reasonCode: "config_missing",
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
let config;
|
|
116
|
+
try {
|
|
117
|
+
config = parse(tomlFile.content);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err instanceof PercherTomlError) {
|
|
121
|
+
const problems = tomlErrorToRecoveryProblems(err);
|
|
122
|
+
const isParse = err.code === "PARSE_ERROR";
|
|
123
|
+
return failure({
|
|
124
|
+
title: isParse
|
|
125
|
+
? "percher.toml has invalid TOML syntax"
|
|
126
|
+
: "percher.toml failed schema validation",
|
|
127
|
+
explanation: err.message,
|
|
128
|
+
suggestion: isParse
|
|
129
|
+
? "Fix the syntax error and retry."
|
|
130
|
+
: "Fix the schema issues (check the percher.toml spec) and retry.",
|
|
131
|
+
recovery: recoveryFixConfig({ problems, reasonCode: "config_invalid" }),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
// `name` arg overrides the percher.toml app name when supplied.
|
|
137
|
+
if (input.name) {
|
|
138
|
+
config.app.name = input.name;
|
|
139
|
+
}
|
|
140
|
+
// ── 2. Tarball — built entirely in memory from the inline files ────
|
|
141
|
+
ctx.status("[2/3] Packaging inline files...");
|
|
142
|
+
let tarball;
|
|
143
|
+
try {
|
|
144
|
+
tarball = createTarballFromFiles(input.files.map((f) => ({ path: f.path, data: new TextEncoder().encode(f.content) })), config);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
// createTarballFromFiles rejects traversal / absolute / duplicate
|
|
148
|
+
// paths — an untrusted-input problem, not a Percher bug.
|
|
149
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
150
|
+
return failure({
|
|
151
|
+
title: "Invalid file path in inline source",
|
|
152
|
+
explanation: message,
|
|
153
|
+
suggestion: "Use repo-relative forward-slash paths only — no leading /, no .., no drive letters, no duplicates.",
|
|
154
|
+
recovery: recoveryFixConfig({
|
|
155
|
+
problems: [{ file: "files", message }],
|
|
156
|
+
reasonCode: "config_invalid",
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const packageMs = Date.now() - t0;
|
|
161
|
+
// ── 3. Upload via the curated /apps/publish endpoint ───────────────
|
|
162
|
+
ctx.status("[3/3] Uploading & deploying...");
|
|
163
|
+
const uploadStart = Date.now();
|
|
164
|
+
const tarballBytes = new Uint8Array(await new Response(tarball.stream).arrayBuffer());
|
|
165
|
+
let deployment;
|
|
166
|
+
try {
|
|
167
|
+
deployment = await ctx.client.apps.publishInline({
|
|
168
|
+
name: config.app.name,
|
|
169
|
+
runtime: config.app.runtime,
|
|
170
|
+
framework: config.app.framework,
|
|
171
|
+
tarball: tarballBytes,
|
|
172
|
+
type: input.preview ? "preview" : undefined,
|
|
173
|
+
note: input.message,
|
|
174
|
+
noCache: input.noCache,
|
|
175
|
+
tarballHash: tarball.contentHash,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if (err instanceof PercherApiError && DEPLOY_GATE_CODES.has(err.code)) {
|
|
180
|
+
const classified = classifyPublishApiError(err, {
|
|
181
|
+
configPath: TOML_FILENAME,
|
|
182
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
183
|
+
});
|
|
184
|
+
if (classified)
|
|
185
|
+
return classified;
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
// The curated endpoint returns a serialized Deployment; the
|
|
190
|
+
// already_in_progress short-circuit comes back through the same
|
|
191
|
+
// type-union guard the upload path uses.
|
|
192
|
+
if (isDeployAlreadyInProgress(deployment)) {
|
|
193
|
+
const active = deployment;
|
|
194
|
+
return {
|
|
195
|
+
status: "already_in_progress",
|
|
196
|
+
fileCount: tarball.fileCount,
|
|
197
|
+
bytes: tarball.bytes,
|
|
198
|
+
recovery: recoveryWait({
|
|
199
|
+
app: config.app.name,
|
|
200
|
+
deployId: active.deployId,
|
|
201
|
+
reasonCode: active.deployStatus === "queued"
|
|
202
|
+
? "deploy_queued"
|
|
203
|
+
: active.deployStatus === "building"
|
|
204
|
+
? "deploy_building"
|
|
205
|
+
: "deploy_deploying",
|
|
206
|
+
}),
|
|
207
|
+
summary: `${config.app.name} already has a deploy in progress (${active.deployId}) — wait for it instead of queueing a duplicate.`,
|
|
208
|
+
configPath: TOML_FILENAME,
|
|
209
|
+
bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const uploadMs = Date.now() - uploadStart;
|
|
213
|
+
// The app object isn't returned by the publish endpoint; synthesize a
|
|
214
|
+
// minimal one from what we know (name + runtime + the deploy's url) so
|
|
215
|
+
// finalizeDeploy can shape the result. The deployment row carries the
|
|
216
|
+
// live/preview URL once polled, which is what the summary actually uses.
|
|
217
|
+
const app = {
|
|
218
|
+
id: deployment.appId,
|
|
219
|
+
name: config.app.name,
|
|
220
|
+
runtime: config.app.runtime,
|
|
221
|
+
framework: config.app.framework ?? null,
|
|
222
|
+
url: deployment.url ?? `https://${config.app.name}.percher.run`,
|
|
223
|
+
};
|
|
224
|
+
return finalizeDeploy({
|
|
225
|
+
ctx,
|
|
226
|
+
config,
|
|
227
|
+
input: {
|
|
228
|
+
preview: input.preview,
|
|
229
|
+
message: input.message,
|
|
230
|
+
noCache: input.noCache,
|
|
231
|
+
waitForLive: input.waitForLive,
|
|
232
|
+
},
|
|
233
|
+
app,
|
|
234
|
+
firstDeploy: false,
|
|
235
|
+
tarball: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
236
|
+
deployment,
|
|
237
|
+
t0,
|
|
238
|
+
packageMs,
|
|
239
|
+
uploadMs,
|
|
240
|
+
missingBuildEnvKeys: [],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/** Forward-slash + lowercase a path so a `./percher.toml` or `PERCHER.TOML`
|
|
244
|
+
* still matches. Only used to LOCATE the toml; the tarball keeps exact paths. */
|
|
245
|
+
function normalizeForMatch(path) {
|
|
246
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
247
|
+
}
|
|
248
|
+
/** Shared structured-failure shape for the inline path. `configPath` is the
|
|
249
|
+
* inline toml filename (there's no cwd), and bundle stats are zeroed when the
|
|
250
|
+
* failure preceded packaging. */
|
|
251
|
+
function failure(opts) {
|
|
252
|
+
return {
|
|
253
|
+
status: "failed",
|
|
254
|
+
fileCount: 0,
|
|
255
|
+
bytes: 0,
|
|
256
|
+
error: {
|
|
257
|
+
title: opts.title,
|
|
258
|
+
explanation: opts.explanation,
|
|
259
|
+
suggestion: opts.suggestion,
|
|
260
|
+
},
|
|
261
|
+
recovery: opts.recovery,
|
|
262
|
+
summary: opts.summary ?? `${opts.title} — ${opts.explanation}`,
|
|
263
|
+
configPath: TOML_FILENAME,
|
|
264
|
+
bundle: { fileCount: 0, bytes: 0 },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=publish-inline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-inline.js","sourceRoot":"","sources":["../../src/commands/publish-inline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type App, type BuildProblem, type Deployment } from "@percher/client";
|
|
2
|
+
import { type PercherConfig } from "@percher/toml";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import type { Context } from "../context.js";
|
|
4
5
|
import type { DeployPhase, ErrorClass } from "../error-classifier.js";
|
|
5
6
|
import { type PublishRecovery, type PublishStatus } from "../recovery.js";
|
|
7
|
+
import { type TarballResult } from "../tarball.js";
|
|
6
8
|
export declare const publishInputSchema: z.ZodObject<{
|
|
7
9
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
8
10
|
preview: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -217,5 +219,67 @@ export interface PublishResult {
|
|
|
217
219
|
* structured result that an agent can act on (retry, set env vars, ask the
|
|
218
220
|
* user for info, etc.).
|
|
219
221
|
*/
|
|
220
|
-
export declare function publish(ctx: Context, input?: PublishInput): Promise<PublishResult>;
|
|
222
|
+
export declare function publish(ctx: Context, input?: PublishInput, preParsedConfig?: PercherConfig): Promise<PublishResult>;
|
|
223
|
+
/**
|
|
224
|
+
* Post-upload deploy pipeline shared by the primary publish path and
|
|
225
|
+
* the post-login retry: buffer + upload the tarball (cache probe,
|
|
226
|
+
* idempotent retry loop), poll to a terminal status via pollDeployment
|
|
227
|
+
* (which retries transient network failures), and shape the
|
|
228
|
+
* PublishResult. Both entry paths must route through here so the
|
|
229
|
+
* re-auth path can't drift from the primary one.
|
|
230
|
+
*/
|
|
231
|
+
export declare function executeDeploy(opts: {
|
|
232
|
+
ctx: Context;
|
|
233
|
+
config: PercherConfig;
|
|
234
|
+
input: PublishInput;
|
|
235
|
+
app: App;
|
|
236
|
+
/** True when the app was created during this publish run. */
|
|
237
|
+
firstDeploy: boolean;
|
|
238
|
+
tarball: TarballResult;
|
|
239
|
+
/** Publish start timestamp (ms) — anchors totalSeconds in timing/summary. */
|
|
240
|
+
t0: number;
|
|
241
|
+
/** Time spent packaging the tarball (ms). */
|
|
242
|
+
packageMs: number;
|
|
243
|
+
/** Pre-upload build-env scan findings; empty when the scan didn't run. */
|
|
244
|
+
missingBuildEnvKeys: string[];
|
|
245
|
+
}): Promise<PublishResult>;
|
|
246
|
+
/**
|
|
247
|
+
* Post-deploy tail shared by the upload-publish path (`executeDeploy`)
|
|
248
|
+
* and the hosted inline-publish path (`publishInline`). Both produce a
|
|
249
|
+
* queued `Deployment` (from the upload route or the curated
|
|
250
|
+
* `/apps/publish` route) and need the identical "queued → poll → shape
|
|
251
|
+
* a PublishResult + recovery contract" behavior so an agent's
|
|
252
|
+
* `wait_for_deploy` chain works the same way regardless of entry point.
|
|
253
|
+
*
|
|
254
|
+
* Takes a bundle-stats summary rather than the live tarball stream
|
|
255
|
+
* because the stream has already been consumed by the time the deploy
|
|
256
|
+
* row exists.
|
|
257
|
+
*/
|
|
258
|
+
export declare function finalizeDeploy(opts: {
|
|
259
|
+
ctx: Context;
|
|
260
|
+
config: PercherConfig;
|
|
261
|
+
input: PublishInput;
|
|
262
|
+
app: App;
|
|
263
|
+
firstDeploy: boolean;
|
|
264
|
+
tarball: {
|
|
265
|
+
fileCount: number;
|
|
266
|
+
bytes: number;
|
|
267
|
+
};
|
|
268
|
+
/** The just-created queued deployment row. */
|
|
269
|
+
deployment: Deployment;
|
|
270
|
+
/** Publish start timestamp (ms) — anchors totalSeconds in timing/summary. */
|
|
271
|
+
t0: number;
|
|
272
|
+
/** Time spent packaging the tarball (ms). */
|
|
273
|
+
packageMs: number;
|
|
274
|
+
/** Time spent uploading the tarball (ms). */
|
|
275
|
+
uploadMs: number;
|
|
276
|
+
/** Pre-upload build-env scan findings; empty when the scan didn't run. */
|
|
277
|
+
missingBuildEnvKeys: string[];
|
|
278
|
+
/** True when the deploy short-circuited on a content-hash cache reuse. */
|
|
279
|
+
cacheReused?: boolean;
|
|
280
|
+
}): Promise<PublishResult>;
|
|
281
|
+
export declare function ensureApp(ctx: Context, config: PercherConfig): Promise<{
|
|
282
|
+
app: App;
|
|
283
|
+
firstDeploy: boolean;
|
|
284
|
+
}>;
|
|
221
285
|
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,YAAY,EAEjB,KAAK,UAAU,EAMhB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/publish.js
CHANGED
|
@@ -4,13 +4,14 @@ import { isDeployAlreadyInProgress, PercherApiError, PercherClient, saveConfig,
|
|
|
4
4
|
import { MAX_TARBALL_BYTES } from "@percher/shared";
|
|
5
5
|
import { solvePow } from "@percher/shared/pow";
|
|
6
6
|
import { TIMEOUTS } from "@percher/shared/timeouts";
|
|
7
|
-
import { PercherTomlError, parseFile } from "@percher/toml";
|
|
7
|
+
import { PercherTomlError, parseFile, validate } from "@percher/toml";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { renderDeployEvent } from "../event-renderer.js";
|
|
10
10
|
import { pollDeployment } from "../poll-deployment.js";
|
|
11
11
|
import { runDeployWithRetry } from "../publish-retry.js";
|
|
12
12
|
import { RECOVERY_NEEDS_LOGIN, RECOVERY_NONE, recoveryAsk, recoveryDoctor, recoveryFixConfig, recoveryWait, } from "../recovery.js";
|
|
13
13
|
import { createTarball } from "../tarball.js";
|
|
14
|
+
import { tomlErrorToRecoveryProblems } from "../toml-recovery.js";
|
|
14
15
|
import { scanForMissingBuildEnvRefs } from "./env-scan.js";
|
|
15
16
|
import { init } from "./init.js";
|
|
16
17
|
import { login } from "./login.js";
|
|
@@ -129,9 +130,9 @@ function buildLiveSummary(opts) {
|
|
|
129
130
|
* structured result that an agent can act on (retry, set env vars, ask the
|
|
130
131
|
* user for info, etc.).
|
|
131
132
|
*/
|
|
132
|
-
export async function publish(ctx, input = {}) {
|
|
133
|
+
export async function publish(ctx, input = {}, preParsedConfig) {
|
|
133
134
|
try {
|
|
134
|
-
return await publishInner(ctx, input);
|
|
135
|
+
return await publishInner(ctx, input, preParsedConfig);
|
|
135
136
|
}
|
|
136
137
|
catch (err) {
|
|
137
138
|
// Dogfood 2026-05-13 Fynd #2 — business-rule 403s (plan limits,
|
|
@@ -174,7 +175,7 @@ export async function publish(ctx, input = {}) {
|
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
|
-
async function publishInner(ctx, input) {
|
|
178
|
+
async function publishInner(ctx, input, preParsedConfig) {
|
|
178
179
|
const t0 = Date.now();
|
|
179
180
|
// ── 0. Identity banner ─────────────────────────────────────────────
|
|
180
181
|
//
|
|
@@ -213,7 +214,7 @@ async function publishInner(ctx, input) {
|
|
|
213
214
|
ctx.status("[1/4] Reading config...");
|
|
214
215
|
let config;
|
|
215
216
|
try {
|
|
216
|
-
config = await loadOrGenerate(ctx);
|
|
217
|
+
config = await loadOrGenerate(ctx, preParsedConfig);
|
|
217
218
|
}
|
|
218
219
|
catch (err) {
|
|
219
220
|
// FUTURE11 Fas 2 — differentiate by the toml package's typed error.
|
|
@@ -236,7 +237,7 @@ async function publishInner(ctx, input) {
|
|
|
236
237
|
suggestion: "Fix the syntax error and retry publish.",
|
|
237
238
|
},
|
|
238
239
|
recovery: recoveryFixConfig({
|
|
239
|
-
problems:
|
|
240
|
+
problems: tomlErrorToRecoveryProblems(err),
|
|
240
241
|
reasonCode: "config_invalid",
|
|
241
242
|
}),
|
|
242
243
|
summary: "percher.toml has invalid TOML syntax — fix and retry.",
|
|
@@ -245,10 +246,7 @@ async function publishInner(ctx, input) {
|
|
|
245
246
|
};
|
|
246
247
|
}
|
|
247
248
|
if (err.code === "VALIDATION_ERROR") {
|
|
248
|
-
const problems = (err
|
|
249
|
-
file: "percher.toml",
|
|
250
|
-
message: `${i.path.join(".") || "(root)"}: ${i.message}`,
|
|
251
|
-
}));
|
|
249
|
+
const problems = tomlErrorToRecoveryProblems(err);
|
|
252
250
|
const bullets = problems.map((p) => ` - ${p.message}`).join("\n");
|
|
253
251
|
const count = problems.length;
|
|
254
252
|
return {
|
|
@@ -294,6 +292,36 @@ async function publishInner(ctx, input) {
|
|
|
294
292
|
bundle: { fileCount: 0, bytes: 0 },
|
|
295
293
|
};
|
|
296
294
|
}
|
|
295
|
+
// A pre-parsed config skips the disk read, but a real upload still needs
|
|
296
|
+
// percher.toml IN the tarball — the API reads it from the extracted
|
|
297
|
+
// bundle. Without this guard a non-dry-run injected-config publish would
|
|
298
|
+
// upload a toml-less tarball and get rejected server-side with an opaque
|
|
299
|
+
// "percher.toml not found in tarball". Fail fast and locally instead.
|
|
300
|
+
// Dry-run never uploads, so it's exempt.
|
|
301
|
+
if (preParsedConfig && !input.dryRun && !existsSync(join(ctx.cwd, "percher.toml"))) {
|
|
302
|
+
return {
|
|
303
|
+
status: "failed",
|
|
304
|
+
fileCount: 0,
|
|
305
|
+
bytes: 0,
|
|
306
|
+
error: {
|
|
307
|
+
title: "percher.toml missing from the project",
|
|
308
|
+
explanation: "A pre-parsed config was supplied, but no percher.toml exists in the project directory. The file is packaged into the upload, so it must be present on disk for a real deploy.",
|
|
309
|
+
suggestion: "Run `percher init` (or write percher.toml) before publishing.",
|
|
310
|
+
},
|
|
311
|
+
recovery: recoveryFixConfig({
|
|
312
|
+
problems: [
|
|
313
|
+
{
|
|
314
|
+
file: "percher.toml",
|
|
315
|
+
message: "percher.toml must exist on disk for a real upload (it ships in the tarball).",
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
reasonCode: "config_missing",
|
|
319
|
+
}),
|
|
320
|
+
summary: "percher.toml missing — required on disk for a real upload.",
|
|
321
|
+
configPath: tomlPathFor(ctx.cwd),
|
|
322
|
+
bundle: { fileCount: 0, bytes: 0 },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
297
325
|
// ── 2. Tarball ─────────────────────────────────────────────────────
|
|
298
326
|
ctx.status("[2/4] Packaging files...");
|
|
299
327
|
const tarball = await createTarball({ cwd: ctx.cwd, config });
|
|
@@ -440,7 +468,7 @@ async function publishInner(ctx, input) {
|
|
|
440
468
|
* PublishResult. Both entry paths must route through here so the
|
|
441
469
|
* re-auth path can't drift from the primary one.
|
|
442
470
|
*/
|
|
443
|
-
async function executeDeploy(opts) {
|
|
471
|
+
export async function executeDeploy(opts) {
|
|
444
472
|
const { ctx, config, input, app, firstDeploy, tarball, t0, packageMs, missingBuildEnvKeys } = opts;
|
|
445
473
|
const uploadStart = Date.now();
|
|
446
474
|
ctx.status("[3/4] Uploading...");
|
|
@@ -551,11 +579,41 @@ async function executeDeploy(opts) {
|
|
|
551
579
|
// Surface the auto-replace one-shot signal — only the initial POST
|
|
552
580
|
// response carries it, so capture before the polling loop reassigns
|
|
553
581
|
// `deployment` from getDeployment() (which never sees the flag).
|
|
582
|
+
const uploadMs = Date.now() - uploadStart;
|
|
583
|
+
return finalizeDeploy({
|
|
584
|
+
ctx,
|
|
585
|
+
config,
|
|
586
|
+
input,
|
|
587
|
+
app,
|
|
588
|
+
firstDeploy,
|
|
589
|
+
tarball: { fileCount: tarball.fileCount, bytes: tarball.bytes },
|
|
590
|
+
deployment,
|
|
591
|
+
t0,
|
|
592
|
+
packageMs,
|
|
593
|
+
uploadMs,
|
|
594
|
+
missingBuildEnvKeys,
|
|
595
|
+
cacheReused: cacheReusedDeployment !== null,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Post-deploy tail shared by the upload-publish path (`executeDeploy`)
|
|
600
|
+
* and the hosted inline-publish path (`publishInline`). Both produce a
|
|
601
|
+
* queued `Deployment` (from the upload route or the curated
|
|
602
|
+
* `/apps/publish` route) and need the identical "queued → poll → shape
|
|
603
|
+
* a PublishResult + recovery contract" behavior so an agent's
|
|
604
|
+
* `wait_for_deploy` chain works the same way regardless of entry point.
|
|
605
|
+
*
|
|
606
|
+
* Takes a bundle-stats summary rather than the live tarball stream
|
|
607
|
+
* because the stream has already been consumed by the time the deploy
|
|
608
|
+
* row exists.
|
|
609
|
+
*/
|
|
610
|
+
export async function finalizeDeploy(opts) {
|
|
611
|
+
const { ctx, config, input, app, firstDeploy, tarball, t0, packageMs, uploadMs, missingBuildEnvKeys, cacheReused, } = opts;
|
|
612
|
+
let deployment = opts.deployment;
|
|
554
613
|
const replacedPreview = deployment.replacedPreview === true;
|
|
555
614
|
if (replacedPreview) {
|
|
556
615
|
ctx.status("Replaced previous preview...");
|
|
557
616
|
}
|
|
558
|
-
const uploadMs = Date.now() - uploadStart;
|
|
559
617
|
// FUTURE11 Phase 1 — async opt-in. Caller asked publish to return as
|
|
560
618
|
// soon as the deploy is queued so it can resume with
|
|
561
619
|
// percher_wait_for_deploy. The upload is finished and the server has
|
|
@@ -739,7 +797,7 @@ async function executeDeploy(opts) {
|
|
|
739
797
|
firstDeploy: firstDeploy || undefined,
|
|
740
798
|
cacheHit,
|
|
741
799
|
cacheStatus,
|
|
742
|
-
cacheReused:
|
|
800
|
+
cacheReused: cacheReused || undefined,
|
|
743
801
|
missingBuildEnvKeys: missingBuildEnvKeys.length > 0 ? missingBuildEnvKeys : undefined,
|
|
744
802
|
// Phase 7.9 — operator-facing trace key. Codex P2 follow-up on
|
|
745
803
|
// b8f469a: previously a CLI-generated UUID, but the API/worker
|
|
@@ -977,15 +1035,27 @@ async function handleAnonymousPublish(ctx, config, input, tarball) {
|
|
|
977
1035
|
};
|
|
978
1036
|
}
|
|
979
1037
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
980
|
-
async function loadOrGenerate(ctx) {
|
|
1038
|
+
async function loadOrGenerate(ctx, preParsed) {
|
|
1039
|
+
// Caller already parsed the config (e.g. init() just ran, or a test
|
|
1040
|
+
// injected one) — skip the redundant disk read + parse. Clone it so the
|
|
1041
|
+
// returned object is mutation-safe like the parseFile path: the publish
|
|
1042
|
+
// flow mutates config.app.name in place on the anonymous path, and the
|
|
1043
|
+
// caller's pre-parsed object must not be aliased by that.
|
|
1044
|
+
if (preParsed)
|
|
1045
|
+
return structuredClone(preParsed);
|
|
981
1046
|
const tomlPath = join(ctx.cwd, "percher.toml");
|
|
982
1047
|
if (!existsSync(tomlPath)) {
|
|
983
1048
|
ctx.status("No percher.toml found — detecting framework...");
|
|
984
|
-
|
|
1049
|
+
// init() builds the config in memory and writes it to disk. Validate
|
|
1050
|
+
// that object directly instead of reading + TOML-reparsing the file we
|
|
1051
|
+
// just wrote — validate() applies the same schema (incl. the env-section
|
|
1052
|
+
// transform) as parseFile, so the result is identical to the disk path.
|
|
1053
|
+
const generated = await init(ctx);
|
|
1054
|
+
return validate(generated.config);
|
|
985
1055
|
}
|
|
986
1056
|
return parseFile(tomlPath);
|
|
987
1057
|
}
|
|
988
|
-
async function ensureApp(ctx, config) {
|
|
1058
|
+
export async function ensureApp(ctx, config) {
|
|
989
1059
|
try {
|
|
990
1060
|
const app = await ctx.client.apps.get(config.app.name);
|
|
991
1061
|
return { app, firstDeploy: false };
|