void 1.5.0 → 1.6.0
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/{account-cmd-ChVKPZ-w.mjs → account-cmd-DRhmCWcm.mjs} +3 -3
- package/dist/{application-analysis-2vGZrKzg.mjs → application-analysis-HRFsZOv3.mjs} +3 -3
- package/dist/{application-routing-DnUQoOJD.mjs → application-routing-P4TggvWp.mjs} +1 -1
- package/dist/{auth-link-D4uMdOsw.mjs → auth-link-CVXP1G6G.mjs} +3 -3
- package/dist/{auth-router-62dna450.mjs → auth-router-D1pGdbvB.mjs} +2 -2
- package/dist/{better-auth-shared-CiclgPde.mjs → better-auth-shared-C958aM7W.mjs} +28 -3
- package/dist/{build-cmd-D93K_4Sa.mjs → build-cmd-DmjbuT6d.mjs} +1 -1
- package/dist/{cache-CiIzEED4.mjs → cache-2khBO1MR.mjs} +1 -1
- package/dist/{cancel-deploy-BNoGfn2e.mjs → cancel-deploy-_itbbvY3.mjs} +1 -1
- package/dist/cli/cli.mjs +27 -27
- package/dist/client-CEtr5UOR.mjs +1 -0
- package/dist/{client-Qayy9xEh.mjs → client-CoxPEhJq.mjs} +6 -2
- package/dist/{cloudflare-cmd-DiFGaLow.mjs → cloudflare-cmd-fZ6AMSWk.mjs} +1 -1
- package/dist/{cloudflare-config-Chet188_.mjs → cloudflare-config-eQunqX6A.mjs} +1 -1
- package/dist/{cloudflare-connect-BdgAQBc7.mjs → cloudflare-connect-9OZe4XVQ.mjs} +1 -1
- package/dist/{cloudflare-operations-CWA24vCh.mjs → cloudflare-operations-CrskD4_O.mjs} +4 -4
- package/dist/{cloudflare-operations-D8MoezuN.mjs → cloudflare-operations-oLvAd9Nd.mjs} +1 -1
- package/dist/{config-Bt4rw93x.mjs → config-BBlVXZCU.mjs} +1 -1
- package/dist/{config-DkA9jYF2.mjs → config-BITKQ3O6.mjs} +2 -1
- package/dist/{config-BszFVhpk.mjs → config-BgysUhnm.mjs} +1 -1
- package/dist/{config-CzsNzGVH.d.mts → config-CPxJmt4M.d.mts} +2 -0
- package/dist/config-entry.d.mts +1 -1
- package/dist/{connect-b9mJbb6X.mjs → connect-CKvmkl2R.mjs} +4 -4
- package/dist/{create-project-rnjum4_V.mjs → create-project-BiMmR5wA.mjs} +1 -1
- package/dist/{create-project-eomwm0wa.mjs → create-project-Bq0M8MtO.mjs} +1 -1
- package/dist/{db-UPWmiSk1.mjs → db-cq5RW4Vy.mjs} +17 -17
- package/dist/{delete-BeeYi1WO.mjs → delete-CY4L8fi2.mjs} +1 -1
- package/dist/deploy-B029oHlE.mjs +2 -0
- package/dist/{deploy-CwmCcTlq.mjs → deploy-D_gEojD2.mjs} +79 -42
- package/dist/{deploy-managed-static-gPDxyp3v.mjs → deploy-managed-static-BHVbB_-A.mjs} +11 -11
- package/dist/{deploy-transport-L6zzPE9T.mjs → deploy-transport-DEyOUfac.mjs} +2 -2
- package/dist/deploy.mjs +5 -4
- package/dist/{dev-inbox-CwH5VuZj.mjs → dev-inbox-CUUS6iUg.mjs} +1 -1
- package/dist/{dispatch-CiJ1Xrba.mjs → dispatch-BNLB5_Gm.mjs} +1 -1
- package/dist/{domain-3xdH9p2L.mjs → domain-BlH90Ods.mjs} +2 -2
- package/dist/{email-Du3YRnM5.mjs → email-Bkq8ZL1p.mjs} +6 -6
- package/dist/{email-cloudflare-CtD8qE13.mjs → email-cloudflare-MvJIxrwx.mjs} +5 -107
- package/dist/{env-Cnvp0pGy.mjs → env-AvuCSeCH.mjs} +2 -2
- package/dist/{gen-BvVmpZ5f.mjs → gen-BD-dfjxY.mjs} +3 -3
- package/dist/gen-CIHXh9I8.mjs +1 -0
- package/dist/{github-cmd-X2GKiLi-.mjs → github-cmd-CA-lBdkn.mjs} +1 -1
- package/dist/{handler-Drm_lyrV.d.mts → handler-BR6Tvjkp.d.mts} +8 -3
- package/dist/{handler-xmzz0U38.mjs → handler-CKeTtYID.mjs} +2 -12
- package/dist/headers-CuTEdP9C.mjs +445 -0
- package/dist/{help-EWDNxST-.mjs → help-1OCFDsRr.mjs} +9 -1
- package/dist/help-Ba8Dq5-r.mjs +1 -0
- package/dist/{inbound-DSKSHDUj.d.mts → inbound-BHt4dIlL.d.mts} +16 -5
- package/dist/{inbound-DQfekMtz.mjs → inbound-CdO-qY0w.mjs} +2 -76
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +65 -37
- package/dist/{info-jW8_d3VI.mjs → info-CJxua_db.mjs} +4 -4
- package/dist/{init-jDQ5G9QC.mjs → init-B-4CluYx.mjs} +12 -11
- package/dist/{isr-config-DhBkzCdY.mjs → isr-config-Czzb4mIq.mjs} +1 -47
- package/dist/{link-DOyxX5Xs.mjs → link-DAXOT-G_.mjs} +2 -2
- package/dist/{list-C9pTYDtl.mjs → list-BYSOo9fm.mjs} +2 -2
- package/dist/{live-CDBHyYiR.d.mts → live-0xrcVpyq.d.mts} +1 -1
- package/dist/{local-d1-CKfek6Z4.mjs → local-d1-BLgsNUj9.mjs} +1 -1
- package/dist/login-BpbYeu-n.mjs +1 -0
- package/dist/{login-koQbjzPd.mjs → login-CJopTWX4.mjs} +2 -2
- package/dist/{logs-cTU5TsRW.mjs → logs-DE0weBsR.mjs} +13 -11
- package/dist/{migrate-C62pGTfr.mjs → migrate-ChHHZBTi.mjs} +3 -3
- package/dist/migrate-HdA6Kh2p.mjs +1 -0
- package/dist/{mime-DKkO7ZtA.mjs → mime-CtP6-qAF.mjs} +143 -15
- package/dist/nitro.mjs +3 -3
- package/dist/{node-BfAt2rz8.mjs → node-CZ6lGNLn.mjs} +1 -1
- package/dist/{operator-cmd-CnAvSqJ5.mjs → operator-cmd-BUqsdk2K.mjs} +28 -10
- package/dist/operator-cmd-wBYyp8iQ.mjs +1 -0
- package/dist/pages/client.d.mts +143 -11
- package/dist/pages/client.mjs +173 -12
- package/dist/pages/index.d.mts +22 -3
- package/dist/pages/index.mjs +18 -4
- package/dist/pages/islands-plugin.d.mts +46 -2
- package/dist/pages/islands-plugin.mjs +91 -5
- package/dist/pages/prefetch.d.mts +2 -2
- package/dist/pages/prefetch.mjs +1 -1
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +2 -2
- package/dist/platform-args-C7XUw9mw.mjs +1 -0
- package/dist/{platform-args-Bv9hoLEL.mjs → platform-args-Cx1M-7bq.mjs} +18 -4
- package/dist/{platform-auth-config-CLj0EvnB.mjs → platform-auth-config-K4m_LE8b.mjs} +4 -4
- package/dist/{platform-auth-protection-DQDSxVhr.mjs → platform-auth-protection-KF5Vo-1I.mjs} +2 -2
- package/dist/{platform-auth-recovery-DD3oLerq.mjs → platform-auth-recovery-BOdFrl7R.mjs} +2 -2
- package/dist/{platform-cmd-WnOmdLzR.mjs → platform-cmd-DpCHawGF.mjs} +1 -1
- package/dist/{platform-cmd-PLNLg-2N.mjs → platform-cmd-Ds3EAV5t.mjs} +5 -4
- package/dist/{platform-domain-Bnq0vg0q.mjs → platform-domain-rWJshybd.mjs} +2 -2
- package/dist/{platform-lifecycle-BQiN9HN5.mjs → platform-lifecycle-BH34I0rh.mjs} +1 -1
- package/dist/{platform-lifecycle-BCsFg1Tl.mjs → platform-lifecycle-D9RdGub1.mjs} +66 -31
- package/dist/{platform-management-nzCDwtiN.mjs → platform-management-BRefdHGD.mjs} +21 -3
- package/dist/{platform-management-CMJR8Zi-.mjs → platform-management-BfLW0Zlo.mjs} +1 -1
- package/dist/{platform-recovery-DBNoME3I.mjs → platform-recovery-DZuZGJtt.mjs} +2 -2
- package/dist/platform-runtime-logs-CdJGnk3f.mjs +68 -0
- package/dist/{platform-setup-VEcHu0Vq.mjs → platform-setup-Vw1AaUN8.mjs} +15 -2
- package/dist/{plugin-inference-DmwjC3y2.mjs → plugin-inference-CdLbxZL_.mjs} +1 -1
- package/dist/{prefetch-L93_1Jk7.mjs → prefetch-Dx2jIbtg.mjs} +19 -1
- package/dist/{prefetch-CG0K6JPd.d.mts → prefetch-NdNeubbH.d.mts} +7 -1
- package/dist/{prepare-pfl2LIMy.mjs → prepare-BJW-f1f1.mjs} +4 -4
- package/dist/{prepare-BPsfLWaW.mjs → prepare-BMGeKL16.mjs} +1 -1
- package/dist/prepare-BdxJJCMv.mjs +1 -0
- package/dist/{project-cmd-5Izu4Pg3.mjs → project-cmd-B_bUyZ3J.mjs} +15 -15
- package/dist/{project-team-DSUyjZFh.mjs → project-team-MjT5K4rQ.mjs} +1 -1
- package/dist/{project-token-wj09YQSB.mjs → project-token-BKHGwY3k.mjs} +1 -1
- package/dist/{project-zero-trust-BMHf2YF0.mjs → project-zero-trust-Bzdd05-h.mjs} +1 -1
- package/dist/{protocol-0-lp9ZYx.mjs → protocol-COyySMki.mjs} +130 -107
- package/dist/{protocol-xdrl-In0.d.mts → protocol-CkwjASep.d.mts} +2 -2
- package/dist/{provision-CRiNnlfE.mjs → provision-BbCzpbXs.mjs} +1 -1
- package/dist/{provision-CxHIGBIZ.mjs → provision-DQF6Kw-6.mjs} +1 -1
- package/dist/{requests-DicgB5jU.mjs → requests-daJmVpjk.mjs} +1 -1
- package/dist/{rollback-DrDZOKNE.mjs → rollback-BgL0wY1J.mjs} +2 -2
- package/dist/{route-types-irZc1txT.mjs → route-types-CZc1YXjF.mjs} +40 -4
- package/dist/route-url-C_YIH_zO.d.mts +37 -0
- package/dist/routes-stub.d.mts +6 -2
- package/dist/runtime/better-auth-mysql.mjs +1 -1
- package/dist/runtime/better-auth-pg.mjs +1 -1
- package/dist/runtime/better-auth.mjs +1 -1
- package/dist/runtime/email/testing.d.mts +1 -1
- package/dist/runtime/email/testing.mjs +3 -3
- package/dist/runtime/email.d.mts +1 -1
- package/dist/runtime/email.mjs +3 -3
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/handler.mjs +2 -2
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/route-url.d.mts +2 -2
- package/dist/runtime/routing.d.mts +3 -3
- package/dist/runtime/routing.mjs +37 -16
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +1 -1
- package/dist/runtime/ws.d.mts +1 -1
- package/dist/{scan-DSQtH3oS.mjs → scan-X2rDDRNa.mjs} +18 -10
- package/dist/{secret-C_QtTzJG.mjs → secret-CPq-B_vp.mjs} +2 -2
- package/dist/{static-asset-marker-NR1XS2z5.mjs → static-asset-marker-DsQKz6lt.mjs} +8 -1
- package/dist/validation-error-uAAjlsiW.mjs +82 -0
- package/dist/{version-metadata-Blf5H9BQ.mjs → version-metadata-C8hLzQkv.mjs} +1 -1
- package/dist/{wrangler-p-kLCJqF.mjs → wrangler-DESSbN8L.mjs} +2 -2
- package/dist/wrangler-DUB3MGHV.mjs +1 -0
- package/dist/{wrapper-iejSJDwL.mjs → wrapper-C9SUxoj7.mjs} +2 -2
- package/dist/{ws-DzBD-NUj.d.mts → ws-Dmjg5NH2.d.mts} +1 -1
- package/docs/guide/edge/headers.md +2 -0
- package/docs/guide/edge/rewrites.md +1 -1
- package/docs/guide/email/receiving.md +1 -1
- package/docs/guide/email/sending.md +42 -12
- package/docs/guide/pages-routing/actions-and-forms.md +21 -1
- package/docs/guide/pages-routing/islands.md +3 -3
- package/docs/guide/pages-routing/layouts.md +16 -2
- package/docs/guide/pages-routing/loaders.md +2 -2
- package/docs/guide/pages-routing/markdown.md +1 -1
- package/docs/guide/pages-routing/overview.md +103 -0
- package/docs/guide/platform/administration/connected-apps.md +4 -2
- package/docs/guide/platform/installation/credentials.md +6 -4
- package/docs/guide/platform/installation/maintenance.md +13 -0
- package/docs/guide/platform/installation/setup.md +22 -1
- package/docs/guide/ssg.md +3 -3
- package/docs/guide/type-safety.md +42 -1
- package/docs/integrations/frameworks/astro.md +17 -12
- package/docs/integrations/frameworks/overview.md +2 -2
- package/docs/reference/api/pages.md +101 -50
- package/docs/reference/api/rewrites.md +4 -1
- package/docs/reference/api/types.md +14 -10
- package/docs/reference/api.md +1 -1
- package/docs/reference/cli/platform-installation.md +4 -1
- package/docs/reference/cli/setup.md +1 -1
- package/docs/reference/config.md +10 -0
- package/package.json +8 -7
- package/schema.json +4 -0
- package/skills/void/SKILL.md +5 -0
- package/dist/client-DVtAKeUm.mjs +0 -1
- package/dist/deploy-CfU7CR-b.mjs +0 -2
- package/dist/gen-i61rdQgg.mjs +0 -1
- package/dist/headers-DEUBAM6W.mjs +0 -225
- package/dist/help-BcZDUcTw.mjs +0 -1
- package/dist/login-C0MiQrYs.mjs +0 -1
- package/dist/migrate-BG09-KOv.mjs +0 -1
- package/dist/operator-cmd-BhHXIL2G.mjs +0 -1
- package/dist/platform-args-C1kqYUen.mjs +0 -1
- package/dist/prepare-BJMmfq3W.mjs +0 -1
- package/dist/route-url-BGOiyKNz.d.mts +0 -24
- package/dist/validation-error-rDVbUJet.mjs +0 -47
- package/dist/wrangler-CPRX2d9A.mjs +0 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { a as join } from "./dist-DMYe8CZJ.mjs";
|
|
2
|
+
import { a as convertRewritesConfig, d as isProtocolRelative, f as parseHostPrefixedSource, i as convertRedirectsConfig, n as convertFallbacksConfig, o as countSplats, r as convertHeadersConfig, t as VALID_REDIRECT_STATUSES } from "./config-BITKQ3O6.mjs";
|
|
3
|
+
import { i as voidWarn } from "./log-D9hwSySP.mjs";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
// src/asset-routing.ts
|
|
6
|
+
function isApiRoutePattern(pattern) {
|
|
7
|
+
return pattern === "/api" || pattern.startsWith("/api/");
|
|
8
|
+
}
|
|
9
|
+
function hasDocumentRoutes(routes) {
|
|
10
|
+
return routes.some((route) => !isApiRoutePattern(route.pattern));
|
|
11
|
+
}
|
|
12
|
+
function hasApiRoutes(routes) {
|
|
13
|
+
return routes.some((route) => isApiRoutePattern(route.pattern));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns true when browser document navigations must reach the worker before
|
|
17
|
+
* a static asset fallback can serve HTML.
|
|
18
|
+
*/
|
|
19
|
+
function workerOwnsHtmlRouting(inputs) {
|
|
20
|
+
return inputs.isSsr || inputs.hasPages || inputs.hasDocumentRoutes || inputs.hasMiddleware || inputs.hasWebSockets === true || inputs.liveEnabled === true;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns true when native edge rules must run in the worker before Cloudflare
|
|
24
|
+
* Static Assets can answer: native builds compile HTTPS redirects and header,
|
|
25
|
+
* redirect, rewrite and fallback rules into the worker.
|
|
26
|
+
*/
|
|
27
|
+
function edgeRulesNeedWorker(rules) {
|
|
28
|
+
return rules.alwaysUseHttps === true || (rules.headerRules?.length ?? 0) > 0 || (rules.redirectRules?.length ?? 0) > 0 || (rules.fallbackRules?.length ?? 0) > 0;
|
|
29
|
+
}
|
|
30
|
+
function resolveAssetConfig(inputs) {
|
|
31
|
+
return resolveAssetRoutingPolicy(inputs).assetConfig;
|
|
32
|
+
}
|
|
33
|
+
const VOID_INTERNAL_WORKER_FIRST = ["/__void", "/__void/*"];
|
|
34
|
+
function resolveAssetRoutingPolicy(inputs) {
|
|
35
|
+
const workerFirst = workerOwnsHtmlRouting(inputs);
|
|
36
|
+
const apiWorkerFirst = !workerFirst && (inputs.hasApiRoutes === true || inputs.authEnabled === true);
|
|
37
|
+
const assetConfig = workerFirst ? {
|
|
38
|
+
not_found_handling: "none",
|
|
39
|
+
run_worker_first: ["/**"]
|
|
40
|
+
} : apiWorkerFirst ? {
|
|
41
|
+
not_found_handling: "single-page-application",
|
|
42
|
+
run_worker_first: [
|
|
43
|
+
"/api",
|
|
44
|
+
"/api/*",
|
|
45
|
+
...VOID_INTERNAL_WORKER_FIRST
|
|
46
|
+
]
|
|
47
|
+
} : {
|
|
48
|
+
not_found_handling: "single-page-application",
|
|
49
|
+
run_worker_first: [...VOID_INTERNAL_WORKER_FIRST]
|
|
50
|
+
};
|
|
51
|
+
if (inputs.notFound !== void 0) assetConfig.not_found_handling = inputs.notFound;
|
|
52
|
+
return {
|
|
53
|
+
assetConfig,
|
|
54
|
+
apiWorkerFirst,
|
|
55
|
+
workerFirst,
|
|
56
|
+
workerSpaFallback: workerFirst && !inputs.isSsr && !inputs.hasPages && (inputs.notFound === void 0 || inputs.notFound === "single-page-application")
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// src/asset-worker-bypass.ts
|
|
60
|
+
/**
|
|
61
|
+
* Build-time decision for letting Cloudflare serve fingerprinted build files
|
|
62
|
+
* without starting the Worker. Native Cloudflare builds of apps with no global
|
|
63
|
+
* middleware and no edge rules gain nothing from running the Worker for those
|
|
64
|
+
* files: the Worker would only copy the asset response. Skipping it makes
|
|
65
|
+
* those requests free static requests and keeps auth and its session read off
|
|
66
|
+
* them. Each file gets its own negative `run_worker_first` rule, because a
|
|
67
|
+
* request matching a negative rule never reaches the Worker, even when the
|
|
68
|
+
* file is missing. Misses and other paths in the folder keep the Worker's
|
|
69
|
+
* not-found, SPA and page handling.
|
|
70
|
+
* `routing.alwaysUseHttps` alone does not block this: hashed files are only
|
|
71
|
+
* linked from pages the Worker has already moved to HTTPS.
|
|
72
|
+
*
|
|
73
|
+
* The asset layer does not know which files are fingerprinted, so a generated
|
|
74
|
+
* `_headers` file gives every bypassed chunk the immutable policy the Worker
|
|
75
|
+
* would have set.
|
|
76
|
+
*
|
|
77
|
+
* An `output: "static"` Pages build goes further when nothing site-wide must
|
|
78
|
+
* run first (auth, live, `routing.alwaysUseHttps`): every file answers first,
|
|
79
|
+
* and only paths with server code reach the Worker first. A page without a
|
|
80
|
+
* server module prerenders the HTML the Worker would render, so its file can
|
|
81
|
+
* answer. Hashed files keep their
|
|
82
|
+
* immutable policy from the generated `_headers`, or stay on the Worker when
|
|
83
|
+
* it cannot list them.
|
|
84
|
+
*/
|
|
85
|
+
const GENERATED_ASSET_HEADERS_MARKER = "# Generated by Void: immutable caching for fingerprinted build files. Do not edit.";
|
|
86
|
+
/** Cloudflare accepts at most 100 `run_worker_first` rules. */
|
|
87
|
+
const MAX_RUN_WORKER_FIRST_RULES = 100;
|
|
88
|
+
/** Cloudflare limits each `run_worker_first` rule to 100 characters. */
|
|
89
|
+
const MAX_RUN_WORKER_FIRST_RULE_LENGTH = 100;
|
|
90
|
+
/** Cloudflare accepts at most 100 `_headers` rules. */
|
|
91
|
+
const MAX_HEADERS_RULES = 100;
|
|
92
|
+
const AUTH_PATTERNS = ["/api/auth/*"];
|
|
93
|
+
/** Trim surrounding slashes; null when nothing is left. */
|
|
94
|
+
function normalizeAssetsDir(dir) {
|
|
95
|
+
let start = 0;
|
|
96
|
+
let end = dir.length;
|
|
97
|
+
while (start < end && dir[start] === "/") start++;
|
|
98
|
+
while (end > start && dir[end - 1] === "/") end--;
|
|
99
|
+
const trimmed = dir.slice(start, end);
|
|
100
|
+
return trimmed === "" || trimmed === "." ? null : trimmed;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Whether a route pattern can match a path below `/<dir>/`. Parameters,
|
|
104
|
+
* wildcards and patterns with more segments than the directory count as a
|
|
105
|
+
* match, so the answer errs towards keeping the Worker in front.
|
|
106
|
+
*/
|
|
107
|
+
function patternCanReachUnder(pattern, dir) {
|
|
108
|
+
const patternSegments = (pattern.startsWith("/") ? pattern.slice(1) : pattern).split("/");
|
|
109
|
+
const dirSegments = (dir.startsWith("/") ? dir.slice(1) : dir).split("/");
|
|
110
|
+
for (let i = 0; i < dirSegments.length; i++) {
|
|
111
|
+
const segment = patternSegments[i];
|
|
112
|
+
if (segment === void 0) return false;
|
|
113
|
+
if (segment.includes("*") || segment.includes("{")) return true;
|
|
114
|
+
if (segment.startsWith(":")) continue;
|
|
115
|
+
if (segment.toLowerCase() !== dirSegments[i].toLowerCase()) return false;
|
|
116
|
+
}
|
|
117
|
+
return patternSegments.length > dirSegments.length;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Characters that are plain text in both a `_headers` path and a
|
|
121
|
+
* `run_worker_first` rule. `*`, `:` and `!` have meaning there, and anything
|
|
122
|
+
* that needs URL encoding would not match the request path as written.
|
|
123
|
+
*/
|
|
124
|
+
function isPlainPath(path) {
|
|
125
|
+
for (const char of path) if (!(char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "-" || char === "_" || char === "." || char === "~" || char === "/")) return false;
|
|
126
|
+
return path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether `run_worker_first` is the rule an earlier bypass wrote. A watch
|
|
130
|
+
* rebuild of only the client still finds it, because the Worker build that
|
|
131
|
+
* would write `['/**']` again did not run.
|
|
132
|
+
*/
|
|
133
|
+
function isAssetBypassRule(runWorkerFirst, assetsDir) {
|
|
134
|
+
const dir = normalizeAssetsDir(assetsDir);
|
|
135
|
+
return dir !== null && Array.isArray(runWorkerFirst) && runWorkerFirst.length > 1 && runWorkerFirst[0] === "/**" && runWorkerFirst.slice(1).every((rule) => typeof rule === "string" && rule.startsWith("!/" + dir + "/"));
|
|
136
|
+
}
|
|
137
|
+
/** The bypass this build can use, or null when any rule keeps the Worker in front. */
|
|
138
|
+
function resolveAssetWorkerBypass(facts) {
|
|
139
|
+
const { runWorkerFirst } = facts;
|
|
140
|
+
if (!(Array.isArray(runWorkerFirst) && runWorkerFirst.length === 1 && runWorkerFirst[0] === "/**") || facts.notFoundHandling !== "none" || facts.base !== "/" || facts.hasMiddleware || facts.headerRuleCount > 0 || facts.redirectRuleCount > 0 || facts.fallbackRuleCount > 0 || facts.publicEdgeFiles) return null;
|
|
141
|
+
const assetsDir = normalizeAssetsDir(facts.assetsDir);
|
|
142
|
+
const files = assetsDir === null ? [] : [...new Set(facts.immutableFiles)].filter((file) => file.startsWith(assetsDir + "/")).sort();
|
|
143
|
+
return resolveStaticPageBypass(facts, assetsDir, files) ?? resolveHashedFileBypass(facts, assetsDir, files);
|
|
144
|
+
}
|
|
145
|
+
/** One negative rule per hashed file, so only those files skip the Worker. */
|
|
146
|
+
function resolveHashedFileBypass(facts, assetsDir, files) {
|
|
147
|
+
if (!assetsDir || !isPlainPath(assetsDir)) return null;
|
|
148
|
+
if ([
|
|
149
|
+
...facts.patterns,
|
|
150
|
+
...VOID_INTERNAL_WORKER_FIRST,
|
|
151
|
+
...facts.authEnabled ? AUTH_PATTERNS : []
|
|
152
|
+
].some((pattern) => patternCanReachUnder(pattern, assetsDir))) return null;
|
|
153
|
+
const rules = ["/**", ...files.map((file) => "!/" + file)];
|
|
154
|
+
if (files.length === 0 || files.some((file) => !isPlainPath(file)) || !fitsRunWorkerFirstLimits(rules)) return null;
|
|
155
|
+
return {
|
|
156
|
+
runWorkerFirst: rules,
|
|
157
|
+
headers: renderGeneratedAssetHeaders(files)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Void's own paths, then every server pattern widened to the static prefix
|
|
162
|
+
* before its first parameter: matching too much only keeps the Worker in
|
|
163
|
+
* front. A request that matches no file still reaches the Worker, since these
|
|
164
|
+
* builds use `not_found_handling: 'none'`. Hashed files get the generated
|
|
165
|
+
* `_headers`, or stay on the Worker when it cannot list them all. Null when
|
|
166
|
+
* site-wide behavior must run first or a pattern covers every path.
|
|
167
|
+
*/
|
|
168
|
+
function resolveStaticPageBypass(facts, assetsDir, files) {
|
|
169
|
+
if (!facts.serverPatterns || facts.authEnabled || facts.liveEnabled || facts.alwaysUseHttps) return null;
|
|
170
|
+
const rules = /* @__PURE__ */ new Set();
|
|
171
|
+
for (const pattern of facts.serverPatterns) {
|
|
172
|
+
const patternRules = workerPathRules(pattern);
|
|
173
|
+
if (patternRules === null) return null;
|
|
174
|
+
for (const rule of patternRules) rules.add(rule);
|
|
175
|
+
}
|
|
176
|
+
let headers = null;
|
|
177
|
+
if (files.length > MAX_HEADERS_RULES || !files.every(isPlainPath)) {
|
|
178
|
+
if (!assetsDir || !isPlainPath(assetsDir)) return null;
|
|
179
|
+
rules.add("/" + assetsDir + "/*");
|
|
180
|
+
} else if (files.length > 0) headers = renderGeneratedAssetHeaders(files);
|
|
181
|
+
const runWorkerFirst = [...VOID_INTERNAL_WORKER_FIRST, ...[...rules].sort()];
|
|
182
|
+
return fitsRunWorkerFirstLimits(runWorkerFirst) ? {
|
|
183
|
+
runWorkerFirst,
|
|
184
|
+
headers
|
|
185
|
+
} : null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The rules covering every path `pattern` matches; null when that is every
|
|
189
|
+
* path. A trailing `*` also matches the path before it: `/api/*` matches `/api`.
|
|
190
|
+
*/
|
|
191
|
+
function workerPathRules(pattern) {
|
|
192
|
+
if (pattern === "/") return ["/"];
|
|
193
|
+
const segments = (pattern.startsWith("/") ? pattern.slice(1) : pattern).split("/");
|
|
194
|
+
const dynamic = segments.findIndex((segment) => segment.startsWith(":") || segment.includes("*") || segment.includes("{"));
|
|
195
|
+
if (dynamic === 0) return null;
|
|
196
|
+
const prefix = (dynamic === -1 ? segments : segments.slice(0, dynamic)).join("/");
|
|
197
|
+
if (!isPlainPath(prefix)) return null;
|
|
198
|
+
if (dynamic === -1) return ["/" + prefix];
|
|
199
|
+
const rule = "/" + prefix + "/*";
|
|
200
|
+
return dynamic === segments.length - 1 && segments[dynamic] === "*" ? ["/" + prefix, rule] : [rule];
|
|
201
|
+
}
|
|
202
|
+
function fitsRunWorkerFirstLimits(rules) {
|
|
203
|
+
return rules.length <= MAX_RUN_WORKER_FIRST_RULES && rules.every((rule) => rule.length <= MAX_RUN_WORKER_FIRST_RULE_LENGTH);
|
|
204
|
+
}
|
|
205
|
+
/** One immutable rule per file after the marker line. */
|
|
206
|
+
function renderGeneratedAssetHeaders(files) {
|
|
207
|
+
const lines = [GENERATED_ASSET_HEADERS_MARKER];
|
|
208
|
+
for (const file of files) lines.push("/" + file, " Cache-Control: public, max-age=31536000, immutable");
|
|
209
|
+
return lines.join("\n") + "\n";
|
|
210
|
+
}
|
|
211
|
+
/** Whether a `_headers` file is the one Void generated. */
|
|
212
|
+
function isGeneratedAssetHeaders(text) {
|
|
213
|
+
const newline = text.indexOf("\n");
|
|
214
|
+
let firstLine = newline === -1 ? text : text.slice(0, newline);
|
|
215
|
+
if (firstLine.endsWith("\r")) firstLine = firstLine.slice(0, -1);
|
|
216
|
+
return firstLine === GENERATED_ASSET_HEADERS_MARKER;
|
|
217
|
+
}
|
|
218
|
+
// src/cli/headers.ts
|
|
219
|
+
/**
|
|
220
|
+
* Parse a Cloudflare/Netlify `_headers` file into HeaderRule[].
|
|
221
|
+
*
|
|
222
|
+
* Format:
|
|
223
|
+
* - Non-indented lines = path pattern (starts a new block)
|
|
224
|
+
* - Indented lines = `Header-Name: value` (split on first `:`)
|
|
225
|
+
* - Lines starting with `#` are comments
|
|
226
|
+
* - `! Header-Name` unset directives are skipped (last-match-wins handles this)
|
|
227
|
+
*
|
|
228
|
+
* Output is sorted by pattern length ascending (shortest first) so that
|
|
229
|
+
* more-specific patterns come last — matching dispatch's last-match-wins semantics.
|
|
230
|
+
*
|
|
231
|
+
* Returns `undefined` if the file doesn't exist or yields no valid rules.
|
|
232
|
+
*/
|
|
233
|
+
function parseHeadersFile(dir) {
|
|
234
|
+
const filePath = join(dir, "_headers");
|
|
235
|
+
if (!existsSync(filePath)) return;
|
|
236
|
+
const content = readFileSync(filePath, "utf-8");
|
|
237
|
+
const rules = [];
|
|
238
|
+
let current = null;
|
|
239
|
+
for (const line of content.split("\n")) {
|
|
240
|
+
const trimmed = line.trimEnd();
|
|
241
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
242
|
+
if (line[0] === " " || line[0] === " ") {
|
|
243
|
+
if (!current) continue;
|
|
244
|
+
const stripped = trimmed.trimStart();
|
|
245
|
+
if (stripped.startsWith("!")) continue;
|
|
246
|
+
const colonIdx = stripped.indexOf(":");
|
|
247
|
+
if (colonIdx === -1) continue;
|
|
248
|
+
const name = stripped.slice(0, colonIdx).trim();
|
|
249
|
+
const value = stripped.slice(colonIdx + 1).trim();
|
|
250
|
+
if (name) current.headers.push([name, value]);
|
|
251
|
+
} else {
|
|
252
|
+
if (current && current.headers.length > 0) rules.push(current);
|
|
253
|
+
current = {
|
|
254
|
+
pattern: trimmed,
|
|
255
|
+
headers: []
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (current && current.headers.length > 0) rules.push(current);
|
|
260
|
+
if (rules.length === 0) return;
|
|
261
|
+
rules.sort((a, b) => a.pattern.length - b.pattern.length);
|
|
262
|
+
return rules;
|
|
263
|
+
}
|
|
264
|
+
/** The `_headers` text in `dir` when Void generated it, otherwise undefined. */
|
|
265
|
+
function readGeneratedAssetHeadersFile(dir) {
|
|
266
|
+
const filePath = join(dir, "_headers");
|
|
267
|
+
if (!existsSync(filePath)) return;
|
|
268
|
+
const content = readFileSync(filePath, "utf-8");
|
|
269
|
+
return isGeneratedAssetHeaders(content) ? content : void 0;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Parse a `_redirects` file: `source destination [status]` per line (status
|
|
273
|
+
* defaults to 302, `#` starts a comment). Keeps 3xx redirects and 200 rewrites.
|
|
274
|
+
*
|
|
275
|
+
* A `!` suffix on 200 sets `force`, so the rewrite applies even when a static
|
|
276
|
+
* asset matches. On a 3xx it is a no-op, stripped and counted in
|
|
277
|
+
* `redundantForceOn3xx`.
|
|
278
|
+
*
|
|
279
|
+
* Returns `undefined` when the file doesn't exist or yields no valid rules.
|
|
280
|
+
*/
|
|
281
|
+
function parseRedirectsFile(dir) {
|
|
282
|
+
const filePath = join(dir, "_redirects");
|
|
283
|
+
if (!existsSync(filePath)) return;
|
|
284
|
+
const content = readFileSync(filePath, "utf-8");
|
|
285
|
+
const rules = [];
|
|
286
|
+
let redundantForceOn3xx = 0;
|
|
287
|
+
const rawLines = content.split("\n");
|
|
288
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
289
|
+
const lineNumber = i + 1;
|
|
290
|
+
const trimmed = rawLines[i].trim();
|
|
291
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
292
|
+
const parts = trimmed.split(/\s+/);
|
|
293
|
+
if (parts.length < 2) continue;
|
|
294
|
+
const origin = `_redirects:${lineNumber}`;
|
|
295
|
+
const [rawSource, destination, statusStr] = parts;
|
|
296
|
+
const parsedSource = parseHostPrefixedSource(rawSource);
|
|
297
|
+
if ("error" in parsedSource) {
|
|
298
|
+
voidWarn(`Skipping _redirects entry "${rawSource} ${destination}${statusStr ? ` ${statusStr}` : ""}" (line ${lineNumber}): source ${parsedSource.error}.`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const { host, path: source } = parsedSource;
|
|
302
|
+
if (countSplats(source) > 1) {
|
|
303
|
+
voidWarn(`Skipping _redirects entry "${rawSource} ${destination}${statusStr ? ` ${statusStr}` : ""}" (line ${lineNumber}): source must contain at most one "*" wildcard (captured as :splat).`);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
let status;
|
|
307
|
+
let force = false;
|
|
308
|
+
if (statusStr) {
|
|
309
|
+
if (statusStr.endsWith("!")) {
|
|
310
|
+
const numericPart = statusStr.slice(0, -1);
|
|
311
|
+
status = parseInt(numericPart, 10);
|
|
312
|
+
if (status === 200) force = true;
|
|
313
|
+
else if (status >= 300 && status < 400) redundantForceOn3xx++;
|
|
314
|
+
} else status = parseInt(statusStr, 10);
|
|
315
|
+
} else status = 302;
|
|
316
|
+
if (status === 200) {
|
|
317
|
+
if (!destination.startsWith("/")) {
|
|
318
|
+
voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must start with "/" (only internal paths are supported).`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (isProtocolRelative(destination)) {
|
|
322
|
+
voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must not start with "//" (protocol-relative URLs parse as cross-origin; only internal paths are supported).`);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (destination.includes("?")) {
|
|
326
|
+
voidWarn(`Skipping _redirects rewrite "${rawSource} ${destination} ${force ? "200!" : "200"}": destination must not contain "?" (query strings are not supported in rewrite destinations; the incoming request's query is preserved automatically).`);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
rules.push({
|
|
330
|
+
source,
|
|
331
|
+
destination,
|
|
332
|
+
status,
|
|
333
|
+
force,
|
|
334
|
+
origin,
|
|
335
|
+
...host && { host }
|
|
336
|
+
});
|
|
337
|
+
} else if (status >= 300 && status < 400) {
|
|
338
|
+
if (!VALID_REDIRECT_STATUSES.has(status)) {
|
|
339
|
+
voidWarn(`Skipping _redirects entry "${rawSource} ${destination} ${statusStr}" (line ${lineNumber}): redirect status must be 301, 302, 303, 307, or 308.`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
rules.push({
|
|
343
|
+
source,
|
|
344
|
+
destination,
|
|
345
|
+
status,
|
|
346
|
+
origin,
|
|
347
|
+
...host && { host }
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (rules.length === 0 && redundantForceOn3xx === 0) return;
|
|
352
|
+
return {
|
|
353
|
+
rules,
|
|
354
|
+
redundantForceOn3xx
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Split `_redirects` rules into rules that always apply and fallbacks: unforced
|
|
359
|
+
* 200 rewrites, which apply only when no static asset matches.
|
|
360
|
+
*/
|
|
361
|
+
function splitRedirectRulesByForce(rules) {
|
|
362
|
+
const always = [];
|
|
363
|
+
const fallbacks = [];
|
|
364
|
+
if (!rules) return {
|
|
365
|
+
always,
|
|
366
|
+
fallbacks
|
|
367
|
+
};
|
|
368
|
+
for (const rule of rules) if (rule.status === 200 && rule.force !== true) fallbacks.push(rule);
|
|
369
|
+
else always.push(rule);
|
|
370
|
+
return {
|
|
371
|
+
always,
|
|
372
|
+
fallbacks
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Merge `_redirects` and `_headers` in `dir` with `routing` from void.config.ts.
|
|
377
|
+
* Deploy and dev share it so both build the same rule tables; callers lint them.
|
|
378
|
+
*/
|
|
379
|
+
function mergeRoutingRules(opts) {
|
|
380
|
+
const { dir, routing } = opts;
|
|
381
|
+
const fileDir = dir !== null && existsSync(dir) ? dir : null;
|
|
382
|
+
const fileHeaders = fileDir !== null ? parseHeadersFile(fileDir) : void 0;
|
|
383
|
+
const fileParsed = fileDir !== null ? parseRedirectsFile(fileDir) : void 0;
|
|
384
|
+
const { always: fileAlways, fallbacks: fileFallbacks } = splitRedirectRulesByForce(fileParsed?.rules);
|
|
385
|
+
const cfgHeaders = convertHeadersConfig(routing?.headers);
|
|
386
|
+
const cfgRedirects = convertRedirectsConfig(routing?.redirects);
|
|
387
|
+
const cfgRewrites = convertRewritesConfig(routing?.rewrites);
|
|
388
|
+
const cfgFallbacks = convertFallbacksConfig(routing?.fallbacks);
|
|
389
|
+
const headerRules = [...fileHeaders ?? [], ...cfgHeaders ?? []];
|
|
390
|
+
const redirectRules = [
|
|
391
|
+
...cfgRedirects ?? [],
|
|
392
|
+
...cfgRewrites ?? [],
|
|
393
|
+
...fileAlways
|
|
394
|
+
];
|
|
395
|
+
const mergedFallbacks = [...cfgFallbacks ?? [], ...fileFallbacks];
|
|
396
|
+
return {
|
|
397
|
+
headerRules,
|
|
398
|
+
redirectRules,
|
|
399
|
+
fallbackRules: mergedFallbacks.length > 0 ? mergedFallbacks : void 0,
|
|
400
|
+
redundantForceOn3xx: fileParsed?.redundantForceOn3xx ?? 0
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Lint each phase on its own: only duplicates within a phase shadow each other,
|
|
405
|
+
* so a redirect and a fallback with the same source are fine.
|
|
406
|
+
*/
|
|
407
|
+
function lintRoutingPhases(rules) {
|
|
408
|
+
lintDuplicateSources(rules.redirectRules, "redirects/rewrites (pre-asset)");
|
|
409
|
+
lintDuplicateSources(rules.fallbackRules, "fallbacks (post-asset)");
|
|
410
|
+
lintDestinationSplats(rules.redirectRules, "redirects/rewrites (pre-asset)");
|
|
411
|
+
lintDestinationSplats(rules.fallbackRules, "fallbacks (post-asset)");
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Warn about a literal `*` in a destination. Destinations substitute only
|
|
415
|
+
* `:name` params such as `:splat`, so a `*` passes through unchanged.
|
|
416
|
+
*/
|
|
417
|
+
function lintDestinationSplats(rules, phaseName) {
|
|
418
|
+
if (!rules || rules.length === 0) return;
|
|
419
|
+
const affected = rules.filter((r) => r.destination.includes("*"));
|
|
420
|
+
if (affected.length === 0) return;
|
|
421
|
+
const lines = affected.map((r) => ` ${r.source} → ${r.destination} (${r.origin ?? "<unknown>"})`);
|
|
422
|
+
voidWarn(`destinations in ${phaseName} contain a literal "*" — splat tokens are not substituted in destinations; use ":splat" (or other named params) or rewrite the destination:\n${lines.join("\n")}`);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Warn when one phase repeats an exact `source`: first match wins, so the later
|
|
426
|
+
* rule never applies. Phases run separately, so callers lint each one.
|
|
427
|
+
*/
|
|
428
|
+
function lintDuplicateSources(rules, phaseName) {
|
|
429
|
+
if (!rules || rules.length < 2) return;
|
|
430
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const rule of rules) {
|
|
432
|
+
const existing = bySource.get(rule.source);
|
|
433
|
+
if (existing) existing.push(rule);
|
|
434
|
+
else bySource.set(rule.source, [rule]);
|
|
435
|
+
}
|
|
436
|
+
const lines = [];
|
|
437
|
+
for (const [source, occurrences] of bySource) {
|
|
438
|
+
if (occurrences.length < 2) continue;
|
|
439
|
+
const origins = occurrences.map((r) => r.origin ?? "<unknown>").join(", ");
|
|
440
|
+
lines.push(` ${source} — ${origins}`);
|
|
441
|
+
}
|
|
442
|
+
if (lines.length === 0) return;
|
|
443
|
+
voidWarn(`duplicate rule sources detected in ${phaseName} (first match wins — the earlier entry takes effect):\n${lines.join("\n")}`);
|
|
444
|
+
}
|
|
445
|
+
export { isAssetBypassRule as a, VOID_INTERNAL_WORKER_FIRST as c, hasDocumentRoutes as d, resolveAssetConfig as f, readGeneratedAssetHeadersFile as i, edgeRulesNeedWorker as l, mergeRoutingRules as n, isGeneratedAssetHeaders as o, resolveAssetRoutingPolicy as p, parseRedirectsFile as r, resolveAssetWorkerBypass as s, lintRoutingPhases as t, hasApiRoutes as u };
|
|
@@ -688,6 +688,10 @@ const HELP_TREE = {
|
|
|
688
688
|
command: "--runtime <path>",
|
|
689
689
|
description: "Deploy an integrity-checked runtime directory instead of the package"
|
|
690
690
|
},
|
|
691
|
+
{
|
|
692
|
+
command: "--runtime-logs <full|minimal>",
|
|
693
|
+
description: "minimal keeps request URLs, console and exception messages out of runtime logs"
|
|
694
|
+
},
|
|
691
695
|
{
|
|
692
696
|
command: "--yes",
|
|
693
697
|
description: "Acknowledge changes without an interactive prompt"
|
|
@@ -1780,7 +1784,7 @@ function withProjectOption(summary, usage) {
|
|
|
1780
1784
|
function lifecycleHelp(command, summary) {
|
|
1781
1785
|
return {
|
|
1782
1786
|
summary,
|
|
1783
|
-
usage: [`void platform ${command} [id] [--plan]${command === "disable" ? "" : " [--runtime <path>]"}${command === "upgrade" || command === "repair" ? " [--dashboard-url <origin>] [--custom-domains]" : ""}${command === "repair" ? " [--auth-config <path>]" : ""} [--yes]`],
|
|
1787
|
+
usage: [`void platform ${command} [id] [--plan]${command === "disable" ? "" : " [--runtime <path>]"}${command === "upgrade" || command === "repair" ? " [--dashboard-url <origin>] [--custom-domains]" : ""}${command === "upgrade" ? " [--runtime-logs <full|minimal>]" : ""}${command === "repair" ? " [--auth-config <path>]" : ""} [--yes]`],
|
|
1784
1788
|
options: [
|
|
1785
1789
|
{
|
|
1786
1790
|
command: "--plan",
|
|
@@ -1797,6 +1801,10 @@ function lifecycleHelp(command, summary) {
|
|
|
1797
1801
|
command: "--custom-domains",
|
|
1798
1802
|
description: "Turn on project custom domains; omission keeps the saved setting"
|
|
1799
1803
|
}] : [],
|
|
1804
|
+
...command === "upgrade" ? [{
|
|
1805
|
+
command: "--runtime-logs <full|minimal>",
|
|
1806
|
+
description: "minimal keeps request URLs, console and exception messages out of runtime logs; omission keeps the saved setting"
|
|
1807
|
+
}] : [],
|
|
1800
1808
|
...command === "repair" ? [{
|
|
1801
1809
|
command: "--auth-config <path>",
|
|
1802
1810
|
description: "While first administrator setup is pending, replace its login settings (install file format)"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { r as printHelpPage } from "./help-1OCFDsRr.mjs";
|
|
@@ -33,8 +33,21 @@ interface SendEmailOptions {
|
|
|
33
33
|
replyTo?: Address;
|
|
34
34
|
cc?: Address | Array<Address>;
|
|
35
35
|
bcc?: Address | Array<Address>;
|
|
36
|
+
/** Custom headers. Reserved names such as `From`, `Date` and the threading headers are ignored. */
|
|
36
37
|
headers?: Record<string, string>;
|
|
37
38
|
attachments?: Array<Attachment>;
|
|
39
|
+
/**
|
|
40
|
+
* Threads this email under an earlier message, such as a reply sent from a
|
|
41
|
+
* queue job after the inbound handler returned. `inReplyTo` is the parent's
|
|
42
|
+
* `Message-ID`. `references` lists the thread's ids, oldest first, ending
|
|
43
|
+
* with the parent; it defaults to `inReplyTo`. An id that cannot be sent,
|
|
44
|
+
* such as one with a control character, is left out. If `references`
|
|
45
|
+
* cannot be sent, it falls back to `inReplyTo`. The email still sends.
|
|
46
|
+
*/
|
|
47
|
+
threading?: {
|
|
48
|
+
inReplyTo?: string;
|
|
49
|
+
references?: string;
|
|
50
|
+
};
|
|
38
51
|
/**
|
|
39
52
|
* Deduplicates a platform send for 30 days. Reuse the same key and message
|
|
40
53
|
* when checking an uncertain attempt; a changed message is a conflict.
|
|
@@ -145,11 +158,9 @@ interface NormalizedEmail {
|
|
|
145
158
|
headers: Record<string, string>;
|
|
146
159
|
attachments: Array<NormalizedAttachment>;
|
|
147
160
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* always dropped (they're in `RESERVED_HEADERS`); this field is the
|
|
152
|
-
* supported channel for the trusted reply path.
|
|
161
|
+
* `In-Reply-To` / `References` after the ids were checked, from the
|
|
162
|
+
* `threading` option or `replyEmail`. User-supplied `headers` with these
|
|
163
|
+
* names are always dropped (they're in `RESERVED_HEADERS`).
|
|
153
164
|
*/
|
|
154
165
|
threading?: {
|
|
155
166
|
inReplyTo?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as base64ToBytes } from "./base64-ikl9yBy3.mjs";
|
|
2
|
-
import { c as
|
|
2
|
+
import { a as normalizeEmail, c as fitsHeaderLine, l as replyReferences, n as MimeError, r as buildMime, s as validateAddressParts, u as threadingId } from "./mime-CtP6-qAF.mjs";
|
|
3
3
|
import { a as getRawRuntimeEnv } from "./env-raw-e8qgqAzQ.mjs";
|
|
4
4
|
import { o as withTimeout } from "./email-protocol-DTs_TjCS.mjs";
|
|
5
5
|
import { EMAIL_MAX_ACTIONS, EMAIL_MAX_INBOUND_BYTES, EMAIL_MAX_INBOUND_HEADERS, EMAIL_MAX_METADATA_BYTES, EMAIL_MAX_REPLY_BYTES, EMAIL_MAX_RESPONSE_BYTES } from "@void/platform/email-protocol";
|
|
@@ -349,52 +349,6 @@ function createMessageProxy(envelope, opts = {}) {
|
|
|
349
349
|
};
|
|
350
350
|
}
|
|
351
351
|
/**
|
|
352
|
-
* Whether `Name: value` fits RFC 5322's 998-octet line. Folding happens only
|
|
353
|
-
* at SP or HTAB, so the widest unbroken run decides.
|
|
354
|
-
*/
|
|
355
|
-
function fitsHeaderLine(name, value) {
|
|
356
|
-
if (!value) return false;
|
|
357
|
-
const budget = 998 - (name.length + 2);
|
|
358
|
-
let run = 0;
|
|
359
|
-
for (let i = 0; i < value.length; i++) {
|
|
360
|
-
const c = value.charCodeAt(i);
|
|
361
|
-
if (c === 32 || c === 9) {
|
|
362
|
-
run = 0;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
if (c < 128) run += 1;
|
|
366
|
-
else if (c < 2048) run += 2;
|
|
367
|
-
else if (c >= 55296 && c <= 56319 && i + 1 < value.length && (value.charCodeAt(i + 1) & 64512) === 56320) {
|
|
368
|
-
run += 4;
|
|
369
|
-
i++;
|
|
370
|
-
} else run += 3;
|
|
371
|
-
if (run > budget) return false;
|
|
372
|
-
}
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* `chain` without the ids that hold a control character, joined by single
|
|
377
|
-
* spaces. Called only on a chain already cut to `MAX_REPLY_REFERENCES_CHARS`.
|
|
378
|
-
*/
|
|
379
|
-
function dropControlIds(chain) {
|
|
380
|
-
if (indexOfControl(chain) === -1) return chain;
|
|
381
|
-
const kept = [];
|
|
382
|
-
let start = -1;
|
|
383
|
-
let tainted = false;
|
|
384
|
-
for (let i = 0; i <= chain.length; i++) {
|
|
385
|
-
const c = i < chain.length ? chain.charCodeAt(i) : 32;
|
|
386
|
-
if (c === 32 || c === 9) {
|
|
387
|
-
if (start >= 0 && !tainted) kept.push(chain.slice(start, i));
|
|
388
|
-
start = -1;
|
|
389
|
-
tainted = false;
|
|
390
|
-
} else {
|
|
391
|
-
if (start < 0) start = i;
|
|
392
|
-
if (c < 32 || c === 127) tainted = true;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return kept.join(" ");
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
352
|
* Longest decoded inbound subject the default reply subject is built from;
|
|
399
353
|
* longer falls back to a bare `Re:`. Real subjects are far shorter, and the
|
|
400
354
|
* bound keeps the control-character replacement cheap.
|
|
@@ -410,34 +364,6 @@ function prefixReplySubject(original) {
|
|
|
410
364
|
return prefixed;
|
|
411
365
|
}
|
|
412
366
|
/**
|
|
413
|
-
* Longest `References` value a reply carries, in UTF-16 code units. 8 KiB
|
|
414
|
-
* keeps any real thread whole; past it the newest ids are kept, since mail
|
|
415
|
-
* clients thread on any id they recognise and the parent is `In-Reply-To`.
|
|
416
|
-
*/
|
|
417
|
-
const MAX_REPLY_REFERENCES_CHARS = 8192;
|
|
418
|
-
/**
|
|
419
|
-
* The reply's `References`, or `''`: the inbound chain plus the parent's id,
|
|
420
|
-
* trimmed to its newest whole ids, or the parent's id alone when an id
|
|
421
|
-
* cannot fit a line.
|
|
422
|
-
*/
|
|
423
|
-
function replyReferences(originalReferences, messageId) {
|
|
424
|
-
const parent = messageId ? ` ${messageId}` : "";
|
|
425
|
-
let chain = originalReferences;
|
|
426
|
-
const budget = MAX_REPLY_REFERENCES_CHARS - parent.length;
|
|
427
|
-
if (chain.length > budget) {
|
|
428
|
-
let cut = chain.length - budget;
|
|
429
|
-
while (cut < chain.length) {
|
|
430
|
-
const c = chain.charCodeAt(cut);
|
|
431
|
-
if (c === 32 || c === 9) break;
|
|
432
|
-
cut++;
|
|
433
|
-
}
|
|
434
|
-
chain = chain.slice(cut);
|
|
435
|
-
}
|
|
436
|
-
chain = dropControlIds(chain);
|
|
437
|
-
chain = `${chain}${parent}`.trim();
|
|
438
|
-
return fitsHeaderLine("References", chain) ? chain : messageId;
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
367
|
* Minimal RFC 2047 decoder for the reply subject: B and Q encoded-words,
|
|
442
368
|
* unknown charsets read as UTF-8, malformed input returned unchanged.
|
|
443
369
|
*/
|
|
@@ -504,7 +430,7 @@ async function buildReplyMessage(message, opts) {
|
|
|
504
430
|
const toAddr = opts.to ?? message.from;
|
|
505
431
|
const subject = opts.subject ?? prefixReplySubject(originalSubject);
|
|
506
432
|
const threading = {};
|
|
507
|
-
const messageId =
|
|
433
|
+
const messageId = threadingId("In-Reply-To", originalMessageId);
|
|
508
434
|
if (messageId) threading.inReplyTo = messageId;
|
|
509
435
|
const references = replyReferences(originalReferences, messageId);
|
|
510
436
|
if (references) threading.references = references;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as HeadDescriptor } from "./head-MeF_5M_3.mjs";
|
|
2
2
|
import { r as AuthUser } from "./auth-CH4PLAX1.mjs";
|
|
3
3
|
import { s as RouteEnvironment } from "./types-agfx0alu.mjs";
|
|
4
|
-
import { C as defer, E as ValidatorSlots, S as InferProps, T as HandlerInput, _ as defineQueue, a as CloudEnv, b as Deferred, c as QueueRetryOptions, d as TypedQueueHandler, f as VoidAssetRewriteError, g as defineMiddleware, h as defineHead, i as CloudContextVariables, l as RenderAssetTags, m as defineHandler, o as QueueBatch, p as basicAuth, r as CloudContext, s as QueueMessage, t as BasicAuthOptions, u as TypedHandler, v as defineRender, x as DeferredState, y as defineScheduled } from "./handler-
|
|
4
|
+
import { C as defer, E as ValidatorSlots, S as InferProps, T as HandlerInput, _ as defineQueue, a as CloudEnv, b as Deferred, c as QueueRetryOptions, d as TypedQueueHandler, f as VoidAssetRewriteError, g as defineMiddleware, h as defineHead, i as CloudContextVariables, l as RenderAssetTags, m as defineHandler, o as QueueBatch, p as basicAuth, r as CloudContext, s as QueueMessage, t as BasicAuthOptions, u as TypedHandler, v as defineRender, x as DeferredState, y as defineScheduled } from "./handler-BR6Tvjkp.mjs";
|
|
5
5
|
import { t as QueueMap } from "./queues-DdM42xXW.mjs";
|
|
6
6
|
import { Plugin } from "vite";
|
|
7
7
|
import { convertToWranglerConfig } from "@cloudflare/config";
|