You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
7.4 KiB
208 lines
7.4 KiB
import { sep } from 'node:path';
|
|
import { mdsvex } from 'mdsvex';
|
|
import remarkBreaks from 'remark-breaks';
|
|
import { defineConfig } from 'vitest/config';
|
|
import { playwright } from '@vitest/browser-playwright';
|
|
import adapter from '@sveltejs/adapter-node';
|
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
import type { Plugin } from 'vite';
|
|
import type { PreprocessorGroup } from 'svelte/compiler';
|
|
import { conExtracto } from './src/lib/content/extracto.ts';
|
|
import { ancla } from './src/lib/content/anclas.ts';
|
|
|
|
/**
|
|
* Dos aplicaciones, un repositorio.
|
|
*
|
|
* `APP=panel` compila la administración; sin esa variable, la web pública. Cada
|
|
* una tiene su carpeta de rutas, sus hooks y su carpeta de salida, así que **el
|
|
* servidor público no lleva dentro ni una línea del panel**: no es que la
|
|
* esconda, es que ahí no existe. Cada una se sirve en su dominio y en su
|
|
* proceso, como los archivos en `m.senzapaura.es`.
|
|
*
|
|
* Comparten `src/lib`, que es donde están el esquema, las consultas y el
|
|
* acceso. Con dos repositorios habría que mantener las migraciones por
|
|
* duplicado, y el día que se desincronizaran nadie se enteraría hasta que el
|
|
* panel guardara en una columna que ya no existe.
|
|
*/
|
|
const esPanel = process.env.APP === 'panel';
|
|
const esEstudio = process.env.APP === 'estudio';
|
|
|
|
/**
|
|
* Escribe en el frontmatter de cada cancion los primeros versos de su letra.
|
|
*
|
|
* Tiene que correr ANTES que mdsvex: despues, el cuerpo ya es un componente
|
|
* compilado y el texto ha dejado de ser texto. Con esto, las tarjetas pueden
|
|
* ensenar unos versos sin que nadie mantenga a mano un campo de resumen.
|
|
*/
|
|
const inyectarExtracto: PreprocessorGroup = {
|
|
name: 'extracto-de-cancion',
|
|
markup({ content, filename }) {
|
|
// En Windows los nombres llegan con barras invertidas.
|
|
if (!filename?.split(sep).join('/').includes('/src/content/canciones/')) return;
|
|
const conFragmento = conExtracto(content);
|
|
return conFragmento === content ? undefined : { code: conFragmento };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Pone un `id` a cada encabezado de nivel 2 del Markdown.
|
|
*
|
|
* Hace falta para que el indice lateral de los textos largos pueda enlazar a
|
|
* sus apartados. Se escribe a mano en vez de traer `rehype-slug` porque son
|
|
* quince lineas, y porque el ancla tiene que salir de la misma funcion que usa
|
|
* el cargador al construir el indice: con dos implementaciones parecidas, un
|
|
* dia dejan de coincidir.
|
|
*/
|
|
type NodoHast = {
|
|
type: string;
|
|
tagName?: string;
|
|
value?: string;
|
|
properties?: Record<string, unknown>;
|
|
children?: NodoHast[];
|
|
};
|
|
|
|
function textoDe(nodo: NodoHast): string {
|
|
if (nodo.type === 'text') return nodo.value ?? '';
|
|
return (nodo.children ?? []).map(textoDe).join('');
|
|
}
|
|
|
|
function anclasEnEncabezados() {
|
|
return (arbol: NodoHast) => {
|
|
const recorrer = (nodo: NodoHast) => {
|
|
if (nodo.tagName === 'h2') {
|
|
nodo.properties = { ...nodo.properties, id: ancla(textoDe(nodo)) };
|
|
}
|
|
for (const hijo of nodo.children ?? []) recorrer(hijo);
|
|
};
|
|
recorrer(arbol);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* mdsvex 0.12 emite `<script context="module">` para exportar el frontmatter,
|
|
* sintaxis que Svelte 5 marca como obsoleta. El aviso apunta a los .md de
|
|
* contenido, donde no hay nada que corregir, y sale uno por archivo en cada
|
|
* compilacion.
|
|
*
|
|
* Se corrige la salida en lugar de silenciar el aviso: `sveltekit()` ignora
|
|
* `onwarn` cuando la configuracion viaja en el propio vite.config.ts.
|
|
*/
|
|
const corregirScriptDeMdsvex: PreprocessorGroup = {
|
|
name: 'mdsvex-script-module',
|
|
markup({ content, filename }) {
|
|
if (!filename?.endsWith('.md') && !filename?.endsWith('.svx')) return;
|
|
if (!content.includes('<script context="module">')) return;
|
|
|
|
return {
|
|
code: content.replaceAll('<script context="module">', '<script module>')
|
|
};
|
|
}
|
|
};
|
|
|
|
// Vitest ejecuta módulos y componentes, no el servidor SSR de desarrollo.
|
|
// Mantiene los aliases y módulos virtuales de Kit sin inicializar su runner
|
|
// fuera de una petición, que causaba wrapDynamicImport al arrancar las pruebas.
|
|
async function pluginsDePrueba(plugins: Promise<Plugin[]>): Promise<Plugin[]> {
|
|
const resueltos = await plugins;
|
|
if (!process.env.VITEST) return resueltos;
|
|
return resueltos.map((plugin) =>
|
|
plugin.name === 'vite-plugin-sveltekit-compile'
|
|
? { ...plugin, configureServer: undefined }
|
|
: plugin
|
|
);
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
pluginsDePrueba(
|
|
sveltekit({
|
|
compilerOptions: {
|
|
// Force runes mode for the project, except for libraries. Can be removed in svelte 6.
|
|
runes: ({ filename }) =>
|
|
filename.split(/[/\\]/).includes('node_modules') ? undefined : true
|
|
},
|
|
/*
|
|
* Lo único que distingue a las dos aplicaciones: de dónde salen las
|
|
* rutas, qué hooks corren y dónde se escribe el resultado. Los `.svelte-kit`
|
|
* también van separados, porque ahí dentro está el manifiesto de rutas
|
|
* y con uno compartido cada build borraría el del otro.
|
|
*/
|
|
files: esEstudio
|
|
? { routes: 'src/estudio/rutas', hooks: { server: 'src/estudio/hooks.server' } }
|
|
: esPanel
|
|
? { routes: 'src/panel/rutas', hooks: { server: 'src/panel/hooks.server' } }
|
|
: undefined,
|
|
outDir: esEstudio ? '.svelte-kit-estudio' : esPanel ? '.svelte-kit-panel' : '.svelte-kit',
|
|
adapter: adapter({ out: esEstudio ? 'build-estudio' : esPanel ? 'build-panel' : 'build' }),
|
|
paths: {
|
|
/*
|
|
* Rutas absolutas para los recursos —`/_app/…`— en vez de las
|
|
* relativas que SvelteKit usa por defecto.
|
|
*
|
|
* Hace falta por la página de espera: su HTML se sirve en la
|
|
* dirección que pidió quien llega, no en `/proximamente`. Con rutas
|
|
* relativas, un visitante en `/musica/baladas-romanticas` pedía sus
|
|
* hojas de estilo en `/musica/_app/…` y no encontraba ninguna.
|
|
*
|
|
* No se pierde nada: las relativas sirven para poder mover el sitio a
|
|
* un subdirectorio sin reconstruir, y este vive en la raíz de su
|
|
* dominio.
|
|
*/
|
|
relative: false
|
|
},
|
|
preprocess: [
|
|
inyectarExtracto,
|
|
mdsvex({
|
|
extensions: ['.svx', '.md'],
|
|
// Sin remark-breaks, Markdown junta los versos de una letra en un
|
|
// unico parrafo: en una cancion el salto de linea es contenido.
|
|
// mdsvex declara los plugins con una version antigua de los tipos de
|
|
// unified, incompatible con la de remark-breaks 4. En ejecucion
|
|
// encajan; lo que choca es solo la firma.
|
|
// @ts-expect-error tipos de unified desalineados entre mdsvex y remark-breaks
|
|
remarkPlugins: [remarkBreaks],
|
|
rehypePlugins: [anclasEnEncabezados]
|
|
}),
|
|
corregirScriptDeMdsvex
|
|
],
|
|
extensions: ['.svelte', '.svx', '.md'],
|
|
typescript: {
|
|
config: (config) => {
|
|
config.include.push('../drizzle.config.ts');
|
|
}
|
|
}
|
|
})
|
|
)
|
|
],
|
|
test: {
|
|
expect: { requireAssertions: true },
|
|
projects: [
|
|
{
|
|
extends: './vite.config.ts',
|
|
test: {
|
|
name: 'client',
|
|
browser: {
|
|
enabled: true,
|
|
provider: playwright(),
|
|
instances: [{ browser: 'chromium', headless: true }]
|
|
},
|
|
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
|
exclude: ['src/lib/server/**']
|
|
}
|
|
},
|
|
|
|
{
|
|
extends: './vite.config.ts',
|
|
test: {
|
|
name: 'server',
|
|
environment: 'node',
|
|
// Tambien las herramientas de `scripts/`: colocan archivos de audio
|
|
// segun una convencion de nombres, y equivocarla no da ningun error.
|
|
include: ['src/**/*.{test,spec}.{js,ts}', 'scripts/**/*.spec.mjs'],
|
|
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|