diff --git a/graph/client/src/app/console-migrate/migrate.app.tsx b/graph/client/src/app/console-migrate/migrate.app.tsx index 25851f6ce8..555594d9e1 100644 --- a/graph/client/src/app/console-migrate/migrate.app.tsx +++ b/graph/client/src/app/console-migrate/migrate.app.tsx @@ -85,6 +85,13 @@ export function MigrateApp({ }); }; + const onUndoMigration = (migration: MigrationDetailsWithId) => { + externalApiService.postEvent({ + type: 'undo-migration', + payload: { migration }, + }); + }; + const onViewImplementation = (migration: MigrationDetailsWithId) => { externalApiService.postEvent({ type: 'view-implementation', @@ -109,6 +116,7 @@ export function MigrateApp({ onFinish={onFinish} onFileClick={onFileClick} onSkipMigration={onSkipMigration} + onUndoMigration={onUndoMigration} onViewImplementation={onViewImplementation} onViewDocumentation={onViewDocumentation} > diff --git a/graph/migrate/src/lib/components/automatic-migration.tsx b/graph/migrate/src/lib/components/automatic-migration.tsx index 666d89e6d5..8d7e3aa1a7 100644 --- a/graph/migrate/src/lib/components/automatic-migration.tsx +++ b/graph/migrate/src/lib/components/automatic-migration.tsx @@ -21,6 +21,7 @@ export function AutomaticMigration(props: { nxConsoleMetadata: MigrationsJsonMetadata; onRunMigration: (migration: MigrationDetailsWithId) => void; onSkipMigration: (migration: MigrationDetailsWithId) => void; + onUndoMigration: (migration: MigrationDetailsWithId) => void; onFileClick: ( migration: MigrationDetailsWithId, file: Omit @@ -87,6 +88,7 @@ export function AutomaticMigration(props: { isInit={isInit} onRunMigration={props.onRunMigration} onSkipMigration={props.onSkipMigration} + onUndoMigration={props.onUndoMigration} onFileClick={props.onFileClick} onViewImplementation={props.onViewImplementation} onViewDocumentation={props.onViewDocumentation} diff --git a/graph/migrate/src/lib/components/migration-timeline.tsx b/graph/migrate/src/lib/components/migration-timeline.tsx index 9f9e7ac123..d95c607877 100644 --- a/graph/migrate/src/lib/components/migration-timeline.tsx +++ b/graph/migrate/src/lib/components/migration-timeline.tsx @@ -33,6 +33,7 @@ export interface MigrationTimelineProps { isInit: boolean; onRunMigration: (migration: MigrationDetailsWithId) => void; onSkipMigration: (migration: MigrationDetailsWithId) => void; + onUndoMigration: (migration: MigrationDetailsWithId) => void; onFileClick: ( migration: MigrationDetailsWithId, file: Omit @@ -53,6 +54,7 @@ export function MigrationTimeline({ currentMigrationHasChanges, onRunMigration, onSkipMigration, + onUndoMigration, onFileClick, onViewImplementation, onViewDocumentation, @@ -335,17 +337,30 @@ export function MigrationTimeline({ )} {currentMigrationHasChanges && ( - + <> + + + )} )} diff --git a/graph/migrate/src/lib/migrate.stories.tsx b/graph/migrate/src/lib/migrate.stories.tsx index 285274edeb..170b41570e 100644 --- a/graph/migrate/src/lib/migrate.stories.tsx +++ b/graph/migrate/src/lib/migrate.stories.tsx @@ -290,6 +290,9 @@ export const PendingApproval: Story = { onSkipMigration: (migration) => { console.log('skip migration', migration); }, + onUndoMigration: (migration) => { + console.log('undo migration', migration); + }, onFileClick: (migration, file) => { console.log('file click', migration, file); }, diff --git a/graph/migrate/src/lib/migrate.tsx b/graph/migrate/src/lib/migrate.tsx index 2c512d944d..ffe115adac 100644 --- a/graph/migrate/src/lib/migrate.tsx +++ b/graph/migrate/src/lib/migrate.tsx @@ -36,6 +36,7 @@ export interface MigrateUIProps { } ) => void; onSkipMigration: (migration: MigrationDetailsWithId) => void; + onUndoMigration: (migration: MigrationDetailsWithId) => void; onCancel: () => void; onFinish: (squashCommits: boolean) => void; onFileClick: ( @@ -188,13 +189,10 @@ export function MigrateUI(props: MigrateUIProps) { onRunMigration={(migration) => props.onRunMigration(migration, { createCommits }) } - onSkipMigration={(migration) => props.onSkipMigration(migration)} - onViewImplementation={(migration) => - props.onViewImplementation(migration) - } - onViewDocumentation={(migration) => - props.onViewDocumentation(migration) - } + onSkipMigration={props.onSkipMigration} + onUndoMigration={props.onUndoMigration} + onViewImplementation={props.onViewImplementation} + onViewDocumentation={props.onViewDocumentation} onFileClick={props.onFileClick} /> diff --git a/packages/nx/src/command-line/migrate/migrate-ui-api.ts b/packages/nx/src/command-line/migrate/migrate-ui-api.ts index 6f00dda89d..beff69c431 100644 --- a/packages/nx/src/command-line/migrate/migrate-ui-api.ts +++ b/packages/nx/src/command-line/migrate/migrate-ui-api.ts @@ -171,6 +171,16 @@ export async function runSingleMigration( cwd: workspacePath, encoding: 'utf-8', }); + // The revision changes after the amend, so we need to update it + const amendedGitRef = execSync('git rev-parse HEAD', { + cwd: workspacePath, + encoding: 'utf-8', + }).trim(); + + modifyMigrationsJsonMetadata( + workspacePath, + updateRefForSuccessfulMigration(migration.id, amendedGitRef) + ); } } catch (e) { modifyMigrationsJsonMetadata( @@ -245,6 +255,24 @@ export function addSuccessfulMigration( }; } +export function updateRefForSuccessfulMigration(id: string, ref: string) { + return ( + migrationsJsonMetadata: MigrationsJsonMetadata + ): MigrationsJsonMetadata => { + const copied = { ...migrationsJsonMetadata }; + if (!copied.completedMigrations) { + copied.completedMigrations = {}; + } + const existing = copied.completedMigrations[id]; + if (existing && existing.type === 'successful') { + existing.ref = ref; + } else { + throw new Error(`Attempted to update ref for unsuccessful migration`); + } + return copied; + }; +} + export function addFailedMigration(id: string, error: string) { return (migrationsJsonMetadata: MigrationsJsonMetadata) => { const copied = { ...migrationsJsonMetadata }; @@ -307,3 +335,19 @@ export function readMigrationsJsonMetadata( const migrationsJson = JSON.parse(readFileSync(migrationsJsonPath, 'utf-8')); return migrationsJson['nx-console']; } + +export function undoMigration(workspacePath: string, id: string) { + return (migrationsJsonMetadata: MigrationsJsonMetadata) => { + const existing = migrationsJsonMetadata.completedMigrations[id]; + if (existing.type !== 'successful') + throw new Error(`undoMigration called on unsuccessful migration: ${id}`); + execSync(`git reset --hard ${existing.ref}^`, { + cwd: workspacePath, + encoding: 'utf-8', + }); + migrationsJsonMetadata.completedMigrations[id] = { + type: 'skipped', + }; + return migrationsJsonMetadata; + }; +}