void 0.10.11 → 0.10.12
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/cli/cli.mjs +14 -14
- package/dist/cli/env-schema-probe.mjs +2 -2
- package/dist/{config-E03l1C_h.mjs → config-BkTvs43g.mjs} +13 -0
- package/dist/{db-PZBsLSGb.mjs → db-DOiJMRt2.mjs} +3 -3
- package/dist/{deploy-DT8wsPZd.mjs → deploy-u7Rv9q_q.mjs} +187 -51
- package/dist/{env-AAHU02L6.mjs → env-CZy5MorI.mjs} +1 -1
- package/dist/{env-validation-BdDlGhbN.mjs → env-validation-Dea3v3ej.mjs} +1 -1
- package/dist/{gen-oup1xBN0.mjs → gen-o_w-8yI8.mjs} +1 -1
- package/dist/{git-metadata-CBKaL0v5.mjs → git-metadata-D-gurjzG.mjs} +6 -5
- package/dist/{github-cmd-BdNaOVNa.mjs → github-cmd-DKcGUNsj.mjs} +1 -1
- package/dist/{headers-BQknpzkn.mjs → headers-nsHIFixA.mjs} +1 -1
- package/dist/index.mjs +63 -25
- package/dist/{init-Dl2PKuQn.mjs → init-3rgHKBVi.mjs} +2 -2
- package/dist/{node-BDx8pmhq.mjs → node-Da0UcsGA.mjs} +1 -1
- package/dist/pages/index.mjs +2 -2
- package/dist/{prepare-DOBTY0o4.mjs → prepare-BoKHgMNx.mjs} +1 -1
- package/dist/{preset-BGrvB4Bl.mjs → preset-BjyR3lzz.mjs} +55 -2
- package/dist/{provision-BLrCEBbI.mjs → provision-rShh6MKY.mjs} +2 -2
- package/package.json +2 -2
- package/schema.json +5 -0
- package/skills/void/docs/guide/app-types.md +43 -0
- package/skills/void/docs/guide/edge/static-assets.md +23 -0
- package/skills/void/docs/integrations/frameworks/overview.md +1 -0
- package/skills/void/docs/reference/config.md +27 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
//#region src/cli/git-metadata.ts
|
|
3
|
-
function git(root, args) {
|
|
3
|
+
function git(root, args, env) {
|
|
4
4
|
try {
|
|
5
5
|
return execFileSync("git", args, {
|
|
6
6
|
cwd: root,
|
|
@@ -9,7 +9,8 @@ function git(root, args) {
|
|
|
9
9
|
"ignore",
|
|
10
10
|
"pipe",
|
|
11
11
|
"ignore"
|
|
12
|
-
]
|
|
12
|
+
],
|
|
13
|
+
env
|
|
13
14
|
}).trim();
|
|
14
15
|
} catch {
|
|
15
16
|
return null;
|
|
@@ -60,17 +61,17 @@ function getGitHubRepositoryUrl(env) {
|
|
|
60
61
|
if (!env.GITHUB_REPOSITORY) return null;
|
|
61
62
|
return normalizeGitRemoteUrl(`${env.GITHUB_SERVER_URL ?? "https://github.com"}/${env.GITHUB_REPOSITORY}`);
|
|
62
63
|
}
|
|
63
|
-
function collectGitDeployMetadata(root, commit, env = process.env) {
|
|
64
|
+
function collectGitDeployMetadata(root, commit, env = process.env, childEnv = process.env) {
|
|
64
65
|
const repositoryUrl = getGitHubRepositoryUrl(env) ?? normalizeGitRemoteUrl(git(root, [
|
|
65
66
|
"remote",
|
|
66
67
|
"get-url",
|
|
67
68
|
"origin"
|
|
68
|
-
]));
|
|
69
|
+
], childEnv));
|
|
69
70
|
const gitRef = getGitRefFromEnv(env) ?? git(root, [
|
|
70
71
|
"rev-parse",
|
|
71
72
|
"--abbrev-ref",
|
|
72
73
|
"HEAD"
|
|
73
|
-
]);
|
|
74
|
+
], childEnv);
|
|
74
75
|
return {
|
|
75
76
|
commit,
|
|
76
77
|
commitUrl: buildCommitUrl(repositoryUrl, commit),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { A as text, S as confirm, T as log, j as isCancel, k as select, u as import_picocolors, x as cancel } from "./agents-CtgBYqld.mjs";
|
|
2
2
|
import { b as readProjectConfig, c as createCodeChallenge, f as getToken, h as openBrowser, l as createCodeVerifier, n as PlatformClient, o as parsePlatformErrorBody, s as awaitBrowserCallback, t as PlatformApiError } from "./client-Gb71-XkG.mjs";
|
|
3
3
|
import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-BBMtLLV9.mjs";
|
|
4
|
-
import { t as collectGitDeployMetadata } from "./git-metadata-
|
|
4
|
+
import { t as collectGitDeployMetadata } from "./git-metadata-D-gurjzG.mjs";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
//#region src/cli/github-cmd.ts
|
|
7
7
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as join } from "./dist-DaKKDf8D.mjs";
|
|
2
|
-
import { a as convertRedirectsConfig, d as parseHostPrefixedSource, i as convertHeadersConfig, o as convertRewritesConfig, r as convertFallbacksConfig, s as countSplats, t as VALID_REDIRECT_STATUSES, u as isProtocolRelative } from "./config-
|
|
2
|
+
import { a as convertRedirectsConfig, d as parseHostPrefixedSource, i as convertHeadersConfig, o as convertRewritesConfig, r as convertFallbacksConfig, s as countSplats, t as VALID_REDIRECT_STATUSES, u as isProtocolRelative } from "./config-BkTvs43g.mjs";
|
|
3
3
|
import { i as voidWarn } from "./log-ChfPKsVd.mjs";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
//#region src/cli/headers.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { r as __toESM } from "./rolldown-runtime-DJK8HYOj.mjs";
|
|
2
2
|
import { a as join, n as dirname, s as relative } from "./dist-DaKKDf8D.mjs";
|
|
3
3
|
import { M as require_picocolors } from "./agents-CtgBYqld.mjs";
|
|
4
|
-
import { A as resolveAssetRoutingPolicy, C as syncWranglerBindings, D as resolveSandboxConfig, E as isSandboxEnabled, O as hasApiRoutes, S as resolveLiveBindingConfig, T as SANDBOX_MIGRATION_TAG, _ as nextDurableObjectMigrationTag, a as prerenderPagesNode, c as filterLoadedEnv, d as LIVE_MIGRATION_TAG, g as hasDurableObjectSqliteMigration, h as ensureWranglerNodejsAlsFlag, i as prerenderPages, k as hasDocumentRoutes, l as resolveRemoteMode, m as ensureWranglerCompatibilityDate, n as resolveDistDir, o as collectRevalidate, p as ensureWranglerCompat, r as resolveWorkerDirName, s as hasRevalidate, u as stripInternalEnvKeys, v as normalizeWranglerCompatibilityFlags, x as readWranglerCompat, y as readExistingLiveBindingConfig } from "./deploy-
|
|
4
|
+
import { A as resolveAssetRoutingPolicy, C as syncWranglerBindings, D as resolveSandboxConfig, E as isSandboxEnabled, O as hasApiRoutes, S as resolveLiveBindingConfig, T as SANDBOX_MIGRATION_TAG, _ as nextDurableObjectMigrationTag, a as prerenderPagesNode, c as filterLoadedEnv, d as LIVE_MIGRATION_TAG, g as hasDurableObjectSqliteMigration, h as ensureWranglerNodejsAlsFlag, i as prerenderPages, k as hasDocumentRoutes, l as resolveRemoteMode, m as ensureWranglerCompatibilityDate, n as resolveDistDir, o as collectRevalidate, p as ensureWranglerCompat, r as resolveWorkerDirName, s as hasRevalidate, u as stripInternalEnvKeys, v as normalizeWranglerCompatibilityFlags, x as readWranglerCompat, y as readExistingLiveBindingConfig } from "./deploy-u7Rv9q_q.mjs";
|
|
5
5
|
import { i as resolveProjectPaths, n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-BQd7OmIo.mjs";
|
|
6
6
|
import { a as scanQueuesSync, c as loadVoidAuthConfig, i as scanWebSocketRoutesSync, n as scanRoutePatternsSync, o as scanJobsSync, r as scanRoutes, s as findVoidAuthConfig, t as scanMiddlewareSync } from "./scan-DYXkrasO.mjs";
|
|
7
7
|
import { b as readProjectConfig, f as getToken, m as isStagingMode } from "./client-Gb71-XkG.mjs";
|
|
8
8
|
import { n as ensureProjectTsconfig, r as writeFileIfChanged } from "./project-tsconfig-B-QtXjLQ.mjs";
|
|
9
|
-
import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-
|
|
9
|
+
import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-BkTvs43g.mjs";
|
|
10
10
|
import { a as inferBindingsFromSource, i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, o as walk, r as detectFramework, t as DEFAULT_SCAN_DIRS } from "./plugin-inference-DMeavIJ6.mjs";
|
|
11
11
|
import { a as validateSsrEntries, c as isIdentifier, l as isLiteral, n as SSR_SERVER_ENTRY_RELATIVE_PATHS, o as validateSsrEntry, t as SSR_CLIENT_ENTRY_RELATIVE_PATHS, u as isMemberExpression } from "./entry-D7yy4xVH.mjs";
|
|
12
12
|
import { i as voidWarn, n as voidError, r as voidLog, t as setShowTimestamp } from "./log-ChfPKsVd.mjs";
|
|
13
|
-
import { l as parseDotenvFile } from "./preset-
|
|
13
|
+
import { l as parseDotenvFile } from "./preset-BjyR3lzz.mjs";
|
|
14
14
|
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, o as DRIZZLE_OPERATORS, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-COI2DsZv.mjs";
|
|
15
15
|
import { t as discoverSchema } from "./discover-CJHyvYfR.mjs";
|
|
16
16
|
import { t as MagicString } from "./magic-string.es-ZQjdJFFn.mjs";
|
|
17
17
|
import { n as findEnvFileForPaths, r as generateEnvTypes, t as findEnvFile } from "./env-types-QBj-ndax.mjs";
|
|
18
|
-
import { h as loadUserEnvFile } from "./env-validation-
|
|
18
|
+
import { h as loadUserEnvFile } from "./env-validation-Dea3v3ej.mjs";
|
|
19
19
|
import { i as resolveLayoutIds } from "./scan-DEwlM_Xy.mjs";
|
|
20
20
|
import { n as STAGING_PROXY_URL, t as DEFAULT_PROXY_URL } from "./proxy-D-3_D-Gl.mjs";
|
|
21
|
-
import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-
|
|
21
|
+
import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-nsHIFixA.mjs";
|
|
22
22
|
import { n as INTERNAL_BINDING_PREFIXES, t as INTERNAL_BINDING_EXACT_NAMES } from "./env-raw-Cx8ElDdj.mjs";
|
|
23
23
|
import { n as defer } from "./defer-YsYDUoii.mjs";
|
|
24
24
|
import { VoidAssetRewriteError, basicAuth, defineHandler, defineHead, defineMiddleware, defineQueue, defineRender, defineScheduled } from "./runtime/handler.mjs";
|
|
@@ -1687,7 +1687,7 @@ function migrationPlugin(root, dialect, databaseId = "local", persistRoot = join
|
|
|
1687
1687
|
if (applied.length > 0) voidLog(`Applied ${applied.length} migration(s): ${applied.join(", ")}`);
|
|
1688
1688
|
}
|
|
1689
1689
|
if (auth?.enabled) {
|
|
1690
|
-
const { runLocalVoidAuthMigrations } = await import("./node-
|
|
1690
|
+
const { runLocalVoidAuthMigrations } = await import("./node-Da0UcsGA.mjs");
|
|
1691
1691
|
await runLocalVoidAuthMigrations(root, dialect, persistRoot, databaseId, auth.configPath);
|
|
1692
1692
|
voidLog("Applied Better Auth migrations");
|
|
1693
1693
|
}
|
|
@@ -3562,9 +3562,19 @@ function compilePageRoutes(lines, scan, pages, options) {
|
|
|
3562
3562
|
* Class A framework wrappers. If the app returns 404, check ASSETS binding
|
|
3563
3563
|
* for static files and set cache headers. Edge caching of hashed assets
|
|
3564
3564
|
* happens at the dispatch layer (see design doc 0042).
|
|
3565
|
+
*
|
|
3566
|
+
* `notFound` is the RESOLVED `assetConfig.not_found_handling` for this build
|
|
3567
|
+
* (`resolveAssetRoutingPolicy`), and it decides what happens once the asset
|
|
3568
|
+
* binding has also answered 404. It is a separate argument from
|
|
3569
|
+
* `serveSpaFallback` because the two are not derived from each other: the
|
|
3570
|
+
* common worker-first app runs with `not_found_handling: 'none'` and STILL
|
|
3571
|
+
* wants the worker-side shell fallback (that is the whole reason the flag
|
|
3572
|
+
* exists). They are mutually exclusive in practice — `resolveAssetRoutingPolicy`
|
|
3573
|
+
* clears `workerSpaFallback` whenever an explicit `routing.notFound` asks for
|
|
3574
|
+
* anything other than the SPA shell.
|
|
3565
3575
|
*/
|
|
3566
|
-
function compileAssetFallbackHelper(assetsDir = "assets", serveSpaFallback = false) {
|
|
3567
|
-
const
|
|
3576
|
+
function compileAssetFallbackHelper(assetsDir = "assets", serveSpaFallback = false, notFound = "none") {
|
|
3577
|
+
const fallthroughCode = serveSpaFallback ? ` // SPA fallback: when HTML-owning features make
|
|
3568
3578
|
// assetConfig.not_found_handling = 'none', the asset binding returns 404
|
|
3569
3579
|
// instead of falling back to index.html itself. Do that fallback here, after
|
|
3570
3580
|
// user routes/middleware have run, so auth gates and OAuth callbacks see the request first.
|
|
@@ -3583,6 +3593,13 @@ function compileAssetFallbackHelper(assetsDir = "assets", serveSpaFallback = fal
|
|
|
3583
3593
|
return res;
|
|
3584
3594
|
}
|
|
3585
3595
|
}
|
|
3596
|
+
return response;` : notFound === "404-page" ? ` const accept = request.headers.get("accept") ?? "";
|
|
3597
|
+
const assetContentType = asset.headers.get("content-type") ?? "";
|
|
3598
|
+
if (accept.includes("text/html") && assetContentType.includes("text/html")) {
|
|
3599
|
+
const res = new Response(asset.body, asset);
|
|
3600
|
+
res.headers.set("Cache-Control", "no-store");
|
|
3601
|
+
return res;
|
|
3602
|
+
}
|
|
3586
3603
|
return response;` : ` return response;`;
|
|
3587
3604
|
return `
|
|
3588
3605
|
async function serveWithAssets(request, env, response) {
|
|
@@ -3597,7 +3614,7 @@ async function serveWithAssets(request, env, response) {
|
|
|
3597
3614
|
}
|
|
3598
3615
|
return res;
|
|
3599
3616
|
}
|
|
3600
|
-
${
|
|
3617
|
+
${fallthroughCode}
|
|
3601
3618
|
}
|
|
3602
3619
|
`;
|
|
3603
3620
|
}
|
|
@@ -3616,7 +3633,7 @@ ${spaFallbackCode}
|
|
|
3616
3633
|
* remote mode (`__VOID_REMOTE`) and replace local bindings with
|
|
3617
3634
|
* proxy-backed versions.
|
|
3618
3635
|
*/
|
|
3619
|
-
function compileWorkerEntry(hasQueues = false, remote, assetsDir = "assets", webSocketClasses = [], runtimeEnvPath = "void/_env", envSchemaModule, sandbox, serveSpaFallback = false, live, isr) {
|
|
3636
|
+
function compileWorkerEntry(hasQueues = false, remote, assetsDir = "assets", webSocketClasses = [], runtimeEnvPath = "void/_env", envSchemaModule, sandbox, serveSpaFallback = false, live, isr, notFound = "none") {
|
|
3620
3637
|
const remotePath = remote?.remotePath;
|
|
3621
3638
|
const remoteBindingNames = JSON.stringify(remote?.bindingNames);
|
|
3622
3639
|
const createRemoteEnvCall = `createRemoteEnv(env${remote?.bindingNames ? `, ${remoteBindingNames}` : ""})`;
|
|
@@ -3661,7 +3678,7 @@ function __filterInternalBindings(env) {
|
|
|
3661
3678
|
});
|
|
3662
3679
|
}
|
|
3663
3680
|
`;
|
|
3664
|
-
const assetHelper = compileAssetFallbackHelper(assetsDir, serveSpaFallback);
|
|
3681
|
+
const assetHelper = compileAssetFallbackHelper(assetsDir, serveSpaFallback, notFound);
|
|
3665
3682
|
const isrImport = isr ? `import { handleIsr } from ${JSON.stringify(isr.runtimePath)};\nimport { __VOID_ISR_REVALIDATE, __VOID_ISR_DEPLOYMENT_ID } from 'virtual:void-routes';\n` : "";
|
|
3666
3683
|
const isrEnvCapture = isr ? ` env.__VOID_ISR_DEPLOYMENT_ID ??= __VOID_ISR_DEPLOYMENT_ID;\n env.__VOID_ISR_HOST = new URL(request.url).hostname;\n` : "";
|
|
3667
3684
|
const isrDeploymentStamp = isr ? ` env.__VOID_ISR_DEPLOYMENT_ID ??= __VOID_ISR_DEPLOYMENT_ID;\n` : "";
|
|
@@ -4418,6 +4435,15 @@ function routingPlugin(root, options) {
|
|
|
4418
4435
|
if (!scan) return false;
|
|
4419
4436
|
return resolveCurrentAssetRoutingPolicy().workerSpaFallback;
|
|
4420
4437
|
}
|
|
4438
|
+
/**
|
|
4439
|
+
* The `not_found_handling` the generated worker entry must agree with. Read
|
|
4440
|
+
* off the SAME resolved policy that produces the wrangler asset config, so
|
|
4441
|
+
* the emitted `serveWithAssets` can never disagree with the platform switch
|
|
4442
|
+
* it is standing in for.
|
|
4443
|
+
*/
|
|
4444
|
+
function currentNotFoundHandling() {
|
|
4445
|
+
return resolveCurrentAssetRoutingPolicy().assetConfig.not_found_handling ?? "none";
|
|
4446
|
+
}
|
|
4421
4447
|
function resolveCurrentAssetRoutingPolicy() {
|
|
4422
4448
|
if (!scan) return resolveAssetRoutingPolicy({
|
|
4423
4449
|
isSsr: Boolean(ssr),
|
|
@@ -4427,7 +4453,8 @@ function routingPlugin(root, options) {
|
|
|
4427
4453
|
hasMiddleware: false,
|
|
4428
4454
|
hasWebSockets: false,
|
|
4429
4455
|
authEnabled: compileOptions.auth !== null,
|
|
4430
|
-
liveEnabled: options.live !== void 0
|
|
4456
|
+
liveEnabled: options.live !== void 0,
|
|
4457
|
+
notFound: options.routing?.notFound
|
|
4431
4458
|
});
|
|
4432
4459
|
return resolveAssetRoutingPolicy({
|
|
4433
4460
|
isSsr: Boolean(ssr),
|
|
@@ -4437,7 +4464,8 @@ function routingPlugin(root, options) {
|
|
|
4437
4464
|
hasMiddleware: scan.middleware.length > 0,
|
|
4438
4465
|
hasWebSockets: hasDocumentRoutes(scan.websockets ?? []),
|
|
4439
4466
|
authEnabled: compileOptions.auth !== null,
|
|
4440
|
-
liveEnabled: options.live !== void 0
|
|
4467
|
+
liveEnabled: options.live !== void 0,
|
|
4468
|
+
notFound: options.routing?.notFound
|
|
4441
4469
|
});
|
|
4442
4470
|
}
|
|
4443
4471
|
const outDir = join(root, ".void");
|
|
@@ -4520,7 +4548,7 @@ function routingPlugin(root, options) {
|
|
|
4520
4548
|
bindingName: options.sandboxBindingName,
|
|
4521
4549
|
className: options.sandboxClassName,
|
|
4522
4550
|
runtimePath: sandboxRuntimePath
|
|
4523
|
-
} : false, shouldServeSpaFallback(), options.live, isr));
|
|
4551
|
+
} : false, shouldServeSpaFallback(), options.live, isr, currentNotFoundHandling()));
|
|
4524
4552
|
}
|
|
4525
4553
|
},
|
|
4526
4554
|
resolveId: {
|
|
@@ -4571,7 +4599,7 @@ function routingPlugin(root, options) {
|
|
|
4571
4599
|
bindingName: options.sandboxBindingName,
|
|
4572
4600
|
className: options.sandboxClassName,
|
|
4573
4601
|
runtimePath: sandboxRuntimePath
|
|
4574
|
-
} : false, shouldServeSpaFallback(), options.live));
|
|
4602
|
+
} : false, shouldServeSpaFallback(), options.live, void 0, currentNotFoundHandling()));
|
|
4575
4603
|
const structural = event !== "change";
|
|
4576
4604
|
const pageManifestChanged = isInDirectory(normalizedPath, normalizedPagesDir) && (structural || normalizedPath.endsWith(".md"));
|
|
4577
4605
|
for (const env of Object.values(devServer.environments)) {
|
|
@@ -4706,10 +4734,10 @@ function warnIfPackageJsonIsNotEsm(root) {
|
|
|
4706
4734
|
* dead-code-eliminated in `vite build`. Production deploys go through
|
|
4707
4735
|
* `cli/wrapper.ts` instead.
|
|
4708
4736
|
*/
|
|
4709
|
-
function getFrameworkEntry(framework, root, devTriggerToken) {
|
|
4737
|
+
function getFrameworkEntry(framework, root, devTriggerToken, notFound) {
|
|
4710
4738
|
const isBuild = isViteBuildOrPreview();
|
|
4711
4739
|
if (!isBuild) writeFrameworkTriggersDispatch(root, devTriggerToken);
|
|
4712
|
-
return generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), isBuild);
|
|
4740
|
+
return generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), isBuild, void 0, notFound);
|
|
4713
4741
|
}
|
|
4714
4742
|
/** The framework's own server entry, before our trigger wrapper. */
|
|
4715
4743
|
function getOriginalFrameworkEntry(framework, root) {
|
|
@@ -4738,7 +4766,7 @@ function getOriginalFrameworkEntry(framework, root) {
|
|
|
4738
4766
|
* dispatch file (and its transitive user-cron/queue imports + dev token)
|
|
4739
4767
|
* would still ship in the production bundle.
|
|
4740
4768
|
*/
|
|
4741
|
-
function generateClassATriggerWrapper(root, framework, original, isBuild, assetsDir = "assets") {
|
|
4769
|
+
function generateClassATriggerWrapper(root, framework, original, isBuild, assetsDir = "assets", notFound) {
|
|
4742
4770
|
const outDir = join(root, ".void");
|
|
4743
4771
|
mkdirSync(outDir, { recursive: true });
|
|
4744
4772
|
const wrapperPath = join(outDir, `${framework}-trigger-wrapper.ts`);
|
|
@@ -4751,7 +4779,7 @@ export default {
|
|
|
4751
4779
|
return serveWithAssets(request, env, await original.fetch(request, env, ctx));
|
|
4752
4780
|
},
|
|
4753
4781
|
};
|
|
4754
|
-
${compileAssetFallbackHelper(assetsDir)}
|
|
4782
|
+
${compileAssetFallbackHelper(assetsDir, false, notFound)}
|
|
4755
4783
|
` : `// @ts-nocheck — Auto-generated by Void in dev. Do NOT edit.
|
|
4756
4784
|
import original from ${JSON.stringify(original)};
|
|
4757
4785
|
|
|
@@ -5210,9 +5238,10 @@ function voidPlugin(options) {
|
|
|
5210
5238
|
mkdirSync(outDir, { recursive: true });
|
|
5211
5239
|
function mergeBindings(resolved) {
|
|
5212
5240
|
const result = { ...workerConfig };
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
delete resolved.assets.
|
|
5241
|
+
const generatedAssets = workerConfig.assets;
|
|
5242
|
+
if (generatedAssets && resolved.assets) {
|
|
5243
|
+
if (generatedAssets.not_found_handling !== void 0) delete resolved.assets.not_found_handling;
|
|
5244
|
+
if (generatedAssets.run_worker_first !== void 0) delete resolved.assets.run_worker_first;
|
|
5216
5245
|
}
|
|
5217
5246
|
const d1Name = bindingNames?.d1 ?? "DB";
|
|
5218
5247
|
const d1 = [];
|
|
@@ -5448,12 +5477,20 @@ function voidPlugin(options) {
|
|
|
5448
5477
|
}
|
|
5449
5478
|
};
|
|
5450
5479
|
if (framework) {
|
|
5451
|
-
workerConfig.main = getFrameworkEntry(framework, root, devTriggerToken);
|
|
5480
|
+
workerConfig.main = getFrameworkEntry(framework, root, devTriggerToken, config.routing?.notFound);
|
|
5452
5481
|
const frameworkBuildWrapperPlugin = {
|
|
5453
5482
|
name: "void:framework-build-wrapper",
|
|
5483
|
+
config(_userConfig, env) {
|
|
5484
|
+
const notFound = config.routing?.notFound;
|
|
5485
|
+
if (env.command !== "build" || notFound === void 0) return;
|
|
5486
|
+
workerConfig.assets = {
|
|
5487
|
+
...workerConfig.assets,
|
|
5488
|
+
not_found_handling: notFound
|
|
5489
|
+
};
|
|
5490
|
+
},
|
|
5454
5491
|
configResolved(resolved) {
|
|
5455
5492
|
if (resolved.command !== "build") return;
|
|
5456
|
-
generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), true, resolved.build.assetsDir);
|
|
5493
|
+
generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), true, resolved.build.assetsDir, config.routing?.notFound);
|
|
5457
5494
|
}
|
|
5458
5495
|
};
|
|
5459
5496
|
const existingFlags = workerConfig.compatibility_flags ?? [];
|
|
@@ -5554,7 +5591,8 @@ function voidPlugin(options) {
|
|
|
5554
5591
|
hasMiddleware,
|
|
5555
5592
|
hasWebSockets: hasDocumentRoutes(websocketRoutes),
|
|
5556
5593
|
authEnabled,
|
|
5557
|
-
liveEnabled: liveConfig !== void 0
|
|
5594
|
+
liveEnabled: liveConfig !== void 0,
|
|
5595
|
+
notFound: config.routing?.notFound
|
|
5558
5596
|
});
|
|
5559
5597
|
workerConfig.main = join(root, ".void", "entry.ts");
|
|
5560
5598
|
if (jobs.length > 0) workerConfig.triggers = { crons: jobs.flatMap((job) => job.crons) };
|
|
@@ -3,9 +3,9 @@ import { A as text, D as outro, E as note, S as confirm, T as log, a as cliTitle
|
|
|
3
3
|
import { n as readProjectPaths } from "./project-paths-BQd7OmIo.mjs";
|
|
4
4
|
import { S as writeProjectConfig, b as readProjectConfig, f as getToken, m as isStagingMode, n as PlatformClient } from "./client-Gb71-XkG.mjs";
|
|
5
5
|
import { n as ensureProjectTsconfig, t as createProjectTsconfig } from "./project-tsconfig-B-QtXjLQ.mjs";
|
|
6
|
-
import { c as getDatabaseDialect, f as readConfig } from "./config-
|
|
6
|
+
import { c as getDatabaseDialect, f as readConfig } from "./config-BkTvs43g.mjs";
|
|
7
7
|
import { r as detectFramework } from "./plugin-inference-DMeavIJ6.mjs";
|
|
8
|
-
import { l as parseDotenvFile, r as formatProjectCommand, s as expandDotenv } from "./preset-
|
|
8
|
+
import { l as parseDotenvFile, r as formatProjectCommand, s as expandDotenv } from "./preset-BjyR3lzz.mjs";
|
|
9
9
|
import { t as MagicString } from "./magic-string.es-ZQjdJFFn.mjs";
|
|
10
10
|
import { n as writeDrizzleConfig } from "./config-CutEMNGJ.mjs";
|
|
11
11
|
import { n as promptAndCreateProject, r as promptProjectSelection } from "./create-project-DsYvl3TB.mjs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as join } from "./dist-DaKKDf8D.mjs";
|
|
2
2
|
import { c as loadVoidAuthConfig, s as findVoidAuthConfig } from "./scan-DYXkrasO.mjs";
|
|
3
|
-
import { f as readConfig } from "./config-
|
|
3
|
+
import { f as readConfig } from "./config-BkTvs43g.mjs";
|
|
4
4
|
import { a as resolveVoidAuthMigrationSchema } from "./route-types-COI2DsZv.mjs";
|
|
5
5
|
import { n as applySqliteAuthMigrations, t as applyPgAuthMigrations } from "./auth-migrations-BTZ-ATvQ.mjs";
|
|
6
6
|
import { getMiniflareDatabasePath } from "./runner-kapo9aPs.mjs";
|
package/dist/pages/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as join } from "../dist-DaKKDf8D.mjs";
|
|
2
2
|
import { r as resolvePagesDir } from "../project-paths-BQd7OmIo.mjs";
|
|
3
|
-
import { f as readConfig } from "../config-
|
|
3
|
+
import { f as readConfig } from "../config-BkTvs43g.mjs";
|
|
4
4
|
import { a as resolveNamedLayout, i as resolveLayoutIds, n as resolveEffectiveLayoutChain, r as resolveLayoutChain, t as scanPages } from "../scan-DEwlM_Xy.mjs";
|
|
5
|
-
import { i as parseRedirectsFile } from "../headers-
|
|
5
|
+
import { i as parseRedirectsFile } from "../headers-nsHIFixA.mjs";
|
|
6
6
|
import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs, resolveHead } from "./head.mjs";
|
|
7
7
|
import { serializePageData } from "./serialize.mjs";
|
|
8
8
|
//#region src/pages/manifest.ts
|
|
@@ -3,7 +3,7 @@ import { D as outro, E as note, T as log, a as cliTitle, w as intro } from "./ag
|
|
|
3
3
|
import { i as resolveProjectPaths } from "./project-paths-BQd7OmIo.mjs";
|
|
4
4
|
import { c as loadVoidAuthConfig, r as scanRoutes, s as findVoidAuthConfig } from "./scan-DYXkrasO.mjs";
|
|
5
5
|
import { n as ensureProjectTsconfig, r as writeFileIfChanged } from "./project-tsconfig-B-QtXjLQ.mjs";
|
|
6
|
-
import { c as getDatabaseDialect, f as readConfig } from "./config-
|
|
6
|
+
import { c as getDatabaseDialect, f as readConfig } from "./config-BkTvs43g.mjs";
|
|
7
7
|
import { i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, r as detectFramework } from "./plugin-inference-DMeavIJ6.mjs";
|
|
8
8
|
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-COI2DsZv.mjs";
|
|
9
9
|
import { t as discoverSchema } from "./discover-CJHyvYfR.mjs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as join, c as resolve, n as dirname } from "./dist-DaKKDf8D.mjs";
|
|
1
|
+
import { a as join, c as resolve, n as dirname, s as relative } from "./dist-DaKKDf8D.mjs";
|
|
2
2
|
import { n as readProjectPaths } from "./project-paths-BQd7OmIo.mjs";
|
|
3
|
-
import { i as hasSsrEntry } from "./entry-D7yy4xVH.mjs";
|
|
3
|
+
import { i as hasSsrEntry, r as getSsrServerEntryPath } from "./entry-D7yy4xVH.mjs";
|
|
4
4
|
import { i as readPackageJson, n as hasPackageDependency, t as getPackageDependencies } from "./package-json-Cx1osYo6.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -446,6 +446,15 @@ function formatProjectCommand(root, command) {
|
|
|
446
446
|
/**
|
|
447
447
|
* Detect a static deploy preset based on the project root.
|
|
448
448
|
* Returns null when the project requires a full worker deploy.
|
|
449
|
+
*
|
|
450
|
+
* `strictBackend` refuses (throws) instead of returning an SSG preset when the
|
|
451
|
+
* project ALSO has backend sources. SSG detection (step 2) deliberately runs
|
|
452
|
+
* before the backend check (step 3), so without this an SSG repo that grew a
|
|
453
|
+
* single `routes/api/*.ts` would deploy as a pure static site with the backend
|
|
454
|
+
* silently dropped and no diagnostic. Opt-in because only the UNCONFIGURED
|
|
455
|
+
* auto-detect path is ambiguous: `--dir` and a configured
|
|
456
|
+
* `inference.appType: "static" | "spa"` are the user stating what they want, and
|
|
457
|
+
* must keep working silently.
|
|
449
458
|
*/
|
|
450
459
|
function detectPreset(root, options) {
|
|
451
460
|
if (options?.dir) return {
|
|
@@ -457,6 +466,10 @@ function detectPreset(root, options) {
|
|
|
457
466
|
if (pkg) {
|
|
458
467
|
const allDeps = getPackageDependencies(pkg);
|
|
459
468
|
for (const preset of Object.values(SSG_PRESETS)) if (preset.dep in allDeps) {
|
|
469
|
+
if (options?.strictBackend) {
|
|
470
|
+
const backendSources = findBackendSources(root);
|
|
471
|
+
if (backendSources.length > 0) throw new Error(ssgWithBackendMessage(preset.dep, backendSources));
|
|
472
|
+
}
|
|
460
473
|
const overrides = preset.resolve?.(root);
|
|
461
474
|
const cmd = overrides?.buildCommand ?? preset.buildCommand;
|
|
462
475
|
const out = overrides?.outputDir ?? preset.outputDir;
|
|
@@ -486,6 +499,25 @@ function detectPreset(root, options) {
|
|
|
486
499
|
return null;
|
|
487
500
|
}
|
|
488
501
|
/**
|
|
502
|
+
* The refusal shown when an SSG project also carries backend sources and no
|
|
503
|
+
* `inference.appType` says which one to deploy. Names the exact directories
|
|
504
|
+
* found and both ways out.
|
|
505
|
+
*/
|
|
506
|
+
function ssgWithBackendMessage(ssgDep, backendSources) {
|
|
507
|
+
return [
|
|
508
|
+
`deploy: "${ssgDep}" was detected, but this project also has backend code: ${backendSources.join(", ")}.`,
|
|
509
|
+
"A static deploy would silently drop it, so Void will not guess. Pick one in void.json:",
|
|
510
|
+
"",
|
|
511
|
+
" Deploy the static output only (the backend is NOT deployed):",
|
|
512
|
+
" { \"inference\": { \"appType\": \"static\" } }",
|
|
513
|
+
"",
|
|
514
|
+
" Deploy both — the SSG emits the site, then Void packages it with the worker:",
|
|
515
|
+
" { \"inference\": { \"appType\": \"void\", \"build\": \"<ssg> build && vite build\" } }",
|
|
516
|
+
" ...and in vite.config.ts add voidPlugin() plus `publicDir: \"<ssg output dir>\"`,",
|
|
517
|
+
" so the generated site is copied into the client build alongside the worker."
|
|
518
|
+
].join("\n");
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
489
521
|
* Resolve a local node_modules/.bin executable path.
|
|
490
522
|
* Falls back to bare command name if the bin doesn't exist locally.
|
|
491
523
|
*/
|
|
@@ -523,6 +555,27 @@ function detectViteBinary(root) {
|
|
|
523
555
|
return "vite";
|
|
524
556
|
}
|
|
525
557
|
/**
|
|
558
|
+
* Every backend source in the project that requires a full worker deploy, named
|
|
559
|
+
* relative to the root (e.g. `routes/`, `src/main.ssr.tsx`).
|
|
560
|
+
*
|
|
561
|
+
* Returned as a list rather than a boolean so a caller that has to REJECT a
|
|
562
|
+
* static deploy can tell the user exactly what would have been dropped.
|
|
563
|
+
*/
|
|
564
|
+
function findBackendSources(root) {
|
|
565
|
+
const paths = readProjectPaths(root);
|
|
566
|
+
const found = [];
|
|
567
|
+
for (const dir of [
|
|
568
|
+
paths.routesDir,
|
|
569
|
+
paths.pagesDir,
|
|
570
|
+
paths.middlewareDir,
|
|
571
|
+
paths.cronsDir,
|
|
572
|
+
paths.queuesDir
|
|
573
|
+
]) if (existsSync(dir)) found.push(`${relative(root, dir)}/`);
|
|
574
|
+
const ssrEntry = getSsrServerEntryPath(root, paths);
|
|
575
|
+
if (ssrEntry) found.push(relative(root, ssrEntry));
|
|
576
|
+
return found;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
526
579
|
* Check whether the project has backend files that require a full worker deploy.
|
|
527
580
|
*/
|
|
528
581
|
function hasBackendFiles(root) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as join, c as resolve, n as dirname, o as normalize, t as basename } from "./dist-DaKKDf8D.mjs";
|
|
2
|
-
import { b as readProvisionedBindingEntry, f as assertNoDuplicateMembers, w as writeProvisionedWranglerConfig } from "./deploy-
|
|
2
|
+
import { b as readProvisionedBindingEntry, f as assertNoDuplicateMembers, w as writeProvisionedWranglerConfig } from "./deploy-u7Rv9q_q.mjs";
|
|
3
3
|
import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
|
|
4
|
-
import { o as resolveWranglerArgv, u as scanWranglerDotenvKeys } from "./preset-
|
|
4
|
+
import { o as resolveWranglerArgv, u as scanWranglerDotenvKeys } from "./preset-BjyR3lzz.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "void",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.12",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -356,7 +356,7 @@
|
|
|
356
356
|
"valibot": ">=1.0.0-beta.7",
|
|
357
357
|
"vite": "^8.0.0",
|
|
358
358
|
"zod": "^3.25.0 || ^4.0.0",
|
|
359
|
-
"@void/md": "0.10.
|
|
359
|
+
"@void/md": "0.10.12"
|
|
360
360
|
},
|
|
361
361
|
"peerDependenciesMeta": {
|
|
362
362
|
"@void/md": {
|
package/schema.json
CHANGED
|
@@ -144,6 +144,11 @@
|
|
|
144
144
|
"type": "object",
|
|
145
145
|
"description": "Routing configuration — headers, redirects, rewrites, fallbacks, revalidation, prerendering.",
|
|
146
146
|
"properties": {
|
|
147
|
+
"notFound": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"enum": ["single-page-application", "404-page", "none"],
|
|
150
|
+
"description": "Override how the asset layer answers a request that matched no asset and no worker route. Void infers \"single-page-application\" when the worker does not own HTML routing; set \"404-page\" for a static-site-generator build so unknown URLs serve 404.html with a real HTTP 404 instead of index.html with a 200. Does not change run_worker_first. Ignored for SvelteKit, Nuxt, Analog, and Astro deploys, which pin not_found_handling to \"none\" because the framework's own worker owns unmatched HTML."
|
|
151
|
+
},
|
|
147
152
|
"revalidate": {
|
|
148
153
|
"description": "Edge caching TTL in seconds. A number sets a global TTL for all SSR pages. An object maps URL patterns to TTLs (first match wins). Set to 0 to disable caching for a path.",
|
|
149
154
|
"oneOf": [
|
|
@@ -73,6 +73,48 @@ At the edge, the resolution order is:
|
|
|
73
73
|
|
|
74
74
|
**Deploy:** `void deploy` or `void deploy --dir <path>` uploads static files directly. Assets are served from the edge with automatic caching.
|
|
75
75
|
|
|
76
|
+
## Adding a backend to a static site
|
|
77
|
+
|
|
78
|
+
A static site generator plus a few API routes is a Void app, not a static site. Auto-detection sees the SSG dependency first (priority 2 below), so if you leave `inference.appType` unset, `void deploy` refuses rather than deploying the site and silently dropping `routes/`. Tell it which you meant.
|
|
79
|
+
|
|
80
|
+
To deploy the site **and** the backend, run both builds from one command and let Void's build fold the generated site into its client output:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
// void.json
|
|
84
|
+
{
|
|
85
|
+
"inference": {
|
|
86
|
+
"appType": "void",
|
|
87
|
+
"build": "vitepress build && vite build"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// vite.config.ts
|
|
94
|
+
import { defineConfig } from 'vite';
|
|
95
|
+
import { voidPlugin } from 'void';
|
|
96
|
+
|
|
97
|
+
export default defineConfig({
|
|
98
|
+
plugins: [voidPlugin()],
|
|
99
|
+
// The SSG's output becomes the static assets of the Void build.
|
|
100
|
+
publicDir: '.vitepress/dist',
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`vitepress build` emits the site, then `vite build` copies `publicDir` into `dist/client` and emits the worker into `dist/ssr`. Keep `voidPlugin()` in the **root** `vite.config.ts` only — not in the generator's own config (e.g. `.vitepress/config.ts`).
|
|
105
|
+
|
|
106
|
+
Because the worker now owns unmatched requests, add [`routing.notFound`](../reference/config.md#routing-notfound) if you want the generator's `404.html` instead of the SPA-style `index.html` fallback:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{ "routing": { "notFound": "404-page" } }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
To deploy the static output only and **not** the backend, say so explicitly:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "inference": { "appType": "static" } }
|
|
116
|
+
```
|
|
117
|
+
|
|
76
118
|
## Auto-Detection
|
|
77
119
|
|
|
78
120
|
When running `void deploy` and no `inference.appType` is set in `void.json`, the detection logic runs in this order:
|
|
@@ -81,6 +123,7 @@ When running `void deploy` and no `inference.appType` is set in `void.json`, the
|
|
|
81
123
|
| -------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
82
124
|
| 1 | `--dir` flag | Static (or SPA with `--spa`) |
|
|
83
125
|
| 2 | Known SSG in dependencies (`vitepress`, `@docusaurus/core`) | Static, builds with SSG CLI |
|
|
126
|
+
| 2a | ...and backend files also exist | Refused — set `inference.appType` yourself |
|
|
84
127
|
| 3 | `@tanstack/react-start`, `@react-router/dev`, `@sveltejs/kit`, `nuxt`, `@analogjs/platform`, or `astro` in deps | Framework |
|
|
85
128
|
| 4 | Backend files exist (`routes/`, `pages/`, `middleware/`, `crons/`, `queues/`, SSR entry) | Void app |
|
|
86
129
|
| 5 | `vite` or `vite-plus` in dependencies, no backend files | SPA, builds with `vite build` or `vp build` |
|
|
@@ -97,6 +97,29 @@ This is the path needed for preview auth and other middleware. Cloudflare's plat
|
|
|
97
97
|
|
|
98
98
|
For Void apps with only `/api` routes, Void keeps the platform SPA fallback and scopes `run_worker_first` to `/api` and `/api/*`. Static assets and non-API SPA navigations stay on the asset platform. API requests, including browser document navigations such as OAuth callbacks, reach the worker instead of being rewritten to `index.html`.
|
|
99
99
|
|
|
100
|
+
### Unmatched requests
|
|
101
|
+
|
|
102
|
+
`not_found_handling` decides what the asset layer does with a request that matched no asset and no worker route. Void infers it:
|
|
103
|
+
|
|
104
|
+
| App shape | Inferred value | Result for an unknown URL |
|
|
105
|
+
| ------------------------------------------------- | -------------------------------- | -------------------------------- |
|
|
106
|
+
| Pages or SSR | `none` | The worker's own 404 |
|
|
107
|
+
| Worker owns HTML, no `pages/`, no SSR entry | `none` + worker fallback | `index.html` with status **200** |
|
|
108
|
+
| Asset-first (only `/api` routes, or none) | `single-page-application` | `index.html` with status **200** |
|
|
109
|
+
| Framework deploy (SvelteKit, Nuxt, Analog, Astro) | `none` — pinned, not overridable | The framework worker's own 404 |
|
|
110
|
+
|
|
111
|
+
The middle row is a worker with `middleware/`, a route outside `/api`, a document websocket, or Live, but no `pages/` and no SSR entry. `run_worker_first: ['/**']` sends everything to the worker, which bypasses the platform's SPA switch, so the generated worker does that fallback itself for HTML navigations — after your middleware has run, so auth gates and OAuth callbacks still see the request first.
|
|
112
|
+
|
|
113
|
+
The SPA fallback is right for a single-page app, where deep links must boot the client router. It is wrong for a site whose HTML was generated per page: unknown URLs return 200 instead of 404, and the generator's `404.html` is never served. Nothing in a built asset tree distinguishes the two, so Void does not guess — override it with [`routing.notFound`](../../reference/config.md#routing-notfound):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{ "routing": { "notFound": "404-page" } }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Accepted values are `"single-page-application"`, `"404-page"`, and `"none"`. `run_worker_first` keeps its inferred value, so API routes, auth, and `/__void/*` still reach the worker first. Setting anything other than `"single-page-application"` also turns off the worker-side `index.html` fallback described above — otherwise it would answer the request before `not_found_handling` was ever consulted. With `"404-page"` the worker serves whatever the asset binding returns for the unmatched path, which is Cloudflare's nearest `404.html` — but only for HTML navigations (requests whose `Accept` includes `text/html`), so an API route that deliberately returns a 404 keeps its own body, status and headers. If the build has no `404.html`, the worker's own 404 is kept.
|
|
120
|
+
|
|
121
|
+
The last row is the exception: `routing.notFound` is **ignored** for SvelteKit, Nuxt, Analog, and Astro deploys, and `void deploy` warns when you set it. Those deploys emit no `run_worker_first`, so the asset layer already answers first and real prerendered files win — `not_found_handling` would only change what the framework's own `env.ASSETS.fetch()` delegation returns, where `"single-page-application"` turns genuine 404s into the prerendered home page at 200 and `"404-page"` takes the 404 away from the framework's own error route. TanStack Start and React Router deploys are not affected; they follow the rows above.
|
|
122
|
+
|
|
100
123
|
### Generated config
|
|
101
124
|
|
|
102
125
|
Void owns the generated asset routing policy during dev and build. If a root `wrangler.jsonc` contains stale `not_found_handling` or `run_worker_first` values, Void replaces those fields so generated config cannot accidentally change which layer sees a request first.
|
|
@@ -77,6 +77,7 @@ Most configuration is inferred automatically. Use `void.json` to override defaul
|
|
|
77
77
|
| `routing.prerender` | Paths to prerender as static HTML at deploy time |
|
|
78
78
|
| `routing.revalidate` | Default revalidation TTL in seconds for cached responses |
|
|
79
79
|
| `inference.bindings` | Override inferred bindings. You only need this if auto-detection is not doing what you want. Accepts `true` or a custom binding name such as `"db": "MY_DB"`. |
|
|
80
|
+
| `routing.notFound` | **Ignored.** SvelteKit, Nuxt, Analog, and Astro pin `not_found_handling` to `"none"` — the framework worker owns unmatched HTML. `void deploy` warns if set. |
|
|
80
81
|
|
|
81
82
|
See [Configuration](../../reference/config.md) for the full reference.
|
|
82
83
|
|
|
@@ -378,6 +378,24 @@ Paths to prerender as static HTML at deploy time. Each path must start with `/`.
|
|
|
378
378
|
}
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
+
#### `routing.notFound`
|
|
382
|
+
|
|
383
|
+
Override how the asset layer answers a request that matched no asset and no worker route. One of `"single-page-application"`, `"404-page"`, or `"none"`. If omitted, Void infers it — see [Static Assets](../guide/edge/static-assets.md#unmatched-requests).
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"routing": {
|
|
388
|
+
"notFound": "404-page"
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Set `"404-page"` when your assets come from a static site generator (VitePress, Docusaurus, …) and you want unknown URLs to serve the generator's own `404.html` with a real HTTP 404. Without it, an app whose only backend is API routes keeps the inferred SPA fallback, so every unknown URL returns `index.html` with a `200` — correct for a single-page app, wrong for a generated site, and bad for crawlers.
|
|
394
|
+
|
|
395
|
+
`run_worker_first` is untouched, so API routes, auth, and `/__void/*` still reach the worker first. When the worker owns HTML routing it also serves the resolved behavior itself, since `run_worker_first: ["/**"]` means the platform switch never runs — see [Unmatched requests](../guide/edge/static-assets.md#unmatched-requests).
|
|
396
|
+
|
|
397
|
+
This does **not** apply to SvelteKit, Nuxt, Analog, or Astro deploys: those pin `not_found_handling` to `"none"` because the framework's own worker owns unmatched HTML, and `void deploy` warns if you set `routing.notFound` anyway.
|
|
398
|
+
|
|
381
399
|
### `inference`
|
|
382
400
|
|
|
383
401
|
Configuration for build-time inference, including how Void detects your app type, bindings, and build process.
|
|
@@ -403,12 +421,20 @@ Custom names are used in the deploy manifest, Wrangler config generation/sync, r
|
|
|
403
421
|
|
|
404
422
|
#### `inference.build`
|
|
405
423
|
|
|
406
|
-
Override the build command. Useful for frameworks with their own CLIs (Nuxt, Astro) or static apps with custom build scripts. If omitted, the CLI uses the detected default build command for the current app type.
|
|
424
|
+
Override the build command. Useful for frameworks with their own CLIs (Nuxt, Astro) or static apps with custom build scripts. If omitted, the CLI uses the detected default build command for the current app type. It applies to every app type, including `"void"` (where the default is `vite build`).
|
|
407
425
|
|
|
408
426
|
```json
|
|
409
427
|
{ "inference": { "build": "nuxt build --preset cloudflare-module" } }
|
|
410
428
|
```
|
|
411
429
|
|
|
430
|
+
The command is run through a shell from the project root, with the project's own `node_modules/.bin` on `PATH`, so bare binaries and `&&` chains work:
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{ "inference": { "build": "vitepress build && vite build" } }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
That chain is how a static site generator and a Void backend ship together — see [Adding a backend to a static site](../guide/app-types.md#adding-a-backend-to-a-static-site).
|
|
437
|
+
|
|
412
438
|
#### `inference.scanDirs`
|
|
413
439
|
|
|
414
440
|
Override which directories are scanned for [binding inference](./resource-inference.md). Paths are relative to the project root. Replaces the default directories for the current mode.
|