@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,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MANUAL_RESTART_KEYS = exports.RESTART_REASONS = exports.HOT_KEYS = void 0;
4
+ exports.changedKeys = changedKeys;
5
+ exports.manualRestartReason = manualRestartReason;
6
+ exports.restartReason = restartReason;
7
+ exports.isConfigLayer = isConfigLayer;
8
+ exports.watchConfig = watchConfig;
9
+ const config_1 = require("@ressjs/config");
10
+ const load_1 = require("./load");
11
+ /**
12
+ * Recarga de la configuración en desarrollo.
13
+ *
14
+ * Se observa la carpeta `config/`: cualquiera de sus capas, no sólo la del modo
15
+ * activo, porque `default` y `local` también participan de la fusión.
16
+ */
17
+ /**
18
+ * Claves que se pueden aplicar sin reiniciar.
19
+ *
20
+ * Todas se consultan al atender una petición, así que la siguiente ya ve el
21
+ * valor nuevo.
22
+ */
23
+ // `security` no está acá: `createViteRouter`/`ressCorePlugin` la resuelven una
24
+ // sola vez, dentro de `setup()`/el hook `config`, así que un cambio en caliente
25
+ // de `config/*.js` no la vuelve a completar. Está en `RESTART_REASONS`.
26
+ exports.HOT_KEYS = ['redirects', 'rewrites', 'headers', 'trailingSlash'];
27
+ /**
28
+ * Claves que exigen reiniciar, y por qué.
29
+ *
30
+ * El framework lo dice nombrando la clave en vez de aplicar la mitad de la
31
+ * configuración: media configuración aplicada es un estado que nadie declaró y
32
+ * que produce errores que no se corresponden con ningún archivo.
33
+ */
34
+ exports.RESTART_REASONS = {
35
+ basePath: 'cambia la URL bajo la que se sirve todo, incluidos los artefactos ya emitidos',
36
+ extensions: 'cambia qué archivos son páginas, y eso redefine el catálogo de entradas',
37
+ platform: 'redefine los ejes de plataforma, y con ellos las variantes que se construyen',
38
+ publicEnv: 'las variables se reemplazan en tiempo de build, no al atender una petición',
39
+ assets: 'el índice de artefactos se arma una vez, con el prefijo vigente al arrancar',
40
+ security: 'las cabeceras, la política de contenido y los límites se resuelven una vez al armar el router',
41
+ isr: 'la caché incremental se crea una vez, con sus límites y su secreto de vista previa',
42
+ // F-033: `beforeRouter`/`afterRouter` se montan una vez, al construir la
43
+ // aplicación de servidor. Tratar su cambio como aplicable en caliente
44
+ // dejaría un "recargado" en el log sin ningún efecto real.
45
+ server: 'los middlewares de la vía de escape se montan una sola vez, al construir el servidor',
46
+ };
47
+ /**
48
+ * Claves que se leen al construir la aplicación de servidor de ress —el
49
+ * router, la caché incremental, los middlewares de la vía de escape—, no el
50
+ * servidor de Vite.
51
+ *
52
+ * Reiniciar Vite no las aplica: la CLI arma esa aplicación una sola vez, fuera
53
+ * de Vite. Para ellas no se reinicia nada ni se dice que se reinició: se pide
54
+ * detener y volver a arrancar `ress dev`.
55
+ */
56
+ exports.MANUAL_RESTART_KEYS = new Set([
57
+ 'basePath',
58
+ 'assets',
59
+ 'security',
60
+ 'isr',
61
+ 'server',
62
+ ]);
63
+ /** Qué claves de primer nivel cambiaron entre dos configuraciones resueltas. */
64
+ function changedKeys(previous, next) {
65
+ const keys = new Set([...Object.keys(previous), ...Object.keys(next)]);
66
+ const changed = [];
67
+ for (const key of keys) {
68
+ const a = stable(previous[key]);
69
+ const b = stable(next[key]);
70
+ if (a !== b)
71
+ changed.push(key);
72
+ }
73
+ return changed.sort();
74
+ }
75
+ /**
76
+ * Serialización estable para comparar.
77
+ *
78
+ * Las claves de un objeto se ordenan: dos configuraciones equivalentes escritas
79
+ * en distinto orden no son un cambio, y tratarlas como tal reiniciaría el
80
+ * servidor por nada.
81
+ */
82
+ function stable(value) {
83
+ if (typeof value === 'function')
84
+ return `fn:${String(value)}`;
85
+ if (value === undefined)
86
+ return 'undefined';
87
+ return JSON.stringify(value, (_key, inner) => {
88
+ if (typeof inner === 'function')
89
+ return `fn:${String(inner)}`;
90
+ if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
91
+ return Object.fromEntries(Object.entries(inner).sort(([a], [b]) => a.localeCompare(b)));
92
+ }
93
+ return inner;
94
+ });
95
+ }
96
+ /** Si alguna de las claves cambiadas exige volver a arrancar `ress dev` a mano. */
97
+ function manualRestartReason(changed) {
98
+ for (const key of changed) {
99
+ if (exports.MANUAL_RESTART_KEYS.has(key))
100
+ return `\`${key}\`: ${exports.RESTART_REASONS[key]}`;
101
+ }
102
+ return undefined;
103
+ }
104
+ /** El motivo del reinicio, o nada si todo lo que cambió se aplica en caliente. */
105
+ function restartReason(changed) {
106
+ for (const key of changed) {
107
+ const reason = exports.RESTART_REASONS[key];
108
+ if (reason)
109
+ return `\`${key}\`: ${reason}`;
110
+ }
111
+ return undefined;
112
+ }
113
+ /** ¿Este archivo es una capa de configuración del proyecto? */
114
+ function isConfigLayer(file, root) {
115
+ const normalized = file.replace(/\\/g, '/');
116
+ const base = root.replace(/\\/g, '/').replace(/\/+$/, '');
117
+ if (!normalized.startsWith(base + '/'))
118
+ return false;
119
+ const relative = normalized.slice(base.length + 1);
120
+ return /^config\/[^/]+$/.test(relative) || /^config\.[^/]+$/.test(relative);
121
+ }
122
+ /** Conecta el observador de Vite con la carpeta de configuración. */
123
+ function watchConfig(server, options) {
124
+ const log = options.log ?? ((message) => console.log(message));
125
+ const restart = options.onRestart ??
126
+ ((reason) => {
127
+ log(`[ress] Reiniciando: ${reason}`);
128
+ void server?.restart?.();
129
+ });
130
+ const onChange = async (file) => {
131
+ if (!isConfigLayer(file, options.root))
132
+ return;
133
+ // Lo resuelto se descarta entero: la fusión depende de todas las capas, así
134
+ // que un cambio en cualquiera de ellas invalida el resultado.
135
+ (0, config_1.invalidateConfig)();
136
+ let next;
137
+ try {
138
+ next = await (0, load_1.loadConfig)(options.root, options.mode);
139
+ }
140
+ catch (error) {
141
+ // Un archivo a medio escribir no tiene que voltear el servidor: se informa
142
+ // y se sigue con la configuración anterior hasta que vuelva a guardarse.
143
+ log(`[ress] La configuración no se pudo cargar: ${error.message}`);
144
+ return;
145
+ }
146
+ const changed = changedKeys(options.current(), next);
147
+ if (changed.length === 0)
148
+ return;
149
+ // Lo que no aplica ni Vite: se dice qué hacer en vez de fingir un reinicio.
150
+ const manual = manualRestartReason(changed);
151
+ if (manual) {
152
+ return log(`[ress] Cambió ${manual}. Detené y volvé a arrancar \`ress dev\` para aplicarlo.`);
153
+ }
154
+ const reason = restartReason(changed);
155
+ if (reason)
156
+ return restart(reason);
157
+ options.onReload(next);
158
+ log(`[ress] Configuración recargada: ${changed.join(', ')}`);
159
+ };
160
+ for (const event of ['add', 'change', 'unlink']) {
161
+ server?.watcher?.on(event, (file) => void onChange(file));
162
+ }
163
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Qué extensiones acepta el framework, declarado una sola vez.
3
+ *
4
+ * Antes cada punto del pipeline tenía su propia idea: el descubrimiento globeaba
5
+ * `.tsx` y `.jsx`, el render exigía `.tsx` y lanzaba con cualquier otra cosa, y
6
+ * la carga en producción ramificaba por extensión. El resultado era que un
7
+ * proyecto en JavaScript descubría sus páginas y reventaba al renderizarlas.
8
+ *
9
+ * Ninguna función de este módulo toca el sistema de archivos: son puras sobre
10
+ * cadenas, y por eso se pueden probar sin montar un proyecto.
11
+ */
12
+ export declare const SCRIPT_EXTENSIONS: readonly [".tsx", ".ts", ".jsx", ".js"];
13
+ export type ScriptExtension = (typeof SCRIPT_EXTENSIONS)[number];
14
+ export interface ExtensionPolicy {
15
+ /** Extensiones válidas para un archivo de página. */
16
+ page: readonly ScriptExtension[];
17
+ /** Extensiones válidas para un archivo de middleware. */
18
+ middleware: readonly ScriptExtension[];
19
+ /** Extensión que produce el build. Siempre una sola. */
20
+ compiled: '.js';
21
+ /** Sufijos que se ignoran aunque la extensión coincida. */
22
+ ignoredSuffixes: readonly string[];
23
+ }
24
+ export declare const DEFAULT_EXTENSION_POLICY: ExtensionPolicy;
25
+ export interface PageExclusion {
26
+ /** Identifica la convención que reservó el nombre, para diagnósticos. */
27
+ owner: string;
28
+ /** Se evalúa sobre la ruta relativa a pages, sin extensión. */
29
+ matches(relativePath: string): boolean;
30
+ }
31
+ /**
32
+ * Convenciones de archivos que pueden vivir bajo `app/pages` sin ser páginas.
33
+ *
34
+ * Este es el único registro: tanto el descubrimiento legado como el manifest
35
+ * consultan `isPageFile`, de modo que ampliar las extensiones no convierte un
36
+ * middleware, test o auxiliar en una ruta accidental.
37
+ */
38
+ export declare const DEFAULT_EXCLUSIONS: readonly PageExclusion[];
39
+ /** `['.tsx','.ts']` → `{tsx,ts}`, la forma que entiende un glob. */
40
+ export declare function globBrace(extensions: readonly ScriptExtension[]): string;
41
+ export declare function pageGlob(policy: ExtensionPolicy, pagesDir?: string): string;
42
+ export declare function middlewareGlobs(policy: ExtensionPolicy, pagesDir?: string): string[];
43
+ /**
44
+ * La extensión de un archivo, si es una de las que el framework reconoce.
45
+ *
46
+ * Se compara en minúsculas: en macOS y Windows un `Index.TSX` es el mismo
47
+ * archivo, y rechazarlo por la capitalización sería un fallo incomprensible.
48
+ */
49
+ export declare function scriptExtensionOf(file: string): ScriptExtension | undefined;
50
+ export declare function isIgnoredModule(file: string, policy?: ExtensionPolicy): boolean;
51
+ /**
52
+ * El archivo sin su extensión.
53
+ *
54
+ * Lanza en vez de devolver el path intacto: un archivo que llegó hasta acá sin
55
+ * una extensión reconocida es un error de configuración, y devolverlo tal cual
56
+ * lo convertiría en un fallo más adelante y más difícil de ubicar.
57
+ */
58
+ export declare function stripScriptExtension(file: string): string;
59
+ /**
60
+ * Archivos que viven junto a las páginas sin ser una.
61
+ *
62
+ * Hace falta desde que la política acepta `.ts` y `.js`: un archivo suelto ahí
63
+ * es casi siempre un ayudante —un cliente de API, un tipo compartido, un test— y
64
+ * tomarlo por una ruta produce una URL que existe y falla al renderizar, porque
65
+ * no exporta ningún componente.
66
+ */
67
+ export declare function pageExclusionOf(file: string, pagesDir?: string, exclusions?: readonly PageExclusion[]): PageExclusion | undefined;
68
+ export declare function isPageFile(file: string, policy?: ExtensionPolicy, pagesDir?: string, exclusions?: readonly PageExclusion[]): boolean;
69
+ /**
70
+ * La ruta que un archivo de middleware protege.
71
+ *
72
+ * `pokedex/middlewares.tsx` protege `/pokedex`; `home/index.middlewares.js`
73
+ * protege `/home/index`. Antes esto se derivaba con una expresión que sólo
74
+ * recortaba `.ts` y `.js`, así que un middleware `.tsx` producía una ruta con la
75
+ * extensión adentro y no coincidía con ninguna página: no corría, y sin aviso.
76
+ */
77
+ export declare function middlewareRoute(file: string, pagesDir?: string): string;
78
+ export declare function isMiddlewareFile(file: string, policy?: ExtensionPolicy): boolean;
79
+ /**
80
+ * Identificador de una página, sin su extensión ni el prefijo del directorio.
81
+ *
82
+ * Es la clave que comparten el catálogo de variantes y el manifest de rutas, así
83
+ * que dos archivos que sólo difieren en la extensión producen el mismo: es lo
84
+ * que permite detectar que ambos existen.
85
+ */
86
+ export declare function pageIdOf(file: string, pagesDir?: string): string;
87
+ /**
88
+ * Dónde queda un módulo después de compilarse.
89
+ *
90
+ * El build emite siempre `.js`, cualquiera sea la extensión de origen, y sanea
91
+ * los corchetes de los parámetros porque no pueden formar parte de un nombre de
92
+ * archivo en todos los sistemas.
93
+ */
94
+ export declare function toCompiledPath(file: string, policy?: ExtensionPolicy, pagesDir?: string): string;
95
+ /**
96
+ * Dos archivos que producen la misma página son ambiguos.
97
+ *
98
+ * Cuál gana dependería del orden en que el sistema de archivos los devolvió, y
99
+ * ese orden cambia entre sistemas. Mejor fallar al arrancar nombrando los dos.
100
+ */
101
+ export declare function assertNoModuleCollision(files: readonly string[], pagesDir?: string, options?: {
102
+ strict?: boolean;
103
+ }): void;
104
+ export declare function setExtensionPolicy(policy: ExtensionPolicy): void;
105
+ export declare function getExtensionPolicy(): ExtensionPolicy;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ /**
3
+ * Qué extensiones acepta el framework, declarado una sola vez.
4
+ *
5
+ * Antes cada punto del pipeline tenía su propia idea: el descubrimiento globeaba
6
+ * `.tsx` y `.jsx`, el render exigía `.tsx` y lanzaba con cualquier otra cosa, y
7
+ * la carga en producción ramificaba por extensión. El resultado era que un
8
+ * proyecto en JavaScript descubría sus páginas y reventaba al renderizarlas.
9
+ *
10
+ * Ninguna función de este módulo toca el sistema de archivos: son puras sobre
11
+ * cadenas, y por eso se pueden probar sin montar un proyecto.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.DEFAULT_EXCLUSIONS = exports.DEFAULT_EXTENSION_POLICY = exports.SCRIPT_EXTENSIONS = void 0;
15
+ exports.globBrace = globBrace;
16
+ exports.pageGlob = pageGlob;
17
+ exports.middlewareGlobs = middlewareGlobs;
18
+ exports.scriptExtensionOf = scriptExtensionOf;
19
+ exports.isIgnoredModule = isIgnoredModule;
20
+ exports.stripScriptExtension = stripScriptExtension;
21
+ exports.pageExclusionOf = pageExclusionOf;
22
+ exports.isPageFile = isPageFile;
23
+ exports.middlewareRoute = middlewareRoute;
24
+ exports.isMiddlewareFile = isMiddlewareFile;
25
+ exports.pageIdOf = pageIdOf;
26
+ exports.toCompiledPath = toCompiledPath;
27
+ exports.assertNoModuleCollision = assertNoModuleCollision;
28
+ exports.setExtensionPolicy = setExtensionPolicy;
29
+ exports.getExtensionPolicy = getExtensionPolicy;
30
+ exports.SCRIPT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
31
+ exports.DEFAULT_EXTENSION_POLICY = {
32
+ page: exports.SCRIPT_EXTENSIONS,
33
+ middleware: exports.SCRIPT_EXTENSIONS,
34
+ compiled: '.js',
35
+ // Un `.d.ts` declara tipos: nunca es un módulo que se pueda ejecutar.
36
+ ignoredSuffixes: ['.d.ts'],
37
+ };
38
+ /**
39
+ * Convenciones de archivos que pueden vivir bajo `app/pages` sin ser páginas.
40
+ *
41
+ * Este es el único registro: tanto el descubrimiento legado como el manifest
42
+ * consultan `isPageFile`, de modo que ampliar las extensiones no convierte un
43
+ * middleware, test o auxiliar en una ruta accidental.
44
+ */
45
+ exports.DEFAULT_EXCLUSIONS = [
46
+ { owner: 'middlewares', matches: (relative) => /(^|\/)middlewares$/.test(relative) },
47
+ { owner: 'middlewares', matches: (relative) => /\.middlewares$/.test(relative) },
48
+ { owner: 'auxiliar', matches: (relative) => /(^|\/)_[^/]*/.test(relative) },
49
+ { owner: 'test', matches: (relative) => /\.(?:test|spec)$/.test(relative) },
50
+ ];
51
+ /** `['.tsx','.ts']` → `{tsx,ts}`, la forma que entiende un glob. */
52
+ function globBrace(extensions) {
53
+ if (extensions.length === 0) {
54
+ throw new Error('[ress] La política de extensiones quedó vacía. ' +
55
+ `Declará al menos una de: ${exports.SCRIPT_EXTENSIONS.join(', ')}.`);
56
+ }
57
+ const bare = extensions.map((e) => e.slice(1));
58
+ return bare.length === 1 ? bare[0] : `{${bare.join(',')}}`;
59
+ }
60
+ function pageGlob(policy, pagesDir = 'app/pages') {
61
+ return `${pagesDir.replace(/\\/g, '/')}/**/*.${globBrace(policy.page)}`;
62
+ }
63
+ function middlewareGlobs(policy, pagesDir = 'app/pages') {
64
+ const ext = globBrace(policy.middleware);
65
+ const normalizedPagesDir = pagesDir.replace(/\\/g, '/');
66
+ return [`${normalizedPagesDir}/**/middlewares.${ext}`, `${normalizedPagesDir}/**/*.middlewares.${ext}`];
67
+ }
68
+ /**
69
+ * La extensión de un archivo, si es una de las que el framework reconoce.
70
+ *
71
+ * Se compara en minúsculas: en macOS y Windows un `Index.TSX` es el mismo
72
+ * archivo, y rechazarlo por la capitalización sería un fallo incomprensible.
73
+ */
74
+ function scriptExtensionOf(file) {
75
+ const lower = file.toLowerCase();
76
+ // De más larga a más corta: `.tsx` termina en `x`, no en `.ts`, pero el orden
77
+ // deja explícito que la comparación no es ambigua.
78
+ return exports.SCRIPT_EXTENSIONS.find((ext) => lower.endsWith(ext));
79
+ }
80
+ function isIgnoredModule(file, policy = exports.DEFAULT_EXTENSION_POLICY) {
81
+ const lower = file.toLowerCase();
82
+ return policy.ignoredSuffixes.some((suffix) => lower.endsWith(suffix.toLowerCase()));
83
+ }
84
+ /**
85
+ * El archivo sin su extensión.
86
+ *
87
+ * Lanza en vez de devolver el path intacto: un archivo que llegó hasta acá sin
88
+ * una extensión reconocida es un error de configuración, y devolverlo tal cual
89
+ * lo convertiría en un fallo más adelante y más difícil de ubicar.
90
+ */
91
+ function stripScriptExtension(file) {
92
+ const ext = scriptExtensionOf(file);
93
+ if (!ext) {
94
+ throw new Error(`[ress] "${file}" no tiene una extensión que el framework reconozca. ` +
95
+ `Válidas: ${exports.SCRIPT_EXTENSIONS.join(', ')}.`);
96
+ }
97
+ return file.slice(0, -ext.length);
98
+ }
99
+ /**
100
+ * Archivos que viven junto a las páginas sin ser una.
101
+ *
102
+ * Hace falta desde que la política acepta `.ts` y `.js`: un archivo suelto ahí
103
+ * es casi siempre un ayudante —un cliente de API, un tipo compartido, un test— y
104
+ * tomarlo por una ruta produce una URL que existe y falla al renderizar, porque
105
+ * no exporta ningún componente.
106
+ */
107
+ function pageExclusionOf(file, pagesDir = 'app/pages', exclusions = exports.DEFAULT_EXCLUSIONS) {
108
+ const relative = stripScriptExtension(relativeToPages(file, pagesDir));
109
+ return exclusions.find((exclusion) => exclusion.matches(relative));
110
+ }
111
+ function isPageFile(file, policy = exports.DEFAULT_EXTENSION_POLICY, pagesDir = 'app/pages', exclusions = exports.DEFAULT_EXCLUSIONS) {
112
+ if (isIgnoredModule(file, policy))
113
+ return false;
114
+ const ext = scriptExtensionOf(file);
115
+ if (!ext || !policy.page.includes(ext))
116
+ return false;
117
+ return pageExclusionOf(file, pagesDir, exclusions) === undefined;
118
+ }
119
+ /**
120
+ * La ruta que un archivo de middleware protege.
121
+ *
122
+ * `pokedex/middlewares.tsx` protege `/pokedex`; `home/index.middlewares.js`
123
+ * protege `/home/index`. Antes esto se derivaba con una expresión que sólo
124
+ * recortaba `.ts` y `.js`, así que un middleware `.tsx` producía una ruta con la
125
+ * extensión adentro y no coincidía con ninguna página: no corría, y sin aviso.
126
+ */
127
+ function middlewareRoute(file, pagesDir = 'app/pages') {
128
+ const relative = stripScriptExtension(relativeToPages(file, pagesDir));
129
+ return '/' + relative.replace(/\.middlewares$/, '').replace(/\/?middlewares$/, '');
130
+ }
131
+ function isMiddlewareFile(file, policy = exports.DEFAULT_EXTENSION_POLICY) {
132
+ if (isIgnoredModule(file, policy))
133
+ return false;
134
+ const ext = scriptExtensionOf(file);
135
+ if (!ext || !policy.middleware.includes(ext))
136
+ return false;
137
+ const name = normalize(file).split('/').pop() ?? '';
138
+ return name.startsWith('middlewares.') || name.includes('.middlewares.');
139
+ }
140
+ /**
141
+ * Identificador de una página, sin su extensión ni el prefijo del directorio.
142
+ *
143
+ * Es la clave que comparten el catálogo de variantes y el manifest de rutas, así
144
+ * que dos archivos que sólo difieren en la extensión producen el mismo: es lo
145
+ * que permite detectar que ambos existen.
146
+ */
147
+ function pageIdOf(file, pagesDir = 'app/pages') {
148
+ const relative = relativeToPages(file, pagesDir);
149
+ return stripScriptExtension(relative);
150
+ }
151
+ /**
152
+ * Dónde queda un módulo después de compilarse.
153
+ *
154
+ * El build emite siempre `.js`, cualquiera sea la extensión de origen, y sanea
155
+ * los corchetes de los parámetros porque no pueden formar parte de un nombre de
156
+ * archivo en todos los sistemas.
157
+ */
158
+ function toCompiledPath(file, policy = exports.DEFAULT_EXTENSION_POLICY, pagesDir = 'app/pages') {
159
+ // `stripScriptExtension` lanza si no reconoce la extensión, que es lo correcto:
160
+ // devolver el nombre intacto producía `foo.mjs` → `foo.mjs.js`, un artefacto
161
+ // que no existe y cuyo fallo aparece recién al importarlo en producción.
162
+ const relative = stripScriptExtension(relativeToPages(file, pagesDir));
163
+ return relative.replace(/\[([^\]]+)\]/g, '_$1_') + policy.compiled;
164
+ }
165
+ /**
166
+ * Dos archivos que producen la misma página son ambiguos.
167
+ *
168
+ * Cuál gana dependería del orden en que el sistema de archivos los devolvió, y
169
+ * ese orden cambia entre sistemas. Mejor fallar al arrancar nombrando los dos.
170
+ */
171
+ function assertNoModuleCollision(files, pagesDir = 'app/pages', options = {}) {
172
+ const byId = new Map();
173
+ for (const file of files) {
174
+ const id = pageIdOf(file, pagesDir);
175
+ const previous = byId.get(id);
176
+ if (previous) {
177
+ const message = `[ress] "${previous}" y "${file}" definen la misma página. ` +
178
+ 'Cuál gana dependería del orden del sistema de archivos, que cambia entre ' +
179
+ 'sistemas operativos. Dejá uno solo.';
180
+ // Al construir es un error; en desarrollo se avisa y se sigue con el
181
+ // primero, para no dejar el servidor caído mientras alguien renombra.
182
+ if (options.strict)
183
+ throw new Error(message);
184
+ console.warn(`${message}\nPor ahora responde "${previous}".`);
185
+ continue;
186
+ }
187
+ byId.set(id, file);
188
+ }
189
+ }
190
+ /** Los separadores de Windows se normalizan: el resto del framework usa `/`. */
191
+ function normalize(file) {
192
+ return file.replace(/\\/g, '/');
193
+ }
194
+ function relativeToPages(file, pagesDir) {
195
+ const normalized = normalize(file);
196
+ const marker = normalize(pagesDir).replace(/\/$/, '') + '/';
197
+ const index = normalized.indexOf(marker);
198
+ return index === -1 ? normalized.replace(/^\//, '') : normalized.slice(index + marker.length);
199
+ }
200
+ /**
201
+ * Política vigente del proceso.
202
+ *
203
+ * El descubrimiento corre en varios lugares que no reciben opciones —el
204
+ * catálogo, el escaneo de rutas, la carga de middlewares—, así que el plugin la
205
+ * fija una vez al resolver la configuración y todos leen la misma.
206
+ */
207
+ let current = exports.DEFAULT_EXTENSION_POLICY;
208
+ function setExtensionPolicy(policy) {
209
+ current = policy;
210
+ }
211
+ function getExtensionPolicy() {
212
+ return current;
213
+ }
@@ -0,0 +1,5 @@
1
+ import type { PlatformInfo } from '../platform';
2
+ import type { CollectedHeadTag, PageConfig, ResolvedPageHead, RobotsConfig } from './types';
3
+ export declare function resolvePageHead(pageConfig: PageConfig | undefined, collected?: readonly CollectedHeadTag[], platform?: PlatformInfo | null): ResolvedPageHead;
4
+ export declare function robotsContent(value: RobotsConfig | undefined): string | undefined;
5
+ export declare function deduplicateHeadTags(tags: readonly CollectedHeadTag[]): CollectedHeadTag[];
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePageHead = resolvePageHead;
4
+ exports.robotsContent = robotsContent;
5
+ exports.deduplicateHeadTags = deduplicateHeadTags;
6
+ const platform_tokens_1 = require("../variants/platform-tokens");
7
+ const resolve_1 = require("../variants/resolve");
8
+ const suffix_1 = require("../variants/suffix");
9
+ function resolvePageHead(pageConfig, collected = [], platform) {
10
+ const config = resolvePageConfigVariant(pageConfig, platform);
11
+ const tags = deduplicateHeadTags([...configToTags(config), ...collected]);
12
+ const robotsTag = [...tags].reverse().find((tag) => tag.type === 'meta' && String(tag.attrs.name ?? '').toLowerCase() === 'robots');
13
+ const robots = sanitizeRobotsValue(robotsTag?.attrs.content);
14
+ const normalizedTags = robotsTag && robots
15
+ ? tags.map((tag) => tag === robotsTag
16
+ ? { ...tag, attrs: { ...tag.attrs, content: robots } }
17
+ : tag)
18
+ : tags;
19
+ return {
20
+ tags: orderHeadTags(normalizedTags),
21
+ htmlAttributes: config.htmlAttributes ?? {},
22
+ bodyAttributes: config.bodyAttributes ?? {},
23
+ ...(robots ? { robots } : {}),
24
+ };
25
+ }
26
+ function robotsContent(value) {
27
+ if (typeof value === 'string')
28
+ return sanitizeRobotsValue(value);
29
+ if (!value)
30
+ return undefined;
31
+ const directives = [];
32
+ if (value.index !== undefined)
33
+ directives.push(value.index ? 'index' : 'noindex');
34
+ if (value.follow !== undefined)
35
+ directives.push(value.follow ? 'follow' : 'nofollow');
36
+ if (value.noarchive)
37
+ directives.push('noarchive');
38
+ if (value.nosnippet)
39
+ directives.push('nosnippet');
40
+ if (value.noimageindex)
41
+ directives.push('noimageindex');
42
+ if (value.notranslate)
43
+ directives.push('notranslate');
44
+ return directives.length ? directives.join(', ') : undefined;
45
+ }
46
+ function sanitizeRobotsValue(value) {
47
+ if (value == null)
48
+ return undefined;
49
+ const normalized = String(value).replace(/[\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim();
50
+ return normalized || undefined;
51
+ }
52
+ function resolvePageConfigVariant(pageConfig, platform) {
53
+ if (!pageConfig)
54
+ return {};
55
+ const { variants, ...base } = pageConfig;
56
+ if (!platform || !variants || Object.keys(variants).length === 0)
57
+ return base;
58
+ const available = [
59
+ { suffix: '', tags: [] },
60
+ ...Object.keys(variants).sort().map((suffix) => ({
61
+ suffix,
62
+ tags: (0, suffix_1.parseVariantSuffix)(suffix, platform_tokens_1.defaultPlatformRegistry),
63
+ })),
64
+ ];
65
+ const selected = (0, resolve_1.resolveVariant)(platform, available, platform_tokens_1.defaultPlatformRegistry);
66
+ return selected.suffix ? mergeConfig(base, variants[selected.suffix] ?? {}) : base;
67
+ }
68
+ function mergeConfig(base, variant) {
69
+ return {
70
+ ...base,
71
+ ...variant,
72
+ htmlAttributes: { ...base.htmlAttributes, ...variant.htmlAttributes },
73
+ bodyAttributes: { ...base.bodyAttributes, ...variant.bodyAttributes },
74
+ head: {
75
+ ...base.head,
76
+ ...variant.head,
77
+ meta: [...(base.head?.meta ?? []), ...(variant.head?.meta ?? [])],
78
+ link: [...(base.head?.link ?? []), ...(variant.head?.link ?? [])],
79
+ script: [...(base.head?.script ?? []), ...(variant.head?.script ?? [])],
80
+ style: [...(base.head?.style ?? []), ...(variant.head?.style ?? [])],
81
+ noscript: [...(base.head?.noscript ?? []), ...(variant.head?.noscript ?? [])],
82
+ },
83
+ };
84
+ }
85
+ function configToTags(config) {
86
+ const head = config.head;
87
+ if (!head)
88
+ return [];
89
+ const tags = [];
90
+ const robots = robotsContent(head.robots);
91
+ if (head.title !== undefined)
92
+ tags.push({ type: 'title', attrs: {}, content: head.title });
93
+ if (robots)
94
+ tags.push({ type: 'meta', attrs: { name: 'robots', content: robots } });
95
+ for (const attrs of head.meta ?? [])
96
+ tags.push({ type: 'meta', attrs });
97
+ for (const attrs of head.link ?? [])
98
+ tags.push({ type: 'link', attrs });
99
+ for (const { content, ...attrs } of head.script ?? [])
100
+ tags.push({ type: 'script', attrs, content });
101
+ for (const { content, ...attrs } of head.style ?? [])
102
+ tags.push({ type: 'style', attrs, content });
103
+ if (head.base)
104
+ tags.push({ type: 'base', attrs: head.base });
105
+ for (const item of head.noscript ?? [])
106
+ tags.push({ type: 'noscript', attrs: {}, content: item.content });
107
+ return tags;
108
+ }
109
+ function deduplicateHeadTags(tags) {
110
+ const output = [];
111
+ const positions = new Map();
112
+ for (const tag of tags) {
113
+ const key = dedupKey(tag);
114
+ if (!key) {
115
+ output.push(tag);
116
+ continue;
117
+ }
118
+ const previous = positions.get(key);
119
+ if (previous === undefined) {
120
+ positions.set(key, output.length);
121
+ output.push(tag);
122
+ }
123
+ else {
124
+ output[previous] = tag;
125
+ }
126
+ }
127
+ return output;
128
+ }
129
+ function dedupKey(tag) {
130
+ if (tag.type === 'title' || tag.type === 'base')
131
+ return tag.type;
132
+ if (tag.type === 'meta') {
133
+ if (tag.attrs.name != null)
134
+ return `meta:name:${String(tag.attrs.name).toLowerCase()}`;
135
+ if (tag.attrs.property != null)
136
+ return `meta:property:${String(tag.attrs.property).toLowerCase()}`;
137
+ if (tag.attrs.charset != null || tag.attrs.charSet != null)
138
+ return 'meta:charset';
139
+ }
140
+ if (tag.type === 'script' && tag.attrs.src != null)
141
+ return `script:${String(tag.attrs.src)}`;
142
+ if (tag.type === 'link' && String(tag.attrs.rel).toLowerCase() === 'canonical')
143
+ return 'link:canonical';
144
+ return undefined;
145
+ }
146
+ function orderHeadTags(tags) {
147
+ return tags
148
+ .map((tag, index) => ({ tag, index, rank: tagRank(tag) }))
149
+ .sort((a, b) => a.rank - b.rank || a.index - b.index)
150
+ .map(({ tag }) => tag);
151
+ }
152
+ function tagRank(tag) {
153
+ if (tag.type === 'meta' && (tag.attrs.charset != null || tag.attrs.charSet != null))
154
+ return 0;
155
+ if (tag.type === 'meta' && String(tag.attrs.name).toLowerCase() === 'viewport')
156
+ return 1;
157
+ if (tag.type === 'link' && ['preload', 'modulepreload'].includes(String(tag.attrs.rel).toLowerCase()))
158
+ return 2;
159
+ if (tag.type === 'link' && String(tag.attrs.rel).toLowerCase() === 'stylesheet')
160
+ return 3;
161
+ if (tag.type === 'title')
162
+ return 4;
163
+ if (tag.type === 'meta')
164
+ return 5;
165
+ return 6;
166
+ }