@ressjs/vite-router 0.5.2 → 0.6.0-experimental.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 (110) hide show
  1. package/README.md +42 -32
  2. package/dist/client/entry.d.mts +2 -0
  3. package/dist/client/entry.mjs +1 -0
  4. package/dist/client/index.d.ts +2 -0
  5. package/dist/client/index.js +8 -0
  6. package/dist/config/base-path.d.ts +44 -0
  7. package/dist/config/base-path.js +100 -0
  8. package/dist/config/codegen.d.ts +23 -0
  9. package/dist/config/codegen.js +118 -0
  10. package/dist/config/define.d.ts +8 -0
  11. package/dist/config/define.js +12 -0
  12. package/dist/config/env.d.ts +19 -0
  13. package/dist/config/env.js +40 -0
  14. package/dist/config/index.d.ts +18 -0
  15. package/dist/config/index.js +44 -0
  16. package/dist/config/load.d.ts +26 -0
  17. package/dist/config/load.js +73 -0
  18. package/dist/config/middleware.d.ts +18 -0
  19. package/dist/config/middleware.js +79 -0
  20. package/dist/config/rules.d.ts +48 -0
  21. package/dist/config/rules.js +149 -0
  22. package/dist/config/types.d.ts +164 -0
  23. package/dist/config/types.js +2 -0
  24. package/dist/config/validate.d.ts +19 -0
  25. package/dist/config/validate.js +105 -0
  26. package/dist/config/watch.d.ts +41 -0
  27. package/dist/config/watch.js +132 -0
  28. package/dist/fs/module-extensions.d.ts +82 -0
  29. package/dist/fs/module-extensions.js +196 -0
  30. package/dist/helpers/html-generator.d.ts +22 -5
  31. package/dist/helpers/html-generator.js +89 -120
  32. package/dist/helpers/middlewares.d.ts +7 -7
  33. package/dist/helpers/middlewares.js +51 -105
  34. package/dist/helpers/request-handler.d.ts +2 -2
  35. package/dist/helpers/request-handler.js +118 -25
  36. package/dist/index.d.ts +30 -3
  37. package/dist/index.js +70 -8
  38. package/dist/pages.d.ts +10 -9
  39. package/dist/pages.js +42 -261
  40. package/dist/platform.d.ts +11 -14
  41. package/dist/platform.js +18 -102
  42. package/dist/plugin/environments.d.ts +66 -0
  43. package/dist/plugin/environments.js +72 -0
  44. package/dist/plugin/index.d.ts +54 -0
  45. package/dist/plugin/index.js +178 -0
  46. package/dist/plugin/virtual-entries.d.ts +26 -0
  47. package/dist/plugin/virtual-entries.js +80 -0
  48. package/dist/render.d.ts +23 -2
  49. package/dist/render.js +40 -53
  50. package/dist/router.d.ts +32 -13
  51. package/dist/router.js +147 -146
  52. package/dist/routes/dispatcher.d.ts +37 -0
  53. package/dist/routes/dispatcher.js +69 -0
  54. package/dist/routes/manifest.d.ts +17 -0
  55. package/dist/routes/manifest.js +56 -0
  56. package/dist/routes/match.d.ts +20 -0
  57. package/dist/routes/match.js +75 -0
  58. package/dist/routes/parse.d.ts +31 -0
  59. package/dist/routes/parse.js +114 -0
  60. package/dist/routes/rank.d.ts +12 -0
  61. package/dist/routes/rank.js +49 -0
  62. package/dist/routes/router.d.ts +44 -0
  63. package/dist/routes/router.js +81 -0
  64. package/dist/routes/scan.d.ts +16 -0
  65. package/dist/routes/scan.js +82 -0
  66. package/dist/routes/types.d.ts +85 -0
  67. package/dist/routes/types.js +13 -0
  68. package/dist/runtime/dev-server.d.ts +62 -0
  69. package/dist/runtime/dev-server.js +94 -0
  70. package/dist/runtime/module-loader.d.ts +55 -0
  71. package/dist/runtime/module-loader.js +122 -0
  72. package/dist/runtime/prod-server.d.ts +42 -0
  73. package/dist/runtime/prod-server.js +142 -0
  74. package/dist/runtime/template.d.ts +10 -0
  75. package/dist/runtime/template.js +33 -0
  76. package/dist/security/client-props.d.ts +25 -0
  77. package/dist/security/client-props.js +50 -0
  78. package/dist/security/config.d.ts +130 -0
  79. package/dist/security/config.js +73 -0
  80. package/dist/security/csp.d.ts +53 -0
  81. package/dist/security/csp.js +118 -0
  82. package/dist/security/dev-hardening.d.ts +46 -0
  83. package/dist/security/dev-hardening.js +65 -0
  84. package/dist/security/errors.d.ts +37 -0
  85. package/dist/security/errors.js +84 -0
  86. package/dist/security/escape.d.ts +40 -0
  87. package/dist/security/escape.js +90 -0
  88. package/dist/security/head-tags.d.ts +32 -0
  89. package/dist/security/head-tags.js +111 -0
  90. package/dist/security/headers.d.ts +62 -0
  91. package/dist/security/headers.js +187 -0
  92. package/dist/security/index.d.ts +24 -0
  93. package/dist/security/index.js +47 -0
  94. package/dist/security/serialize.d.ts +48 -0
  95. package/dist/security/serialize.js +146 -0
  96. package/dist/variants/assets.d.ts +23 -0
  97. package/dist/variants/assets.js +78 -0
  98. package/dist/variants/catalog.d.ts +33 -0
  99. package/dist/variants/catalog.js +79 -0
  100. package/dist/variants/platform-tokens.d.ts +9 -0
  101. package/dist/variants/platform-tokens.js +15 -0
  102. package/dist/variants/resolve.d.ts +40 -0
  103. package/dist/variants/resolve.js +90 -0
  104. package/dist/variants/suffix.d.ts +8 -0
  105. package/dist/variants/suffix.js +12 -0
  106. package/dist/variants/types.d.ts +69 -0
  107. package/dist/variants/types.js +2 -0
  108. package/package.json +22 -10
  109. package/dist/helpers/vite-config.d.ts +0 -10
  110. package/dist/helpers/vite-config.js +0 -90
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseRoutePattern = parseRoutePattern;
4
+ exports.pageIdFromFile = pageIdFromFile;
5
+ exports.classifyEntry = classifyEntry;
6
+ const EXTENSION = /\.(tsx|ts|jsx|js|mts|mjs)$/;
7
+ function parseRoutePattern(sourceFile, pagesDir = 'app/pages') {
8
+ const relative = stripPagesDir(sourceFile, pagesDir).replace(EXTENSION, '');
9
+ const segments = [];
10
+ const paramNames = [];
11
+ const groups = [];
12
+ const parts = relative.split('/').filter(Boolean);
13
+ for (const [index, raw] of parts.entries()) {
14
+ // Un grupo organiza archivos sin aparecer en la URL.
15
+ if (raw.startsWith('(') && raw.endsWith(')')) {
16
+ groups.push(raw.slice(1, -1));
17
+ continue;
18
+ }
19
+ // `index` nombra la ruta de su carpeta, así que no aporta segmento propio.
20
+ if (raw === 'index' && index === parts.length - 1)
21
+ continue;
22
+ const segment = parseSegment(raw, sourceFile);
23
+ // Todo lo que siga a un catch-all sería inalcanzable: el catch-all ya
24
+ // consumió el resto de la URL.
25
+ const previous = segments[segments.length - 1];
26
+ if (previous && (previous.kind === 'catchAll' || previous.kind === 'optionalCatchAll')) {
27
+ throw new Error(`[ress] "${sourceFile}" pone un segmento después de "[...${previous.value}]". ` +
28
+ 'Un catch-all consume el resto de la URL, así que nada que lo siga puede alcanzarse.');
29
+ }
30
+ if (segment.kind !== 'static') {
31
+ if (paramNames.includes(segment.value)) {
32
+ throw new Error(`[ress] "${sourceFile}" usa el parámetro "${segment.value}" dos veces. ` +
33
+ 'Cada parámetro tiene que tener un nombre distinto para poder leerse.');
34
+ }
35
+ paramNames.push(segment.value);
36
+ }
37
+ segments.push(segment);
38
+ }
39
+ return { pattern: toPattern(segments), segments, paramNames, groups };
40
+ }
41
+ function parseSegment(raw, sourceFile) {
42
+ const open = raw.indexOf('[');
43
+ const close = raw.lastIndexOf(']');
44
+ if (open === -1 && close === -1)
45
+ return { kind: 'static', value: raw };
46
+ if (open !== 0 || close !== raw.length - 1) {
47
+ throw new Error(`[ress] "${sourceFile}" tiene corchetes desbalanceados o mezclados con texto en "${raw}". ` +
48
+ 'Un segmento con parámetro se escribe entero entre corchetes: [id], [...slug], [[...slug]].');
49
+ }
50
+ const inner = raw.slice(1, -1);
51
+ // `[[...slug]]` — el doble corchete lo vuelve opcional.
52
+ if (inner.startsWith('[') && inner.endsWith(']')) {
53
+ const name = requireName(inner.slice(1, -1).replace(/^\.\.\./, ''), raw, sourceFile);
54
+ if (!inner.slice(1, -1).startsWith('...')) {
55
+ throw new Error(`[ress] "${sourceFile}" escribe "${raw}" con doble corchete pero sin "...". ` +
56
+ 'El doble corchete es para un catch-all opcional: [[...slug]].');
57
+ }
58
+ return { kind: 'optionalCatchAll', value: name };
59
+ }
60
+ if (inner.startsWith('...')) {
61
+ return { kind: 'catchAll', value: requireName(inner.slice(3), raw, sourceFile) };
62
+ }
63
+ return { kind: 'dynamic', value: requireName(inner, raw, sourceFile) };
64
+ }
65
+ function requireName(name, raw, sourceFile) {
66
+ if (!name) {
67
+ throw new Error(`[ress] "${sourceFile}" declara el parámetro "${raw}" sin nombre. ` +
68
+ 'Sin nombre no hay forma de leer su valor desde la página.');
69
+ }
70
+ return name;
71
+ }
72
+ function toPattern(segments) {
73
+ if (segments.length === 0)
74
+ return '/';
75
+ return ('/' +
76
+ segments
77
+ .map((s) => {
78
+ if (s.kind === 'static')
79
+ return s.value;
80
+ if (s.kind === 'dynamic')
81
+ return `[${s.value}]`;
82
+ if (s.kind === 'catchAll')
83
+ return `[...${s.value}]`;
84
+ return `[[...${s.value}]]`;
85
+ })
86
+ .join('/'));
87
+ }
88
+ /**
89
+ * Identificador estable de una página.
90
+ *
91
+ * Es la misma clave que usa el catálogo de variantes de F-016, para que las dos
92
+ * estructuras hablen del mismo objeto sin traducir nada entre ellas.
93
+ */
94
+ function pageIdFromFile(sourceFile, pagesDir = 'app/pages') {
95
+ return stripPagesDir(sourceFile, pagesDir)
96
+ .replace(EXTENSION, '')
97
+ .replace(/\//g, '__')
98
+ .replace(/\[([^\]]+)\]/g, '_$1_');
99
+ }
100
+ /** Qué clase de entrada es, según dónde vive el archivo. */
101
+ function classifyEntry(sourceFile, pagesDir = 'app/pages') {
102
+ const relative = stripPagesDir(sourceFile, pagesDir);
103
+ if (relative.startsWith('api/'))
104
+ return 'api';
105
+ if (/(^|\/)_error(\.|\/)|(^|\/)error\//.test(relative))
106
+ return 'error';
107
+ return 'page';
108
+ }
109
+ function stripPagesDir(sourceFile, pagesDir) {
110
+ const normalized = sourceFile.replace(/\\/g, '/');
111
+ const marker = pagesDir.replace(/\\/g, '/').replace(/\/$/, '') + '/';
112
+ const index = normalized.indexOf(marker);
113
+ return index === -1 ? normalized.replace(/^\//, '') : normalized.slice(index + marker.length);
114
+ }
@@ -0,0 +1,12 @@
1
+ import type { RouteEntry, RouteSegment } from './types';
2
+ /** Clave de comparación de una ruta: la especificidad de cada segmento, en orden. */
3
+ export declare function computeRank(segments: readonly RouteSegment[]): number[];
4
+ /**
5
+ * Orden en que se prueban las rutas.
6
+ *
7
+ * Se compara segmento a segmento; el primero que difiere decide. A igual
8
+ * especificidad gana la más larga —describe más de la URL— y, si aún empatan, el
9
+ * orden alfabético del patrón, para que el resultado no dependa de en qué orden
10
+ * el sistema de archivos devolvió los nombres.
11
+ */
12
+ export declare function compareEntries(a: RouteEntry, b: RouteEntry): number;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeRank = computeRank;
4
+ exports.compareEntries = compareEntries;
5
+ /**
6
+ * Cuán específico es cada tipo de segmento. Menor gana.
7
+ *
8
+ * Un literal describe una sola URL; un parámetro, todas las de su posición; un
9
+ * catch-all, todas las que empiecen igual. Ordenar por esto es lo que hace que
10
+ * `/users/nuevo` gane sobre `/users/[id]` sin que nadie declare la prioridad.
11
+ */
12
+ const KIND_RANK = {
13
+ static: 0,
14
+ dynamic: 1,
15
+ catchAll: 2,
16
+ optionalCatchAll: 3,
17
+ };
18
+ /** Clave de comparación de una ruta: la especificidad de cada segmento, en orden. */
19
+ function computeRank(segments) {
20
+ return segments.map((s) => KIND_RANK[s.kind]);
21
+ }
22
+ /**
23
+ * Orden en que se prueban las rutas.
24
+ *
25
+ * Se compara segmento a segmento; el primero que difiere decide. A igual
26
+ * especificidad gana la más larga —describe más de la URL— y, si aún empatan, el
27
+ * orden alfabético del patrón, para que el resultado no dependa de en qué orden
28
+ * el sistema de archivos devolvió los nombres.
29
+ */
30
+ function compareEntries(a, b) {
31
+ const length = Math.min(a.rank.length, b.rank.length);
32
+ for (let i = 0; i < length; i++) {
33
+ if (a.rank[i] !== b.rank[i])
34
+ return a.rank[i] - b.rank[i];
35
+ }
36
+ if (a.rank.length !== b.rank.length) {
37
+ const [corta, larga] = a.rank.length < b.rank.length ? [a, b] : [b, a];
38
+ const extra = larga.rank[corta.rank.length];
39
+ // Un catch-all opcional puede no consumir nada, así que la ruta que lo lleva
40
+ // atiende también todo lo que atiende la corta: es la más general de las dos.
41
+ // Sin esto, `/users/[id]/[[...rest]]` se probaría antes que `/users/[id]` y
42
+ // esta última quedaría inalcanzable.
43
+ if (extra === KIND_RANK.optionalCatchAll)
44
+ return corta === a ? -1 : 1;
45
+ // En cualquier otro caso, la más larga describe más de la URL.
46
+ return b.rank.length - a.rank.length;
47
+ }
48
+ return a.pattern.localeCompare(b.pattern);
49
+ }
@@ -0,0 +1,44 @@
1
+ import { Router, type RequestHandler } from 'express';
2
+ import type { ModuleLoader } from '../runtime/module-loader';
3
+ import type { RouteEntry, RouteManifest } from './types';
4
+ export interface ChainContext {
5
+ moduleLoader?: ModuleLoader;
6
+ renderOptions: {
7
+ enablePlatformDetection: boolean;
8
+ renderFunction?: (options: {
9
+ url: string;
10
+ platform: string;
11
+ req: any;
12
+ }) => Promise<string>;
13
+ viteConfig: unknown;
14
+ security: unknown;
15
+ };
16
+ }
17
+ /**
18
+ * La cadena de middlewares de una entrada.
19
+ *
20
+ * Es el cuerpo que estaba duplicado entre los dos constructores de router del
21
+ * modelo anterior: lo que hace un middleware de carpeta, `getServerSideProps` y
22
+ * el render, en ese orden. Tenerlo una vez es lo que garantiza que los dos
23
+ * caminos se comporten igual.
24
+ */
25
+ export declare function buildChain(entry: RouteEntry, ctx: ChainContext): Promise<RequestHandler[]>;
26
+ export interface RessRouterState {
27
+ manifest: RouteManifest;
28
+ chains: Map<string, RequestHandler[]>;
29
+ }
30
+ /**
31
+ * Construye el estado de ruteo: el manifest más la cadena de cada entrada.
32
+ *
33
+ * Se arma entero y se devuelve entero, de modo que reemplazarlo en desarrollo
34
+ * sea una sola asignación y ninguna petición vea una mezcla de dos versiones.
35
+ */
36
+ export declare function buildRouterState(manifest: RouteManifest, ctx: ChainContext): Promise<RessRouterState>;
37
+ /**
38
+ * El router de páginas.
39
+ *
40
+ * Un solo despachador para todas las rutas y un solo manejador de errores, que
41
+ * sabe qué ruta falló porque el despachador lo dejó en la petición. El modelo
42
+ * anterior registraba un manejador por página.
43
+ */
44
+ export declare function createPageRouter(getState: () => RessRouterState, ctx: ChainContext): Router;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildChain = buildChain;
4
+ exports.buildRouterState = buildRouterState;
5
+ exports.createPageRouter = createPageRouter;
6
+ const express_1 = require("express");
7
+ const middlewares_1 = require("../helpers/middlewares");
8
+ const render_1 = require("../render");
9
+ const dispatcher_1 = require("./dispatcher");
10
+ /**
11
+ * La cadena de middlewares de una entrada.
12
+ *
13
+ * Es el cuerpo que estaba duplicado entre los dos constructores de router del
14
+ * modelo anterior: lo que hace un middleware de carpeta, `getServerSideProps` y
15
+ * el render, en ese orden. Tenerlo una vez es lo que garantiza que los dos
16
+ * caminos se comporten igual.
17
+ */
18
+ async function buildChain(entry, ctx) {
19
+ return [
20
+ // Express reasigna `req.params` cada vez que la petición entra a un router
21
+ // anidado, así que los valores que extrajo el despachador se restauran acá,
22
+ // ya dentro de la cadena de la página.
23
+ (req, _res, next) => {
24
+ if (req.ressParams)
25
+ req.params = req.ressParams;
26
+ next();
27
+ },
28
+ // Se busca por la ubicación del archivo y no por el patrón: un grupo no
29
+ // aparece en la URL, así que `app/pages/(marketing)/about.tsx` tiene patrón
30
+ // `/about` y sus middlewares viven en `/(marketing)`. Compararlos daría
31
+ // siempre falso y la carpeta quedaría sin proteger.
32
+ ...(await (0, middlewares_1.loadPageMiddlewares)(middlewareRouteOf(entry), ctx.moduleLoader)),
33
+ await (0, middlewares_1.getServerSidePropsAsMiddleware)(entry.sourceFile, ctx.moduleLoader),
34
+ (0, render_1.createRenderMiddleware)({ route: entry.pattern, file: entry.sourceFile, pageId: entry.pageId }, ctx.renderOptions),
35
+ ];
36
+ }
37
+ /**
38
+ * La ruta con la que se buscan los middlewares de una entrada: la que describe
39
+ * dónde vive el archivo, con los grupos incluidos.
40
+ */
41
+ function middlewareRouteOf(entry) {
42
+ const relative = entry.sourceFile
43
+ .replace(/\\/g, '/')
44
+ .replace(/^.*app\/pages\//, '')
45
+ .replace(/\.[^./]+$/, '')
46
+ .replace(/\/?index$/, '');
47
+ return '/' + relative;
48
+ }
49
+ /**
50
+ * Construye el estado de ruteo: el manifest más la cadena de cada entrada.
51
+ *
52
+ * Se arma entero y se devuelve entero, de modo que reemplazarlo en desarrollo
53
+ * sea una sola asignación y ninguna petición vea una mezcla de dos versiones.
54
+ */
55
+ async function buildRouterState(manifest, ctx) {
56
+ const chains = new Map();
57
+ for (const entry of manifest.entries) {
58
+ if (entry.kind === 'api')
59
+ continue;
60
+ chains.set(entry.pageId, await buildChain(entry, ctx));
61
+ }
62
+ return { manifest, chains };
63
+ }
64
+ /**
65
+ * El router de páginas.
66
+ *
67
+ * Un solo despachador para todas las rutas y un solo manejador de errores, que
68
+ * sabe qué ruta falló porque el despachador lo dejó en la petición. El modelo
69
+ * anterior registraba un manejador por página.
70
+ */
71
+ function createPageRouter(getState, ctx) {
72
+ const router = (0, express_1.Router)();
73
+ router.use((0, dispatcher_1.createDispatcher)(getState));
74
+ router.use((err, req, res, next) => {
75
+ const entry = req.ressRoute;
76
+ if (!entry)
77
+ return next(err);
78
+ return (0, middlewares_1.createPageErrorHandler)(entry.sourceFile, ctx.moduleLoader)(err, req, res, next);
79
+ });
80
+ return router;
81
+ }
@@ -0,0 +1,16 @@
1
+ import { type RouteManifestData } from './types';
2
+ export interface ScanOptions {
3
+ pagesDir?: string;
4
+ /**
5
+ * Un conflicto en build detiene el proceso; en desarrollo avisa y sigue, para
6
+ * no dejar el servidor caído mientras alguien renombra un archivo.
7
+ */
8
+ strict?: boolean;
9
+ }
10
+ /**
11
+ * Qué rutas existen en el proyecto.
12
+ *
13
+ * Lee el sistema de archivos **una vez**. El modelo anterior lo globeaba en cada
14
+ * arranque, también en producción, donde el árbol ya no puede cambiar.
15
+ */
16
+ export declare function scanRoutes(options?: ScanOptions): Promise<RouteManifestData>;
@@ -0,0 +1,82 @@
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.scanRoutes = scanRoutes;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const fast_glob_1 = __importDefault(require("fast-glob"));
9
+ const module_extensions_1 = require("../fs/module-extensions");
10
+ const parse_1 = require("./parse");
11
+ const rank_1 = require("./rank");
12
+ const rank_2 = require("./rank");
13
+ const types_1 = require("./types");
14
+ /**
15
+ * Archivos que viven junto a las páginas pero no son rutas.
16
+ *
17
+ * `middlewares.ts` declara la cadena de su carpeta y `algo.middlewares.ts` la de
18
+ * su página; ninguno se navega. Los que empiezan con `_` son la convención para
19
+ * lo que acompaña a una página sin ser una.
20
+ */
21
+ const NOT_A_ROUTE = /(^|\/)middlewares\.[^.]+$|\.(middlewares|test|spec|d)\.[^.]+$|(^|\/)_/;
22
+ /**
23
+ * Qué rutas existen en el proyecto.
24
+ *
25
+ * Lee el sistema de archivos **una vez**. El modelo anterior lo globeaba en cada
26
+ * arranque, también en producción, donde el árbol ya no puede cambiar.
27
+ */
28
+ async function scanRoutes(options = {}) {
29
+ const pagesDir = options.pagesDir ?? 'app/pages';
30
+ // Qué extensiones cuentan lo declara la política del proyecto, no este archivo.
31
+ const files = await (0, fast_glob_1.default)((0, module_extensions_1.pageGlob)((0, module_extensions_1.getExtensionPolicy)(), pagesDir), { absolute: false });
32
+ const entries = [];
33
+ const byPattern = new Map();
34
+ const conflicts = [];
35
+ const manifestRoot = node_path_1.default.resolve(node_path_1.default.dirname(node_path_1.default.resolve(pagesDir)), '..');
36
+ for (const file of files.sort()) {
37
+ // fast-glob devuelve separadores nativos en Windows; el parser de rutas y
38
+ // los identificadores del catálogo usan `/` para que el resultado sea
39
+ // idéntico en todos los sistemas.
40
+ const normalizedFile = file.replace(/\\/g, '/');
41
+ const sourceFile = node_path_1.default.isAbsolute(pagesDir)
42
+ ? node_path_1.default.relative(manifestRoot, normalizedFile).replace(/\\/g, '/')
43
+ : normalizedFile;
44
+ // Se evalúa sobre el path relativo entero: una carpeta `_lib/` no es una
45
+ // ruta, igual que no lo es un archivo `_helper.tsx`.
46
+ const normalizedPagesDir = pagesDir.replace(/\\/g, '/');
47
+ const relative = normalizedFile.slice(normalizedFile.indexOf(normalizedPagesDir) + normalizedPagesDir.length + 1);
48
+ if (NOT_A_ROUTE.test('/' + relative))
49
+ continue;
50
+ const parsed = (0, parse_1.parseRoutePattern)(normalizedFile, normalizedPagesDir);
51
+ const entry = {
52
+ pageId: (0, parse_1.pageIdFromFile)(normalizedFile, normalizedPagesDir),
53
+ pattern: parsed.pattern,
54
+ segments: parsed.segments,
55
+ kind: (0, parse_1.classifyEntry)(normalizedFile, normalizedPagesDir),
56
+ sourceFile,
57
+ paramNames: parsed.paramNames,
58
+ groups: parsed.groups,
59
+ rank: (0, rank_1.computeRank)(parsed.segments),
60
+ server: { devId: '/' + sourceFile },
61
+ };
62
+ // Dos archivos que producen la misma URL son ambiguos: cuál gana dependería
63
+ // del orden en que el sistema de archivos los devolvió.
64
+ const clash = byPattern.get(`${entry.kind} ${entry.pattern}`);
65
+ if (clash) {
66
+ const message = `[ress] "${clash.sourceFile}" y "${file}" producen la misma ruta "${entry.pattern}". ` +
67
+ 'Renombrá uno de los dos.';
68
+ if (options.strict)
69
+ throw new Error(message);
70
+ console.warn(`${message}\nPor ahora responde "${clash.sourceFile}".`);
71
+ conflicts.push({ pattern: entry.pattern, files: [clash.sourceFile, file] });
72
+ continue;
73
+ }
74
+ byPattern.set(`${entry.kind} ${entry.pattern}`, entry);
75
+ entries.push(entry);
76
+ }
77
+ return {
78
+ version: types_1.ROUTE_MANIFEST_VERSION,
79
+ entries: entries.sort(rank_2.compareEntries),
80
+ ...(conflicts.length ? { conflicts } : {}),
81
+ };
82
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Qué rutas existen y cuál atiende cada URL.
3
+ *
4
+ * El modelo anterior derivaba la ruta del archivo en cada arranque, globeando el
5
+ * sistema de archivos también en producción, y traducía el patrón a una cadena
6
+ * de Express que no podía expresar ni catch-all ni grupos. Acá el conjunto de
7
+ * rutas es un dato: se calcula una vez, se serializa en el build y se carga.
8
+ */
9
+ export type SegmentKind = 'static' | 'dynamic' | 'catchAll' | 'optionalCatchAll';
10
+ export interface RouteSegment {
11
+ kind: SegmentKind;
12
+ /** Literal de URL para `static`; nombre del parámetro para el resto. */
13
+ value: string;
14
+ }
15
+ /** Qué es una entrada. Determina si participa del matcheo de navegación. */
16
+ export type RouteEntryKind = 'page' | 'error' | 'api';
17
+ /** Identificador estable de una página. La misma clave que usa el catálogo de variantes. */
18
+ export type PageId = string;
19
+ export interface ServerModuleRef {
20
+ /** Identificador que entiende el cargador de módulos en desarrollo. */
21
+ devId: string;
22
+ /**
23
+ * Chunk compilado, relativo a la raíz de salida del build de servidor. Lo
24
+ * completa el build; en desarrollo no existe.
25
+ */
26
+ buildChunk?: string;
27
+ }
28
+ export interface RouteEntry {
29
+ pageId: PageId;
30
+ /** Patrón canónico con notación de archivos. Ej: `/users/[id]`. */
31
+ pattern: string;
32
+ segments: RouteSegment[];
33
+ kind: RouteEntryKind;
34
+ /** Archivo fuente, relativo a la raíz del proyecto. */
35
+ sourceFile: string;
36
+ /** Nombres de parámetro, en orden de aparición. */
37
+ paramNames: string[];
38
+ /** Grupos atravesados. Informativo: no afectan la URL. */
39
+ groups: string[];
40
+ /** Clave de precedencia precalculada. */
41
+ rank: number[];
42
+ server: ServerModuleRef;
43
+ /** Sólo para `error`: qué código de estado atiende. */
44
+ statusCode?: number;
45
+ /** Sólo para `api`: métodos declarados. Ausente significa todos. */
46
+ methods?: string[];
47
+ }
48
+ export interface RouteMatch {
49
+ entry: RouteEntry;
50
+ params: Record<string, string | string[]>;
51
+ }
52
+ export interface MatchOptions {
53
+ /** Qué clases de entrada participan. Por defecto sólo `page`. */
54
+ kinds?: readonly RouteEntryKind[];
55
+ }
56
+ /** Dos archivos que compiten por el mismo patrón, detectados al escanear. */
57
+ export interface RouteConflict {
58
+ pattern: string;
59
+ files: string[];
60
+ }
61
+ /** Lo que se serializa al disco. */
62
+ export interface RouteManifestData {
63
+ /** Sube cuando el formato cambia de forma incompatible. */
64
+ version: number;
65
+ entries: RouteEntry[];
66
+ /**
67
+ * Conflictos detectados en modo no estricto (desarrollo).
68
+ *
69
+ * En modo estricto (`build`) `scanRoutes` lanza en vez de completar esta
70
+ * lista: un build no deja artefactos parciales publicables. F-033 (`ress
71
+ * info`) es quien la consume para diagnosticar sin abortar.
72
+ */
73
+ conflicts?: RouteConflict[];
74
+ }
75
+ export interface RouteManifest {
76
+ readonly entries: readonly RouteEntry[];
77
+ /** Qué entrada atiende esta URL. `undefined` si ninguna. */
78
+ match(pathname: string, options?: MatchOptions): RouteMatch | undefined;
79
+ /** Una entrada por su identificador. */
80
+ get(pageId: PageId): RouteEntry | undefined;
81
+ /** Las entradas de una clase, en orden de precedencia. */
82
+ byKind(kind: RouteEntryKind): readonly RouteEntry[];
83
+ }
84
+ /** Sube cuando el formato del manifest cambia de forma incompatible. */
85
+ export declare const ROUTE_MANIFEST_VERSION = 1;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * Qué rutas existen y cuál atiende cada URL.
4
+ *
5
+ * El modelo anterior derivaba la ruta del archivo en cada arranque, globeando el
6
+ * sistema de archivos también en producción, y traducía el patrón a una cadena
7
+ * de Express que no podía expresar ni catch-all ni grupos. Acá el conjunto de
8
+ * rutas es un dato: se calcula una vez, se serializa en el build y se carga.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.ROUTE_MANIFEST_VERSION = void 0;
12
+ /** Sube cuando el formato del manifest cambia de forma incompatible. */
13
+ exports.ROUTE_MANIFEST_VERSION = 1;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Runtime de desarrollo.
3
+ *
4
+ * Crea el servidor de Vite, resuelve el entorno de servidor y construye el cargador de
5
+ * módulos una sola vez, que es el que usa el resto del framework.
6
+ */
7
+ import { type FetchableEnvironment, type ModuleLoader } from './module-loader';
8
+ import type { AssetIndex, VariantCatalog } from '../variants/types';
9
+ import type { RouteManifest } from '../routes/types';
10
+ export interface RessRuntime {
11
+ /** Instancia de Vite. `null` en producción. */
12
+ vite: any | null;
13
+ /** Carga módulos de usuario. Único en dev y prod. */
14
+ moduleLoader: ModuleLoader;
15
+ templateHtml: string;
16
+ isProduction: boolean;
17
+ /** Qué variantes tiene cada página. Se arma una vez al arrancar. */
18
+ catalog: VariantCatalog;
19
+ /** Qué rutas existen y cuál atiende cada URL. */
20
+ routes: RouteManifest;
21
+ /** Artefactos por página y variante. */
22
+ assets: AssetIndex;
23
+ /** Archivos que emitió el build de cliente. Vacío en desarrollo. */
24
+ buildOutputs: ReadonlySet<string>;
25
+ /**
26
+ * Registra un aviso de cambio en el código de usuario. Devuelve la función
27
+ * para dejar de escuchar. En producción no se invoca nunca.
28
+ */
29
+ onSourceChange(cb: (file: string) => void): () => void;
30
+ close(): Promise<void>;
31
+ }
32
+ /**
33
+ * Elige el entorno desde el que se cargarán los módulos de usuario.
34
+ *
35
+ * Se prefiere `ssr` por nombre; si un proyecto declaró otros entornos, se cae al
36
+ * primero disponible en vez de fallar. Nunca se asume que el entorno sea del
37
+ * tipo ejecutable: el cargador se construye sobre `fetchModule()`, presente en
38
+ * todos los tipos.
39
+ */
40
+ export declare function resolveSsrEnvironment(server: any): FetchableEnvironment;
41
+ export interface DevRuntimeOptions {
42
+ /**
43
+ * Plugins de Vite a aplicar, además de los que el proyecto declare en su
44
+ * propio `vite.config.*` si lo tiene.
45
+ *
46
+ * F-033 (la CLI) los necesita: un proyecto sin `vite.config.ts` no tiene
47
+ * quién le pase `ressPlugin()` a Vite si `createDevRuntime` no acepta
48
+ * plugins inline. Antes de esta opción, `createServer` dependía por completo
49
+ * de que Vite encontrara un archivo de configuración en el directorio de
50
+ * trabajo — que es justamente lo que la CLI existe para volver innecesario.
51
+ */
52
+ plugins?: unknown[];
53
+ }
54
+ export declare function createDevRuntime(base?: string,
55
+ /**
56
+ * Prefijo con el que se emiten las URL de artefactos.
57
+ *
58
+ * Va aparte del `base` de Vite porque el prefijo de ruta lo administra el
59
+ * framework: el pipeline de petición lo quita antes de que Vite vea la URL, así
60
+ * que Vite sigue viviendo en `/` y sólo lo que se escribe en el HTML lo lleva.
61
+ */
62
+ assetPrefix?: string, options?: DevRuntimeOptions): Promise<RessRuntime>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime de desarrollo.
4
+ *
5
+ * Crea el servidor de Vite, resuelve el entorno de servidor y construye el cargador de
6
+ * módulos una sola vez, que es el que usa el resto del framework.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.resolveSsrEnvironment = resolveSsrEnvironment;
10
+ exports.createDevRuntime = createDevRuntime;
11
+ const module_loader_1 = require("./module-loader");
12
+ const assets_1 = require("../variants/assets");
13
+ const manifest_1 = require("../routes/manifest");
14
+ const scan_1 = require("../routes/scan");
15
+ const index_1 = require("../plugin/index");
16
+ const template_1 = require("./template");
17
+ /**
18
+ * Elige el entorno desde el que se cargarán los módulos de usuario.
19
+ *
20
+ * Se prefiere `ssr` por nombre; si un proyecto declaró otros entornos, se cae al
21
+ * primero disponible en vez de fallar. Nunca se asume que el entorno sea del
22
+ * tipo ejecutable: el cargador se construye sobre `fetchModule()`, presente en
23
+ * todos los tipos.
24
+ */
25
+ function resolveSsrEnvironment(server) {
26
+ const envs = server?.environments;
27
+ if (!envs) {
28
+ throw new Error('[ress] El servidor de Vite no expone environments. ' +
29
+ 'ress.js requiere Vite 8 o superior.');
30
+ }
31
+ const env = envs.ssr ?? Object.values(envs)[0];
32
+ if (!env || typeof env.fetchModule !== 'function') {
33
+ throw new Error('[ress] No se encontró un entorno de servidor utilizable. ' +
34
+ `Entornos disponibles: ${Object.keys(envs).join(', ') || '(ninguno)'}`);
35
+ }
36
+ return env;
37
+ }
38
+ async function createDevRuntime(base = '/',
39
+ /**
40
+ * Prefijo con el que se emiten las URL de artefactos.
41
+ *
42
+ * Va aparte del `base` de Vite porque el prefijo de ruta lo administra el
43
+ * framework: el pipeline de petición lo quita antes de que Vite vea la URL, así
44
+ * que Vite sigue viviendo en `/` y sólo lo que se escribe en el HTML lo lleva.
45
+ */
46
+ assetPrefix = base, options = {}) {
47
+ const { createServer } = await import('vite');
48
+ const vite = await createServer({
49
+ server: { middlewareMode: true },
50
+ appType: 'custom',
51
+ base,
52
+ plugins: options.plugins,
53
+ });
54
+ const env = resolveSsrEnvironment(vite);
55
+ const moduleLoader = await (0, module_loader_1.createDevModuleLoader)(env);
56
+ // El runner mantiene su propia caché de módulos evaluados y corre sin canal de
57
+ // HMR, así que nada la descarta por su cuenta. Conectar el watcher es lo que
58
+ // hace que editar una página o un middleware se refleje en la siguiente
59
+ // petición en vez de exigir reiniciar el servidor.
60
+ const listeners = new Set();
61
+ const onFileChange = (file) => {
62
+ moduleLoader.invalidate(file);
63
+ for (const cb of listeners)
64
+ cb(file);
65
+ };
66
+ vite.watcher.on('change', onFileChange);
67
+ vite.watcher.on('unlink', onFileChange);
68
+ const templateHtml = await (0, template_1.readTemplateHtml)();
69
+ // El plugin lo arma al resolver la configuración; si por algún motivo no
70
+ // corrió, se arma acá para no dejar el runtime sin catálogo.
71
+ const catalog = (0, index_1.getVariantCatalog)() ?? (await (0, index_1.refreshVariantCatalog)());
72
+ const routes = (0, manifest_1.createRouteManifest)(await (0, scan_1.scanRoutes)());
73
+ return {
74
+ vite,
75
+ moduleLoader,
76
+ templateHtml,
77
+ isProduction: false,
78
+ catalog,
79
+ routes,
80
+ assets: (0, assets_1.createDevAssetIndex)(catalog, assetPrefix),
81
+ // En desarrollo los sirve Vite, no el middleware de estáticos.
82
+ buildOutputs: new Set(),
83
+ onSourceChange: (cb) => {
84
+ listeners.add(cb);
85
+ return () => listeners.delete(cb);
86
+ },
87
+ close: async () => {
88
+ vite.watcher.off('change', onFileChange);
89
+ vite.watcher.off('unlink', onFileChange);
90
+ await moduleLoader.close();
91
+ await vite.close();
92
+ },
93
+ };
94
+ }