@vandenberghinc/volt 1.2.12 → 1.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/backend/dist/cjs/backend/src/endpoint.d.ts +1 -1
  2. package/backend/dist/cjs/backend/src/endpoint.js +1 -1
  3. package/backend/dist/cjs/backend/src/payments/stripe/products.js +6 -6
  4. package/backend/dist/cjs/backend/src/payments/stripe/stripe.js +7 -0
  5. package/backend/dist/cjs/backend/src/payments/stripe/webhooks.js +4 -0
  6. package/backend/dist/cjs/backend/src/plugins/caddy/caddy.d.ts +1 -0
  7. package/backend/dist/cjs/backend/src/plugins/caddy/caddy.js +15 -0
  8. package/backend/dist/cjs/backend/src/server.js +7 -3
  9. package/backend/dist/cjs/backend/src/users.js +19 -0
  10. package/backend/dist/esm/backend/src/endpoint.d.ts +1 -1
  11. package/backend/dist/esm/backend/src/endpoint.js +1 -1
  12. package/backend/dist/esm/backend/src/payments/stripe/products.js +6 -6
  13. package/backend/dist/esm/backend/src/payments/stripe/stripe.js +7 -0
  14. package/backend/dist/esm/backend/src/payments/stripe/webhooks.js +4 -0
  15. package/backend/dist/esm/backend/src/plugins/caddy/caddy.d.ts +1 -0
  16. package/backend/dist/esm/backend/src/plugins/caddy/caddy.js +592 -0
  17. package/backend/dist/esm/backend/src/server.js +4 -0
  18. package/backend/dist/esm/backend/src/users.js +19 -0
  19. package/frontend/dist/backend/src/endpoint.d.ts +1 -1
  20. package/frontend/dist/backend/src/endpoint.js +1 -1
  21. package/frontend/dist/backend/src/payments/stripe/products.js +6 -6
  22. package/frontend/dist/backend/src/payments/stripe/stripe.js +7 -0
  23. package/frontend/dist/backend/src/payments/stripe/webhooks.js +4 -0
  24. package/frontend/dist/backend/src/server.js +4 -0
  25. package/frontend/dist/backend/src/users.js +19 -0
  26. package/package.json +2 -2
