nuxt.config.ts
Rama main · réplica de lectura del repositorio original.
import tailwindcss from '@tailwindcss/vite'
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { LOUIS_ENV_BINDINGS, louisRuntimeConfigDefaults } from './shared/louis-env.mjs'
const packageJson = JSON.parse(
readFileSync(fileURLToPath(new URL('./package.json', import.meta.url)), 'utf8'),
) as { version?: string }
const louisEnv = louisRuntimeConfigDefaults()
/** Injected into nitro.mjs immediately before _sharedRuntimeConfig is frozen. */
const louisEnvAliasPreamble = `(()=>{try{const b=${JSON.stringify(
LOUIS_ENV_BINDINGS.map(({ louis, nuxt }) => [louis, nuxt]),
)};for(const[l,n]of b){const v=process.env[l];if(v!=null&&String(v).trim()!=="")process.env[n]=String(v).trim()}}catch(_){}})();\n`
/**
* Nitro emits the server bundle under chunks/nitro/ on macOS/Linux, but often
* chunks/_/ on Windows (Rollup getChunkName path-separator mismatch).
*/
function resolveNitroBundlePath(outputDir: string): string | null {
const chunksDir = join(outputDir, 'server/chunks')
const preferred = [
join(chunksDir, 'nitro/nitro.mjs'),
join(chunksDir, '_/nitro.mjs'),
]
for (const candidate of preferred) {
if (existsSync(candidate)) return candidate
}
if (!existsSync(chunksDir)) return null
for (const entry of readdirSync(chunksDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue
const candidate = join(chunksDir, entry.name, 'nitro.mjs')
if (existsSync(candidate)) return candidate
}
return null
}
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: false },
nitro: {
hooks: {
compiled(nitro) {
// Must patch the nitro chunk: index.mjs import hoisting would run freeze before a preamble.
const nitroPath = resolveNitroBundlePath(nitro.options.output.dir)
if (!nitroPath) {
nitro.logger.warn('[louis-env] could not find nitro.mjs to inject LOUIS_* alias')
return
}
const source = readFileSync(nitroPath, 'utf8')
const marker = 'const _sharedRuntimeConfig'
if (source.includes('/*louis-env-alias*/')) return
const idx = source.indexOf(marker)
if (idx === -1) {
nitro.logger.warn('[louis-env] could not find _sharedRuntimeConfig to inject LOUIS_* alias')
return
}
writeFileSync(
nitroPath,
`${source.slice(0, idx)}/*louis-env-alias*/${louisEnvAliasPreamble}${source.slice(idx)}`,
)
},
},
},
// Production builds don't need client sourcemaps; disabling avoids noisy
// SOURCEMAP_BROKEN warnings from @tailwindcss/vite and nuxt internals.
sourcemap: false,
css: ['./app/assets/css/main.css'],
app: {
head: {
title: 'Louis',
link: [
{ rel: 'icon', href: '/favicons/favicon.ico', sizes: 'any' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicons/favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicons/favicon-16x16.png' },
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/favicons/apple-touch-icon.png' },
{ rel: 'manifest', href: '/favicons/manifest.json' },
],
meta: [
{
name: 'viewport',
content: 'width=device-width, initial-scale=1, viewport-fit=cover',
},
{ name: 'theme-color', content: '#fff5f0' },
{ name: 'mobile-web-app-capable', content: 'yes' },
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
{ name: 'apple-mobile-web-app-title', content: 'Louis' },
{
name: 'apple-mobile-web-app-status-bar-style',
content: 'default',
},
{ name: 'application-name', content: 'Louis' },
],
// Paint splash ground before Vue boots so the app never flashes underneath.
style: [
{
key: 'app-splash-pending',
textContent:
'html.app-splash-pending{background:#fff5f0}'
+ 'html.app-splash-pending body{background:#fff5f0}'
+ 'html.app-splash-pending::before{content:"";position:fixed;inset:0;z-index:114;background:#fff5f0;pointer-events:none}',
},
],
script: [
{
key: 'app-vvh',
textContent:
'(function(){try{var h=(window.visualViewport&&window.visualViewport.height)||window.innerHeight;'
+ 'if(h>0)document.documentElement.style.setProperty("--app-vvh",Math.round(h)+"px");'
+ '}catch(e){}})();',
},
{
key: 'app-splash-pending',
// Runs before body parse; mirrors useAppSplash session/debug rules.
// TEMP: `force=true` matches FORCE_SPLASH_EVERY_REFRESH in useAppSplash.
textContent:
'(function(){try{var d=document.documentElement;'
+ 'var force=true;'
+ 'var debug=new URLSearchParams(location.search).get("splash")==="debug";'
+ 'var seen=sessionStorage.getItem("louis.splash.seen")==="1";'
+ 'var reduced=window.matchMedia("(prefers-reduced-motion: reduce)").matches;'
+ 'if(force||debug||(!seen&&!reduced))d.classList.add("app-splash-pending");'
+ '}catch(e){document.documentElement.classList.add("app-splash-pending")}})();',
},
],
},
},
components: [
{
path: '~/components/ui',
pathPrefix: false,
},
{
path: '~/components',
ignore: ['ui'],
},
],
devServer: {
port: 4000,
},
// Prefer LOUIS_* at runtime (see .env.example); legacy NUXT_* still works.
// Production: nitro compiled hook aliases LOUIS_* → NUXT_* in nitro.mjs before freeze.
runtimeConfig: {
youtubeApiKey: louisEnv.youtubeApiKey,
youtubeSafeSearch: louisEnv.youtubeSafeSearch || 'moderate',
yotoClientId: louisEnv.yotoClientId,
yotoClientSecret: louisEnv.yotoClientSecret,
yotoRedirectUri: louisEnv.yotoRedirectUri,
ytdlpPath: louisEnv.ytdlpPath,
// Optional Netscape cookies.txt for yt-dlp (LOUIS_YTDLP_COOKIES_FILE). Anon-first; used on escalate.
ytdlpCookiesFile: louisEnv.ytdlpCookiesFile,
/** yt-dlp `--js-runtimes` value (`node` or `node:/abs/shim` on desktop). */
ytdlpJsRuntime: louisEnv.ytdlpJsRuntime,
audioWorkDir: louisEnv.audioWorkDir,
audioJobMaxAgeMs: louisEnv.audioJobMaxAgeMs,
audioCacheMaxAgeMs: louisEnv.audioCacheMaxAgeMs,
audioCacheMaxBytes: louisEnv.audioCacheMaxBytes,
enableDebugRoutes: louisEnv.enableDebugRoutes,
public: {
appVersion: packageJson.version || '0.0.0',
/** Set LOUIS_PUBLIC_DESKTOP=1 by the Electron host when spawning Nitro. */
desktop: louisEnv.publicDesktop,
},
},
vite: {
plugins: [
tailwindcss(),
{
name: 'suppress-sourcemap-broken-warnings',
apply: 'build',
configResolved(config) {
const previous = config.build.rollupOptions.onwarn
config.build.rollupOptions.onwarn = (warning, warn) => {
if (warning.code === 'SOURCEMAP_BROKEN') return
if (previous) previous(warning, warn)
else warn(warning)
}
},
},
],
build: {
sourcemap: false,
},
},
})