@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.
Files changed (40) hide show
  1. package/dist/commands/appstore-kit.d.ts.map +1 -1
  2. package/dist/commands/appstore-kit.js +4 -1
  3. package/dist/commands/appstore-kit.js.map +1 -1
  4. package/dist/commands/deploy-repo.d.ts +28 -0
  5. package/dist/commands/deploy-repo.d.ts.map +1 -0
  6. package/dist/commands/deploy-repo.js +265 -0
  7. package/dist/commands/deploy-repo.js.map +1 -0
  8. package/dist/commands/doctor.d.ts +1 -1
  9. package/dist/commands/mcp.d.ts +37 -5
  10. package/dist/commands/mcp.d.ts.map +1 -1
  11. package/dist/commands/mcp.js +39 -6
  12. package/dist/commands/mcp.js.map +1 -1
  13. package/dist/commands/publish-inline.d.ts +28 -0
  14. package/dist/commands/publish-inline.d.ts.map +1 -0
  15. package/dist/commands/publish-inline.js +267 -0
  16. package/dist/commands/publish-inline.js.map +1 -0
  17. package/dist/commands/publish.d.ts +65 -1
  18. package/dist/commands/publish.d.ts.map +1 -1
  19. package/dist/commands/publish.js +86 -16
  20. package/dist/commands/publish.js.map +1 -1
  21. package/dist/index.d.ts +7 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +13 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/static-docker.d.ts.map +1 -1
  26. package/dist/static-docker.js +2 -1
  27. package/dist/static-docker.js.map +1 -1
  28. package/dist/tarball.d.ts +39 -1
  29. package/dist/tarball.d.ts.map +1 -1
  30. package/dist/tarball.js +92 -16
  31. package/dist/tarball.js.map +1 -1
  32. package/dist/templates.d.ts +6 -0
  33. package/dist/templates.d.ts.map +1 -1
  34. package/dist/templates.js +8 -0
  35. package/dist/templates.js.map +1 -1
  36. package/dist/toml-recovery.d.ts +11 -0
  37. package/dist/toml-recovery.d.ts.map +1 -0
  38. package/dist/toml-recovery.js +17 -0
  39. package/dist/toml-recovery.js.map +1 -0
  40. 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":"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"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"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"}
@@ -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: [{ file: "percher.toml", message: err.message }],
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.issues ?? []).map((i) => ({
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: cacheReusedDeployment !== null || undefined,
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
- await init(ctx);
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 };