@@ -546,6 +546,56 @@ function derivePlantumlFriendlyError(details?: string | null, exitCode?: number)
546546 return 'Local PlantUML renderer failed to produce output.' ;
547547}
548548
549+ function isPackagedAsarPath ( filePath : string ) : boolean {
550+ return / \. a s a r ( $ | [ \\ / ] ) / . test ( filePath ) ;
551+ }
552+
553+ async function ensurePlantumlJarExtracted ( sourcePath : string ) : Promise < string > {
554+ if ( ! isPackagedAsarPath ( sourcePath ) ) {
555+ return sourcePath ;
556+ }
557+
558+ const tempDir = path . join ( app . getPath ( 'temp' ) , 'docforge-plantuml' ) ;
559+ const destinationPath = path . join ( tempDir , 'plantuml.jar' ) ;
560+
561+ await fs . mkdir ( tempDir , { recursive : true } ) ;
562+
563+ let needsExtraction = true ;
564+ try {
565+ const [ sourceStats , destStats ] = await Promise . all ( [ fs . stat ( sourcePath ) , fs . stat ( destinationPath ) ] ) ;
566+ if ( sourceStats . size === destStats . size ) {
567+ needsExtraction = false ;
568+ }
569+ } catch {
570+ // Either the destination does not exist yet or we could not stat one of the files.
571+ needsExtraction = true ;
572+ }
573+
574+ if ( ! needsExtraction ) {
575+ return destinationPath ;
576+ }
577+
578+ const pipeline = promisify ( stream . pipeline ) ;
579+
580+ try {
581+ await pipeline ( createReadStream ( sourcePath ) , createWriteStream ( destinationPath ) ) ;
582+ } catch ( error ) {
583+ try {
584+ await fs . unlink ( destinationPath ) ;
585+ } catch {
586+ // Ignore cleanup failures; we'll retry extraction later if needed.
587+ }
588+
589+ const message =
590+ error instanceof Error
591+ ? error . message
592+ : 'Failed to extract bundled PlantUML renderer from application archive.' ;
593+ throw new Error ( `Unable to prepare PlantUML renderer: ${ message } ` ) ;
594+ }
595+
596+ return destinationPath ;
597+ }
598+
549599async function resolvePlantUmlJar ( ) : Promise < string > {
550600 if ( cachedPlantumlJarPath ) {
551601 return cachedPlantumlJarPath ;
@@ -571,8 +621,9 @@ async function resolvePlantUmlJar(): Promise<string> {
571621 for ( const candidate of candidates ) {
572622 try {
573623 await fs . access ( candidate ) ;
574- cachedPlantumlJarPath = candidate ;
575- return candidate ;
624+ const usablePath = await ensurePlantumlJarExtracted ( candidate ) ;
625+ cachedPlantumlJarPath = usablePath ;
626+ return usablePath ;
576627 } catch {
577628 // Continue searching
578629 }
0 commit comments