@@ -256,9 +256,19 @@ public function clearRegisterErrorCallbacks(): void {
256256 * This table stores information about which migrations and seeders
257257 * have been applied, including timestamps and execution status.
258258 * Required for tracking database change history.
259+ *
260+ * If the table already exists but is missing the 'status' column
261+ * (upgrade from older version), the column will be added automatically.
259262 */
260263 public function createSchemaTable () {
261264 $ this ->createTables ();
265+
266+ // Add status column if missing (upgrade path for existing installations)
267+ try {
268+ $ this ->table ('schema_changes ' )->select (['status ' ])->limit (1 )->execute ();
269+ } catch (\Throwable $ e ) {
270+ $ this ->table ('schema_changes ' )->addCol ('status ' )->execute ();
271+ }
262272 }
263273
264274 /**
@@ -527,6 +537,127 @@ public function rollbackUpTo(?string $changeName): array {
527537 return $ rolled ;
528538 }
529539
540+ /**
541+ * Skip a single change without executing it (baseline).
542+ *
543+ * Records the change as 'skipped' in the schema_changes table so it
544+ * won't be executed by future apply() calls.
545+ *
546+ * @param DatabaseChange|string $change The change instance or class name.
547+ * @return bool True if skipped successfully, false if already applied/skipped or not found.
548+ */
549+ public function skip (DatabaseChange |string $ change ): bool {
550+ $ instance = is_string ($ change ) ? $ this ->findChangeByName ($ change ) : $ change ;
551+
552+ if ($ instance === null ) {
553+ return false ;
554+ }
555+
556+ if ($ this ->isApplied ($ instance ->getName ())) {
557+ return false ;
558+ }
559+
560+ $ batch = $ this ->getRepository ()->getNextBatchNumber ();
561+ $ this ->getRepository ()->recordSkipped ($ instance , $ batch );
562+
563+ return true ;
564+ }
565+
566+ /**
567+ * Skip all pending changes without executing them (baseline all).
568+ *
569+ * @return array Array of skipped DatabaseChange instances.
570+ */
571+ public function skipAll (): array {
572+ $ skipped = [];
573+ $ batch = $ this ->getRepository ()->getNextBatchNumber ();
574+
575+ foreach ($ this ->dbChanges as $ change ) {
576+ if ($ this ->isApplied ($ change ->getName ())) {
577+ continue ;
578+ }
579+
580+ if (!$ this ->shouldRunInEnvironment ($ change )) {
581+ continue ;
582+ }
583+
584+ $ this ->getRepository ()->recordSkipped ($ change , $ batch );
585+ $ skipped [] = $ change ;
586+ }
587+
588+ return $ skipped ;
589+ }
590+
591+ /**
592+ * Skip the next N pending changes without executing them.
593+ *
594+ * Changes are skipped in dependency order (same order apply() would use).
595+ *
596+ * @param int $count Number of changes to skip.
597+ * @return array Array of skipped DatabaseChange instances.
598+ */
599+ public function skipNext (int $ count = 1 ): array {
600+ $ skipped = [];
601+ $ batch = $ this ->getRepository ()->getNextBatchNumber ();
602+
603+ foreach ($ this ->dbChanges as $ change ) {
604+ if (count ($ skipped ) >= $ count ) {
605+ break ;
606+ }
607+
608+ if ($ this ->isApplied ($ change ->getName ())) {
609+ continue ;
610+ }
611+
612+ if (!$ this ->shouldRunInEnvironment ($ change )) {
613+ continue ;
614+ }
615+
616+ $ this ->getRepository ()->recordSkipped ($ change , $ batch );
617+ $ skipped [] = $ change ;
618+ }
619+
620+ return $ skipped ;
621+ }
622+
623+ /**
624+ * Skip all pending changes up to and including the named one.
625+ *
626+ * @param string $changeName The class name of the change to skip up to.
627+ * @return array Array of skipped DatabaseChange instances.
628+ */
629+ public function skipUpTo (string $ changeName ): array {
630+ $ skipped = [];
631+ $ batch = $ this ->getRepository ()->getNextBatchNumber ();
632+
633+ foreach ($ this ->dbChanges as $ change ) {
634+ if ($ this ->isApplied ($ change ->getName ())) {
635+ if ($ change ->getName () === $ changeName ) {
636+ break ;
637+ }
638+
639+ continue ;
640+ }
641+
642+ if (!$ this ->shouldRunInEnvironment ($ change )) {
643+ if ($ change ->getName () === $ changeName ) {
644+ break ;
645+ }
646+
647+ continue ;
648+ }
649+
650+ $ this ->getRepository ()->recordSkipped ($ change , $ batch );
651+ $ skipped [] = $ change ;
652+
653+ if ($ change ->getName () === $ changeName ) {
654+ break ;
655+ }
656+ }
657+
658+ return $ skipped ;
659+ }
660+
530661 private function areDependenciesSatisfied (DatabaseChange $ change ): bool {
531662 foreach ($ change ->getDependencies () as $ depName ) {
532663 if (!$ this ->isApplied ($ depName )) {
0 commit comments