@@ -46,6 +46,7 @@ define(function (require, exports, module) {
4646 KEY_UPDATE_AVAILABLE = "PH_UPDATE_AVAILABLE" ;
4747
4848 const PREFS_AUTO_UPDATE = "autoUpdate" ;
49+ const MAX_LOG_LINES = 500 ;
4950 let isAutoUpdateFlow = true ;
5051 let updateScheduled = false ;
5152 let cachedUpdateDetails = null ;
@@ -146,7 +147,6 @@ define(function (require, exports, module) {
146147 */
147148 async function isUpgradableLocation ( ) {
148149 try {
149- return true ; //todo remove
150150 const isPackaged = await window . electronAPI . isPackaged ( ) ;
151151 if ( ! isPackaged ) {
152152 return false ;
@@ -259,41 +259,171 @@ define(function (require, exports, module) {
259259 }
260260 }
261261
262- async function launchLinuxUpdater ( ) {
263- const stageValue = Phoenix . config . environment ;
264- console . log ( 'Stage:' , stageValue ) ;
265- let execCommand = 'wget -qO- https://updates.phcode.io/linux/installer.sh | bash -s -- --upgrade' ;
266- if ( stageValue === 'dev' || stageValue === 'stage' ) {
267- execCommand = "wget -qO- https://updates.phcode.io/linux/installer-latest-experimental-build.sh" +
268- " | bash -s -- --upgrade" ;
269- }
270- const result = await window . electronAPI . runShellCommand ( execCommand ) ;
271- if ( result . code !== 0 ) {
272- throw new Error ( "Update script exit with non-0 exit code: " + result . code ) ;
273- }
262+ /**
263+ * Launches the Linux updater using spawnProcess with streaming output
264+ * @param {function } onOutput - Callback for stdout/stderr lines
265+ * @returns {Promise } Resolves when update completes, rejects on error
266+ */
267+ function launchLinuxUpdater ( onOutput ) {
268+ return new Promise ( ( resolve , reject ) => {
269+ const stageValue = Phoenix . config . environment ;
270+ console . log ( 'Stage:' , stageValue ) ;
271+ let scriptUrl = 'https://updates.phcode.io/linux/installer.sh' ;
272+ if ( stageValue === 'dev' || stageValue === 'stage' ) {
273+ scriptUrl = "https://updates.phcode.io/linux/installer-latest-experimental-build.sh" ;
274+ }
275+
276+ // Use spawnProcess to run bash with the wget|bash command
277+ const command = '/bin/bash' ;
278+ const args = [ '-c' , `wget -qO- ${ scriptUrl } | bash -s -- --upgrade` ] ;
279+
280+ window . electronAppAPI . spawnProcess ( command , args )
281+ . then ( instanceId => {
282+ // Set up output handlers
283+ window . electronAppAPI . onProcessStdout ( ( id , line ) => {
284+ if ( id === instanceId && onOutput ) {
285+ onOutput ( 'stdout' , line ) ;
286+ }
287+ } ) ;
288+ window . electronAppAPI . onProcessStderr ( ( id , line ) => {
289+ if ( id === instanceId && onOutput ) {
290+ onOutput ( 'stderr' , line ) ;
291+ }
292+ } ) ;
293+ window . electronAppAPI . onProcessClose ( ( id , data ) => {
294+ if ( id === instanceId ) {
295+ if ( data . code === 0 ) {
296+ resolve ( ) ;
297+ } else {
298+ reject ( new Error ( `Update script exited with code: ${ data . code } ` ) ) ;
299+ }
300+ }
301+ } ) ;
302+ window . electronAppAPI . onProcessError ( ( id , err ) => {
303+ if ( id === instanceId ) {
304+ reject ( new Error ( `Update process error: ${ err } ` ) ) ;
305+ }
306+ } ) ;
307+ } )
308+ . catch ( reject ) ;
309+ } ) ;
274310 }
275311
276312 async function quitTimeAppUpdateHandler ( ) {
277313 if ( ! updateScheduled ) {
278314 return ;
279315 }
280316 console . log ( "Installing update at quit time" ) ;
281- return new Promise ( resolve => {
317+ return new Promise ( resolve => {
282318 let dialog ;
319+ let logLines = [ ] ;
320+
321+ function appendLogLine ( text ) {
322+ // Split text into lines and add each
323+ const lines = text . split ( '\n' ) . filter ( l => l . trim ( ) ) ;
324+ for ( const line of lines ) {
325+ logLines . push ( line ) ;
326+ // Keep only last MAX_LOG_LINES
327+ if ( logLines . length > MAX_LOG_LINES ) {
328+ logLines . shift ( ) ;
329+ }
330+ }
331+ // Update the log display
332+ const logElement = document . getElementById ( 'update-log-output' ) ;
333+ if ( logElement ) {
334+ logElement . textContent = logLines . join ( '\n' ) ;
335+ logElement . scrollTop = logElement . scrollHeight ;
336+ }
337+ }
338+
283339 function failUpdateDialogAndExit ( err ) {
284340 console . error ( "error updating: " , err ) ;
285341 dialog && dialog . close ( ) ;
286- Dialogs . showInfoDialog ( Strings . UPDATE_FAILED_TITLE , Strings . UPDATE_FAILED_VISIT_SITE_MESSAGE )
287- . done ( ( ) => {
288- NativeApp . openURLInDefaultBrowser ( Phoenix . config . update_download_page )
289- . catch ( console . error )
290- . finally ( resolve ) ;
291- } ) ;
342+ // Build full log text for copying
343+ const fullLogText = logLines . join ( '\n' ) + '\n\nError: ' + ( err . message || err ) ;
344+ // Show failure dialog with log output and hover copy icon
345+ const failContent = `
346+ <p>${ Strings . UPDATE_FAILED_VISIT_SITE_MESSAGE } </p>
347+ <div id="update-fail-log-container" style="
348+ position: relative;
349+ margin-top: 10px;
350+ ">
351+ <pre id="update-fail-log" style="
352+ background: #1e1e1e;
353+ color: #d4d4d4;
354+ padding: 10px;
355+ border-radius: 4px;
356+ font-family: 'Consolas', 'Monaco', monospace;
357+ font-size: 11px;
358+ height: 200px;
359+ overflow-y: auto;
360+ white-space: pre-wrap;
361+ word-wrap: break-word;
362+ margin: 0;
363+ ">${ fullLogText } </pre>
364+ <i id="update-log-copy-btn" class="fa-solid fa-copy" title="${ Strings . CMD_COPY } " style="
365+ position: absolute;
366+ top: 8px;
367+ right: 8px;
368+ color: #888;
369+ cursor: pointer;
370+ padding: 5px;
371+ border-radius: 3px;
372+ opacity: 0;
373+ transition: opacity 0.2s;
374+ "></i>
375+ </div>
376+ ` ;
377+ const failDialog = Dialogs . showModalDialog (
378+ DefaultDialogs . DIALOG_ID_ERROR ,
379+ Strings . UPDATE_FAILED_TITLE ,
380+ failContent ,
381+ [ { className : Dialogs . DIALOG_BTN_CLASS_PRIMARY , id : Dialogs . DIALOG_BTN_OK , text : Strings . OK } ]
382+ ) ;
383+ // Set up hover and click handlers for copy icon
384+ const $container = $ ( '#update-fail-log-container' ) ;
385+ const $copyBtn = $ ( '#update-log-copy-btn' ) ;
386+ $container . on ( 'mouseenter' , ( ) => $copyBtn . css ( 'opacity' , '1' ) ) ;
387+ $container . on ( 'mouseleave' , ( ) => $copyBtn . css ( 'opacity' , '0' ) ) ;
388+ $copyBtn . on ( 'click' , ( ) => {
389+ Phoenix . app . copyToClipboard ( fullLogText ) ;
390+ $copyBtn . removeClass ( 'fa-copy' ) . addClass ( 'fa-check' ) ;
391+ setTimeout ( ( ) => {
392+ $copyBtn . removeClass ( 'fa-check' ) . addClass ( 'fa-copy' ) ;
393+ } , 1500 ) ;
394+ } ) ;
395+ $copyBtn . on ( 'mouseenter' , ( ) => $copyBtn . css ( { 'background' : '#333' , 'color' : '#fff' } ) ) ;
396+ $copyBtn . on ( 'mouseleave' , ( ) => $copyBtn . css ( { 'background' : 'transparent' , 'color' : '#888' } ) ) ;
397+
398+ failDialog . done ( ( ) => {
399+ NativeApp . openURLInDefaultBrowser ( Phoenix . config . update_download_page )
400+ . catch ( console . error )
401+ . finally ( resolve ) ;
402+ } ) ;
292403 }
404+
405+ // Create dialog with terminal-style log output
406+ const dialogContent = `
407+ <p>${ Strings . UPDATE_INSTALLING_MESSAGE } </p>
408+ <pre id="update-log-output" style="
409+ background: #1e1e1e;
410+ color: #d4d4d4;
411+ padding: 10px;
412+ border-radius: 4px;
413+ font-family: 'Consolas', 'Monaco', monospace;
414+ font-size: 11px;
415+ height: 200px;
416+ overflow-y: auto;
417+ white-space: pre-wrap;
418+ word-wrap: break-word;
419+ margin-top: 10px;
420+ "></pre>
421+ ` ;
422+
293423 dialog = Dialogs . showModalDialog (
294424 DefaultDialogs . DIALOG_ID_INFO ,
295425 Strings . UPDATE_INSTALLING ,
296- Strings . UPDATE_INSTALLING_MESSAGE ,
426+ dialogContent ,
297427 [
298428 {
299429 className : "forced-hidden" ,
@@ -303,7 +433,10 @@ define(function (require, exports, module) {
303433 ] ,
304434 false
305435 ) ;
306- launchLinuxUpdater ( )
436+
437+ launchLinuxUpdater ( ( type , text ) => {
438+ appendLogLine ( text ) ;
439+ } )
307440 . then ( resolve )
308441 . catch ( failUpdateDialogAndExit ) ;
309442 } ) ;
0 commit comments