void 0.10.11 → 0.10.13

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 (36) hide show
  1. package/dist/cli/cli.mjs +14 -14
  2. package/dist/cli/env-schema-probe.mjs +2 -2
  3. package/dist/{config-E03l1C_h.mjs → config-qHGgPuWT.mjs} +23 -0
  4. package/dist/{db-PZBsLSGb.mjs → db-C0i0sYMS.mjs} +3 -3
  5. package/dist/{deploy-DT8wsPZd.mjs → deploy-jBJT1fUT.mjs} +408 -66
  6. package/dist/{env-AAHU02L6.mjs → env-CZy5MorI.mjs} +1 -1
  7. package/dist/{env-validation-BdDlGhbN.mjs → env-validation-Dea3v3ej.mjs} +1 -1
  8. package/dist/{gen-oup1xBN0.mjs → gen-BzXf3Jh2.mjs} +1 -1
  9. package/dist/{git-metadata-CBKaL0v5.mjs → git-metadata-D-gurjzG.mjs} +6 -5
  10. package/dist/{github-cmd-BdNaOVNa.mjs → github-cmd-DKcGUNsj.mjs} +1 -1
  11. package/dist/{headers-BQknpzkn.mjs → headers-ChAADPQu.mjs} +1 -1
  12. package/dist/index.mjs +91 -43
  13. package/dist/{init-Dl2PKuQn.mjs → init-7JCAcKNQ.mjs} +2 -2
  14. package/dist/{node-BDx8pmhq.mjs → node-pRg81HqV.mjs} +3 -3
  15. package/dist/pages/index.mjs +3 -3
  16. package/dist/pages/islands-plugin.mjs +1 -1
  17. package/dist/{prepare-DOBTY0o4.mjs → prepare-BvvgAz-3.mjs} +3 -3
  18. package/dist/{preset-BGrvB4Bl.mjs → preset-BjyR3lzz.mjs} +55 -2
  19. package/dist/{provision-BLrCEBbI.mjs → provision-CPx2ZxsH.mjs} +2 -2
  20. package/dist/{route-types-COI2DsZv.mjs → route-types-z1jtHEi_.mjs} +8 -1
  21. package/dist/{scan-DYXkrasO.mjs → scan-ChWt4pX1.mjs} +12 -13
  22. package/dist/{scan-DEwlM_Xy.mjs → scan-NU4xKGci.mjs} +4 -1
  23. package/package.json +2 -2
  24. package/schema.json +18 -0
  25. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
  26. package/skills/void/docs/guide/app-types.md +43 -0
  27. package/skills/void/docs/guide/deployment.md +68 -1
  28. package/skills/void/docs/guide/edge/static-assets.md +37 -1
  29. package/skills/void/docs/guide/server-routing.md +21 -1
  30. package/skills/void/docs/guide/websockets.md +3 -0
  31. package/skills/void/docs/integrations/frameworks/overview.md +1 -0
  32. package/skills/void/docs/node_modules/void/CLAUDE.md +3 -1
  33. package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
  34. package/skills/void/docs/reference/cli.md +24 -0
  35. package/skills/void/docs/reference/config.md +42 -1
  36. package/skills/void/docs/reference/structure.md +1 -0
@@ -1,26 +1,26 @@
1
- import { n as __exportAll } from "./rolldown-runtime-DJK8HYOj.mjs";
1
+ import { n as __exportAll, r as __toESM } from "./rolldown-runtime-DJK8HYOj.mjs";
2
2
  import { a as join, c as resolve, i as isAbsolute, n as dirname, o as normalize, r as extname, s as relative, t as basename } from "./dist-DaKKDf8D.mjs";