@@ -0,0 +1,592 @@
1
+ // /**
2
+ // * @author Daan van den Bergh
3
+ // * @copyright © 2022 - 2026 Daan van den Bergh.
4
+ // */
5
+ export {};
6
+ // import { promises as fs } from "node:fs";
7
+ // import { spawn } from "node:child_process";
8
+ // import { platform } from "node:os";
9
+ // import { createHash } from "node:crypto";
10
+ // /**
11
+ // * Caddy reverse proxy setup utilities.
12
+ // *
13
+ // * Exposes an idempotent setup API for installing Caddy and writing validated configs.
14
+ // */
15
+ // export namespace Caddy {
16
+ // /**
17
+ // * Represents a reverse proxy upstream configuration.
18
+ // */
19
+ // export interface ReverseProxyOpts {
20
+ // /** List of upstream targets (host:port). */
21
+ // readonly upstreams: readonly string[];
22
+ // /**
23
+ // * Optional reverse_proxy sub-directives expressed as a simple key/value map.
24
+ // *
25
+ // * Use this for widely-compatible configuration without forcing a massive typed surface.
26
+ // * Each entry is rendered as: `key value` inside the `reverse_proxy { ... }` block.
27
+ // *
28
+ // * Docs:
29
+ // * - reverse_proxy directive: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
30
+ // * - reverse_proxy sub-directives: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#syntax
31
+ // *
32
+ // * @example
33
+ // * {Reverse proxy with health checks and header overrides}
34
+ // * Adds active health checks and forwards the original host and client IP information.
35
+ // * ```ts
36
+ // * const reverse_proxy: Caddy.ReverseProxyOpts = {
37
+ // * upstreams: ["127.0.0.1:3000"],
38
+ // * options: {
39
+ // * health_uri: "/health",
40
+ // * health_interval: "10s",
41
+ // * health_timeout: "2s",
42
+ // * lb_policy: "round_robin",
43
+ // * header_up: "Host {host}",
44
+ // * },
45
+ // * };
46
+ // * ```
47
+ // */
48
+ // readonly options?: Record<string, string | number | boolean>;
49
+ // }
50
+ // /**
51
+ // * A header operation in the `header` directive.
52
+ // *
53
+ // * Docs:
54
+ // * - header directive: https://caddyserver.com/docs/caddyfile/directives/header
55
+ // */
56
+ // export type HeaderOperation =
57
+ // | {
58
+ // /** Operation kind. */
59
+ // readonly kind: "set";
60
+ // /** Header field name. */
61
+ // readonly field: string;
62
+ // /** Header value. */
63
+ // readonly value: string;
64
+ // }
65
+ // | {
66
+ // /** Operation kind. */
67
+ // readonly kind: "add";
68
+ // /** Header field name. */
69
+ // readonly field: string;
70
+ // /** Header value. */
71
+ // readonly value: string;
72
+ // }
73
+ // | {
74
+ // /** Operation kind. */
75
+ // readonly kind: "delete";
76
+ // /** Header field name. */
77
+ // readonly field: string;
78
+ // };
79
+ // /**
80
+ // * Represents a structured `header` directive.
81
+ // *
82
+ // * This is rendered as:
83
+ // * `header [<matcher>] { ... }`
84
+ // *
85
+ // * Docs:
86
+ // * - header directive: https://caddyserver.com/docs/caddyfile/directives/header
87
+ // *
88
+ // * @example
89
+ // * {Set common security headers}
90
+ // * Adds HSTS and a few common hardening headers for all responses.
91
+ // * ```ts
92
+ // * const headers: Caddy.HeaderDirective = {
93
+ // * directive: "header",
94
+ // * operations: [
95
+ // * { kind: "set", field: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
96
+ // * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
97
+ // * { kind: "set", field: "X-Frame-Options", value: "DENY" },
98
+ // * { kind: "set", field: "Referrer-Policy", value: "no-referrer" },
99
+ // * ],
100
+ // * };
101
+ // * ```
102
+ // */
103
+ // export interface HeaderDirective {
104
+ // /** Discriminator for directive unions. */
105
+ // readonly directive: "header";
106
+ // /**
107
+ // * Optional Caddyfile request matcher token(s), e.g. `@api` or `@static`.
108
+ // *
109
+ // * If you need a complex matcher definition, use a raw string directive in `extra_directives`.
110
+ // */
111
+ // readonly matcher?: string;
112
+ // /** Header operations to apply. */
113
+ // readonly operations: readonly HeaderOperation[];
114
+ // }
115
+ // /**
116
+ // * Represents a structured `encode` directive.
117
+ // *
118
+ // * Docs:
119
+ // * - encode directive: https://caddyserver.com/docs/caddyfile/directives/encode
120
+ // *
121
+ // * @example
122
+ // * {Enable gzip and zstd compression}
123
+ // * Enables response compression where supported.
124
+ // * ```ts
125
+ // * const encode: Caddy.EncodeDirective = {
126
+ // * directive: "encode",
127
+ // * encodings: ["zstd", "gzip"],
128
+ // * };
129
+ // * ```
130
+ // */
131
+ // export interface EncodeDirective {
132
+ // /** Discriminator for directive unions. */
133
+ // readonly directive: "encode";
134
+ // /** List of encodings, e.g. `["zstd", "gzip"]`. */
135
+ // readonly encodings: readonly string[];
136
+ // }
137
+ // /**
138
+ // * Represents a structured `log` directive.
139
+ // *
140
+ // * Docs:
141
+ // * - log directive: https://caddyserver.com/docs/caddyfile/directives/log
142
+ // *
143
+ // * @example
144
+ // * {Write JSON access logs to a file}
145
+ // * Configures JSON formatted access logs written to a persistent file.
146
+ // * ```ts
147
+ // * const log: Caddy.LogDirective = {
148
+ // * directive: "log",
149
+ // * output: { kind: "file", path: "/var/log/caddy/access.json" },
150
+ // * format: "json",
151
+ // * };
152
+ // * ```
153
+ // */
154
+ // export interface LogDirective {
155
+ // /** Discriminator for directive unions. */
156
+ // readonly directive: "log";
157
+ // /**
158
+ // * Optional Caddyfile request matcher token(s).
159
+ // *
160
+ // * If you need a complex matcher definition, use a raw string directive in `extra_directives`.
161
+ // */
162
+ // readonly matcher?: string;
163
+ // /**
164
+ // * Output configuration for logs.
165
+ // *
166
+ // * Note: This intentionally supports only the most common commercial-grade sinks
167
+ // * while keeping escape hatches via raw directives.
168
+ // */
169
+ // readonly output?: LogOutput;
170
+ // /** Log format name, e.g. `json` or `console`. */
171
+ // readonly format?: "json" | "console";
172
+ // }
173
+ // /**
174
+ // * Describes a log output sink.
175
+ // *
176
+ // * Docs:
177
+ // * - log output: https://caddyserver.com/docs/caddyfile/directives/log#output-modules
178
+ // */
179
+ // export type LogOutput =
180
+ // | {
181
+ // /** Output kind. */
182
+ // readonly kind: "file";
183
+ // /** File path for logs. */
184
+ // readonly path: string;
185
+ // }
186
+ // | {
187
+ // /** Output kind. */
188
+ // readonly kind: "stdout";
189
+ // }
190
+ // | {
191
+ // /** Output kind. */
192
+ // readonly kind: "stderr";
193
+ // };
194
+ // /**
195
+ // * Represents a structured `tls` directive for common use cases.
196
+ // *
197
+ // * Docs:
198
+ // * - tls directive: https://caddyserver.com/docs/caddyfile/directives/tls
199
+ // *
200
+ // * @example
201
+ // * {Use a DNS challenge issuer}
202
+ // * Configures TLS with a DNS provider module (requires the appropriate Caddy build).
203
+ // * ```ts
204
+ // * const tls: Caddy.TlsDirective = {
205
+ // * directive: "tls",
206
+ // * issuer: "dns",
207
+ // * issuer_args: ["cloudflare", "{env.CLOUDFLARE_API_TOKEN}"],
208
+ // * };
209
+ // * ```
210
+ // */
211
+ // export interface TlsDirective {
212
+ // /** Discriminator for directive unions. */
213
+ // readonly directive: "tls";
214
+ // /**
215
+ // * Issuer module name, commonly `internal`, `acme`, or `dns`.
216
+ // *
217
+ // * For advanced TLS configuration, use raw directives.
218
+ // */
219
+ // readonly issuer?: "internal" | "acme" | "dns";
220
+ // /**
221
+ // * Optional issuer arguments.
222
+ // *
223
+ // * These are rendered as raw tokens after the issuer declaration.
224
+ // */
225
+ // readonly issuer_args?: readonly string[];
226
+ // }
227
+ // /**
228
+ // * A typed extra directive which can be rendered into a Caddyfile snippet.
229
+ // *
230
+ // * This provides common structured directives while keeping a raw string escape hatch.
231
+ // */
232
+ // export type ExtraDirective = string | HeaderDirective | EncodeDirective | LogDirective | TlsDirective;
233
+ // /**
234
+ // * Represents a single site served by Caddy.
235
+ // */
236
+ // export interface SiteOpts {
237
+ // /** Fully-qualified domain name. */
238
+ // readonly domain: string;
239
+ // /** Reverse proxy configuration. */
240
+ // readonly reverse_proxy: ReverseProxyOpts;
241
+ // /**
242
+ // * Optional extra Caddyfile directives added inside the site block.
243
+ // *
244
+ // * Supports both raw strings (fully flexible) and typed directive objects for common cases.
245
+ // *
246
+ // * Docs:
247
+ // * - Caddyfile directives overview: https://caddyserver.com/docs/caddyfile/directives
248
+ // *
249
+ // * @example
250
+ // * {Mix typed and raw directives}
251
+ // * Uses typed directives for common needs while falling back to raw Caddyfile lines when needed.
252
+ // * ```ts
253
+ // * const site: Caddy.SiteOpts = {
254
+ // * domain: "example.com",
255
+ // * reverse_proxy: { upstreams: ["127.0.0.1:3000"] },
256
+ // * extra_directives: [
257
+ // * { directive: "encode", encodings: ["zstd", "gzip"] },
258
+ // * {
259
+ // * directive: "header",
260
+ // * operations: [
261
+ // * { kind: "set", field: "X-Frame-Options", value: "DENY" },
262
+ // * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
263
+ // * ],
264
+ // * },
265
+ // * // Raw escape hatch for anything not covered by types:
266
+ // * "request_body { max_size 10MB }",
267
+ // * ],
268
+ // * };
269
+ // * ```
270
+ // */
271
+ // readonly extra_directives?: readonly ExtraDirective[];
272
+ // }
273
+ // /**
274
+ // * Top-level Caddy configuration options.
275
+ // */
276
+ // export interface ConfigOpts {
277
+ // /** Destination path of the generated Caddy config file. */
278
+ // readonly config_path: string;
279
+ // /** List of sites to configure. */
280
+ // readonly sites: readonly SiteOpts[];
281
+ // /**
282
+ // * Optional global Caddyfile options written in the global options block `{ ... }`.
283
+ // *
284
+ // * Each entry is rendered as: `key value` within the global block.
285
+ // *
286
+ // * Docs:
287
+ // * - Global options block: https://caddyserver.com/docs/caddyfile/options
288
+ // * - Common global options: https://caddyserver.com/docs/caddyfile/options#global-options
289
+ // *
290
+ // * @example
291
+ // * {Set ACME email and enable debug logging globally}
292
+ // * Configures the email used for ACME registration and turns on debug mode.
293
+ // * ```ts
294
+ // * const config: Caddy.ConfigOpts = {
295
+ // * config_path: "/etc/caddy/Caddyfile",
296
+ // * global_options: {
297
+ // * email: "ops@example.com",
298
+ // * debug: true,
299
+ // * },
300
+ // * sites: [
301
+ // * {
302
+ // * domain: "example.com",
303
+ // * reverse_proxy: { upstreams: ["127.0.0.1:3000"] },
304
+ // * },
305
+ // * ],
306
+ // * };
307
+ // * ```
308
+ // */
309
+ // readonly global_options?: Record<string, string | number | boolean>;
310
+ // }
311
+ // /**
312
+ // * Ensures that the Caddy binary is installed on the system.
313
+ // *
314
+ // * This function is idempotent and safe to run multiple times.
315
+ // *
316
+ // * @example
317
+ // * {Install Caddy if it is missing}
318
+ // * Ensures the `caddy` binary is available before writing configs.
319
+ // * ```ts
320
+ // * import { Caddy } from "./caddy_reverse_proxy_setup";
321
+ // *
322
+ // * await Caddy.ensure_installed();
323
+ // * ```
324
+ // */
325
+ // export async function ensure_installed(): Promise<void> {
326
+ // if (await is_caddy_available()) {
327
+ // return;
328
+ // }
329
+ // const current_platform: string = platform();
330
+ // // On Linux we install via the official Caddy apt repository (production-friendly).
331
+ // if (current_platform === "linux") {
332
+ // await install_caddy_linux();
333
+ // return;
334
+ // }
335
+ // throw new Error(`unsupported_platform: ${current_platform}`);
336
+ // }
337
+ // /**
338
+ // * Writes a Caddy configuration file in an idempotent manner.
339
+ // *
340
+ // * The file is only replaced if content has changed, then validated via `caddy validate`.
341
+ // *
342
+ // * @example
343
+ // * {Write and validate a reverse proxy configuration}
344
+ // * Generates a Caddyfile, writes it only if changed, and validates it.
345
+ // * ```ts
346
+ // * import { Caddy } from "./caddy_reverse_proxy_setup";
347
+ // *
348
+ // * await Caddy.write_config({
349
+ // * config_path: "/etc/caddy/Caddyfile",
350
+ // * global_options: { email: "ops@example.com" },
351
+ // * sites: [
352
+ // * {
353
+ // * domain: "example.com",
354
+ // * reverse_proxy: {
355
+ // * upstreams: ["127.0.0.1:3000"],
356
+ // * options: {
357
+ // * health_uri: "/health",
358
+ // * health_interval: "10s",
359
+ // * },
360
+ // * },
361
+ // * extra_directives: [
362
+ // * { directive: "encode", encodings: ["gzip", "zstd"] },
363
+ // * { directive: "log", output: { kind: "file", path: "/var/log/caddy/access.json" }, format: "json" },
364
+ // * {
365
+ // * directive: "header",
366
+ // * operations: [
367
+ // * { kind: "set", field: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
368
+ // * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
369
+ // * ],
370
+ // * },
371
+ // * // Raw directive escape hatch:
372
+ // * "request_body { max_size 10MB }",
373
+ // * ],
374
+ // * },
375
+ // * ],
376
+ // * });
377
+ // * ```
378
+ // */
379
+ // export async function write_config(opts: ConfigOpts): Promise<void> {
380
+ // const rendered_config: string = render_caddyfile(opts);
381
+ // await write_file_if_changed(opts.config_path, rendered_config);
382
+ // await validate_config(opts.config_path);
383
+ // }
384
+ // /**
385
+ // * Checks whether the caddy binary is available.
386
+ // */
387
+ // async function is_caddy_available(): Promise<boolean> {
388
+ // try {
389
+ // await exec("caddy", ["version"]);
390
+ // return true;
391
+ // } catch {
392
+ // return false;
393
+ // }
394
+ // }
395
+ // /**
396
+ // * Installs Caddy on Linux using official repositories.
397
+ // *
398
+ // * Note: This requires appropriate privileges (typically root).
399
+ // */
400
+ // async function install_caddy_linux(): Promise<void> {
401
+ // // Ensure apt can use HTTPS and has required keyring tooling.
402
+ // await exec("sh", [
403
+ // "-c",
404
+ // "apt-get update && apt-get install -y debian-keyring debian-archive-keyring apt-transport-https",
405
+ // ]);
406
+ // // Install the official Caddy signing key into the system keyrings.
407
+ // await exec("sh", [
408
+ // "-c",
409
+ // "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable.gpg",
410
+ // ]);
411
+ // // Add the official Caddy stable repository.
412
+ // await exec("sh", [
413
+ // "-c",
414
+ // "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list",
415
+ // ]);
416
+ // // Install Caddy.
417
+ // await exec("apt-get", ["update"]);
418
+ // await exec("apt-get", ["install", "-y", "caddy"]);
419
+ // }
420
+ // /**
421
+ // * Renders a Caddyfile from typed configuration.
422
+ // *
423
+ // * This keeps the surface area minimal while allowing users to pass raw directives
424
+ // * via `extra_directives` and reverse_proxy `options`.
425
+ // */
426
+ // function render_caddyfile(opts: ConfigOpts): string {
427
+ // const lines: string[] = [];
428
+ // // Global options block (e.g. email, acme_ca, servers, etc).
429
+ // if (opts.global_options) {
430
+ // lines.push("{");
431
+ // for (const [key, value] of Object.entries(opts.global_options)) {
432
+ // lines.push(` ${key} ${value}`);
433
+ // }
434
+ // lines.push("}");
435
+ // lines.push("");
436
+ // }
437
+ // // Each site gets its own server block.
438
+ // for (const site of opts.sites) {
439
+ // lines.push(`${site.domain} {`);
440
+ // // reverse_proxy is the core directive for proxying to an upstream app.
441
+ // lines.push(` reverse_proxy ${site.reverse_proxy.upstreams.join(" ")} {`);
442
+ // // Sub-directives within reverse_proxy (health checks, header manipulation, lb policy, etc).
443
+ // if (site.reverse_proxy.options) {
444
+ // for (const [key, value] of Object.entries(site.reverse_proxy.options)) {
445
+ // lines.push(` ${key} ${value}`);
446
+ // }
447
+ // }
448
+ // lines.push(" }");
449
+ // // Extra directives: render typed objects safely, or pass through raw strings.
450
+ // if (site.extra_directives) {
451
+ // for (const directive of site.extra_directives) {
452
+ // for (const rendered_line of render_extra_directive(directive)) {
453
+ // lines.push(` ${rendered_line}`);
454
+ // }
455
+ // }
456
+ // }
457
+ // lines.push("}");
458
+ // lines.push("");
459
+ // }
460
+ // return lines.join("\n");
461
+ // }
462
+ // /**
463
+ // * Renders an extra directive into one or more Caddyfile lines.
464
+ // */
465
+ // function render_extra_directive(directive: ExtraDirective): readonly string[] {
466
+ // // Raw directives are passed through unchanged to preserve full Caddy compatibility.
467
+ // if (typeof directive === "string") {
468
+ // return directive.split("\n");
469
+ // }
470
+ // if (directive.directive === "encode") {
471
+ // return [`encode ${directive.encodings.join(" ")}`];
472
+ // }
473
+ // if (directive.directive === "tls") {
474
+ // // Keep TLS typed support intentionally narrow and escape-hatch friendly.
475
+ // const issuer_tokens: string[] = [];
476
+ // if (directive.issuer) {
477
+ // issuer_tokens.push(directive.issuer);
478
+ // }
479
+ // if (directive.issuer_args) {
480
+ // for (const arg of directive.issuer_args) {
481
+ // issuer_tokens.push(arg);
482
+ // }
483
+ // }
484
+ // if (issuer_tokens.length === 0) {
485
+ // return ["tls"];
486
+ // }
487
+ // return [`tls ${issuer_tokens.join(" ")}`];
488
+ // }
489
+ // if (directive.directive === "log") {
490
+ // const lines: string[] = [];
491
+ // const matcher_prefix: string = directive.matcher ? ` ${directive.matcher}` : "";
492
+ // lines.push(`log${matcher_prefix} {`);
493
+ // if (directive.output) {
494
+ // if (directive.output.kind === "file") {
495
+ // lines.push(` output file ${directive.output.path}`);
496
+ // } else if (directive.output.kind === "stdout") {
497
+ // lines.push(" output stdout");
498
+ // } else if (directive.output.kind === "stderr") {
499
+ // lines.push(" output stderr");
500
+ // }
501
+ // }
502
+ // if (directive.format) {
503
+ // lines.push(` format ${directive.format}`);
504
+ // }
505
+ // lines.push("}");
506
+ // return lines;
507
+ // }
508
+ // if (directive.directive === "header") {
509
+ // const lines: string[] = [];
510
+ // const matcher_prefix: string = directive.matcher ? ` ${directive.matcher}` : "";
511
+ // lines.push(`header${matcher_prefix} {`);
512
+ // // Render each operation line in the most widely-compatible Caddyfile form.
513
+ // for (const op of directive.operations) {
514
+ // if (op.kind === "set") {
515
+ // lines.push(` ${op.field} "${escape_header_value(op.value)}"`);
516
+ // continue;
517
+ // }
518
+ // if (op.kind === "add") {
519
+ // // In Caddyfile, repeated header fields effectively "add" values.
520
+ // lines.push(` +${op.field} "${escape_header_value(op.value)}"`);
521
+ // continue;
522
+ // }
523
+ // if (op.kind === "delete") {
524
+ // lines.push(` -${op.field}`);
525
+ // continue;
526
+ // }
527
+ // }
528
+ // lines.push("}");
529
+ // return lines;
530
+ // }
531
+ // // Exhaustiveness guard: if a new directive is added to the union, TypeScript will flag this.
532
+ // // Returning raw empty would be unsafe; we throw to avoid silently misconfiguring production.
533
+ // throw new Error("unsupported_extra_directive");
534
+ // }
535
+ // /**
536
+ // * Escapes a header value for safe embedding into a quoted Caddyfile token.
537
+ // */
538
+ // function escape_header_value(value: string): string {
539
+ // // We only escape backslashes and double-quotes to keep the resulting Caddyfile valid.
540
+ // return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
541
+ // }
542
+ // /**
543
+ // * Writes a file only if its content has changed.
544
+ // *
545
+ // * This makes the setup idempotent and reduces unnecessary disk writes/reloads.
546
+ // */
547
+ // async function write_file_if_changed(file_path: string, content: string): Promise<void> {
548
+ // const new_hash: string = hash_content(content);
549
+ // try {
550
+ // const existing: string = await fs.readFile(file_path, "utf8");
551
+ // const existing_hash: string = hash_content(existing);
552
+ // if (existing_hash === new_hash) {
553
+ // return;
554
+ // }
555
+ // } catch {
556
+ // // File does not exist or is unreadable, proceed with write.
557
+ // }
558
+ // await fs.writeFile(file_path, content, { mode: 0o644 });
559
+ // }
560
+ // /**
561
+ // * Validates a Caddy configuration file.
562
+ // *
563
+ // * This catches invalid directives before the user attempts to reload/start Caddy.
564
+ // */
565
+ // async function validate_config(config_path: string): Promise<void> {
566
+ // await exec("caddy", ["validate", "--config", config_path]);
567
+ // }
568
+ // /**
569
+ // * Executes a system command safely.
570
+ // *
571
+ // * Uses inherited stdio so the user sees relevant output (useful for install/validate).
572
+ // */
573
+ // async function exec(command: string, args: readonly string[]): Promise<void> {
574
+ // await new Promise<void>((resolve, reject) => {
575
+ // const child = spawn(command, args, { stdio: "inherit" });
576
+ // child.on("error", reject);
577
+ // child.on("exit", (code: number | null) => {
578
+ // if (code === 0) {
579
+ // resolve();
580
+ // return;
581
+ // }
582
+ // reject(new Error(`command_failed: ${command}`));
583
+ // });
584
+ // });
585
+ // }
586
+ // /**
587
+ // * Computes a stable hash for content comparison.
588
+ // */
589
+ // function hash_content(content: string): string {
590
+ // return createHash("sha256").update(content).digest("hex");
591
+ // }
592
+ // }
@@ -609,6 +609,7 @@ export class Server {
609
609
  content_type: this.get_content_type(favicon.extension()),
610
610
  _is_static: true,
611
611
  server: this,
612
+ cache: true,
612
613
  });
613
614
  }
614
615
  // Create status endpoint.
@@ -632,6 +633,7 @@ export class Server {
632
633
  params: {
633
634
  key: "string",
634
635
  },
636
+ cache: false,
635
637
  callback: async (stream, params) => {
636
638
  // Check key.
637
639
  if (params.key !== status_key) {
@@ -699,6 +701,7 @@ export class Server {
699
701
  data: sitemap,
700
702
  content_type: "application/xml",
701
703
  _compress: false,
704
+ cache: true,
702
705
  });
703
706
  }
704
707
  // Create the robots.txt endpoint.
@@ -724,6 +727,7 @@ export class Server {
724
727
  content_type: "text/plain",
725
728
  data: robots,
726
729
  _compress: false,
730
+ cache: true,
727
731
  });
728
732
  }
729
733
  // Create admin endpoint.