@ressjs/vite-router 0.5.2 → 0.6.0-rc.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.
Files changed (141) hide show
  1. package/LICENSE +69 -0
  2. package/README.md +9 -515
  3. package/dist/client/entry.d.mts +2 -0
  4. package/dist/client/entry.mjs +1 -0
  5. package/dist/client/index.d.ts +2 -0
  6. package/dist/client/index.js +8 -0
  7. package/dist/config/base-path.d.ts +44 -0
  8. package/dist/config/base-path.js +100 -0
  9. package/dist/config/codegen.d.ts +29 -0
  10. package/dist/config/codegen.js +131 -0
  11. package/dist/config/define.d.ts +8 -0
  12. package/dist/config/define.js +12 -0
  13. package/dist/config/env.d.ts +19 -0
  14. package/dist/config/env.js +40 -0
  15. package/dist/config/index.d.ts +18 -0
  16. package/dist/config/index.js +46 -0
  17. package/dist/config/load.d.ts +28 -0
  18. package/dist/config/load.js +82 -0
  19. package/dist/config/middleware.d.ts +18 -0
  20. package/dist/config/middleware.js +79 -0
  21. package/dist/config/rules.d.ts +59 -0
  22. package/dist/config/rules.js +162 -0
  23. package/dist/config/types.d.ts +189 -0
  24. package/dist/config/types.js +2 -0
  25. package/dist/config/validate.d.ts +19 -0
  26. package/dist/config/validate.js +143 -0
  27. package/dist/config/watch.d.ts +53 -0
  28. package/dist/config/watch.js +163 -0
  29. package/dist/fs/module-extensions.d.ts +105 -0
  30. package/dist/fs/module-extensions.js +213 -0
  31. package/dist/head/resolve.d.ts +5 -0
  32. package/dist/head/resolve.js +166 -0
  33. package/dist/head/types.d.ts +44 -0
  34. package/dist/head/types.js +2 -0
  35. package/dist/helpers/html-generator.d.ts +26 -6
  36. package/dist/helpers/html-generator.js +96 -138
  37. package/dist/helpers/middlewares.d.ts +32 -21
  38. package/dist/helpers/middlewares.js +181 -159
  39. package/dist/helpers/page-config-merge.d.ts +11 -0
  40. package/dist/helpers/page-config-merge.js +84 -0
  41. package/dist/helpers/request-handler.d.ts +15 -2
  42. package/dist/helpers/request-handler.js +164 -27
  43. package/dist/index.d.ts +35 -3
  44. package/dist/index.js +77 -8
  45. package/dist/isr/capture.d.ts +79 -0
  46. package/dist/isr/capture.js +222 -0
  47. package/dist/isr/handler.d.ts +49 -0
  48. package/dist/isr/handler.js +207 -0
  49. package/dist/isr/key.d.ts +26 -0
  50. package/dist/isr/key.js +59 -0
  51. package/dist/isr/page-config.d.ts +17 -0
  52. package/dist/isr/page-config.js +71 -0
  53. package/dist/isr/preview.d.ts +5 -0
  54. package/dist/isr/preview.js +38 -0
  55. package/dist/isr/public-request.d.ts +50 -0
  56. package/dist/isr/public-request.js +108 -0
  57. package/dist/isr/response.d.ts +41 -0
  58. package/dist/isr/response.js +128 -0
  59. package/dist/isr/store.d.ts +42 -0
  60. package/dist/isr/store.js +108 -0
  61. package/dist/isr/types.d.ts +62 -0
  62. package/dist/isr/types.js +2 -0
  63. package/dist/pages.d.ts +10 -9
  64. package/dist/pages.js +42 -261
  65. package/dist/platform.d.ts +11 -14
  66. package/dist/platform.js +18 -102
  67. package/dist/plugin/environments.d.ts +68 -0
  68. package/dist/plugin/environments.js +74 -0
  69. package/dist/plugin/index.d.ts +59 -0
  70. package/dist/plugin/index.js +195 -0
  71. package/dist/plugin/route-manifest.d.ts +19 -0
  72. package/dist/plugin/route-manifest.js +55 -0
  73. package/dist/plugin/virtual-entries.d.ts +26 -0
  74. package/dist/plugin/virtual-entries.js +80 -0
  75. package/dist/render.d.ts +39 -2
  76. package/dist/render.js +75 -77
  77. package/dist/router.d.ts +32 -13
  78. package/dist/router.js +180 -146
  79. package/dist/routes/dispatcher.d.ts +58 -0
  80. package/dist/routes/dispatcher.js +70 -0
  81. package/dist/routes/manifest.d.ts +17 -0
  82. package/dist/routes/manifest.js +62 -0
  83. package/dist/routes/match.d.ts +20 -0
  84. package/dist/routes/match.js +75 -0
  85. package/dist/routes/module.d.ts +14 -0
  86. package/dist/routes/module.js +30 -0
  87. package/dist/routes/parse.d.ts +31 -0
  88. package/dist/routes/parse.js +114 -0
  89. package/dist/routes/rank.d.ts +12 -0
  90. package/dist/routes/rank.js +49 -0
  91. package/dist/routes/router.d.ts +54 -0
  92. package/dist/routes/router.js +153 -0
  93. package/dist/routes/scan.d.ts +26 -0
  94. package/dist/routes/scan.js +98 -0
  95. package/dist/routes/types.d.ts +114 -0
  96. package/dist/routes/types.js +17 -0
  97. package/dist/runtime/dev-server.d.ts +64 -0
  98. package/dist/runtime/dev-server.js +94 -0
  99. package/dist/runtime/dev-styles.d.ts +13 -0
  100. package/dist/runtime/dev-styles.js +50 -0
  101. package/dist/runtime/module-loader.d.ts +55 -0
  102. package/dist/runtime/module-loader.js +122 -0
  103. package/dist/runtime/prod-server.d.ts +55 -0
  104. package/dist/runtime/prod-server.js +188 -0
  105. package/dist/runtime/template.d.ts +10 -0
  106. package/dist/runtime/template.js +33 -0
  107. package/dist/security/client-props.d.ts +25 -0
  108. package/dist/security/client-props.js +51 -0
  109. package/dist/security/config.d.ts +121 -0
  110. package/dist/security/config.js +77 -0
  111. package/dist/security/csp.d.ts +53 -0
  112. package/dist/security/csp.js +118 -0
  113. package/dist/security/dev-hardening.d.ts +46 -0
  114. package/dist/security/dev-hardening.js +65 -0
  115. package/dist/security/errors.d.ts +37 -0
  116. package/dist/security/errors.js +84 -0
  117. package/dist/security/escape.d.ts +40 -0
  118. package/dist/security/escape.js +90 -0
  119. package/dist/security/head-tags.d.ts +32 -0
  120. package/dist/security/head-tags.js +156 -0
  121. package/dist/security/headers.d.ts +76 -0
  122. package/dist/security/headers.js +278 -0
  123. package/dist/security/index.d.ts +24 -0
  124. package/dist/security/index.js +49 -0
  125. package/dist/security/serialize.d.ts +48 -0
  126. package/dist/security/serialize.js +146 -0
  127. package/dist/variants/assets.d.ts +33 -0
  128. package/dist/variants/assets.js +144 -0
  129. package/dist/variants/catalog.d.ts +33 -0
  130. package/dist/variants/catalog.js +79 -0
  131. package/dist/variants/platform-tokens.d.ts +9 -0
  132. package/dist/variants/platform-tokens.js +15 -0
  133. package/dist/variants/resolve.d.ts +40 -0
  134. package/dist/variants/resolve.js +90 -0
  135. package/dist/variants/suffix.d.ts +8 -0
  136. package/dist/variants/suffix.js +12 -0
  137. package/dist/variants/types.d.ts +72 -0
  138. package/dist/variants/types.js +2 -0
  139. package/package.json +23 -11
  140. package/dist/helpers/vite-config.d.ts +0 -10
  141. package/dist/helpers/vite-config.js +0 -90
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Carga de módulos de usuario — páginas y middlewares.
3
+ *
4
+ * La interfaz es única para desarrollo y producción, de modo que quien carga un
5
+ * módulo no tenga que saber en qué entorno corre.
6
+ */
7
+ /** Superficie mínima de un entorno de desarrollo de Vite que necesitamos. */
8
+ export interface FetchableEnvironment {
9
+ fetchModule(id: string, importer?: string, options?: {
10
+ cached?: boolean;
11
+ startOffset?: number;
12
+ }): Promise<Record<string, unknown>>;
13
+ }
14
+ export interface ModuleLoader {
15
+ /** Carga un módulo de usuario y devuelve sus exports. */
16
+ load<T = Record<string, unknown>>(id: string): Promise<T>;
17
+ /**
18
+ * Descarta de la caché el módulo correspondiente a un archivo, para que la
19
+ * próxima carga lo vuelva a evaluar. Sin argumento, descarta todo.
20
+ */
21
+ invalidate(file?: string): void;
22
+ /** Libera recursos. Idempotente. */
23
+ close(): Promise<void>;
24
+ }
25
+ /**
26
+ * Cargador de desarrollo.
27
+ *
28
+ * El runner se construye sobre `environment.fetchModule()` y no sobre el getter
29
+ * `RunnableDevEnvironment.runner`, porque ese getter arma su transporte de forma
30
+ * síncrona leyendo `environment.hot.api.outsideEmitter`, propiedad que sólo
31
+ * existe una vez que el servidor empezó a escuchar. Ress.js carga módulos de
32
+ * usuario durante el registro de rutas, antes de ese momento, y ahí el getter
33
+ * falla con `Cannot read properties of undefined (reading 'outsideEmitter')`.
34
+ * `fetchModule()` es un método asíncrono plano, disponible en todos los tipos de
35
+ * entorno y en cualquier punto del ciclo de vida.
36
+ *
37
+ * El runner se crea una vez por servidor, nunca por petición: `runner.import()`
38
+ * mantiene su propia caché de módulos y crear uno por petición la anularía.
39
+ *
40
+ * Esa caché es también la razón de `invalidate()`. El runner corre con `hmr:
41
+ * false` —el canal de HMR no es utilizable durante el registro de rutas—, así
42
+ * que nada descarta un módulo cuando su archivo cambia. Sin invalidación
43
+ * explícita, editar una página o un middleware no tendría efecto hasta reiniciar
44
+ * el servidor. `createDevRuntime` conecta el watcher de Vite a este método.
45
+ */
46
+ export declare function createDevModuleLoader(env: FetchableEnvironment): Promise<ModuleLoader>;
47
+ /**
48
+ * Cargador de producción: importa el artefacto ya compilado.
49
+ *
50
+ * `resolveId` traduce el identificador lógico de una página al path de su
51
+ * artefacto de servidor. Se recibe como parámetro para que la traducción sea
52
+ * responsabilidad de quien conoce la correspondencia — F-018 la toma del
53
+ * manifest de rutas, donde el build la registró.
54
+ */
55
+ export declare function createProdModuleLoader(resolveId: (id: string) => string): ModuleLoader;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * Carga de módulos de usuario — páginas y middlewares.
4
+ *
5
+ * La interfaz es única para desarrollo y producción, de modo que quien carga un
6
+ * módulo no tenga que saber en qué entorno corre.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createDevModuleLoader = createDevModuleLoader;
13
+ exports.createProdModuleLoader = createProdModuleLoader;
14
+ /**
15
+ * Cargador de desarrollo.
16
+ *
17
+ * El runner se construye sobre `environment.fetchModule()` y no sobre el getter
18
+ * `RunnableDevEnvironment.runner`, porque ese getter arma su transporte de forma
19
+ * síncrona leyendo `environment.hot.api.outsideEmitter`, propiedad que sólo
20
+ * existe una vez que el servidor empezó a escuchar. Ress.js carga módulos de
21
+ * usuario durante el registro de rutas, antes de ese momento, y ahí el getter
22
+ * falla con `Cannot read properties of undefined (reading 'outsideEmitter')`.
23
+ * `fetchModule()` es un método asíncrono plano, disponible en todos los tipos de
24
+ * entorno y en cualquier punto del ciclo de vida.
25
+ *
26
+ * El runner se crea una vez por servidor, nunca por petición: `runner.import()`
27
+ * mantiene su propia caché de módulos y crear uno por petición la anularía.
28
+ *
29
+ * Esa caché es también la razón de `invalidate()`. El runner corre con `hmr:
30
+ * false` —el canal de HMR no es utilizable durante el registro de rutas—, así
31
+ * que nada descarta un módulo cuando su archivo cambia. Sin invalidación
32
+ * explícita, editar una página o un middleware no tendría efecto hasta reiniciar
33
+ * el servidor. `createDevRuntime` conecta el watcher de Vite a este método.
34
+ */
35
+ async function createDevModuleLoader(env) {
36
+ const { ModuleRunner, ESModulesEvaluator, createNodeImportMeta } = await import(
37
+ /* @vite-ignore */ 'vite/module-runner');
38
+ const runner = new ModuleRunner({
39
+ transport: {
40
+ // `normalizeModuleRunnerTransport` ya desempaqueta el payload, así que
41
+ // `payload.data` llega como `{ id, name, data: args }`.
42
+ invoke: async (payload) => {
43
+ const { name, data: args } = payload.data;
44
+ if (name === 'fetchModule') {
45
+ const [id, importer, options] = args;
46
+ return { result: await env.fetchModule(id, importer, options) };
47
+ }
48
+ if (name === 'getBuiltins') {
49
+ // Los módulos corren en el proceso Node anfitrión, que ya tiene
50
+ // acceso nativo a los built-ins.
51
+ return { result: [] };
52
+ }
53
+ return {
54
+ error: {
55
+ name: 'Error',
56
+ message: `[ress] invocación inesperada del module runner: ${name}`,
57
+ },
58
+ };
59
+ },
60
+ },
61
+ createImportMeta: createNodeImportMeta,
62
+ sourcemapInterceptor: false,
63
+ hmr: false,
64
+ }, new ESModulesEvaluator());
65
+ let closed = false;
66
+ return {
67
+ load: (id) => runner.import(id),
68
+ invalidate: (file) => {
69
+ if (!file) {
70
+ runner.clearCache();
71
+ return;
72
+ }
73
+ // Un archivo puede tener varios módulos evaluados si se importó con
74
+ // distintas queries. Se descartan todos.
75
+ const mods = runner.evaluatedModules.getModulesByFile(file);
76
+ if (!mods || mods.size === 0) {
77
+ // El archivo no está en el grafo de este runner: nada que descartar.
78
+ return;
79
+ }
80
+ for (const mod of mods) {
81
+ runner.evaluatedModules.invalidateModule(mod);
82
+ }
83
+ },
84
+ close: async () => {
85
+ if (closed)
86
+ return;
87
+ closed = true;
88
+ await runner.close();
89
+ },
90
+ };
91
+ }
92
+ /**
93
+ * Cargador de producción: importa el artefacto ya compilado.
94
+ *
95
+ * `resolveId` traduce el identificador lógico de una página al path de su
96
+ * artefacto de servidor. Se recibe como parámetro para que la traducción sea
97
+ * responsabilidad de quien conoce la correspondencia — F-018 la toma del
98
+ * manifest de rutas, donde el build la registró.
99
+ */
100
+ function createProdModuleLoader(resolveId) {
101
+ return {
102
+ load: async (id) => {
103
+ const target = resolveId(id);
104
+ // El loader ESM de Node interpreta una ruta `C:\\...` como una URL con
105
+ // esquema `c:` en Windows. Convertir sólo las rutas de archivos mantiene
106
+ // intactos los identificadores `node:`, `data:` y `file:` usados por
107
+ // integraciones y tests.
108
+ const specifier = process.platform === 'win32' && !isModuleSpecifier(target)
109
+ ? (0, node_url_1.pathToFileURL)(node_path_1.default.resolve(target)).href
110
+ : target;
111
+ return (await import(/* @vite-ignore */ specifier));
112
+ },
113
+ // En producción los artefactos no cambian mientras el servidor corre.
114
+ invalidate: () => { },
115
+ close: async () => { },
116
+ };
117
+ }
118
+ function isModuleSpecifier(value) {
119
+ return /^(?:node:|data:|file:|https?:)/i.test(value);
120
+ }
121
+ const node_path_1 = __importDefault(require("node:path"));
122
+ const node_url_1 = require("node:url");
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Runtime de producción.
3
+ *
4
+ * No instancia Vite: en producción se sirven artefactos ya construidos.
5
+ */
6
+ import { type ResolvedSecurityOptions } from '../security';
7
+ import type { RouteManifest } from '../routes/types';
8
+ import { type DevRuntimeOptions, type RessRuntime } from './dev-server';
9
+ /**
10
+ * Dónde quedó el artefacto de servidor de una página.
11
+ *
12
+ * La traducción del nombre la hace `toCompiledPath`, que es la única
13
+ * implementación del framework: antes vivía repetida en tres lugares con reglas
14
+ * que ya habían divergido —una sólo recortaba `.ts`, otra sólo `\w+` dentro de
15
+ * los corchetes—.
16
+ */
17
+ export declare function resolveServerModulePath(pageFile: string, ssrOutDir?: string): string;
18
+ /**
19
+ * Traduce la identidad lógica que pide el runtime al chunk registrado.
20
+ *
21
+ * Los middlewares se buscan en la lista que registró el build. La resolución
22
+ * por convención queda sólo para un manifest armado sin ella (un runtime
23
+ * propio). Una página conocida jamás cae por ese camino: si su `buildChunk`
24
+ * falta, `resolveServerModule` falla y exige rebuild.
25
+ */
26
+ export declare function resolveProductionModuleId(id: string, manifest: RouteManifest, options?: {
27
+ root?: string;
28
+ ssrOutDir?: string;
29
+ }): string;
30
+ export declare function createProdRuntime(opts?: {
31
+ clientOutDir?: string;
32
+ ssrOutDir?: string;
33
+ /** Prefijo de las URL de artefactos: prefijo de ruta o CDN. */
34
+ assetPrefix?: string;
35
+ }): Promise<RessRuntime>;
36
+ /**
37
+ * Assets estáticos en producción.
38
+ *
39
+ * Cada archivo se cachea según lo que su nombre garantice: los que llevan el
40
+ * hash de su contenido pueden guardarse para siempre, el resto tiene que
41
+ * revalidarse. El default es revalidar, porque un archivo guardado como
42
+ * inmutable por error no se puede invalidar en el navegador de quien ya lo bajó.
43
+ */
44
+ export declare function createProdAssetMiddleware(clientOutDir?: string, options?: {
45
+ security?: ResolvedSecurityOptions;
46
+ /** Archivos que emitió el build, tomados del manifest. */
47
+ buildOutputs?: ReadonlySet<string>;
48
+ }): Promise<import("express-serve-static-core").Router>;
49
+ /** Los archivos que el build de cliente emitió, según el manifest. */
50
+ export declare function buildOutputsFrom(manifest: Record<string, {
51
+ file: string;
52
+ css?: string[];
53
+ }>): Set<string>;
54
+ /** Punto de entrada único: elige el runtime según el entorno. */
55
+ export declare function createRuntime(isProduction: boolean, base?: string, assetPrefix?: string, devOptions?: DevRuntimeOptions): Promise<RessRuntime>;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime de producción.
4
+ *
5
+ * No instancia Vite: en producción se sirven artefactos ya construidos.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.resolveServerModulePath = resolveServerModulePath;
12
+ exports.resolveProductionModuleId = resolveProductionModuleId;
13
+ exports.createProdRuntime = createProdRuntime;
14
+ exports.createProdAssetMiddleware = createProdAssetMiddleware;
15
+ exports.buildOutputsFrom = buildOutputsFrom;
16
+ exports.createRuntime = createRuntime;
17
+ const node_crypto_1 = require("node:crypto");
18
+ const promises_1 = __importDefault(require("node:fs/promises"));
19
+ const node_path_1 = __importDefault(require("node:path"));
20
+ const express_1 = __importDefault(require("express"));
21
+ const security_1 = require("../security");
22
+ const module_loader_1 = require("./module-loader");
23
+ const assets_1 = require("../variants/assets");
24
+ const manifest_1 = require("../routes/manifest");
25
+ const module_1 = require("../routes/module");
26
+ const environments_1 = require("../plugin/environments");
27
+ const module_extensions_1 = require("../fs/module-extensions");
28
+ const dev_server_1 = require("./dev-server");
29
+ const template_1 = require("./template");
30
+ /**
31
+ * El manifest de rutas que emitió el build.
32
+ *
33
+ * Si no está —un proyecto construido con una versión anterior— se escanea, para
34
+ * no dejar el servidor sin arrancar por un archivo que se puede regenerar.
35
+ */
36
+ async function loadRoutes(clientOutDir) {
37
+ const manifestPath = node_path_1.default.join(node_path_1.default.dirname(clientOutDir), 'route-manifest.json');
38
+ let raw;
39
+ try {
40
+ raw = await promises_1.default.readFile(manifestPath, 'utf-8');
41
+ }
42
+ catch {
43
+ throw new Error(`[ress] No se encontró el manifest de rutas en ${manifestPath}. ` +
44
+ 'Ejecutá el build antes de arrancar en producción.');
45
+ }
46
+ // Un manifest ilegible o de otra versión se reporta tal cual. Escanear el
47
+ // proyecto como respaldo era peor que fallar: en un servidor que sólo tiene
48
+ // `dist/` el escaneo no encuentra nada, y en vez de un error claro el servidor
49
+ // arranca y responde 404 en todas las rutas.
50
+ return (0, manifest_1.loadRouteManifest)(raw);
51
+ }
52
+ /**
53
+ * Dónde quedó el artefacto de servidor de una página.
54
+ *
55
+ * La traducción del nombre la hace `toCompiledPath`, que es la única
56
+ * implementación del framework: antes vivía repetida en tres lugares con reglas
57
+ * que ya habían divergido —una sólo recortaba `.ts`, otra sólo `\w+` dentro de
58
+ * los corchetes—.
59
+ */
60
+ function resolveServerModulePath(pageFile, ssrOutDir = environments_1.OUT_DIR.ssr) {
61
+ return node_path_1.default.resolve(process.cwd(), ssrOutDir, (0, module_extensions_1.toCompiledPath)(pageFile));
62
+ }
63
+ /**
64
+ * Traduce la identidad lógica que pide el runtime al chunk registrado.
65
+ *
66
+ * Los middlewares se buscan en la lista que registró el build. La resolución
67
+ * por convención queda sólo para un manifest armado sin ella (un runtime
68
+ * propio). Una página conocida jamás cae por ese camino: si su `buildChunk`
69
+ * falta, `resolveServerModule` falla y exige rebuild.
70
+ */
71
+ function resolveProductionModuleId(id, manifest, options = {}) {
72
+ const root = options.root ?? process.cwd();
73
+ const ssrOutDir = options.ssrOutDir ?? environments_1.OUT_DIR.ssr;
74
+ const entry = manifest.entries.find((candidate) => candidate.server.devId === id ||
75
+ `/${candidate.sourceFile.replace(/\\/g, '/')}` === id.replace(/\\/g, '/'));
76
+ if (entry) {
77
+ return (0, module_1.resolveServerModule)(entry, {
78
+ root,
79
+ serverOutDir: ssrOutDir,
80
+ isProduction: true,
81
+ });
82
+ }
83
+ const middleware = manifest.middlewares?.find((candidate) => candidate.server.devId.replace(/^\//, '') === id.replace(/\\/g, '/').replace(/^\//, ''));
84
+ if (middleware) {
85
+ return (0, module_1.resolveServerModule)({ pageId: middleware.file, server: middleware.server }, { root, serverOutDir: ssrOutDir, isProduction: true });
86
+ }
87
+ if (/\.middlewares\.[^.]+$|(?:^|\/)middlewares\.[^.]+$/.test(id)) {
88
+ return node_path_1.default.resolve(root, ssrOutDir, (0, module_extensions_1.toCompiledPath)(id));
89
+ }
90
+ throw new Error(`[ress] El módulo "${id}" no figura en el manifest de rutas del build.`);
91
+ }
92
+ async function createProdRuntime(opts = {}) {
93
+ const clientOutDir = opts.clientOutDir ?? environments_1.OUT_DIR.client;
94
+ const ssrOutDir = opts.ssrOutDir ?? environments_1.OUT_DIR.ssr;
95
+ const manifestPath = node_path_1.default.join(clientOutDir, '.vite', 'manifest.json');
96
+ let manifest = {};
97
+ try {
98
+ manifest = JSON.parse(await promises_1.default.readFile(manifestPath, 'utf-8'));
99
+ }
100
+ catch {
101
+ throw new Error(`[ress] No se encontró el manifest del cliente en ${manifestPath}. ` +
102
+ 'Ejecutá el build antes de arrancar en producción.');
103
+ }
104
+ const templateHtml = await (0, template_1.readTemplateHtml)();
105
+ // El manifest lo emite el build y es obligatorio en producción: arrancar sin
106
+ // él respondería 404 para todo en un despliegue que no incluye fuentes.
107
+ const routes = await loadRoutes(clientOutDir);
108
+ const catalog = (0, assets_1.createProdVariantCatalog)(routes, manifest);
109
+ const moduleLoader = (0, module_loader_1.createProdModuleLoader)((id) => resolveProductionModuleId(id, routes, { root: process.cwd(), ssrOutDir }));
110
+ return {
111
+ vite: null,
112
+ moduleLoader,
113
+ templateHtml,
114
+ isProduction: true,
115
+ catalog,
116
+ routes,
117
+ assets: (0, assets_1.createProdAssetIndex)(catalog, manifest, opts.assetPrefix ?? '/'),
118
+ buildOutputs: buildOutputsFrom(manifest),
119
+ buildId: await readBuildId(clientOutDir, manifest),
120
+ // Los artefactos no cambian mientras el servidor corre.
121
+ onSourceChange: () => () => { },
122
+ close: async () => {
123
+ await moduleLoader.close();
124
+ },
125
+ };
126
+ }
127
+ /**
128
+ * Assets estáticos en producción.
129
+ *
130
+ * Cada archivo se cachea según lo que su nombre garantice: los que llevan el
131
+ * hash de su contenido pueden guardarse para siempre, el resto tiene que
132
+ * revalidarse. El default es revalidar, porque un archivo guardado como
133
+ * inmutable por error no se puede invalidar en el navegador de quien ya lo bajó.
134
+ */
135
+ async function createProdAssetMiddleware(clientOutDir = environments_1.OUT_DIR.client, options = {}) {
136
+ const compression = (await import('compression')).default;
137
+ const sirv = (await import('sirv')).default;
138
+ const security = options.security ?? (0, security_1.resolveSecurityOptions)();
139
+ const buildOutputs = options.buildOutputs ?? new Set();
140
+ const middlewares = express_1.default.Router();
141
+ middlewares.use(compression());
142
+ middlewares.use(sirv(clientOutDir, {
143
+ extensions: [],
144
+ etag: true,
145
+ setHeaders(res, pathname) {
146
+ if (security.headers.contentTypeOptions) {
147
+ res.setHeader('X-Content-Type-Options', 'nosniff');
148
+ }
149
+ res.setHeader('Cache-Control', (0, security_1.assetCacheControl)((0, security_1.classifyAsset)(pathname, buildOutputs), security));
150
+ },
151
+ }));
152
+ return middlewares;
153
+ }
154
+ /**
155
+ * El identificador del build vigente.
156
+ *
157
+ * Lo escribe el plugin junto al manifest de rutas. Un build anterior a ese
158
+ * archivo se identifica por el hash de su manifest de cliente, que cambia con
159
+ * cualquier artefacto nuevo.
160
+ */
161
+ async function readBuildId(clientOutDir, manifest) {
162
+ try {
163
+ const id = (await promises_1.default.readFile(node_path_1.default.join(node_path_1.default.dirname(clientOutDir), environments_1.BUILD_ID_FILE), 'utf-8')).trim();
164
+ if (id)
165
+ return id;
166
+ }
167
+ catch {
168
+ // Se identifica por el manifest.
169
+ }
170
+ return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(manifest)).digest('hex').slice(0, 32);
171
+ }
172
+ /** Los archivos que el build de cliente emitió, según el manifest. */
173
+ function buildOutputsFrom(manifest) {
174
+ const out = new Set();
175
+ for (const chunk of Object.values(manifest)) {
176
+ if (chunk.file)
177
+ out.add(chunk.file);
178
+ for (const css of chunk.css ?? [])
179
+ out.add(css);
180
+ }
181
+ return out;
182
+ }
183
+ /** Punto de entrada único: elige el runtime según el entorno. */
184
+ async function createRuntime(isProduction, base = '/', assetPrefix = base, devOptions = {}) {
185
+ if (isProduction)
186
+ return createProdRuntime({ assetPrefix });
187
+ return (0, dev_server_1.createDevRuntime)(base, assetPrefix, devOptions);
188
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Documento mínimo para proyectos que no necesitan personalizar el shell HTML.
3
+ * Los marcadores son consumidos por generateCompleteHTML.
4
+ */
5
+ export declare const DEFAULT_TEMPLATE_HTML = "<!doctype html>\n<html lang=\"en\">\n <head><!--app-head--></head>\n <body><div id=\"root\"><!--app-html--></div></body>\n</html>\n";
6
+ /**
7
+ * Lee el shell del proyecto cuando existe y cae a la plantilla del paquete si no.
8
+ * Sólo ENOENT es una ausencia esperable; otros errores deben ser visibles.
9
+ */
10
+ export declare function readTemplateHtml(root?: string): Promise<string>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_TEMPLATE_HTML = void 0;
7
+ exports.readTemplateHtml = readTemplateHtml;
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ /**
11
+ * Documento mínimo para proyectos que no necesitan personalizar el shell HTML.
12
+ * Los marcadores son consumidos por generateCompleteHTML.
13
+ */
14
+ exports.DEFAULT_TEMPLATE_HTML = `<!doctype html>
15
+ <html lang="en">
16
+ <head><!--app-head--></head>
17
+ <body><div id="root"><!--app-html--></div></body>
18
+ </html>
19
+ `;
20
+ /**
21
+ * Lee el shell del proyecto cuando existe y cae a la plantilla del paquete si no.
22
+ * Sólo ENOENT es una ausencia esperable; otros errores deben ser visibles.
23
+ */
24
+ async function readTemplateHtml(root = process.cwd()) {
25
+ try {
26
+ return await promises_1.default.readFile(node_path_1.default.join(root, 'index.html'), 'utf-8');
27
+ }
28
+ catch (error) {
29
+ if (error.code !== 'ENOENT')
30
+ throw error;
31
+ return exports.DEFAULT_TEMPLATE_HTML;
32
+ }
33
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Qué parte del estado del servidor viaja al cliente.
3
+ *
4
+ * La regla es enumerar lo que **sí** viaja. La inversa —enumerar lo que se
5
+ * excluye— falla sola: cada middleware que agregue una clave nueva a
6
+ * `res.locals` la expone hasta que alguien se acuerde de agregarla a la lista de
7
+ * exclusión, y nadie se acuerda.
8
+ */
9
+ import type { ClientPropsPolicy } from './config';
10
+ /**
11
+ * Las props que se serializan en el documento.
12
+ *
13
+ * Las props del **servidor** no cambian: el componente sigue recibiendo todo
14
+ * durante el renderizado. La restricción es sólo sobre lo que se escribe en el
15
+ * HTML.
16
+ */
17
+ export declare function pickClientProps(locals: Record<string, unknown>, policy: ClientPropsPolicy): Record<string, unknown>;
18
+ /**
19
+ * Qué quedó afuera.
20
+ *
21
+ * Se usa para avisar en desarrollo: una página que renderiza en el servidor con
22
+ * datos que el cliente no tiene hidrata distinto, y ese desajuste es difícil de
23
+ * diagnosticar sin que alguien lo nombre.
24
+ */
25
+ export declare function withheldKeys(locals: Record<string, unknown>, exposed: Record<string, unknown>): string[];
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Qué parte del estado del servidor viaja al cliente.
4
+ *
5
+ * La regla es enumerar lo que **sí** viaja. La inversa —enumerar lo que se
6
+ * excluye— falla sola: cada middleware que agregue una clave nueva a
7
+ * `res.locals` la expone hasta que alguien se acuerde de agregarla a la lista de
8
+ * exclusión, y nadie se acuerda.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.pickClientProps = pickClientProps;
12
+ exports.withheldKeys = withheldKeys;
13
+ /**
14
+ * Las props que se serializan en el documento.
15
+ *
16
+ * Las props del **servidor** no cambian: el componente sigue recibiendo todo
17
+ * durante el renderizado. La restricción es sólo sobre lo que se escribe en el
18
+ * HTML.
19
+ */
20
+ function pickClientProps(locals, policy) {
21
+ // Sin prototipo: así una clave `__proto__` en los datos es una clave más y no
22
+ // altera el objeto.
23
+ const out = Object.create(null);
24
+ // `serverSideProps` es lo que la página pidió explícitamente para el cliente:
25
+ // es el contrato que ya usan las aplicaciones, y por eso sigue activo.
26
+ if (policy.exposeServerSideProps && isPlainObject(locals?.serverSideProps)) {
27
+ Object.assign(out, locals.serverSideProps);
28
+ }
29
+ // La válvula para el estado público que aporta un middleware, como el idioma.
30
+ for (const key of policy.expose) {
31
+ if (Object.hasOwn(locals ?? {}, key))
32
+ out[key] = locals[key];
33
+ }
34
+ // Configuración del servidor: nunca fue para el cliente, bajo ninguna política.
35
+ delete out.htmlConfig;
36
+ delete out.pageConfig;
37
+ return { ...out };
38
+ }
39
+ /**
40
+ * Qué quedó afuera.
41
+ *
42
+ * Se usa para avisar en desarrollo: una página que renderiza en el servidor con
43
+ * datos que el cliente no tiene hidrata distinto, y ese desajuste es difícil de
44
+ * diagnosticar sin que alguien lo nombre.
45
+ */
46
+ function withheldKeys(locals, exposed) {
47
+ return Object.keys(locals ?? {}).filter((key) => key !== 'htmlConfig' && key !== 'pageConfig' && key !== 'serverSideProps' && !(key in exposed));
48
+ }
49
+ function isPlainObject(value) {
50
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Opciones de seguridad del framework.
3
+ *
4
+ * Se resuelven **una vez al arrancar**, no por petición: el objeto resultante
5
+ * está completo y congelado, y viaja por el contexto de seguridad hasta quien lo
6
+ * necesite. Resolver defaults por petición sería trabajo repetido y, peor,
7
+ * abriría la puerta a que dos partes de la misma respuesta usen valores
8
+ * distintos.
9
+ */
10
+ /** Qué claves del estado del servidor viajan al cliente. */
11
+ export interface ClientPropsPolicy {
12
+ /** Claves de `res.locals` que se serializan. Nada más viaja. */
13
+ expose: string[];
14
+ /** Si `res.locals.serverSideProps` se expone completo. */
15
+ exposeServerSideProps: boolean;
16
+ }
17
+ export interface HstsOptions {
18
+ maxAge: number;
19
+ includeSubDomains?: boolean;
20
+ preload?: boolean;
21
+ }
22
+ export interface SecurityOptions {
23
+ clientProps?: Partial<ClientPropsPolicy>;
24
+ maxStateBytes?: number;
25
+ onStateOverLimit?: 'warn' | 'error';
26
+ /**
27
+ * @deprecated Ya no existe: la detección de plataforma usa siempre el
28
+ * User-Agent. Declararla lo rechaza `resolveSecurityOptions` al arrancar.
29
+ */
30
+ varyOnUserAgent?: never;
31
+ /**
32
+ * Si la plataforma detectada se escribe en el documento.
33
+ *
34
+ * `'auto'` la escribe sólo cuando la página tiene variantes. Una página que no
35
+ * varía devuelve el mismo documento a cualquier cliente, y escribir en él quién
36
+ * lo pidió lo vuelve distinto para cada uno sin que nada lo declare: una caché
37
+ * intermedia le serviría a un teléfono la plataforma de un televisor. Cuando no
38
+ * viaja, el cliente la detecta por su cuenta con las mismas reglas.
39
+ */
40
+ serializePlatform?: 'auto' | 'always' | 'never';
41
+ headers?: {
42
+ contentTypeOptions?: boolean;
43
+ referrerPolicy?: string | false;
44
+ frameOptions?: 'DENY' | 'SAMEORIGIN' | false;
45
+ hsts?: HstsOptions | false;
46
+ };
47
+ cache?: {
48
+ html?: string;
49
+ immutableMaxAge?: number;
50
+ staticMaxAge?: number;
51
+ };
52
+ csp?: {
53
+ enabled?: boolean;
54
+ reportOnly?: boolean;
55
+ directives?: Record<string, string[] | false>;
56
+ reportUri?: string;
57
+ };
58
+ head?: {
59
+ allowScriptTags?: boolean;
60
+ extraTagAllowlist?: string[];
61
+ };
62
+ dev?: {
63
+ allowedHosts?: string[];
64
+ allowedOrigins?: string[] | false;
65
+ };
66
+ }
67
+ export interface ResolvedSecurityOptions {
68
+ clientProps: ClientPropsPolicy;
69
+ maxStateBytes: number;
70
+ onStateOverLimit: 'warn' | 'error';
71
+ serializePlatform: 'auto' | 'always' | 'never';
72
+ headers: {
73
+ contentTypeOptions: boolean;
74
+ referrerPolicy: string | false;
75
+ frameOptions: 'DENY' | 'SAMEORIGIN' | false;
76
+ hsts: HstsOptions | false;
77
+ };
78
+ cache: {
79
+ html: string;
80
+ immutableMaxAge: number;
81
+ staticMaxAge: number;
82
+ };
83
+ csp: {
84
+ enabled: boolean;
85
+ reportOnly: boolean;
86
+ directives: Record<string, string[] | false>;
87
+ reportUri?: string;
88
+ };
89
+ head: {
90
+ allowScriptTags: boolean;
91
+ extraTagAllowlist: string[];
92
+ };
93
+ dev: {
94
+ allowedHosts?: string[];
95
+ allowedOrigins: string[] | false;
96
+ };
97
+ }
98
+ /**
99
+ * Defaults.
100
+ *
101
+ * Dos ausencias son deliberadas y están explicadas donde se aplican:
102
+ * `frameOptions` apagada, porque ress.js sirve páginas embebidas por diseño, y
103
+ * `hsts` apagada, porque activarla sobre un dominio que todavía sirve HTTP por
104
+ * alguna ruta lo deja inaccesible.
105
+ */
106
+ export declare function resolveSecurityOptions(user?: SecurityOptions): ResolvedSecurityOptions;
107
+ /** Lo que necesita saber cada parte de la respuesta sobre esta petición. */
108
+ export interface SecurityContext {
109
+ /** Autoriza los bloques inline ante la política de contenido. */
110
+ nonce?: string;
111
+ isProduction: boolean;
112
+ options: ResolvedSecurityOptions;
113
+ /** Ruta de la petición, para que los avisos digan dónde. */
114
+ route: string;
115
+ /**
116
+ * Ejes por los que varía esta página. Vacío significa que su documento es el
117
+ * mismo para cualquier cliente, y entonces nada que dependa del cliente puede
118
+ * escribirse en él.
119
+ */
120
+ varyBy?: readonly string[];
121
+ }