3
- import { D as outro, E as note, S as confirm, T as log, a as cliTitle, j as isCancel, o as createSpinner, u as import_picocolors, w as intro } from "./agents-CtgBYqld.mjs";
3
+ import { D as outro, E as note, M as require_picocolors, S as confirm, T as log, a as cliTitle, j as isCancel, o as createSpinner, u as import_picocolors$1, w as intro } from "./agents-CtgBYqld.mjs";
4
4
  import { i as resolveProjectPaths, n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-BQd7OmIo.mjs";
5
- import { a as scanQueuesSync, i as scanWebSocketRoutesSync, o as scanJobsSync, r as scanRoutes, s as findVoidAuthConfig } from "./scan-DYXkrasO.mjs";
5
+ import { a as scanQueuesSync, i as scanWebSocketRoutesSync, o as scanJobsSync, r as scanRoutes, s as findVoidAuthConfig } from "./scan-ChWt4pX1.mjs";
6
6
  import { t as cfAccessHeaders } from "./cf-access-Bqw81xAf.mjs";
7
7
  import { C as ensureCloudflaredToken, S as writeProjectConfig, a as isExpiredTokenError, b as readProjectConfig, f as getToken, i as isCliOutdatedError, m as isStagingMode, n as PlatformClient, o as parsePlatformErrorBody, p as getTokenSource, t as PlatformApiError, u as getApiUrl } from "./client-Gb71-XkG.mjs";
8
- import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-E03l1C_h.mjs";
8
+ import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-qHGgPuWT.mjs";
9
9
  import { i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, r as detectFramework } from "./plugin-inference-DMeavIJ6.mjs";
10
10
  import { o as validateSsrEntry } from "./entry-D7yy4xVH.mjs";
11
11
  import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
12
- import { a as resolveProjectCommand, c as mergeAndExpand, i as hasBackendFiles, l as parseDotenvFile, n as detectPreset, o as resolveWranglerArgv, r as formatProjectCommand, t as FRAMEWORK_PRESETS, u as scanWranglerDotenvKeys } from "./preset-BGrvB4Bl.mjs";
12
+ import { a as resolveProjectCommand, c as mergeAndExpand, i as hasBackendFiles, l as parseDotenvFile, n as detectPreset, o as resolveWranglerArgv, r as formatProjectCommand, t as FRAMEWORK_PRESETS, u as scanWranglerDotenvKeys } from "./preset-BjyR3lzz.mjs";
13
13
  import { t as discoverSchema } from "./discover-CJHyvYfR.mjs";
14
14
  import { t as findEnvFile } from "./env-types-QBj-ndax.mjs";
15
- import { a as collectPlaintextVarWarningsFromVars, d as getDeployEnvDefaults, f as readEmittedWorkerVars, i as SelfHostSecretListError, l as fetchRemoteSecretNames, m as validateProdEnv, n as SelfHostFirstDeployError, o as deriveSelfHostEnvContextFromVars, p as selfHostSchemaFailureMessage, r as SelfHostSchemaError, s as dischargeSelfHostEnvContext, t as SelfHostEnvValidationError, u as formatEnvReport } from "./env-validation-BdDlGhbN.mjs";
15
+ import { a as collectPlaintextVarWarningsFromVars, d as getDeployEnvDefaults, f as readEmittedWorkerVars, i as SelfHostSecretListError, l as fetchRemoteSecretNames, m as validateProdEnv, n as SelfHostFirstDeployError, o as deriveSelfHostEnvContextFromVars, p as selfHostSchemaFailureMessage, r as SelfHostSchemaError, s as dischargeSelfHostEnvContext, t as SelfHostEnvValidationError, u as formatEnvReport } from "./env-validation-Dea3v3ej.mjs";
16
16
  import { n as writeDrizzleConfig } from "./config-CutEMNGJ.mjs";
17
17
  import { a as buildMigrationPlanBundle, c as collectMigrations, r as validateMigrations, t as assertJournalCoherence } from "./validate-Cw_RLeTj.mjs";
18
- import { t as scanPages } from "./scan-DEwlM_Xy.mjs";
18
+ import { t as scanPages } from "./scan-NU4xKGci.mjs";
19
19
  import { n as STAGING_PROXY_URL, t as DEFAULT_PROXY_URL } from "./proxy-D-3_D-Gl.mjs";
20
20
  import { i as promptProjectSetupAction, n as promptAndCreateProject, r as promptProjectSelection } from "./create-project-DsYvl3TB.mjs";
21
21
  import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-BBMtLLV9.mjs";
22
- import { t as collectGitDeployMetadata } from "./git-metadata-CBKaL0v5.mjs";
23
- import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-BQknpzkn.mjs";
22
+ import { t as collectGitDeployMetadata } from "./git-metadata-D-gurjzG.mjs";
23
+ import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-ChAADPQu.mjs";
24
24
  import { t as promptForLoginToken } from "./login-BT3H8PN3.mjs";
25
25
  import { createRequire } from "node:module";
26
26
  import { copyFileSync, cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
@@ -56,24 +56,26 @@ const VOID_INTERNAL_WORKER_FIRST = ["/__void", "/__void/*"];
56
56
  function resolveAssetRoutingPolicy(inputs) {
57
57
  const workerFirst = workerOwnsHtmlRouting(inputs);
58
58
  const apiWorkerFirst = !workerFirst && (inputs.hasApiRoutes === true || inputs.authEnabled === true);
59
+ const assetConfig = workerFirst ? {
60
+ not_found_handling: "none",
61
+ run_worker_first: ["/**"]
62
+ } : apiWorkerFirst ? {
63
+ not_found_handling: "single-page-application",
64
+ run_worker_first: [
65
+ "/api",
66
+ "/api/*",
67
+ ...VOID_INTERNAL_WORKER_FIRST
68
+ ]
69
+ } : {
70
+ not_found_handling: "single-page-application",
71
+ run_worker_first: [...VOID_INTERNAL_WORKER_FIRST]
72
+ };
73
+ if (inputs.notFound !== void 0) assetConfig.not_found_handling = inputs.notFound;
59
74
  return {
60
- assetConfig: workerFirst ? {
61
- not_found_handling: "none",
62
- run_worker_first: ["/**"]
63
- } : apiWorkerFirst ? {
64
- not_found_handling: "single-page-application",
65
- run_worker_first: [
66
- "/api",
67
- "/api/*",
68
- ...VOID_INTERNAL_WORKER_FIRST
69
- ]
70
- } : {
71
- not_found_handling: "single-page-application",
72
- run_worker_first: [...VOID_INTERNAL_WORKER_FIRST]
73
- },
75
+ assetConfig,
74
76
  apiWorkerFirst,
75
77
  workerFirst,
76
- workerSpaFallback: workerFirst && !inputs.isSsr && !inputs.hasPages
78
+ workerSpaFallback: workerFirst && !inputs.isSsr && !inputs.hasPages && (inputs.notFound === void 0 || inputs.notFound === "single-page-application")
77
79
  };
78
80
  }
79
81
  const SANDBOX_MIGRATION_TAG = "void-sandbox-v1";
@@ -2113,7 +2115,7 @@ async function uploadOne(item, cliLog, fetchImpl) {
2113
2115
  path: item.path,
2114
2116
  attempt: attempt + 1,
2115
2117
  status: res.status,
2116
- body: truncate(errBody)
2118
+ body: truncate$1(errBody)
2117
2119
  });
2118
2120
  await sleep(RETRY_BACKOFF_MS[attempt]);
2119
2121
  attempt++;
@@ -2124,9 +2126,9 @@ async function uploadOne(item, cliLog, fetchImpl) {
2124
2126
  path: item.path,
2125
2127
  durationMs: Date.now() - startedAt,
2126
2128
  status: res.status,
2127
- body: truncate(errBody)
2129
+ body: truncate$1(errBody)
2128
2130
  });
2129
- throw new DirectR2PutError(res.status, item.hash, item.path, truncate(errBody));
2131
+ throw new DirectR2PutError(res.status, item.hash, item.path, truncate$1(errBody));
2130
2132
  }
2131
2133
  }
2132
2134
  async function safeReadText(res) {
@@ -2136,7 +2138,7 @@ async function safeReadText(res) {
2136
2138
  return "";
2137
2139
  }
2138
2140
  }
