@@ -1087,7 +1087,10 @@ func runPHPNewCommand(ctx context.Context, emit func(domain.Event) bool, opt php
10871087 args = append (args , "--force" )
10881088 }
10891089
1090- cmd := exec .CommandContext (ctx , "php" , args ... )
1090+ runCtx , cancel := context .WithCancel (ctx )
1091+ defer cancel ()
1092+
1093+ cmd := exec .CommandContext (runCtx , "php" , args ... )
10911094 if strings .TrimSpace (opt .WorkDir ) != "" {
10921095 cmd .Dir = opt .WorkDir
10931096 }
@@ -1143,6 +1146,11 @@ func runPHPNewCommand(ctx context.Context, emit func(domain.Event) bool, opt php
11431146 for l := range linesCh {
11441147 line := l .text
11451148 tracker .OnLine (line )
1149+ if tracker .HasFailed () {
1150+ // Abort immediately when a step is marked failed (e.g., download failed)
1151+ // even if the PHP command would otherwise continue.
1152+ cancel ()
1153+ }
11461154 stepID := tracker .CurrentStepID ()
11471155
11481156 if shouldSuppressPHPSubprocessLine (line ) {
@@ -1217,6 +1225,10 @@ func runPHPNewCommand(ctx context.Context, emit func(domain.Event) bool, opt php
12171225 tracker .FailRemaining ()
12181226 return err
12191227 }
1228+ if tracker .HasFailed () {
1229+ tracker .FailRemaining ()
1230+ return fmt .Errorf ("installation aborted due to failed step" )
1231+ }
12201232 tracker .FinishRemaining ()
12211233 return nil
12221234}
@@ -1246,13 +1258,17 @@ type stepTracker struct {
12461258 current string
12471259
12481260 done map [string ]bool
1261+
1262+ started map [string ]bool
1263+ failed bool
12491264}
12501265
12511266func newStepTracker (emit func (domain.Event ) bool ) * stepTracker {
12521267 return & stepTracker {
12531268 emit : emit ,
12541269 current : "download" ,
12551270 done : map [string ]bool {},
1271+ started : map [string ]bool {},
12561272 }
12571273}
12581274
@@ -1263,6 +1279,8 @@ func (t *stepTracker) CurrentStepID() string {
12631279 return t .current
12641280}
12651281
1282+ func (t * stepTracker ) HasFailed () bool { return t .failed }
1283+
12661284func (t * stepTracker ) start (stepID , label string , index int ) {
12671285 if t .done [stepID ] {
12681286 return
@@ -1271,6 +1289,7 @@ func (t *stepTracker) start(stepID, label string, index int) {
12711289 return
12721290 }
12731291 t .current = stepID
1292+ t .started [stepID ] = true
12741293 _ = t .emit (domain.Event {
12751294 Type : domain .EventStepStart ,
12761295 StepID : stepID ,
@@ -1289,6 +1308,9 @@ func (t *stepTracker) doneStep(stepID string, ok bool) {
12891308 return
12901309 }
12911310 t .done [stepID ] = true
1311+ if ! ok {
1312+ t .failed = true
1313+ }
12921314 sev := domain .SeverityInfo
12931315 if ! ok {
12941316 sev = domain .SeverityError
@@ -1320,6 +1342,10 @@ func (t *stepTracker) OnLine(line string) {
13201342 t .doneStep ("install" , true )
13211343 t .doneStep ("presets" , true )
13221344 }
1345+ // Install command now reports migrations and composer install as part of Step 4.
1346+ if strings .Contains (line , "Running database migrations" ) || strings .Contains (line , "Running database seeders" ) {
1347+ t .start ("install" , "Step 4: Install Evolution CMS" , 4 )
1348+ }
13231349
13241350 // Step 6 marker: deps update starts after install + presets.
13251351 if strings .Contains (line , "Updating dependencies with Composer" ) {
@@ -1344,6 +1370,12 @@ func (t *stepTracker) OnLine(line string) {
13441370 if strings .Contains (strings .ToLower (line ), "failed to download evolution cms" ) {
13451371 t .doneStep ("download" , false )
13461372 }
1373+ if strings .Contains (strings .ToLower (line ), "migration failed" ) {
1374+ t .doneStep ("install" , false )
1375+ }
1376+ if strings .Contains (strings .ToLower (line ), "failed to install dependencies" ) {
1377+ t .doneStep ("install" , false )
1378+ }
13471379 if strings .Contains (strings .ToLower (line ), "failed to update dependencies" ) {
13481380 t .doneStep ("dependencies" , false )
13491381 }
@@ -1354,12 +1386,16 @@ func (t *stepTracker) FinishRemaining() {
13541386 if t .done ["install" ] {
13551387 t .doneStep ("presets" , true )
13561388 }
1357- // If PHP ran to completion, mark remaining steps as done (best-effort).
1358- t .doneStep ("download" , true )
1359- t .doneStep ("install" , true )
1360- t .doneStep ("presets" , true )
1361- t .doneStep ("dependencies" , true )
1362- t .doneStep ("finalize" , true )
1389+ // If PHP ran to completion, mark any started but not-done steps as OK (best-effort),
1390+ // but never override an explicit failure.
1391+ if t .failed {
1392+ return
1393+ }
1394+ for _ , id := range []string {"download" , "install" , "dependencies" , "finalize" } {
1395+ if t .started [id ] && ! t .done [id ] {
1396+ t .doneStep (id , true )
1397+ }
1398+ }
13631399}
13641400
13651401func (t * stepTracker ) FailRemaining () {
0 commit comments