void 0.10.12 → 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.
- package/dist/cli/cli.mjs +10 -10
- package/dist/{config-BkTvs43g.mjs → config-qHGgPuWT.mjs} +10 -0
- package/dist/{db-DOiJMRt2.mjs → db-C0i0sYMS.mjs} +3 -3
- package/dist/{deploy-u7Rv9q_q.mjs → deploy-jBJT1fUT.mjs} +227 -21
- package/dist/{gen-o_w-8yI8.mjs → gen-BzXf3Jh2.mjs} +1 -1
- package/dist/{headers-nsHIFixA.mjs → headers-ChAADPQu.mjs} +1 -1
- package/dist/index.mjs +49 -39
- package/dist/{init-3rgHKBVi.mjs → init-7JCAcKNQ.mjs} +1 -1
- package/dist/{node-Da0UcsGA.mjs → node-pRg81HqV.mjs} +3 -3
- package/dist/pages/index.mjs +3 -3
- package/dist/pages/islands-plugin.mjs +1 -1
- package/dist/{prepare-BoKHgMNx.mjs → prepare-BvvgAz-3.mjs} +3 -3
- package/dist/{provision-rShh6MKY.mjs → provision-CPx2ZxsH.mjs} +1 -1
- package/dist/{route-types-COI2DsZv.mjs → route-types-z1jtHEi_.mjs} +8 -1
- package/dist/{scan-DYXkrasO.mjs → scan-ChWt4pX1.mjs} +12 -13
- package/dist/{scan-DEwlM_Xy.mjs → scan-NU4xKGci.mjs} +4 -1
- package/package.json +2 -2
- package/schema.json +13 -0
- package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
- package/skills/void/docs/guide/deployment.md +68 -1
- package/skills/void/docs/guide/edge/static-assets.md +15 -2
- package/skills/void/docs/guide/server-routing.md +21 -1
- package/skills/void/docs/guide/websockets.md +3 -0
- package/skills/void/docs/node_modules/void/CLAUDE.md +3 -1
- package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
- package/skills/void/docs/reference/cli.md +24 -0
- package/skills/void/docs/reference/config.md +15 -0
- package/skills/void/docs/reference/structure.md +1 -0
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-jBJT1fUT.mjs";
|
|
5
5
|
import { i as resolveProjectPaths, n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-BQd7OmIo.mjs";
|
|
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-
|
|
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-ChWt4pX1.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-qHGgPuWT.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
13
|
import { l as parseDotenvFile } from "./preset-BjyR3lzz.mjs";
|
|
14
|
-
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, o as DRIZZLE_OPERATORS, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-
|
|
14
|
+
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, o as DRIZZLE_OPERATORS, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-z1jtHEi_.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
18
|
import { h as loadUserEnvFile } from "./env-validation-Dea3v3ej.mjs";
|
|
19
|
-
import { i as resolveLayoutIds } from "./scan-
|
|
19
|
+
import { i as resolveLayoutIds, o as isRouteEnabledForEnvironment } from "./scan-NU4xKGci.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-ChAADPQu.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-pRg81HqV.mjs");
|
|
1691
1691
|
await runLocalVoidAuthMigrations(root, dialect, persistRoot, databaseId, auth.configPath);
|
|
1692
1692
|
voidLog("Applied Better Auth migrations");
|
|
1693
1693
|
}
|
|
@@ -4252,6 +4252,7 @@ const RESOLVE_ID_FILTER = /^(virtual:void-(routes|client-worker(?::(react|vue|sv
|
|
|
4252
4252
|
const LOAD_ID_FILTER = /^virtual:void-(routes|client-worker(?::(react|vue|svelte|solid))?|client-browser(?::(react|vue|svelte|solid))?)$/;
|
|
4253
4253
|
function routingPlugin(root, options) {
|
|
4254
4254
|
let scan = null;
|
|
4255
|
+
let routeEnvironment = "prod";
|
|
4255
4256
|
let ssr = null;
|
|
4256
4257
|
let authConfigPath = null;
|
|
4257
4258
|
const paths = readProjectPaths(root);
|
|
@@ -4286,7 +4287,8 @@ function routingPlugin(root, options) {
|
|
|
4286
4287
|
async function rescan() {
|
|
4287
4288
|
scan = await scanRoutes(root, {
|
|
4288
4289
|
output: options.output,
|
|
4289
|
-
paths
|
|
4290
|
+
paths,
|
|
4291
|
+
environment: routeEnvironment
|
|
4290
4292
|
});
|
|
4291
4293
|
authConfigPath = findVoidAuthConfig(root, paths);
|
|
4292
4294
|
const entries = validateSsrEntries(root, paths);
|
|
@@ -4493,6 +4495,7 @@ function routingPlugin(root, options) {
|
|
|
4493
4495
|
}
|
|
4494
4496
|
},
|
|
4495
4497
|
config(_, env) {
|
|
4498
|
+
routeEnvironment = env.command === "serve" && !env.isPreview ? "dev" : "prod";
|
|
4496
4499
|
if (env.command === "serve" && options.isAuthEnabled()) return { resolve: { alias: [
|
|
4497
4500
|
"client",
|
|
4498
4501
|
"react",
|
|
@@ -5063,6 +5066,7 @@ function voidPlugin(options) {
|
|
|
5063
5066
|
const devTriggerToken = isViteBuildOrPreview() ? crypto.randomUUID() : getOrCreateDevTriggerToken(root);
|
|
5064
5067
|
const config = readConfig(root);
|
|
5065
5068
|
const paths = resolveProjectPaths(root, config);
|
|
5069
|
+
let routeEnvironment = "prod";
|
|
5066
5070
|
assertNoRootSourceConventionConflicts(paths);
|
|
5067
5071
|
const dialect = getDatabaseDialect(config);
|
|
5068
5072
|
const detected = detectFramework(root);
|
|
@@ -5224,6 +5228,7 @@ function voidPlugin(options) {
|
|
|
5224
5228
|
}
|
|
5225
5229
|
const remoteMode = resolveRemoteMode(config.remote);
|
|
5226
5230
|
const workerConfig = {};
|
|
5231
|
+
let assetRouting;
|
|
5227
5232
|
if (config.worker?.vars) workerConfig.vars = { ...config.worker.vars };
|
|
5228
5233
|
let viteCommand = "build";
|
|
5229
5234
|
const wranglerCompat = isNodeTarget(config.target) ? void 0 : readWranglerCompat(root, config);
|
|
@@ -5231,17 +5236,18 @@ function voidPlugin(options) {
|
|
|
5231
5236
|
workerConfig.compatibility_date = wranglerCompat.compatibilityDate;
|
|
5232
5237
|
if (wranglerCompat.compatibilityFlags) workerConfig.compatibility_flags = wranglerCompat.compatibilityFlags;
|
|
5233
5238
|
}
|
|
5239
|
+
if (config.worker?.limits?.cpu_ms) workerConfig.limits = { cpu_ms: config.worker.limits.cpu_ms };
|
|
5234
5240
|
const inspectorPortEnv = process.env.VOID_INSPECTOR_PORT;
|
|
5235
5241
|
const inspectorPort = inspectorPortEnv === "false" ? false : typeof inspectorPortEnv === "string" && inspectorPortEnv.length > 0 ? Number.parseInt(inspectorPortEnv, 10) : void 0;
|
|
5236
5242
|
const inspectorPortOption = inspectorPort === false ? { inspectorPort: false } : typeof inspectorPort === "number" ? { inspectorPort } : {};
|
|
5237
5243
|
const outDir = join(root, ".void");
|
|
5238
5244
|
mkdirSync(outDir, { recursive: true });
|
|
5239
5245
|
function mergeBindings(resolved) {
|
|
5246
|
+
const environmentWebsocketRoutes = websocketRoutes.filter((route) => isRouteEnabledForEnvironment(route, routeEnvironment));
|
|
5240
5247
|
const result = { ...workerConfig };
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
if (generatedAssets.run_worker_first !== void 0) delete resolved.assets.run_worker_first;
|
|
5248
|
+
if (workerConfig.assets && resolved.assets) {
|
|
5249
|
+
delete resolved.assets.not_found_handling;
|
|
5250
|
+
delete resolved.assets.run_worker_first;
|
|
5245
5251
|
}
|
|
5246
5252
|
const d1Name = bindingNames?.d1 ?? "DB";
|
|
5247
5253
|
const d1 = [];
|
|
@@ -5282,16 +5288,16 @@ function voidPlugin(options) {
|
|
|
5282
5288
|
...q.retryDelay != null && { retry_delay: q.retryDelay }
|
|
5283
5289
|
}))
|
|
5284
5290
|
};
|
|
5285
|
-
if (
|
|
5291
|
+
if (environmentWebsocketRoutes.length > 0) {
|
|
5286
5292
|
const existingDurableObjectBindings = resolved.durable_objects?.bindings ?? [];
|
|
5287
|
-
const missingDurableObjectBindings =
|
|
5293
|
+
const missingDurableObjectBindings = environmentWebsocketRoutes.filter((route) => !existingDurableObjectBindings.some((binding) => binding.name === route.bindingName)).map((route) => ({
|
|
5288
5294
|
name: route.bindingName,
|
|
5289
5295
|
class_name: route.className
|
|
5290
5296
|
}));
|
|
5291
5297
|
if (missingDurableObjectBindings.length > 0) result.durable_objects = { bindings: [...existingDurableObjectBindings, ...missingDurableObjectBindings] };
|
|
5292
5298
|
if (!Array.isArray(resolved.migrations)) result.migrations = [{
|
|
5293
5299
|
tag: "void-ws-v1",
|
|
5294
|
-
new_classes:
|
|
5300
|
+
new_classes: environmentWebsocketRoutes.map((route) => route.className)
|
|
5295
5301
|
}];
|
|
5296
5302
|
}
|
|
5297
5303
|
if (bindings.needsSandbox && sandboxConfig) {
|
|
@@ -5361,8 +5367,13 @@ function voidPlugin(options) {
|
|
|
5361
5367
|
const envPlugin = {
|
|
5362
5368
|
name: "void:env",
|
|
5363
5369
|
api: { getVoidDeployBuildConfig: () => voidDeployBuildConfig },
|
|
5364
|
-
config(userConfig, { mode, command }) {
|
|
5370
|
+
config(userConfig, { mode, command, isPreview }) {
|
|
5365
5371
|
viteCommand = command;
|
|
5372
|
+
routeEnvironment = command === "serve" && !isPreview ? "dev" : "prod";
|
|
5373
|
+
if (!framework && !isNodeTarget(config.target)) {
|
|
5374
|
+
assetRouting = resolveConfiguredAssetRoutingPolicy();
|
|
5375
|
+
applyAssetRoutingPolicy(assetRouting);
|
|
5376
|
+
}
|
|
5366
5377
|
const ignored = ["**/.void/**"];
|
|
5367
5378
|
const rawEnvDir = typeof userConfig.envDir === "string" && userConfig.envDir.length > 0 ? userConfig.envDir : void 0;
|
|
5368
5379
|
voidDeployBuildConfig = {
|
|
@@ -5376,7 +5387,7 @@ function voidPlugin(options) {
|
|
|
5376
5387
|
...env
|
|
5377
5388
|
};
|
|
5378
5389
|
const assets = workerConfig.assets;
|
|
5379
|
-
if (command === "serve" && assets?.run_worker_first?.includes("/**")) assets.run_worker_first = [...DEV_ASSET_ROUTING_EXCLUSIONS, "/**"];
|
|
5390
|
+
if (command === "serve" && !isPreview && assets?.run_worker_first?.includes("/**")) assets.run_worker_first = [...DEV_ASSET_ROUTING_EXCLUSIONS, "/**"];
|
|
5380
5391
|
const watchConfig = { server: { watch: { ignored } } };
|
|
5381
5392
|
if (!isNodeTarget(config.target) && config.output !== "static") {
|
|
5382
5393
|
const envNames = framework === "vinext-app" ? ["rsc", "ssr"] : ["ssr"];
|
|
@@ -5478,18 +5489,15 @@ function voidPlugin(options) {
|
|
|
5478
5489
|
};
|
|
5479
5490
|
if (framework) {
|
|
5480
5491
|
workerConfig.main = getFrameworkEntry(framework, root, devTriggerToken, config.routing?.notFound);
|
|
5492
|
+
let notFoundIgnoredWarned = false;
|
|
5481
5493
|
const frameworkBuildWrapperPlugin = {
|
|
5482
5494
|
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
|
-
},
|
|
5491
5495
|
configResolved(resolved) {
|
|
5492
5496
|
if (resolved.command !== "build") return;
|
|
5497
|
+
if (config.routing?.notFound !== void 0 && !process.env.VOID_DEPLOY_BUILD && !notFoundIgnoredWarned) {
|
|
5498
|
+
notFoundIgnoredWarned = true;
|
|
5499
|
+
voidWarn(`Void generates no asset routing policy for a ${framework} build, so "routing.notFound" in void.json is not written to the built worker config. On a self-hosted \`vite build && wrangler deploy\` it takes effect only if your own wrangler config declares a complete \`assets\` policy (\`binding\`, \`directory\`, \`not_found_handling\`, \`run_worker_first\`); otherwise Cloudflare's default applies. Managed \`void deploy\` supplies the policy for you.`);
|
|
5500
|
+
}
|
|
5493
5501
|
generateClassATriggerWrapper(root, framework, getOriginalFrameworkEntry(framework, root), true, resolved.build.assetsDir, config.routing?.notFound);
|
|
5494
5502
|
}
|
|
5495
5503
|
};
|
|
@@ -5580,20 +5588,22 @@ function voidPlugin(options) {
|
|
|
5580
5588
|
const ssrEntry = validateSsrEntry(root, paths);
|
|
5581
5589
|
const hasPages = existsSync(paths.pagesDir);
|
|
5582
5590
|
const hasMiddleware = scanMiddlewareSync(root, paths).length > 0;
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5591
|
+
function resolveConfiguredAssetRoutingPolicy() {
|
|
5592
|
+
const routePatterns = scanRoutePatternsSync(root, paths, routeEnvironment);
|
|
5593
|
+
const environmentWebsocketRoutes = websocketRoutes.filter((route) => isRouteEnabledForEnvironment(route, routeEnvironment));
|
|
5594
|
+
return resolveAssetRoutingPolicy({
|
|
5595
|
+
isSsr: Boolean(ssrEntry),
|
|
5596
|
+
hasPages,
|
|
5597
|
+
hasApiRoutes: hasApiRoutes(routePatterns) || hasApiRoutes(environmentWebsocketRoutes) || authEnabled,
|
|
5598
|
+
hasDocumentRoutes: hasDocumentRoutes(routePatterns),
|
|
5599
|
+
hasMiddleware,
|
|
5600
|
+
hasWebSockets: hasDocumentRoutes(environmentWebsocketRoutes),
|
|
5601
|
+
authEnabled,
|
|
5602
|
+
liveEnabled: liveConfig !== void 0,
|
|
5603
|
+
notFound: config.routing?.notFound
|
|
5604
|
+
});
|
|
5605
|
+
}
|
|
5606
|
+
assetRouting = resolveConfiguredAssetRoutingPolicy();
|
|
5597
5607
|
workerConfig.main = join(root, ".void", "entry.ts");
|
|
5598
5608
|
if (jobs.length > 0) workerConfig.triggers = { crons: jobs.flatMap((job) => job.crons) };
|
|
5599
5609
|
function applyAssetRoutingPolicy(policy) {
|
|
@@ -3,7 +3,7 @@ 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-qHGgPuWT.mjs";
|
|
7
7
|
import { r as detectFramework } from "./plugin-inference-DMeavIJ6.mjs";
|
|
8
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";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as join } from "./dist-DaKKDf8D.mjs";
|
|
2
|
-
import { c as loadVoidAuthConfig, s as findVoidAuthConfig } from "./scan-
|
|
3
|
-
import { f as readConfig } from "./config-
|
|
4
|
-
import { a as resolveVoidAuthMigrationSchema } from "./route-types-
|
|
2
|
+
import { c as loadVoidAuthConfig, s as findVoidAuthConfig } from "./scan-ChWt4pX1.mjs";
|
|
3
|
+
import { f as readConfig } from "./config-qHGgPuWT.mjs";
|
|
4
|
+
import { a as resolveVoidAuthMigrationSchema } from "./route-types-z1jtHEi_.mjs";
|
|
5
5
|
import { n as applySqliteAuthMigrations, t as applyPgAuthMigrations } from "./auth-migrations-BTZ-ATvQ.mjs";
|
|
6
6
|
import { getMiniflareDatabasePath } from "./runner-kapo9aPs.mjs";
|
|
7
7
|
import { loadEnv } from "vite";
|
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-
|
|
4
|
-
import { a as resolveNamedLayout, i as resolveLayoutIds, n as resolveEffectiveLayoutChain, r as resolveLayoutChain, t as scanPages } from "../scan-
|
|
5
|
-
import { i as parseRedirectsFile } from "../headers-
|
|
3
|
+
import { f as readConfig } from "../config-qHGgPuWT.mjs";
|
|
4
|
+
import { a as resolveNamedLayout, i as resolveLayoutIds, n as resolveEffectiveLayoutChain, r as resolveLayoutChain, t as scanPages } from "../scan-NU4xKGci.mjs";
|
|
5
|
+
import { i as parseRedirectsFile } from "../headers-ChAADPQu.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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolve, n as dirname } from "../dist-DaKKDf8D.mjs";
|
|
2
|
-
import { n as resolveEffectiveLayoutChain, t as scanPages } from "../scan-
|
|
2
|
+
import { n as resolveEffectiveLayoutChain, t as scanPages } from "../scan-NU4xKGci.mjs";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { normalizePath } from "vite";
|
|
5
5
|
//#region src/pages/islands-plugin.ts
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as join } from "./dist-DaKKDf8D.mjs";
|
|
2
2
|
import { D as outro, E as note, T as log, a as cliTitle, w as intro } from "./agents-CtgBYqld.mjs";
|
|
3
3
|
import { i as resolveProjectPaths } from "./project-paths-BQd7OmIo.mjs";
|
|
4
|
-
import { c as loadVoidAuthConfig, r as scanRoutes, s as findVoidAuthConfig } from "./scan-
|
|
4
|
+
import { c as loadVoidAuthConfig, r as scanRoutes, s as findVoidAuthConfig } from "./scan-ChWt4pX1.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-qHGgPuWT.mjs";
|
|
7
7
|
import { i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, r as detectFramework } from "./plugin-inference-DMeavIJ6.mjs";
|
|
8
|
-
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-
|
|
8
|
+
import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-z1jtHEi_.mjs";
|
|
9
9
|
import { t as discoverSchema } from "./discover-CJHyvYfR.mjs";
|
|
10
10
|
import { r as generateEnvTypes, t as findEnvFile } from "./env-types-QBj-ndax.mjs";
|
|
11
11
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
@@ -1,5 +1,5 @@
|
|
|
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-jBJT1fUT.mjs";
|
|
3
3
|
import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
|
|
4
4
|
import { o as resolveWranglerArgv, u as scanWranglerDotenvKeys } from "./preset-BjyR3lzz.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -283,7 +283,14 @@ function generateVoidAuthDatabaseModule(options) {
|
|
|
283
283
|
`import { db as __voidDb } from "void/db";`,
|
|
284
284
|
`import { schema as __voidAuthSchema } from ${schemaImport};`,
|
|
285
285
|
"",
|
|
286
|
-
|
|
286
|
+
"// Better Auth reads Drizzle metadata while creating the adapter.",
|
|
287
|
+
"// Defer that work until request handling, when runtime bindings are available.",
|
|
288
|
+
"let __voidAuthDatabaseInstance: ReturnType<typeof drizzleAdapter> | undefined;",
|
|
289
|
+
"",
|
|
290
|
+
"export const __voidAuthDatabase: ReturnType<typeof drizzleAdapter> = (options) => {",
|
|
291
|
+
` __voidAuthDatabaseInstance ??= drizzleAdapter(__voidDb, { provider: ${JSON.stringify(provider)}, schema: __voidAuthSchema });`,
|
|
292
|
+
" return __voidAuthDatabaseInstance(options);",
|
|
293
|
+
"};",
|
|
287
294
|
""
|
|
288
295
|
].join("\n");
|
|
289
296
|
}
|
|
@@ -2,7 +2,7 @@ import { a as join, i as isAbsolute, t as basename } from "./dist-DaKKDf8D.mjs";
|
|
|
2
2
|
import { n as readProjectPaths } from "./project-paths-BQd7OmIo.mjs";
|
|
3
3
|
import { n as globSync, t as glob } from "./dist-BuiRJkTd.mjs";
|
|
4
4
|
import { c as isIdentifier, d as isObjectExpression, l as isLiteral, s as isCallExpression } from "./entry-D7yy4xVH.mjs";
|
|
5
|
-
import { o as
|
|
5
|
+
import { c as parseWebSocketFilename, o as isRouteEnabledForEnvironment, s as parseRouteFilename, t as scanPages } from "./scan-NU4xKGci.mjs";
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { parseSync } from "vite";
|
|
@@ -261,19 +261,19 @@ function buildWebSocketRoute(root, filePath, paths = readProjectPaths(root)) {
|
|
|
261
261
|
hasAuthGate: info?.hasAuthGate ?? false
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
-
function parseWebSocketFiles(root, files, paths = readProjectPaths(root)) {
|
|
265
|
-
return files.filter((f) => isWebSocketRouteFile(f)).filter((f) => !f.split("/").some((segment) => segment.startsWith("_"))).map((filePath) => buildWebSocketRoute(root, filePath, paths)).sort((a, b) => {
|
|
264
|
+
function parseWebSocketFiles(root, files, paths = readProjectPaths(root), environment) {
|
|
265
|
+
return files.filter((f) => isWebSocketRouteFile(f)).filter((f) => !f.split("/").some((segment) => segment.startsWith("_"))).filter((filePath) => isRouteEnabledForEnvironment(parseWebSocketFilename(filePath), environment)).map((filePath) => buildWebSocketRoute(root, filePath, paths)).sort((a, b) => {
|
|
266
266
|
if (a.catchAll !== b.catchAll) return a.catchAll ? 1 : -1;
|
|
267
267
|
return a.pattern.localeCompare(b.pattern);
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
|
-
async function scanWebSocketRoutes(root, paths = readProjectPaths(root)) {
|
|
270
|
+
async function scanWebSocketRoutes(root, paths = readProjectPaths(root), environment) {
|
|
271
271
|
return parseWebSocketFiles(root, await glob(`**/*.{${HANDLER_EXTENSIONS.join(",")}}`, {
|
|
272
272
|
cwd: paths.routesDir,
|
|
273
273
|
absolute: false
|
|
274
|
-
}).catch(() => []), paths);
|
|
274
|
+
}).catch(() => []), paths, environment);
|
|
275
275
|
}
|
|
276
|
-
function scanWebSocketRoutesSync(root, paths = readProjectPaths(root)) {
|
|
276
|
+
function scanWebSocketRoutesSync(root, paths = readProjectPaths(root), environment) {
|
|
277
277
|
const pattern = `**/*.{${HANDLER_EXTENSIONS.join(",")}}`;
|
|
278
278
|
let files;
|
|
279
279
|
try {
|
|
@@ -284,7 +284,7 @@ function scanWebSocketRoutesSync(root, paths = readProjectPaths(root)) {
|
|
|
284
284
|
} catch {
|
|
285
285
|
files = [];
|
|
286
286
|
}
|
|
287
|
-
return parseWebSocketFiles(root, files, paths);
|
|
287
|
+
return parseWebSocketFiles(root, files, paths, environment);
|
|
288
288
|
}
|
|
289
289
|
function isWebSocketPatternFile(filePath) {
|
|
290
290
|
return isWebSocketRouteFile(filePath);
|
|
@@ -329,7 +329,7 @@ async function scanRoutes(root, options) {
|
|
|
329
329
|
cwd: paths.routesDir,
|
|
330
330
|
absolute: false
|
|
331
331
|
}),
|
|
332
|
-
scanWebSocketRoutes(root, paths),
|
|
332
|
+
scanWebSocketRoutes(root, paths, options?.environment),
|
|
333
333
|
glob(ROUTE_PATTERN, {
|
|
334
334
|
cwd: paths.middlewareDir,
|
|
335
335
|
absolute: false
|
|
@@ -343,9 +343,8 @@ async function scanRoutes(root, options) {
|
|
|
343
343
|
]);
|
|
344
344
|
const routesDir = paths.routesDir;
|
|
345
345
|
return {
|
|
346
|
-
routes: routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((
|
|
347
|
-
|
|
348
|
-
route.methods = detectHttpExports(join(routesDir, f));
|
|
346
|
+
routes: routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((file) => parseRouteFilename(file)).filter((route) => isRouteEnabledForEnvironment(route, options?.environment)).map((route) => {
|
|
347
|
+
route.methods = detectHttpExports(join(routesDir, route.filePath));
|
|
349
348
|
return route;
|
|
350
349
|
}).sort(compareRouteSpecificity),
|
|
351
350
|
websockets,
|
|
@@ -367,7 +366,7 @@ function scanMiddlewareSync(root, paths = readProjectPaths(root)) {
|
|
|
367
366
|
}
|
|
368
367
|
return parseMiddlewareDefinitions(middlewareFiles);
|
|
369
368
|
}
|
|
370
|
-
function scanRoutePatternsSync(root, paths = readProjectPaths(root)) {
|
|
369
|
+
function scanRoutePatternsSync(root, paths = readProjectPaths(root), environment) {
|
|
371
370
|
let routeFiles;
|
|
372
371
|
try {
|
|
373
372
|
routeFiles = globSync(ROUTE_PATTERN, {
|
|
@@ -377,7 +376,7 @@ function scanRoutePatternsSync(root, paths = readProjectPaths(root)) {
|
|
|
377
376
|
} catch {
|
|
378
377
|
routeFiles = [];
|
|
379
378
|
}
|
|
380
|
-
return routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((f) => parseRouteFilename(f)).sort(compareRouteSpecificity);
|
|
379
|
+
return routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((f) => parseRouteFilename(f)).filter((route) => isRouteEnabledForEnvironment(route, environment)).sort(compareRouteSpecificity);
|
|
381
380
|
}
|
|
382
381
|
//#endregion
|
|
383
382
|
export { scanQueuesSync as a, loadVoidAuthConfig as c, scanWebSocketRoutesSync as i, scanRoutePatternsSync as n, scanJobsSync as o, scanRoutes as r, findVoidAuthConfig as s, scanMiddlewareSync as t };
|
|
@@ -74,6 +74,9 @@ function parseRouteFilename(relativePath) {
|
|
|
74
74
|
function parseWebSocketFilename(relativePath) {
|
|
75
75
|
return parsePath(relativePath, true);
|
|
76
76
|
}
|
|
77
|
+
function isRouteEnabledForEnvironment(route, environment) {
|
|
78
|
+
return environment === void 0 || route.env === null || route.env === environment;
|
|
79
|
+
}
|
|
77
80
|
//#endregion
|
|
78
81
|
//#region src/pages/layout-resolution.ts
|
|
79
82
|
function isNamedLayout(layout) {
|
|
@@ -348,4 +351,4 @@ function extractFrontmatterLayout(source) {
|
|
|
348
351
|
return value;
|
|
349
352
|
}
|
|
350
353
|
//#endregion
|
|
351
|
-
export { resolveNamedLayout as a, resolveLayoutIds as i, resolveEffectiveLayoutChain as n,
|
|
354
|
+
export { resolveNamedLayout as a, parseWebSocketFilename as c, resolveLayoutIds as i, resolveEffectiveLayoutChain as n, isRouteEnabledForEnvironment as o, resolveLayoutChain as r, parseRouteFilename as s, scanPages as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "void",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.13",
|
|
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.13"
|
|
360
360
|
},
|
|
361
361
|
"peerDependenciesMeta": {
|
|
362
362
|
"@void/md": {
|
package/schema.json
CHANGED
|
@@ -360,6 +360,19 @@
|
|
|
360
360
|
"type": "object",
|
|
361
361
|
"description": "Environment variables (plain text, not secrets)",
|
|
362
362
|
"additionalProperties": { "type": "string" }
|
|
363
|
+
},
|
|
364
|
+
"limits": {
|
|
365
|
+
"type": "object",
|
|
366
|
+
"description": "Worker resource limit overrides.",
|
|
367
|
+
"properties": {
|
|
368
|
+
"cpu_ms": {
|
|
369
|
+
"type": "integer",
|
|
370
|
+
"minimum": 1,
|
|
371
|
+
"maximum": 300000,
|
|
372
|
+
"description": "Caps Workers CPU time per request, in milliseconds (max 300000 = 5 minutes). The platform plan may cap it lower."
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
"additionalProperties": false
|
|
363
376
|
}
|
|
364
377
|
},
|
|
365
378
|
"additionalProperties": false
|
|
@@ -103,7 +103,7 @@ Apply these filename rules exactly when mapping old handlers to `routes/`:
|
|
|
103
103
|
1. Extension and suffix parsing order
|
|
104
104
|
|
|
105
105
|
- Strip extension (`.ts`, `.js`, `.mts`, `.mjs`).
|
|
106
|
-
- Strip env suffix (`.dev`, `.prod`).
|
|
106
|
+
- Strip env suffix (`.dev`, `.prod`). The suffix restricts the route to that environment, so only use it for handlers that must not ship to the other one.
|
|
107
107
|
- Strip HTTP method suffix (`.get`, `.post`, `.put`, `.delete`, `.patch`).
|
|
108
108
|
- Strip trailing `index` segment.
|
|
109
109
|
- Remove route group segments `(group-name)` from URL path.
|
|
@@ -62,10 +62,77 @@ If your CI pipeline runs typechecking or other static analysis before deploy, ru
|
|
|
62
62
|
| `VOID_TOKEN` | Auth token (for CI, skips OAuth) |
|
|
63
63
|
| `VOID_PROJECT` | Project slug override |
|
|
64
64
|
|
|
65
|
-
## GitHub
|
|
65
|
+
## GitHub
|
|
66
66
|
|
|
67
67
|
You can deploy from a GitHub repo on every push to `main`. Running `void init --github` generates `.github/workflows/void-deploy.yml` in your project.
|
|
68
68
|
|
|
69
|
+
### Void GitHub App (recommended)
|
|
70
|
+
|
|
71
|
+
No `.github/workflows/void-deploy.yml` or repository `VOID_TOKEN` is required.
|
|
72
|
+
|
|
73
|
+
1. Initialize your Void project
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
void init
|
|
77
|
+
# Or, for existing Void project:
|
|
78
|
+
void project link
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. Install the Void GitHub app
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
void github install
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
You should receive GitHub url asking you to install and authorize `Void Deploy`.
|
|
88
|
+
|
|
89
|
+
In the browser, select the GitHub account/organization and grant access to the repository.
|
|
90
|
+
|
|
91
|
+
:::details If someone already installed the App for your organization
|
|
92
|
+
Join that installation instead:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
void github join
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
:::
|
|
99
|
+
|
|
100
|
+
3. Link your repository to your project
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
void github connect --executor container
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
4. Verify the connection
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
void github status
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
You should receive an output like:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
Repository <owner/repository>
|
|
116
|
+
Branch main
|
|
117
|
+
Build executor container
|
|
118
|
+
Deploy workflow .github/workflows/void-deploy.yml (unused for container builds)
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
5. Push to the configured branch to trigger a new deploy
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git push origin main
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
6. Follow the build
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
void build logs --follow
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### GitHub Actions
|
|
135
|
+
|
|
69
136
|
The workflow authenticates with [GitHub OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) — there is **no long-lived `VOID_TOKEN` secret to store or rotate**. `void deploy` detects GitHub Actions and does the exchange itself: it requests a short-lived OIDC token from GitHub (audience `void`), exchanges it at `POST $VOID_API_URL/auth/github-oidc` for a short-lived project-scoped deploy token (about 15 minutes on the free tier; longer on higher plans, capped at ~60 minutes), and deploys — so the workflow is a single `void deploy` step. `permissions: id-token: write` is still required: it is what lets the job (and the CLI) mint the OIDC token.
|
|
70
137
|
|
|
71
138
|
```yaml
|
|
@@ -118,11 +118,24 @@ The SPA fallback is right for a single-page app, where deep links must boot the
|
|
|
118
118
|
|
|
119
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
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.
|
|
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.
|
|
122
|
+
|
|
123
|
+
TanStack Start, React Router, and vinext follow the rows above on a managed `void deploy` — that path resolves the asset config itself and applies it to the uploaded worker. Void writes no `assets` policy into their generated worker wrangler config:
|
|
124
|
+
|
|
125
|
+
| Framework | Generated worker config |
|
|
126
|
+
| -------------- | ---------------------------- |
|
|
127
|
+
| TanStack Start | `dist/server/wrangler.json` |
|
|
128
|
+
| React Router | `build/server/wrangler.json` |
|
|
129
|
+
| vinext (App) | `dist/server/wrangler.json` |
|
|
130
|
+
| vinext (Pages) | `dist/ssr/wrangler.json` |
|
|
131
|
+
|
|
132
|
+
So on a self-hosted `wrangler deploy` the setting takes effect only if that config declares a complete `assets` policy of its own — `binding`, `directory`, `not_found_handling`, and `run_worker_first`. Void leaves those fields alone, so a policy you write yourself is honored by the generated wrapper; with no policy at all, Cloudflare's default applies. `vite build` warns when `routing.notFound` is set so the choice is not silent.
|
|
122
133
|
|
|
123
134
|
### Generated config
|
|
124
135
|
|
|
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.
|
|
136
|
+
Void owns the generated asset routing policy during dev and build for Void apps. 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.
|
|
137
|
+
|
|
138
|
+
TanStack Start and React Router are the exception: Void generates no asset policy for them and leaves both fields to your own wrangler config. Writing `not_found_handling` alone would make the asset layer answer unmatched requests and the framework worker would never run, and completing the policy needs `assets.binding` and `assets.directory` that the framework owns, not Void.
|
|
126
139
|
|
|
127
140
|
## API routes and SSR pages
|
|
128
141
|
|
|
@@ -14,7 +14,27 @@ Create route handlers in `routes/**/*.ts`. Each file maps to a URL path based on
|
|
|
14
14
|
|
|
15
15
|
Dynamic segments use brackets: `[id]` becomes a route parameter and `[...slug]` becomes a catch-all. Files or directories starting with `_` are ignored. Directories wrapped in parentheses like `(admin)` are route groups. They help organize files without changing the URL.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Environment-only routes
|
|
18
|
+
|
|
19
|
+
Add a `.dev` or `.prod` suffix before the extension to include a route in only one environment:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
routes/
|
|
23
|
+
api/health.ts → always
|
|
24
|
+
api/debug.dev.ts → development only
|
|
25
|
+
api/metrics.prod.ts → production only
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`dev` is the Vite dev server. `prod` is every build, `void deploy`, and `vite preview` — preview serves a production build, so it uses the production routes.
|
|
29
|
+
|
|
30
|
+
An excluded route is stripped from the worker bundle, from the generated types, and from the generated route and WebSocket config. It is not compiled and it is not deployed.
|
|
31
|
+
|
|
32
|
+
Two things do not follow the suffix:
|
|
33
|
+
|
|
34
|
+
- `void prepare` boots no Vite, so it has no environment to read. It generates types for every route in both environments.
|
|
35
|
+
- [Binding inference](../reference/resource-inference.md) scans your source for imports and does not read the suffix. A `void/storage` import inside `api/debug.dev.ts` still adds an R2 binding to your production config, and `void deploy` still provisions the bucket. Set the binding explicitly with [`inference.bindings`](../reference/config.md#inference-bindings) if you do not want it.
|
|
36
|
+
|
|
37
|
+
The suffix applies to files in `routes/` only. It has no effect in `pages/`, `middleware/`, `crons/`, or `queues/`.
|
|
18
38
|
|
|
19
39
|
Each file exports named HTTP method constants to handle specific methods:
|
|
20
40
|
|
|
@@ -28,6 +28,9 @@ Filename rules match regular server routes:
|
|
|
28
28
|
- `[id].ws.ts` becomes `:id`
|
|
29
29
|
- `[...slug].ws.ts` becomes a catch-all
|
|
30
30
|
- route groups like `(marketing)/chat.ws.ts` are ignored in the URL
|
|
31
|
+
- `chat.dev.ws.ts` / `chat.prod.ws.ts` restrict the route to one environment
|
|
32
|
+
|
|
33
|
+
The environment suffix goes **before** `.ws`. `chat.ws.dev.ts` is not recognised.
|
|
31
34
|
|
|
32
35
|
## `defineRoom()`
|
|
33
36
|
|
|
@@ -144,7 +144,7 @@ Build output uses `.mjs` / `.d.mts` extensions (`vp pack` / tsdown default).
|
|
|
144
144
|
Applied in order to a path relative to `routes/`:
|
|
145
145
|
|
|
146
146
|
1. Strip extension (`.ts`, `.js`, `.mts`, `.mjs`)
|
|
147
|
-
2. Strip env suffix (`.dev`, `.prod`)
|
|
147
|
+
2. Strip env suffix (`.dev`, `.prod`) and record it on `RouteDefinition.env`
|
|
148
148
|
3. Strip trailing `index`
|
|
149
149
|
4. Remove route group dirs `(groupname)`
|
|
150
150
|
5. Convert `[param]` → `:param`, `[...param]` → `:param{.+}`, `[...]` → `*`
|
|
@@ -152,6 +152,8 @@ Applied in order to a path relative to `routes/`:
|
|
|
152
152
|
|
|
153
153
|
HTTP methods are determined by **named exports** in each file (detected by `detect-exports.ts` via `parseSync`), not by filename suffixes. Files/dirs starting with `_` are ignored by the scanner.
|
|
154
154
|
|
|
155
|
+
The recorded `env` is enforced by `isRouteEnabledForEnvironment()`. The scanners take an optional `environment` (`'dev' | 'prod'`); when it is omitted no filtering happens. `dev` is the Vite dev server, `prod` is every build, `void deploy`, and `vite preview`. A dev-only route is absent from the production bundle, types, and generated route/WebSocket config. Two paths do not follow it: `void prepare` boots no Vite, passes no `environment`, and so generates types for both environments; and `inferProjectBindings()` (`plugin-inference.ts`) globs source files without parsing route filenames, so a `void/storage` import in a `.dev.ts` route still emits and provisions an R2 binding in production. Over-emitting is the deliberate safe direction — under-emitting would drop a binding the running code needs — and the environment is not knowable at the `inferProjectBindings()` call site anyway (`index.ts:678`, versus `routeEnvironment` at `index.ts:1324`). The suffix is honoured in `routes/` only, not in `pages/`, `middleware/`, `crons/`, or `queues/`.
|
|
156
|
+
|
|
155
157
|
## Code Generation (`void gen`)
|
|
156
158
|
|
|
157
159
|
Rails/Laravel-style scaffolding commands for common boilerplate:
|
|
@@ -103,7 +103,7 @@ Apply these filename rules exactly when mapping old handlers to `routes/`:
|
|
|
103
103
|
1. Extension and suffix parsing order
|
|
104
104
|
|
|
105
105
|
- Strip extension (`.ts`, `.js`, `.mts`, `.mjs`).
|
|
106
|
-
- Strip env suffix (`.dev`, `.prod`).
|
|
106
|
+
- Strip env suffix (`.dev`, `.prod`). The suffix restricts the route to that environment, so only use it for handlers that must not ship to the other one.
|
|
107
107
|
- Strip HTTP method suffix (`.get`, `.post`, `.put`, `.delete`, `.patch`).
|
|
108
108
|
- Strip trailing `index` segment.
|
|
109
109
|
- Remove route group segments `(group-name)` from URL path.
|