2139
- function truncate(s) {
2141
+ function truncate$1(s) {
2140
2142
  return s.length > 500 ? `${s.slice(0, 500)}...` : s;
2141
2143
  }
2142
2144
  function sleep(ms) {
@@ -2268,6 +2270,23 @@ async function safeText(res) {
2268
2270
  //#endregion
2269
2271
  //#region src/cli/log-file.ts
2270
2272
  /**
2273
+ * Flatten an already-serialized `ErrChainNode` (as read back out of a JSONL
2274
+ * record) to a single inline string. Mirrors `formatErrorChainInline`, which
2275
+ * does the same job for a live `Error` before it is written.
2276
+ */
2277
+ function formatErrorNodeInline(node, depth = 0) {
2278
+ if (node == null || typeof node !== "object") return String(node);
2279
+ const message = node.message == null ? "" : String(node.message);
2280
+ if (depth > 5) return message;
2281
+ let head = node.name ? `${String(node.name)}: ${message}` : message;
2282
+ const tags = [];
2283
+ if (node.code !== void 0) tags.push(`code=${node.code}`);
2284
+ if (node.errno !== void 0) tags.push(`errno=${node.errno}`);
2285
+ if (tags.length > 0) head = `${head} (${tags.join(" ")})`;
2286
+ if (node.cause != null) return `${head} -> ${formatErrorNodeInline(node.cause, depth + 1)}`;
2287
+ return head;
2288
+ }
2289
+ /**
2271
2290
  * Walk `err.cause` up to 6 levels and return a plain JSON-friendly chain.
2272
2291
  * Skips stack traces deliberately — keeps output line-oriented and readable.
2273
2292
  */
@@ -2420,6 +2439,160 @@ function openDeployLog(opts) {
2420
2439
  };
2421
2440
  }
2422
2441
  //#endregion
2442
+ //#region src/cli/log-summary.ts
2443
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
2444
+ const FIELD_VALUE_MAX = 80;
2445
+ const LINE_MAX = 160;
2446
+ const MSG_COLUMN_MAX = 28;
2447
+ const ELAPSED_WIDTH = 7;
2448
+ const LEVEL_WIDTH = 5;
2449
+ /** Width of `' ' + elapsed + ' '`, used to indent the error sub-lines. */
2450
+ const DETAIL_INDENT = " ".repeat(11);
2451
+ /**
2452
+ * `runDeploy` writes this record last, and its `err` field is the very message
2453
+ * printed above the summary. Keeping it out of the error block avoids echoing
2454
+ * the same text twice; it still shows up as one line in the tail.
2455
+ */
2456
+ const TERMINAL_ERROR_MSG = "deploy_end";
2457
+ const RESERVED_KEYS = /* @__PURE__ */ new Set([
2458
+ "ts",
2459
+ "elapsedMs",
2460
+ "lvl",
2461
+ "msg",
2462
+ "err"
2463
+ ]);
2464
+ /**
2465
+ * The trace was a file the user opened by hand; the summary publishes it to
2466
+ * whatever reads stderr, which in CI is a stored, often public, log.
2467
+ *
2468
+ * Scrubbing here covers only credentials this code emits itself — a presigned
2469
+ * URL it built, an auth header it set, a field it named. Scrubbing user data is
2470
+ * left to the CI platform, which owns the secrets and masks them before the log
2471
+ * is written. GitHub Actions does this for everything under `secrets.*`. Void
2472
+ * does not guess at credential-shaped names or at credentials embedded in
2473
+ * values the user supplied.
2474
+ */
2475
+ /** Query parameters whose value must never reach a CI log. */
2476
+ const SIGNED_QUERY_RE = /([?&](?:X-Amz-[A-Za-z0-9-]+|[Ss]ignature|sig|token|access_token|key)=)[^&\s]+/g;
2477
+ const BEARER_RE = /\b(Bearer\s+)\S+/g;
2478
+ /**
2479
+ * Field keys whose value is masked whole, whatever it looks like. `auth` is
2480
+ * deliberately absent — it matches metadata keys such as `authMethod` far more
2481
+ * often than credentials.
2482
+ */
2483
+ const SECRET_KEY_RE = /token|secret|password|passwd|credential|cookie|signature|api[-_]?key|private[-_]?key/i;
2484
+ function redact(text) {
2485
+ return text.replace(SIGNED_QUERY_RE, "$1[redacted]").replace(BEARER_RE, "$1[redacted]");
2486
+ }
2487
+ function truncate(text, max) {
2488
+ return text.length <= max ? text : `${text.slice(0, max)}...`;
2489
+ }
2490
+ /** Collapse every run of whitespace so one record always renders as one line. */
2491
+ function oneLine(text) {
2492
+ return text.replace(/\s+/g, " ").trim();
2493
+ }
2494
+ function stringifyValue(value) {
2495
+ if (typeof value === "string") return value;
2496
+ if (value === null || typeof value === "number" || typeof value === "boolean") return String(value);
2497
+ try {
2498
+ return JSON.stringify(value) ?? String(value);
2499
+ } catch {
2500
+ return String(value);
2501
+ }
2502
+ }
2503
+ /** Render one `key=value` pair, collapsed, redacted, and length-capped. */
2504
+ function formatField(key, value) {
2505
+ if (SECRET_KEY_RE.test(key)) return `${key}=[redacted]`;
2506
+ return `${key}=${truncate(redact(oneLine(stringifyValue(value))), FIELD_VALUE_MAX)}`;
2507
+ }
2508
+ function fieldPairs(record) {
2509
+ const pairs = [];
2510
+ for (const [key, value] of Object.entries(record)) {
2511
+ if (RESERVED_KEYS.has(key) || value === void 0) continue;
2512
+ pairs.push(formatField(key, value));
2513
+ }
2514
+ if (record.err !== void 0) pairs.push(`err=${truncate(redact(oneLine(formatErrorNodeInline(record.err))), FIELD_VALUE_MAX)}`);
2515
+ return pairs;
2516
+ }
2517
+ function formatElapsed(elapsedMs) {
2518
+ return `${((typeof elapsedMs === "number" && Number.isFinite(elapsedMs) ? elapsedMs : 0) / 1e3).toFixed(1)}s`.padStart(ELAPSED_WIDTH);
2519
+ }
2520
+ function colorLevel(level, padded) {
2521
+ if (level === "error") return import_picocolors.default.red(padded);
2522
+ if (level === "warn") return import_picocolors.default.yellow(padded);
2523
+ return import_picocolors.default.dim(padded);
2524
+ }
2525
+ function parseRecords(lines) {
2526
+ const records = [];
2527
+ for (const raw of lines) {
2528
+ const trimmed = raw.trim();
2529
+ if (!trimmed) continue;
2530
+ try {
2531
+ const parsed = JSON.parse(trimmed);
2532
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) records.push(parsed);
2533
+ } catch {}
2534
+ }
2535
+ return records;
2536
+ }
2537
+ /** One aligned line: ` 8.9s error deploy_server_error message=...`. */
2538
+ function formatTailLine(record, msgWidth) {
2539
+ const level = typeof record.lvl === "string" ? record.lvl : "info";
2540
+ const msg = typeof record.msg === "string" ? record.msg : "";
2541
+ const elapsed = formatElapsed(record.elapsedMs);
2542
+ const paddedLevel = level.padEnd(LEVEL_WIDTH);
2543
+ const paddedMsg = msg.padEnd(msgWidth);
2544
+ const head = ` ${elapsed} ${paddedLevel} ${paddedMsg}`;
2545
+ const fields = fieldPairs(record).join(" ");
2546
+ if (!fields) return ` ${elapsed} ${colorLevel(level, paddedLevel)} ${msg}`;
2547
+ const budget = Math.max(20, LINE_MAX - head.length);
2548
+ return ` ${elapsed} ${colorLevel(level, paddedLevel)} ${paddedMsg} ${truncate(fields, budget)}`;
2549
+ }
2550
+ /** An error entry: a headline, then one indented line per field. */
2551
+ function formatErrorEntry(record) {
2552
+ const msg = typeof record.msg === "string" ? record.msg : "error";
2553
+ const lines = [` ${formatElapsed(record.elapsedMs)} ${import_picocolors.default.red(msg)}`];
2554
+ for (const pair of fieldPairs(record)) lines.push(`${DETAIL_INDENT}${pair}`);
2555
+ return lines;
2556
+ }
2557
+ /**
2558
+ * Turn raw JSONL lines into the block appended to a failed deploy's error
2559
+ * message. Returns `null` when there is nothing worth printing.
2560
+ *
2561
+ * The block has two parts: every `error` record with its flattened cause
2562
+ * chain, then the last `LOG_TAIL_LIMIT` records for timeline context.
2563
+ */
2564
+ function formatDeployLogSummary(lines) {
2565
+ const records = parseRecords(lines);
2566
+ if (records.length === 0) return null;
2567
+ const blocks = [];
2568
+ const errors = records.filter((r) => r.lvl === "error" && r.msg !== TERMINAL_ERROR_MSG);
2569
+ if (errors.length > 0) {
2570
+ const shown = errors.slice(0, 5);
2571
+ const errorLines = shown.flatMap((record) => formatErrorEntry(record));
2572
+ if (errors.length > shown.length) errorLines.push(`${DETAIL_INDENT}(+${errors.length - shown.length} more)`);
2573
+ blocks.push([import_picocolors.default.bold("Errors"), ...errorLines].join("\n"));
2574
+ }
2575
+ const tail = records.slice(-20);
2576
+ const msgWidth = Math.min(MSG_COLUMN_MAX, tail.reduce((max, r) => Math.max(max, typeof r.msg === "string" ? r.msg.length : 0), 0));
2577
+ const heading = records.length > tail.length ? `Last 20 of ${records.length} entries` : `Log (${records.length} ${records.length === 1 ? "entry" : "entries"})`;
2578
+ blocks.push([import_picocolors.default.bold(heading), ...tail.map((record) => formatTailLine(record, msgWidth))].join("\n"));
2579
+ return `\n${blocks.join("\n\n")}`;
2580
+ }
2581
+ /**
2582
+ * Read a deploy JSONL log and format its summary. Returns `null` for a missing
2583
+ * path or any read failure — a postmortem aid must never mask the real error.
2584
+ */
2585
+ function buildDeployLogSummary(path) {
2586
+ if (!path) return null;
2587
+ let text;
2588
+ try {
2589
+ text = readFileSync(path, "utf-8");
2590
+ } catch {
2591
+ return null;
2592
+ }
2593
+ return formatDeployLogSummary(text.split("\n"));
2594
+ }
2595
+ //#endregion
2423
2596
  //#region src/shared/utils.ts
2424
2597
  const isWindows = typeof process !== "undefined" && process.platform === "win32";
2425
2598
  function slash(p) {
@@ -2469,11 +2642,24 @@ function stripDevOnlyRuleFields(rules) {
2469
2642
  *
2470
2643
  * When the user has no fallbacks configured, the platform-level SPA switch
2471
2644
  * is left intact (zero worker-side overhead for the common case).
2472
- */
2473
- function resolveSpaFallbackChain(appType, fallbackRules) {
2645
+ *
2646
+ * `notFound` is the explicit `routing.notFound` from void.json. It overrides
2647
+ * the value this function would otherwise infer from `appType`, mirroring
2648
+ * `resolveAssetRoutingPolicy` on the worker-with-assets path. It is resolved
2649
+ * *before* the SPA branch, not patched onto the result afterwards: an override
2650
+ * away from `single-page-application` must also skip the synthetic
2651
+ * `/* -> /index.html` rule below, otherwise the SPA shell would still be
2652
+ * served — just through a fallback rule instead of the platform switch — and
2653
+ * the override would be a no-op. This matters because `detectPreset`
2654
+ * classifies any Vite project without backend files as `spa`, so a plain Vite
2655
+ * MPA lands on `appType: 'spa'` and `routing.notFound: '404-page'` is its only
2656
+ * escape hatch.
2657
+ */
2658
+ function resolveSpaFallbackChain(appType, fallbackRules, notFound) {
2474
2659
  const userRules = fallbackRules && fallbackRules.length > 0 ? fallbackRules : void 0;
2475
- if (appType !== "spa") return {
2476
- assetConfig: { not_found_handling: appType === "static" ? "404-page" : "none" },
2660
+ const handling = notFound ?? (appType === "spa" ? "single-page-application" : appType === "static" ? "404-page" : "none");
2661
+ if (handling !== "single-page-application") return {
2662
+ assetConfig: { not_found_handling: handling },
2477
2663
  fallbackRules: userRules
2478
2664
  };
2479
2665
  if (!userRules) return {
@@ -2724,17 +2910,21 @@ function toLiveManifest(live) {
2724
2910
  };
2725
2911
  }
2726
2912
  /**
2727
- * If the user configured `routing.fallbacks` on a non-SPA app, the platform
2728
- * still applies the rules but no `/* -> /index.html` SPA shell is synthesised
2729
- * — unmatched paths fall through to the platform 404. Returns the warning
2913
+ * If the user configured `routing.fallbacks` on an app whose resolved
2914
+ * `not_found_handling` is not `single-page-application`, the platform still
2915
+ * applies the rules but no `/* -> /index.html` SPA shell is synthesised —
2916
+ * unmatched paths fall through to the platform 404. Returns the warning
2730
2917
  * message string when that mismatch is detected, or `null` otherwise. Pure
2731
2918
  * function; callers decide whether/how to surface the message (CLI progress,
2732
2919
  * `console.warn`, etc.).
2920
+ *
2921
+ * The message names `not_found_handling` rather than the app type because an
2922
+ * explicit `routing.notFound` can land here too — the app may well be a SPA.
2733
2923
  */
2734
2924
  function detectNonSpaFallbackWarning(assetConfig, fallbackRules) {
2735
2925
  if (assetConfig?.not_found_handling === "single-page-application") return null;
2736
2926
  if (!fallbackRules || fallbackRules.length === 0) return null;
2737
- return "routing.fallbacks is set but appType is not \"spa\"; fallbacks will be applied, but no \"/* -> /index.html\" SPA shell fallback is synthesised — unmatched paths fall through to the platform 404.";
2927
+ return "routing.fallbacks is set but not_found_handling is not \"single-page-application\"; fallbacks will be applied, but no \"/* -> /index.html\" SPA shell fallback is synthesised — unmatched paths fall through to the platform 404.";
2738
2928
  }
2739
2929
  /**
2740
2930
  * Build a Void-app deploy manifest from the inferred bindings + per-feature
@@ -2804,6 +2994,7 @@ function buildVoidManifest(opts) {
2804
2994
  if (options?.workerMain) manifest.workerMain = options.workerMain;
2805
2995
  if (options?.compatibilityDate) manifest.compatibilityDate = options.compatibilityDate;
2806
2996
  if (options?.compatibilityFlags && options.compatibilityFlags.length > 0) manifest.compatibilityFlags = options.compatibilityFlags;
2997
+ if (options?.cpuMs) manifest.limits = { cpuMs: options.cpuMs };
2807
2998
  if (hashedAssetsPrefix) manifest.hashedAssetsPrefix = hashedAssetsPrefix;
2808
2999
  if (headerRules && headerRules.length > 0) manifest.headerRules = headerRules;
2809
3000
  if (redirectRules && redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(redirectRules);
@@ -2839,8 +3030,8 @@ function collectWorkerFiles(workerDir) {
2839
3030
  * Build a static-deploy manifest. Pure shape transformation — no I/O.
2840
3031
  */
2841
3032
  function buildStaticManifest(opts) {
2842
- const { appType, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules } = opts;
2843
- const { assetConfig, fallbackRules: resolvedFallbackRules } = resolveSpaFallbackChain(appType, fallbackRules);
3033
+ const { appType, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, notFound } = opts;
3034
+ const { assetConfig, fallbackRules: resolvedFallbackRules } = resolveSpaFallbackChain(appType, fallbackRules, notFound);
2844
3035
  const manifest = {
2845
3036
  version: 3,
2846
3037
  assetManifestVersion: 2,
@@ -2897,6 +3088,7 @@ function buildFrameworkManifest(opts) {
2897
3088
  manifest.workerMain = po?.workerMain ?? opts.workerMain;
2898
3089
  if (po?.compatibilityDate) manifest.compatibilityDate = po.compatibilityDate;
2899
3090
  if (po?.compatibilityFlags && po.compatibilityFlags.length > 0) manifest.compatibilityFlags = po.compatibilityFlags;
3091
+ if (po?.cpuMs) manifest.limits = { cpuMs: po.cpuMs };
2900
3092
  if (opts.hashedAssetsPrefix) manifest.hashedAssetsPrefix = opts.hashedAssetsPrefix;
2901
3093
  if (opts.headerRules && opts.headerRules.length > 0) manifest.headerRules = opts.headerRules;
2902
3094
  if (opts.redirectRules && opts.redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(opts.redirectRules);
@@ -3335,6 +3527,7 @@ var deploy_exports = /* @__PURE__ */ __exportAll({
3335
3527
  ensurePreviouslyDeployedLiveBindingConfig: () => ensurePreviouslyDeployedLiveBindingConfig,
3336
3528
  generateDeployWrapperIfNeeded: () => generateDeployWrapperIfNeeded,
3337
3529
  hasDeploySchemaDrift: () => hasDeploySchemaDrift,
3530
+ hasPrerenderSources: () => hasPrerenderSources,
3338
3531
  hashWranglerUploader: () => hashWranglerUploader,
3339
3532
  loadDeployEnv: () => loadDeployEnv,
3340
3533
  parseRootWranglerConfig: () => parseRootWranglerConfig,
@@ -3344,6 +3537,7 @@ var deploy_exports = /* @__PURE__ */ __exportAll({
3344
3537
  resolveDeployWorkerMain: () => resolveDeployWorkerMain,
3345
3538
  resolveDeployWranglerCompat: () => resolveDeployWranglerCompat,
3346
3539
  resolveDistDir: () => resolveDistDir,
3540
+ resolveFullDeployBuildCommand: () => resolveFullDeployBuildCommand,
3347
3541
  resolveStaticBuildCommand: () => resolveStaticBuildCommand,
3348
3542
  resolveStaticDeployPreset: () => resolveStaticDeployPreset,
3349
3543
  resolveWorkerDirName: () => resolveWorkerDirName,
@@ -3356,6 +3550,7 @@ var deploy_exports = /* @__PURE__ */ __exportAll({
3356
3550
  streamDeployEvents: () => streamDeployEvents,
3357
3551
  validateAuthMigrationSchema: () => validateAuthMigrationSchema,
3358
3552
  verifyGeneratedDeployConfig: () => verifyGeneratedDeployConfig,
3553
+ warnIgnoredNotFoundForFramework: () => warnIgnoredNotFoundForFramework,
3359
3554
  warnRedundantForceOn3xx: () => warnRedundantForceOn3xx,
3360
3555
  warnUnpublishedRouting: () => warnUnpublishedRouting
3361
3556
  });
@@ -3368,7 +3563,7 @@ let loadedProvisionModule;
3368
3563
  * so a managed deploy never imports it — and thus never loads `proper-lockfile`.
3369
3564
  */
3370
3565
  async function loadProvisionModule() {
3371
- loadedProvisionModule ??= await import("./provision-BLrCEBbI.mjs");
3566
+ loadedProvisionModule ??= await import("./provision-CPx2ZxsH.mjs");
3372
3567
  return loadedProvisionModule;
3373
3568
  }
3374
3569
  /**
@@ -3490,6 +3685,13 @@ async function runDeploy(root, options) {
3490
3685
  cliLog.info("deploy_end", { ok: true });
3491
3686
  } catch (err) {
3492
3687
  cliLog.error("deploy_end", err, { ok: false });
3688
+ await cliLog.close().catch(() => {});
3689
+ try {
3690
+ if (err instanceof Error) {
3691
+ const summary = buildDeployLogSummary(cliLog.path);
3692
+ if (summary) err.message = `${err.message}\n${summary}`;
3693
+ }
3694
+ } catch {}
3493
3695
  throw err;
3494
3696
  } finally {
3495
3697
  await cliLog.close().catch(() => {});
@@ -3512,17 +3714,67 @@ async function runDeploy(root, options) {
3512
3714
  * `cfAccessHeaders(process.env)` / the cloudflared pair-guard AFTER the build
3513
3715
  * step. Only the service-token pair is scrubbed; the cloudflared
3514
3716
  * `CF_ACCESS_TOKEN` JWT (dev path) is left untouched.
3515
- */
3516
- function buildEnv(extra) {
3717
+ *
3718
+ * `localBinRoot` is an explicit OPT-IN, not a default. Build commands are run
3719
+ * through a shell (`execSync`) and a user-configured `inference.build` (e.g.
3720
+ * `vitepress build && vite build`) names bare binaries that only exist in the
3721
+ * project's own `node_modules/.bin`; under the build container's
3722
+ * `npx --yes void deploy` that happens to work — npx prepends every ancestor
3723
+ * `.bin` itself — but a directly invoked `node_modules/.bin/void deploy` or a
3724
+ * globally installed `void` inherits no such PATH and the bare binary fails to
3725
+ * resolve. Passing it makes the project's bin dir shadow the system one for
3726
+ * that child, so it belongs ONLY on the three build execs. The other callers
3727
+ * (`getGitCommit`, the drizzle-kit spawn) exec a system binary or an absolute
3728
+ * path and must not hand a dependency-shipped `node_modules/.bin/git` a child
3729
+ * process holding `VOID_TOKEN`.
3730
+ */
3731
+ function buildEnv(extra, options) {
3517
3732
  const env = {
3518
3733
  ...process.env,
3519
3734
  ...extra
3520
3735
  };
3521
3736
  delete env.CF_ACCESS_CLIENT_ID;
3522
3737
  delete env.CF_ACCESS_CLIENT_SECRET;
3738
+ return options?.localBinRoot === void 0 ? env : prependLocalBinToPath(env, options.localBinRoot);
3739
+ }
3740
+ /**
3741
+ * Prepend `<root>/node_modules/.bin` to the child env's PATH, preserving
3742
+ * whatever was already there.
3743
+ *
3744
+ * PATH is looked up case-insensitively and written back under the SAME key the
3745
+ * env already used: Windows reports it as `Path`, and `{ ...process.env }`
3746
+ * drops the case-insensitive lookup that `process.env` itself provides, so
3747
+ * hardcoding `PATH` would leave the real variable untouched and add a dead one.
3748
+ */
3749
+ function prependLocalBinToPath(env, root) {
3750
+ const binDir = join(root, "node_modules", ".bin");
3751
+ const delimiter = process.platform === "win32" ? ";" : ":";
3752
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
3753
+ const existing = env[pathKey];
3754
+ env[pathKey] = existing ? `${binDir}${delimiter}${existing}` : binDir;
3523
3755
  return env;
3524
3756
  }
3525
3757
  /**
3758
+ * Marks a build child as driven by a MANAGED `void deploy`.
3759
+ *
3760
+ * The platform resolves the asset routing policy out-of-band (`resolveAssetConfig`
3761
+ * → `manifest.assetConfig`) and applies it to the uploaded script, so settings the
3762
+ * Vite plugin cannot safely write into the built wrangler config — `routing.notFound`
3763
+ * for a Class A framework, which needs an `assets` policy Void does not own there —
3764
+ * are still honored on this path. `void:framework-build-wrapper` warns about exactly
3765
+ * those when this flag is ABSENT, i.e. on a self-hosted `vite build && wrangler deploy`.
3766
+ *
3767
+ * Passed through the CHILD env only (never `process.env`), the same way
3768
+ * `VOID_DEPLOY_PROJECT_ID` is, so it cannot leak into an unrelated child process or a
3769
+ * later `void dev` in the same shell.
3770
+ *
3771
+ * Deliberately NOT set by `runCloudflareDeploy` (`--backend cloudflare`): that flow
3772
+ * hands the built output to `wrangler deploy` against the user's own account and IS the
3773
+ * self-host path the warning is about. (It rejects framework SSR outright today, so no
3774
+ * Class A build reaches it either way.)
3775
+ */
3776
+ const MANAGED_DEPLOY_BUILD_ENV = { VOID_DEPLOY_BUILD: "1" };
3777
+ /**
3526
3778
  * Deploy-time env selectors that pin the WRONG worker/config out from under the
3527
3779
  * top-level config this flow provisions + preflights. Scrubbed from every child env
3528
3780
  * and fenced in the project dotenv (see below), on top of the provisioner's transport
@@ -5307,7 +5559,7 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
5307
5559
  log.error(`deploy: ${getErrorMessage(err)}`);
5308
5560
  return exit(1);
5309
5561
  }
5310
- if (scanWebSocketRoutesSync(root, paths).length > 0) {
5562
+ if (scanWebSocketRoutesSync(root, paths, "prod").length > 0) {
5311
5563
  log.error([
5312
5564
  "deploy: --backend cloudflare does not yet support WebSocket / Durable Object routes.",
5313
5565
  "A `.ws.ts` route makes the build emit a key-value-backed Durable Object namespace",
@@ -5847,7 +6099,7 @@ async function runDeployInner(root, options, cliLog) {
5847
6099
  name: detected.name,
5848
6100
  class: detected.class
5849
6101
  });
5850
- const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root));
6102
+ const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root), process.env, buildEnv());
5851
6103
  const fwPreset = FRAMEWORK_PRESETS[detected.name];
5852
6104
  if (detected.class === "b" || detected.class === "c") {
5853
6105
  if (!fwPreset) {
@@ -5855,18 +6107,21 @@ async function runDeployInner(root, options, cliLog) {
5855
6107
  process.exit(1);
5856
6108
  }
5857
6109
  await runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, options?.skipBuild, gitMetadata, cliLog);
5858
- } else await runFullDeploy(root, config, client, fwPreset, options?.skipBuild, gitMetadata, detected, cliLog);
6110
+ } else await runFullDeploy(root, config, client, voidConfig, fwPreset, options?.skipBuild, gitMetadata, detected, cliLog);
5859
6111
  return;
5860
6112
  }
5861
- if (!preset && !voidConfig.inference?.appType) preset = detectPreset(root, { spa: options?.spa });
5862
- const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root));
6113
+ if (!preset && !voidConfig.inference?.appType) preset = detectPreset(root, {
6114
+ spa: options?.spa,
6115
+ strictBackend: true
6116
+ });
6117
+ const gitMetadata = collectGitDeployMetadata(root, options?.skipBuild ? null : getGitCommit(root), process.env, buildEnv());
5863
6118
  if (preset) {
5864
6119
  preset = {
5865
6120
  ...preset,
5866
6121
  buildCommand: resolveStaticBuildCommand(preset, voidConfig.inference?.build, Boolean(options?.dir))
5867
6122
  };
5868
6123
  await runStaticDeploy(root, config, client, preset, options?.skipBuild, gitMetadata, void 0, void 0, void 0, void 0, voidConfig.routing, cliLog);
5869
- } else await runFullDeploy(root, config, client, void 0, options?.skipBuild, gitMetadata, void 0, cliLog);
6124
+ } else await runFullDeploy(root, config, client, voidConfig, void 0, options?.skipBuild, gitMetadata, void 0, cliLog);
5870
6125
  return;
5871
6126
  } catch (error) {
5872
6127
  if (isExpiredTokenError(error)) {
@@ -5894,7 +6149,7 @@ async function promptDeployProjectConfig(root, client, cliLog) {
5894
6149
  slug: result.project.slug
5895
6150
  };
5896
6151
  writeProjectConfig(root, config);
5897
- log.step(`Linked to ${import_picocolors.default.blue(config.slug)}`);
6152
+ log.step(`Linked to ${import_picocolors$1.default.blue(config.slug)}`);
5898
6153
  cliLog?.info("project_linked", {
5899
6154
  projectId: config.projectId,
5900
6155
  slug: config.slug
@@ -5905,6 +6160,57 @@ function resolveStaticBuildCommand(preset, buildOverride, hasExplicitDir = false
5905
6160
  if (hasExplicitDir) return null;
5906
6161
  return buildOverride ?? preset.buildCommand;
5907
6162
  }
6163
+ /**
6164
+ * Resolve the build command for a FULL (Class A / Void app) deploy.
6165
+ *
6166
+ * `inference.build` wins when set, matching `runFrameworkDeploy` and the static
6167
+ * path's `resolveStaticBuildCommand`. Without it the full path was the only
6168
+ * deploy mode that ignored the config and hardcoded `vite build`, so an app that
6169
+ * needs more than one build step — an SSG that emits the site, then Void's build
6170
+ * packaging it via `publicDir` alongside the worker — had no way to express it
6171
+ * and died on the missing `index.html` entry.
6172
+ *
6173
+ * The two returned strings are deliberately different for the FALLBACK: `label`
6174
+ * goes to the user (`formatProjectCommand` rewrites `vite` → `vp` without
6175
+ * expanding an absolute bin path), `command` is what actually runs
6176
+ * (`resolveProjectCommand` expands to the local bin). A configured command is
6177
+ * the user's own text and is used verbatim for both, so what is printed is
6178
+ * exactly what is executed.
6179
+ */
6180
+ function resolveFullDeployBuildCommand(root, buildOverride) {
6181
+ if (buildOverride) return {
6182
+ label: buildOverride,
6183
+ command: buildOverride
6184
+ };
6185
+ return {
6186
+ label: formatProjectCommand(root, "vite build"),
6187
+ command: resolveProjectCommand(root, "vite build")
6188
+ };
6189
+ }
6190
+ /**
6191
+ * Whether the built worker's `__voidCollectPrerenderPaths` can return anything
6192
+ * at all. When it cannot, booting it is pure cost and the deploy skips it.
6193
+ *
6194
+ * These three are exactly — and only — the sources `compilePrerenderCollector`
6195
+ * (`router/compile.ts`) emits a `paths.push(...)` for:
6196
+ * 1. `scan.pages.pages` — static patterns + dynamic pages' `getPrerenderPaths()`
6197
+ * 2. the custom SSR entry's top-level `getPrerenderPaths()` (`ssr` argument)
6198
+ * 3. `options.explicitPrerenderPaths` — void.json `routing.prerender`
6199
+ * With none of them present the collector is a generated function whose body
6200
+ * returns `[...new Set([])]`, so skipping is result-identical.
6201
+ *
6202
+ * Skipping matters beyond speed. `collectPrerenderPaths` boots the worker in
6203
+ * Miniflare with bindings from `wranglerVars + loadEnv('production', root, '')`
6204
+ * only, and the generated fetch handler validates the `env.ts` schema at boot.
6205
+ * An app whose secrets live on the platform (never in a committed `.env`) gets
6206
+ * an HTTP 500 there and the deploy dies — for an app that had nothing to
6207
+ * prerender in the first place. The only workaround was moving the secret into
6208
+ * `.env.production`, which `loadDeployEnv` then publishes as a plaintext
6209
+ * manifest var. So this guard also removes a real incentive to leak secrets.
6210
+ */
6211
+ function hasPrerenderSources(inputs) {
6212
+ return inputs.hasPages || inputs.hasSsrEntry || (inputs.explicitPrerenderPaths?.length ?? 0) > 0;
6213
+ }
5908
6214
  function resolveStaticDeployPreset(root, voidConfig, options) {
5909
6215
  if (options?.dir) return detectPreset(root, {
5910
6216
  dir: options.dir,
@@ -6165,7 +6471,7 @@ function assembleVoidManifest(pageScan, isFrameworkMode, opts, revalidateConfig)
6165
6471
  pathRevalidate: isFrameworkMode || revalidateConfig == null || typeof revalidateConfig !== "object" ? void 0 : revalidateConfig
6166
6472
  });
6167
6473
  }
6168
- async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetadata, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, routing, cliLog) {
6474
+ async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetadata, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, routing, cliLog, notFound) {
6169
6475
  const typeLabel = preset.appType === "spa" ? "Static SPA" : "Static Site";
6170
6476
  log.info(`${typeLabel} deploy`);
6171
6477
  cliLog?.info("deploy_mode", {
@@ -6179,7 +6485,7 @@ async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetad
6179
6485
  execSync(preset.buildCommand, {
6180
6486
  cwd: root,
6181
6487
  stdio: "inherit",
6182
- env: buildEnv()
6488
+ env: buildEnv({ ...MANAGED_DEPLOY_BUILD_ENV }, { localBinRoot: root })
6183
6489
  });
6184
6490
  cliLog?.info("build_end", { durationMs: Date.now() - buildStartedAt });
6185
6491
  } else if (skipBuild) cliLog?.info("build_skipped", { reason: "skipBuild" });
@@ -6223,7 +6529,8 @@ async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetad
6223
6529
  hashedAssetsPrefix: hashedAssetsPrefix ?? "assets",
6224
6530
  headerRules,
6225
6531
  redirectRules,
6226
- fallbackRules
6532
+ fallbackRules,
6533
+ notFound: notFound ?? routing?.notFound
6227
6534
  });
6228
6535
  cliLog?.info("package_end", {
6229
6536
  mode: "static",
@@ -6257,6 +6564,27 @@ function warnRedundantForceOn3xx(count) {
6257
6564
  log.warn(`Found ${count} redundant "!" on 3xx entries in _redirects (these always force by nature; safe to remove).`);
6258
6565
  }
6259
6566
  /**
6567
+ * Class B/C framework deploys pin `not_found_handling: 'none'`, so
6568
+ * `routing.notFound` does nothing there — say so instead of dropping it
6569
+ * silently.
6570
+ *
6571
+ * These presets emit no `run_worker_first`, so Cloudflare's default applies and
6572
+ * the asset layer answers before the framework worker: real prerendered files
6573
+ * already win. `not_found_handling` only governs what the framework's own
6574
+ * `env.ASSETS.fetch()` delegation returns, which is framework-internal
6575
+ * plumbing. Honoring the override there would be a behavior change, not a fix —
6576
+ * `single-page-application` makes an unmatched delegation return
6577
+ * `dist/client/index.html` at **200** (Astro/SvelteKit hybrid builds commonly
6578
+ * prerender the root, so real 404s silently become the home page), and
6579
+ * `404-page` steals the 404 from the framework's own error route.
6580
+ *
6581
+ * Silenced when unset.
6582
+ */
6583
+ function warnIgnoredNotFoundForFramework(notFound, frameworkName) {
6584
+ if (notFound === void 0) return;
6585
+ log.warn(`"routing.notFound" in void.json is ignored for ${frameworkName} deploys (not_found_handling is pinned to "none" — the framework's own worker owns unmatched HTML).`);
6586
+ }
6587
+ /**
6260
6588
  * Class B/C framework deploy: build with framework CLI, package with preset output paths.
6261
6589
  */
6262
6590
  async function runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, skipBuild, gitMetadata, cliLog) {
@@ -6274,7 +6602,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
6274
6602
  execSync(voidConfig.inference?.build ?? resolveProjectCommand(root, fwPreset.buildCommand), {
6275
6603
  cwd: root,
6276
6604
  stdio: "inherit",
6277
- env: buildEnv()
6605
+ env: buildEnv({ ...MANAGED_DEPLOY_BUILD_ENV }, { localBinRoot: root })
6278
6606
  });
6279
6607
  cliLog?.info("build_end", { durationMs: Date.now() - buildStartedAt });
6280
6608
  } else cliLog?.info("build_skipped", { reason: "skipBuild" });
@@ -6286,6 +6614,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
6286
6614
  routing: voidConfig.routing
6287
6615
  });
6288
6616
  warnRedundantForceOn3xx(redundantForceOn3xx);
6617
+ warnIgnoredNotFoundForFramework(voidConfig.routing?.notFound, detected.name);
6289
6618
  lintDuplicateSources(redirectRules, "redirects/rewrites (pre-asset)");
6290
6619
  lintDuplicateSources(fallbackRules, "fallbacks (post-asset)");
6291
6620
  lintDestinationSplats(redirectRules, "redirects/rewrites (pre-asset)");
@@ -6414,7 +6743,8 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
6414
6743
  bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
6415
6744
  workerMain: actualWorkerMain,
6416
6745
  compatibilityDate: wranglerCompat?.compatibilityDate,
6417
- compatibilityFlags: wranglerCompat?.compatibilityFlags
6746
+ compatibilityFlags: wranglerCompat?.compatibilityFlags,
6747
+ cpuMs: voidConfig.worker?.limits?.cpu_ms
6418
6748
  };
6419
6749
  const manifest = buildFrameworkManifest({
6420
6750
  frameworkName: detected.name,
@@ -6454,21 +6784,24 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
6454
6784
  warnUnpublishedRouting(result);
6455
6785
  outro("Done!");
6456
6786
  }
6457
- async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetadata, detected, cliLog) {
6787
+ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBuild, gitMetadata, detected, cliLog) {
6458
6788
  cliLog?.info("deploy_mode", {
6459
6789
  mode: "full",
6460
6790
  framework: detected?.name
6461
6791
  });
6462
6792
  if (!skipBuild) {
6463
- const buildCmd = formatProjectCommand(root, "vite build");
6793
+ const { label: buildCmd, command: buildExec } = resolveFullDeployBuildCommand(root, voidConfig.inference?.build);
6464
6794
  log.step(`Building (${buildCmd})...`);
6465
6795
  cliLog?.info("build_start", { command: buildCmd });
6466
6796
  const buildStartedAt = Date.now();
6467
6797
  try {
6468
- execSync(resolveProjectCommand(root, "vite build"), {
6798
+ execSync(buildExec, {
6469
6799
  cwd: root,
6470
6800
  stdio: "inherit",
6471
- env: buildEnv({ VOID_DEPLOY_PROJECT_ID: config.projectId })
6801
+ env: buildEnv({
6802
+ VOID_DEPLOY_PROJECT_ID: config.projectId,
6803
+ ...MANAGED_DEPLOY_BUILD_ENV
6804
+ }, { localBinRoot: root })
6472
6805
  });
6473
6806
  cliLog?.info("build_end", { durationMs: Date.now() - buildStartedAt });
6474
6807
  } catch (err) {
@@ -6489,7 +6822,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6489
6822
  }
6490
6823
  }
6491
6824
  const isFrameworkMode = detected != null;
6492
- const deployConfig = readConfig(root);
6825
+ const deployConfig = voidConfig;
6493
6826
  const paths = resolveProjectPaths(root, deployConfig);
6494
6827
  const dialect = getDatabaseDialect(deployConfig);
6495
6828
  const deployScanDirs = deployConfig.inference?.scanDirs ?? (isFrameworkMode ? FRAMEWORK_SCAN_DIRS : void 0);
@@ -6578,9 +6911,10 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6578
6911
  })));
6579
6912
  const routeScan = await scanRoutes(root, {
6580
6913
  output: deployConfig.output,
6581
- paths
6914
+ paths,
6915
+ environment: "prod"
6582
6916
  });
6583
- const webSockets = routeScan.websockets ?? scanWebSocketRoutesSync(root, paths);
6917
+ const webSockets = routeScan.websockets ?? scanWebSocketRoutesSync(root, paths, "prod");
6584
6918
  const liveConfig = await resolveDeployLiveBindingConfig(root, client, config.projectId, {
6585
6919
  isFrameworkMode,
6586
6920
  needsLive: effectiveBindings.needsLive,
@@ -6600,7 +6934,8 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6600
6934
  const hasDocumentRoutes$1 = hasDocumentRoutes(routeScan.routes);
6601
6935
  const hasMiddleware = routeScan.middleware.length > 0;
6602
6936
  const hasWebSockets = webSockets.length > 0;
6603
- const isSsr = isFrameworkMode ? true : Boolean(validateSsrEntry(root, paths)) || hasPages && deployConfig.output !== "static";
6937
+ const ssrEntry = isFrameworkMode ? null : validateSsrEntry(root, paths);
6938
+ const isSsr = isFrameworkMode ? true : Boolean(ssrEntry) || hasPages && deployConfig.output !== "static";
6604
6939
  const pageScan = existsSync(paths.pagesDir) ? routeScan.pages.pages.length > 0 ? routeScan.pages : await scanPages(root, {
6605
6940
  output: deployConfig.output,
6606
6941
  paths
@@ -6614,10 +6949,15 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6614
6949
  buildCommand: null,
6615
6950
  outputDir: clientDir,
6616
6951
  appType: "static"
6617
- }, true, gitMetadata, assetsPrefix, headerRules, redirectRules, fallbackRules, void 0, cliLog);
6952
+ }, true, gitMetadata, assetsPrefix, headerRules, redirectRules, fallbackRules, void 0, cliLog, deployConfig.routing?.notFound);
6618
6953
  }
6619
6954
  }
6620
- const uniquePrerenderPaths = deployConfig.output === "static" || isFrameworkMode ? [] : isNodeTarget(deployConfig.target) ? await collectPrerenderPathsNode({
6955
+ const prerenderSourcesPresent = hasPrerenderSources({
6956
+ hasPages,
6957
+ hasSsrEntry: ssrEntry !== null,
6958
+ explicitPrerenderPaths: deployConfig.routing?.prerender
6959
+ });
6960
+ const uniquePrerenderPaths = deployConfig.output === "static" || isFrameworkMode || !prerenderSourcesPresent ? [] : isNodeTarget(deployConfig.target) ? await collectPrerenderPathsNode({
6621
6961
  root,
6622
6962
  ssrDir: join(distDir, "ssr"),
6623
6963
  clientDir
@@ -6648,7 +6988,8 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6648
6988
  hasMiddleware,
6649
6989
  hasWebSockets: hasDocumentRoutes(webSockets),
6650
6990
  authEnabled,
6651
- liveEnabled: liveConfig !== void 0
6991
+ liveEnabled: liveConfig !== void 0,
6992
+ notFound: deployConfig.routing?.notFound
6652
6993
  });
6653
6994
  s.start("Checking for changes...");
6654
6995
  cliLog?.info("preflight_start", { dir: clientDir });
@@ -6676,6 +7017,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6676
7017
  const voidPackageOptions = {
6677
7018
  compatibilityDate: wranglerCompat?.compatibilityDate,
6678
7019
  compatibilityFlags: wranglerCompat?.compatibilityFlags,
7020
+ cpuMs: deployConfig.worker?.limits?.cpu_ms,
6679
7021
  ...workerMain && { workerMain },
6680
7022
  ...sandboxConfig && { sandbox: sandboxConfig },
6681
7023
  ...liveConfig && { live: liveConfig },
@@ -6744,7 +7086,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
6744
7086
  outro("Done!");
6745
7087
  }
6746
7088
  async function createProjectFromSlug(root, client, slug, cliLog) {
6747
- log.step(`Creating project ${import_picocolors.default.blue(slug)}...`);
7089
+ log.step(`Creating project ${import_picocolors$1.default.blue(slug)}...`);
6748
7090
  cliLog?.info("project_create_start", { slug });
6749
7091
  const project = await client.createProject(slug);
6750
7092
  const config = {
@@ -6752,7 +7094,7 @@ async function createProjectFromSlug(root, client, slug, cliLog) {
6752
7094
  slug: project.slug
6753
7095
  };
6754
7096
  writeProjectConfig(root, config);
6755
- log.success(`Project created: ${import_picocolors.default.blue(config.slug)}`);
7097
+ log.success(`Project created: ${import_picocolors$1.default.blue(config.slug)}`);
6756
7098
  cliLog?.info("project_create_success", {
6757
7099
  projectId: config.projectId,
6758
7100
  slug: config.slug