2026-08-10 08:38:37 +02:00
import { NEWLINE , TAB } from '../src/lib/constants/code' ;
import { APPLE_DEVICES , BUILD_CONFIG , REGEX_PATTERNS , SPLASH_LINK } from '../src/lib/constants/pwa' ;
import { SplashOrientation } from '../src/lib/enums/splash.enums' ;
import type { SplashDimensions } from '../src/lib/types' ;
import { existsSync , readdirSync , readFileSync , writeFileSync } from 'node:fs' ;
2026-06-12 15:53:26 +02:00
import { resolve } from 'path' ;
import type { Plugin } from 'vite' ;
let processed = false ;
const OUTPUT_DIR = process . env . LLAMA_UI_OUT_DIR ?? BUILD_CONFIG . OUTPUT_DIR ;
/**
* Generate iOS splash screen <link> tags from generated apple-splash-*.png files.
* Returns an array of HTML link strings to be injected into the page head.
*/
export function generateSplashScreenLinks ( outDir : string ) : string [] {
const files = readdirSync ( outDir ). filter (( f ) => f . match ( REGEX_PATTERNS . SPLASH_FILE ));
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
if ( files . length === 0 ) return [];
const dimMap = new Map < string , SplashDimensions >();
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
for ( const [ dims , spec ] of Object . entries ( APPLE_DEVICES )) {
const [ w , h ] = dims . split ( 'x' ). map ( Number );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
// logical-point dimensions
2026-08-10 08:38:37 +02:00
dimMap . set ( ` ${ w } x ${ h } ` , { deviceH : spec.height , deviceW : spec.width , dpr : spec.dpr });
dimMap . set ( ` ${ h } x ${ w } ` , { deviceH : spec.height , deviceW : spec.width , dpr : spec.dpr });
2026-06-12 15:53:26 +02:00
// pixel dimensions (used by actual generated splash files)
dimMap . set ( ` ${ w * spec . dpr } x ${ h * spec . dpr } ` , {
deviceH : spec.height ,
2026-08-10 08:38:37 +02:00
deviceW : spec.width ,
2026-06-12 15:53:26 +02:00
dpr : spec.dpr
});
dimMap . set ( ` ${ h * spec . dpr } x ${ w * spec . dpr } ` , {
deviceH : spec.height ,
2026-08-10 08:38:37 +02:00
deviceW : spec.width ,
2026-06-12 15:53:26 +02:00
dpr : spec.dpr
});
}
const lightLinks : string [] = [];
const darkLinks : string [] = [];
for ( const file of files ) {
const match = file . match ( REGEX_PATTERNS . SPLASH_FILE );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
if ( ! match ) continue ;
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
const orientation = match [ 1 ] as SplashOrientation ;
const isDark = !! match [ 2 ];
const pixelW = parseInt ( match [ 3 ]);
const pixelH = parseInt ( match [ 4 ]);
const key = ` ${ pixelW } x ${ pixelH } ` ;
const spec = dimMap . get ( key );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
if ( ! spec ) {
console . warn ( `Unknown splash screen dimensions: ${ key } ( ${ file } )` );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
continue ;
}
2026-08-10 08:38:37 +02:00
const { deviceH , deviceW , dpr } = spec ;
2026-06-12 15:53:26 +02:00
const media = `screen and (device-width: ${ deviceW } px) and (device-height: ${ deviceH } px) and (-webkit-device-pixel-ratio: ${ dpr } ) and (orientation: ${ orientation } )` ;
const href = `./ ${ file } ` ;
if ( isDark ) {
darkLinks . push (
` ${ SPLASH_LINK . HTML } media=" ${ media }${ SPLASH_LINK . DARK_MEDIA_SUFFIX } " href=" ${ href } ">`
);
} else {
lightLinks . push ( ` ${ SPLASH_LINK . HTML } media=" ${ media } " href=" ${ href } ">` );
}
}
return [... lightLinks , ... darkLinks ];
}
export function splashScreenPlugin () : Plugin {
return {
apply : 'build' ,
closeBundle() {
setTimeout (() => {
try {
if ( processed ) return ;
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
processed = true ;
const outDir = resolve ( OUTPUT_DIR );
const indexPath = resolve ( outDir , 'index.html' );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
if ( ! existsSync ( indexPath )) return ;
let content = readFileSync ( indexPath , 'utf-8' );
// Inject iOS splash screen <link> tags into <head>.
// The @vite-pwa/assets-generator generates apple-splash-*.png files;
// this scans them and creates the <link> tags SvelteKit needs.
const splashLinks = generateSplashScreenLinks ( outDir );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
if ( splashLinks . length > 0 ) {
console . log ( `Generated ${ splashLinks . length } apple-splash link tags` );
const splashHtml = splashLinks . map (( l ) => TAB + TAB + l ). join ( NEWLINE );
2026-08-10 08:38:37 +02:00
2026-06-12 15:53:26 +02:00
content = content . replace (
REGEX_PATTERNS . HEAD_CLOSE ,
splashHtml + NEWLINE + TAB + TAB + '</head>'
);
}
// Remove trailing \r from Windows line endings
content = content . replace ( /\r/g , '' );
content = BUILD_CONFIG . GUIDE_COMMENT + NEWLINE + content ;
writeFileSync ( indexPath , content , 'utf-8' );
console . log ( 'Updated index.html' );
} catch ( error ) {
console . error ( 'Failed to process build output:' , error );
}
}, 100 );
2026-08-10 08:38:37 +02:00
},
name : 'llamacpp:splash-screen'
2026-06-12 15:53:26 +02:00
};
}