@@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises';
44import os from 'node:os' ;
55import path from 'node:path' ;
66
7- import { getPty , SpawnStatus } from '../pty/index.js' ;
7+ import { getPty , SpawnResult , SpawnStatus } from '../pty/index.js' ;
88
99export function isDebug ( ) : boolean {
1010 return process . env . DEBUG != null && process . env . DEBUG . includes ( 'codify' ) ; // TODO: replace with debug library
@@ -146,6 +146,22 @@ export const Utils = {
146146 return process . env . SHELL || os . userInfo ( ) . shell || '/bin/zsh' ;
147147 } ,
148148
149+ /**
150+ * Whether interactive spawns should add `-l` (login shell) on top of `-i`.
151+ * Only needed when `process.env.SHELL` is unset — the signal that Codify was
152+ * launched outside a terminal (e.g. the desktop app via launchd on macOS),
153+ * where GUI-launched processes don't inherit the user's rc-file env (e.g.
154+ * TART_HOME, PATH additions).
155+ *
156+ * When `SHELL` *is* set (normal terminal/CI launches), a login shell must be
157+ * avoided: on Linux, `-l` sources `~/.profile`/`~/.bash_profile` instead of
158+ * `~/.bashrc` (often not sourcing `.bashrc` at all, since CI images
159+ * typically ship no `~/.bash_profile`), so PATH exports written by
160+ * `FileUtils.addToShellRc()` become invisible to later interactive spawns.
161+ */
162+ needsLoginShell ( ) : boolean {
163+ return ! process . env . SHELL ;
164+ } ,
149165
150166 getPrimaryShellRc ( ) : string {
151167 return this . getShellRcFiles ( ) [ 0 ] ;
@@ -274,10 +290,25 @@ Brew can be installed using Codify:
274290 if ( isAptInstalled . status === SpawnStatus . SUCCESS ) {
275291 await $ . spawn ( 'apt-get update' , { requiresRoot : true } ) ;
276292 const flagStr = extraFlags . length > 0 ? `${ extraFlags . join ( ' ' ) } ` : '' ;
277- const { status, data } = await $ . spawnSafe ( `apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${ flagStr } ${ packageName } ` , {
278- requiresRoot : true ,
279- env : { DEBIAN_FRONTEND : 'noninteractive' , NEEDRESTART_MODE : 'a' }
280- } ) ;
293+
294+ let status : SpawnResult [ 'status' ] = SpawnStatus . ERROR ;
295+ let data = '' ;
296+ const maxLockRetries = 5 ;
297+ for ( let attempt = 0 ; attempt <= maxLockRetries ; attempt ++ ) {
298+ ( { status, data } = await $ . spawnSafe ( `apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${ flagStr } ${ packageName } ` , {
299+ requiresRoot : true ,
300+ env : { DEBIAN_FRONTEND : 'noninteractive' , NEEDRESTART_MODE : 'a' }
301+ } ) ) ;
302+
303+ // dpkg/apt lock is held by another process (e.g. unattended-upgrades on a fresh VM).
304+ // This isn't a broken-dependency condition, so back off and retry rather than falling
305+ // through to `apt-get install -f`, which will just hit the same lock and fail too.
306+ const isLockContention = status === SpawnStatus . ERROR
307+ && ( data . includes ( 'Could not get lock' ) || data . includes ( 'dpkg frontend lock' ) ) ;
308+ if ( ! isLockContention || attempt === maxLockRetries ) break ;
309+
310+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 * ( attempt + 1 ) ) ) ;
311+ }
281312
282313 if ( status === SpawnStatus . ERROR && data . includes ( 'E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.' ) ) {
283314 await $ . spawn ( 'dpkg --configure -a' , { requiresRoot : true } ) ;
@@ -288,6 +319,12 @@ Brew can be installed using Codify:
288319 return ;
289320 }
290321
322+ const isLockContention = status === SpawnStatus . ERROR
323+ && ( data . includes ( 'Could not get lock' ) || data . includes ( 'dpkg frontend lock' ) ) ;
324+ if ( isLockContention ) {
325+ throw new Error ( `Failed to install package ${ packageName } via apt: dpkg/apt lock held by another process after ${ maxLockRetries } retries: ${ data } ` ) ;
326+ }
327+
291328 if ( status === SpawnStatus . ERROR ) {
292329 // Attempt to fix broken dependencies then retry
293330 const fixResult = await $ . spawnSafe ( 'apt-get install -f -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0' , {
0 commit comments