feat(gen2-migration): full e2e execution of all apps#14753
Merged
iliapolo merged 40 commits intogen2-migrationfrom Apr 6, 2026
Merged
feat(gen2-migration): full e2e execution of all apps#14753iliapolo merged 40 commits intogen2-migrationfrom
iliapolo merged 40 commits intogen2-migrationfrom
Conversation
…ongside S3 in storage category initializeStorageCategory treated DynamoDB and S3 as mutually exclusive. It only entered the DynamoDB path when storageConfig.type === 'dynamodb' was explicitly set, and returned early before reaching S3 initialization. The discussions app defines both S3 buckets and DynamoDB tables without a type field, so DynamoDB tables were silently skipped. Additionally, addDynamoDBWithGSIWithSettings from amplify-e2e-core hardcodes generic column names (pk, sk, gsi-pk, gsi-sk). A new writeDynamoDBCliInputs method now overwrites cli-inputs.json after table creation with the actual partition key, sort key, and GSI definitions from the migration config. Verified by running the CLI against the discussions app and confirming the generated cli-inputs.json files match the pre.generate snapshot. --- Prompt: Im working on running the discussions app in the gen2-migration e2e system. There is a problem right now that the system doesn't configure DynamoDB tables. Run the following command "npx tsx src/cli.ts --app discussions --profile default". then, check in the directory it creates for the app if dynamo storage is configured. If not, fix the e2e system accordingly. As reference, you can use the discussions pre.generate snapshot to see how the files should look like.
…ystem Remove unused modules and tests from the e2e migration system: configuration-loader, environment-detector, app-selector, category-initializer, cdk-atmosphere-integration, directory-manager, file-manager, aws-profile-manager, types, and math. Streamline the CLI entry point and core components to reduce complexity. Restructure project-boards app by moving backend files into backend/ and migration scripts into migration/ subdirectories. Fix lock.ts to use discover() for finding the GraphQL API ID instead of paginating AppSync APIs directly. Add git utility module, frontest.ts, and cleanup-s3-buckets.sh. --- Prompt: Commit everything I've done. Dont run tests, don't do anything. just commit.
Apply the same restructuring to the discussions migration app that was done for project-boards: - Move backend assets into backend/ with BASH_SOURCE-relative configure.sh - Consolidate three shell scripts into one - Simplify post-generate.ts and post-refactor.ts into migration/ - Replace gen1/gen2 test scripts and test-utils with a single self-contained frontest.ts - Fix snapshot package.json names to avoid workspace collisions - Delete unused migration-config.json - Add @aws-sdk/client-cognito-identity-provider devDependency Documentation updates: - Merge e2e system AGENTS.md into README.md, drop stale refs - Delete MIGRATION_CONFIG.md (no longer used) - Update amplify-migration-apps README with new app structure (backend/, migration/, frontest.ts) and integration testing section - Add discussions to root workspace list --- Prompt: Apply the same changes from project-boards to the discussions app, consolidate e2e system docs, update migration apps README.
Apply the same restructuring pattern to backend-only, fitness-tracker, imported-resources, media-vault, mood-board, product-catalog, and store-locator: - Move backend assets into backend/ with BASH_SOURCE-relative configure.sh scripts - Replace gen1/gen2 test scripts and test-utils with standalone frontest.ts (config-path CLI arg, inline Cognito provisioning, no _test-common imports) - Simplify post-generate/post-refactor scripts into migration/ - Fix snapshot package.json names to avoid workspace collisions - Delete unused migration-config.json files - Add missing migration/post-generate.ts scripts for apps that document post-generate edits in their README - Add missing migration/post-refactor.ts for imported-resources and product-catalog - Remove fixUserPoolRegionInGraphqlApi from all post-generate scripts (bug has been fixed upstream) - Fix signUp in project-boards (missing UserAttributes), media- vault (simplified to email), and store-locator (simplified to email, removed dynamic resolver) - Add all apps to root workspaces list --- Prompt: Apply the same restructuring to all remaining apps, add missing migration scripts based on READMEs, review and simplify signUp functions, remove fixUserPoolRegionInGraphqlApi.
Remove client: any parameters from test functions in discussions, fitness-tracker, and product-catalog frontest.ts scripts. Each function now creates its own generateClient() inline with the correct authMode, enabling IDE click-through navigation on graphql calls. --- Prompt: Remove client: any parameters from frontest.ts test functions and inline the generateClient calls instead.
Add migration/config.json to each app to configure the e2e system per-app. The CLI loads this config once and uses it to determine which scripts to run and what flags to pass. Config fields: - frontest: path to the frontend test script - postPush: path to a script run after amplify push - lock.skipValidations: pass --skip-validations to lock Fitness-tracker specific changes: - Add post-push.ts to set DOMAINALLOWLIST on the PreSignUp Lambda (reads function name from amplify-meta.json) - Add resourceGroupName to function resource.ts files in post-generate to break circular dependencies - Extract REST API names and Amplify.configure into src/api-config.ts so post-generate can rewrite it for Gen2 (branch-suffixed names + parseAmplifyConfig with REST merge) - Update App.tsx and main.tsx to import from api-config.ts - Use amazon.com email domain for test users (PreSignUp allowlist) Also fix imported-resources frontest.ts: add ProjectStatus import, remove unused getCurrentUser. --- Prompt: Add migration config, fix fitness-tracker e2e issues (DOMAINALLOWLIST, circular deps, REST API names, Gen2 config).
Remove frontest from MigrationConfig. The CLI now hardcodes frontest.ts and checks for file existence — if the file is not present, tests are silently skipped. Delete config.json files that only contained the frontest field (7 apps). Keep fitness-tracker config which still has postPush and lock. --- Prompt: Remove frontest from MigrationConfig, hardcode to frontest.ts, skip if file doesn't exist.
Update e2e system README to describe migration/config.json fields and the automatic execution of post-* scripts and frontest.ts. Update migration apps README to explain that the e2e system runs these scripts automatically at the right points in the workflow. --- Prompt: Update docs to describe migration config and explain that the system runs post-* scripts automatically.
Remove postPush from MigrationConfig. All post-* scripts (post-push.ts, post-generate.ts, post-refactor.ts) are now run automatically if the file exists in migration/, using a new runAppScriptIfExists helper. Update docs accordingly. --- Prompt: Remove postPush from config, run all post-* scripts by file existence check.
Extract script execution logic from cli.ts into a new AppScriptExecutor class with frontestGen1, frontestGen2, postPush, postGenerate, and postRefactor methods. The class accepts appPath, gen1BranchName, gen2BranchName in the constructor so all methods are zero-arg. Move git.ts and logger.ts from utils/ into core/ and remove the empty utils/ directory. Update all imports accordingly. --- Prompt: Create AppScriptExecutor class, move utils into core, split frontest into gen1/gen2 methods.
Merge amplify-initializer, app-script-executor, and gen2-migration-executor into a single App class in app.ts. The class accepts appName, profile, and verbose in the constructor, validates the app exists, copies source to a temp directory, and creates its own Logger. All fields are private. The CLI is now a thin script that creates an App and calls its public methods in sequence. Simplify Logger to accept appName as a required constructor arg and derive the log file path internally. Remove all setters. --- Prompt: Consolidate three executor classes into App, make all fields private, move logger creation into App, simplify Logger.
…lpers Move all start/finish/skip logging into runScriptIfExists and runMigrationStep so callers are clean one-liners. Both helpers now log the script path and args. --- Prompt: Centralize logging in runScriptIfExists and runMigrationStep, remove redundant logs from callers.
E2E system: - Refactor Git class with private run() helper and logging - Remove gitignore entries for config files after push (amplifyconfiguration.json) and generate (amplify_outputs*) so they are committed and available on branch switches - Add frontest checkout to gen2 branch in frontestGen2 CLI display: - Fix spacing in assess and refactor plan output - Use green bold for operation group labels --- Prompt: Add gitignore fixes for config files, refactor git.ts, commit everything.
Replace the monolithic frontest.ts script with structured Jest tests in __tests__/fitness-tracker.test.ts. Each model (WorkoutProgram, Exercise, Meal) and REST API (Nutrition, Admin) has its own describe block with isolated tests that assert on actual response values rather than just executing calls. Added jest.config.ts with ts-jest for TypeScript execution without compilation, and updated package.json with test script and devDependencies (jest, ts-jest, @types/jest). --- Prompt: Replace frontest.ts with structured Jest tests that have proper describe blocks, isolated tests, and real assertions on response values.
Convert all migration app frontest.ts scripts into structured Jest test files under tests/. Each app now has: - tests/<app>.test.ts with describe blocks and real assertions - jest.config.ts with ts-jest for TypeScript execution - test:gen1 and test:gen2 npm scripts with APP_CONFIG_PATH env var (overridable from outside via bash default syntax) Update the e2e system to invoke npm run test:gen1/test:gen2 instead of running frontest.ts directly. Rename frontestGen1/ frontestGen2 to testGen1/testGen2 and add runNpmScript helper. Apps converted: discussions, fitness-tracker, imported-resources, media-vault, mood-board, product-catalog, project-boards, store-locator. --- Prompt: Convert all migration app frontest.ts to Jest tests with test:gen1/test:gen2 scripts and APP_CONFIG_PATH env var, update e2e system to use npm scripts.
Add post-generate, post-refactor, and post-push as npm scripts in each migration app's package.json. Default appPath to process.cwd() in all migration scripts so they work when invoked via npm run from the app directory. Update the e2e system to use runNpmScript for post-generate, post-refactor, and post-push instead of runScriptIfExists. Remove the now-unused runScriptIfExists method. --- Prompt: Make post-generate and post-refactor as npm scripts in the app package.json, default appPath to cwd.
…from runNpmScript npm already prints the script name and command when invoked via npm run, so the wrapper logs are noise. --- Prompt: Remove redundant logger lines from runNpmScript since npm reports enough information.
Jest requires ts-node to load TypeScript config files. Switch all migration apps to plain JS configs to avoid the dependency. --- Prompt: Convert jest.config.ts to jest.config.js to avoid ts-node requirement.
… describe names Sync jest.config.js across all migration apps with moduleFileExtensions and allowJs: true. Remove redundant " CRUD" suffix from describe block names in all test files. --- Prompt: Apply jest.config.js changes from project-boards to all apps, remove CRUD from describe blocks.
Reorganize all migration app tests from single monolithic files into category-based test files (api, storage, geo, analytics, auth) with guest/auth describe blocks that test permitted and unpermitted operations based on each app's schema auth rules. Key changes per app: - Shared signup.ts for user provisioning - Tests split by Amplify category (api, storage, geo, etc.) - Separate files for distinct resources (avatars, bookmarks, activity, nutrition API, admin API) - Guest tests assert unauthorized operations are rejected - Auth tests assert full CRUD with field-level assertions Additional improvements: - fitness-tracker: auth.test.ts for PreSignUp email filter - product-catalog: S3 trigger test (upload -> imageUploadedAt) - mood-board: analytics.test.ts with Kinesis write+read, KINESIS_STREAM_NAME extracted to constants.ts - discussions: storage-activity.test.ts with async poll - e2e system: gitDiff after postGenerate/postRefactor, git diff removed from app npm scripts - jest configs: added amplify to modulePathIgnorePatterns, removed maxWorkers, cleaned up test script flags --- Prompt: Restructure all migration app tests by category with guest/auth blocks, add coverage for async triggers and auth rules, move git diff to e2e CLI.
Replace guest getUrl tests with downloadData to actually verify read access (getUrl always resolves regardless of permissions). Add private storage tests to media-vault (upload, download, getUrl, delete, guest-cannot-read). Add --verbose to all jest test scripts. --- Prompt: Fix guest storage tests to use downloadData, add private storage tests for media-vault, add --verbose to jest.
Fix corrupted api-graphql.test.ts in fitness-tracker (missing delete test and garbled assertions). Add test:e2e to all apps and test:gen1/test:gen2 no-ops to backend-only. Fix discussions jest config for JS-only graphql files (ESM transform pattern). --- Prompt: Fix corrupt test file, add missing test:e2e scripts, fix discussions ESM config for .js graphql files.
Add jest.retryTimes(3) via setupFilesAfterEnv in all apps. Use @jest/globals import for discussions ESM config. Fix cli.ts catch block to log error message before exiting. Replace glob with fs.readdir in fitness-tracker post-generate. --- Prompt: Add global 3-time retry for tests, fix error logging in e2e CLI, fix discussions ESM jest setup, remove glob.
Replace hardcoded 'sandbox' branchName with resolveTargetBranch() that reads AWS_BRANCH env var or falls back to the current git branch. The e2e system passes AWS_BRANCH=sandbox when running post-generate. --- Prompt: Apply project-boards post-generate branch resolution pattern to all apps, pass AWS_BRANCH from e2e CLI.
Automate all pure file-manipulation post-generate edits: - media-vault: resourceGroupName, auth callback URLs, backend branchName move - store-locator: PostConfirmation memoryMB and resourceGroupName Replace manual edit instructions in all READMEs with `npm run post-generate` + dependency install block. Keep only edits requiring AWS console lookups (product-catalog AppSync API ID). Add configure-schema scripts for discussions and fitness-tracker. --- Prompt: Automate all file-manipulation post-generate edits, simplify READMEs, add configure-schema scripts.
Register a SIGINT handler in SpinningLogger that stops the spinner before exiting, preventing the cursor from disappearing when the user hits Ctrl+C during a migration command. --- Prompt: Fix cursor disappearing when user hits Ctrl+C while spinner is running.
Update amplify-migration-apps/README.md and the e2e system README to match the current code: Jest test suites instead of frontest.ts, npm scripts instead of dynamic imports, accurate package architecture and workflow steps. --- Prompt: Run the PR stage.
in refactor workflow Replace UpdateStackCommand with a CreateChangeSet + ExecuteChangeSet flow in the refactor workflow. This fixes #14750 where passing Tags: [] to UpdateStackCommand wiped existing stack tags, triggering UPDATE events on all resources. For Cognito user pools, this silently deleted auth triggers because LambdaConfig (defined in a separate stack) was absent from the template and Cognito's UpdateUserPool API replaces the entire configuration. Change sets are now created during plan() and executed during execute(), so a truly empty change set results in a no-op. createChangeSet no longer auto-deletes; a new deleteChangeSet method is provided for preview-only callers. Also fixes #14751 where refactor --rollback failed with a false "Resource already exists" error. The duplicate-resource check in RollbackCategoryRefactorer ran during planning before any moves had executed. The check is moved into Cfn.refactor() where it runs at execution time. Additionally, e2e tests now run immediately after refactor to validate both environments before post-refactor steps. --- Prompt: in gen2-migration cfn.ts, add a method to execute a change set
… apps Add pre-push, post-sandbox hooks to the e2e system and wire up all migration apps for automated end-to-end testing. product-catalog: - pre-push script: sets custom-roles.json app ID, function parameters in team-provider-info.json, and Gen1 secret in SSM - post-generate: resolves Gen1 AppSync API ID via tags instead of writing a placeholder - post-sandbox: writes PRODUCT_CATALOG_SECRET to SSM for Gen2 - tests: fix UserRole type error, add secret value assertion, add userPool auth mode tests, add guest denial tests for User and Comment models mood-board: - tests: add guest denial test for getKinesisEvents store-locator: - migration/config.json: refactor.skipValidations to pass --skip-validations to refactor command media-vault: - migration/config.json: refactor.skip to skip refactor entirely (social providers break refactoring) e2e system: - Add prePush and postSandbox lifecycle hooks to App class - Add refactor.skip and refactor.skipValidations config support - Update cli.ts workflow to call new hooks - All apps: add pre-push and post-sandbox npm scripts (true for apps that don't need them) --- Prompt: Review the migration apps and find automation gaps. Are there manual instructions in the readme that aren't captured in the migration scripts? We need everything to be automated.
…3 trigger key Remove public apiKey auth rule from checkLowStock query so it requires IAM auth only. Fix S3 trigger test to use the correct key path without the public/ prefix.
…, media-vault, mood-board product-catalog: - pre-push: use first 10 chars of app name for custom-roles.json, read deployment name from package.json - post-generate: replace glob with readdirSync for S3 trigger ESM conversion, use AppSync tag lookup via paginator - tests: fix UserRole type, add secret assertion, userPool and guest denial tests media-vault: - pre-push: write dummy social provider creds to deployment-secrets - pre-sandbox: write social provider secrets to SSM before deploy - post-generate: remove unused callback URL functions - tests: add auth.test.ts for admin group access, fix uploadData type annotation mood-board: - signup: configure Kinesis analytics region via parseAmplifyConfig - tests: add guest denial test for getKinesisEvents e2e system: - Add preSandbox lifecycle hook - Add pre-sandbox npm script to all apps --- Prompt: Continue automating remaining migration apps for e2e, fix issues found during test runs.
…erate Remove the externalProviders block from auth/resource.ts during post-generate using brace-counting parser instead of regex. Social login is not tested in e2e and the secrets are complex to provision. Delete the pre-sandbox script that was writing social provider secrets to SSM — no longer needed. --- Prompt: Continue fixing media-vault e2e automation.
Update CFN mock to support the CreateChangeSet + ExecuteChangeSet flow that replaced UpdateStack. The mock now captures template body and parameters from CreateChangeSet, returns them in DescribeChangeSet, and applies the template via _setTemplate on ExecuteChangeSet. This preserves snapshot compatibility with no snapshot file changes. Also fix lock.test.ts mock to include discover() and metaOutput(), and update auth-cognito-forward test to check CreateChangeSet parameters instead of UpdateStack. --- Prompt: Run the cli package tests and fix.
amplify-migration-apps/product-catalog/migration/post-generate.ts
Dismissed
Show dismissed
Hide dismissed
Replace removeExternalProviders with monkeyPatchAuthSecret in the post-generate script. Instead of stripping social login config entirely, the new approach keeps external providers intact and replaces the imported secret() with a local implementation backed by SecretValue.unsafePlainText. This allows the app to deploy without real secrets while preserving the full auth resource structure. --- Prompt: in post generate of media vault, write code to monkey patch the auth resource so that calls to secret() use SecretValue.unsafePlainText with local- prefix
dgandhi62
requested changes
Apr 6, 2026
dgandhi62
approved these changes
Apr 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of changes
Rewrites the gen2-migration e2e system and restructures all migration apps. The goal is to make the e2e workflow simpler to understand, debug, and extend.
E2E System (
amplify-gen2-migration-e2e-system)1. Replace category initializer with snapshot restore
Before:
Each migration app had a
migration-config.json— a custom DSL that described the app's categories in a JSON schema the e2e system invented:{ "categories": { "auth": { "signInMethods": ["phone"], "socialProviders": [] }, "api": { "type": "GraphQL", "authModes": ["API_KEY"], "schema": "schema.graphql" }, "storage": { "buckets": [...], "tables": [{ "name": "activity", "partitionKey": "id", "gsi": [...] }] }, "function": { "functions": [{ "name": "fetchuseractivity", "runtime": "nodejs", "template": "hello-world" }] } } }The
CategoryInitializer(644 lines) parsed this DSL and translated each entry into the corresponding@aws-amplify/amplify-e2e-corehelper call (addAuthWithEmail,addApi,addS3Storage,addDynamoDBWithGSIWithSettings,addFunction,addLambdaTriggerWithModels, etc.). This required maintaining a mapping layer between the DSL and the e2e-core prompt-driven helpers — every new category feature or CLI prompt change required updating both the DSL schema and the translator. The DSL also couldn't express everything the CLI supports (auth triggers, custom roles, complex storage configurations), so apps that needed those features required workarounds.After:
The e2e system restores the pre-generate snapshot (
_snapshot.pre.generate/amplify/) directly into theamplify/directory afteramplify init. This gives us the exact Gen1 project state — all categories, all resources, all configuration — without running anyamplify addcommands. TheApp.configure()method handles merging the init-generated metadata (CloudFormation provider info, environment config) with the snapshot's backend definitions.This eliminates
CategoryInitializer,ConfigurationLoader, thetypes/index.tsDSL definitions (227 lines), and allmigration-config.jsonfiles across every app.2. Consolidate into a single
AppclassBefore:
The e2e system was spread across 15+ files:
cli.ts(657 lines of orchestration),AmplifyInitializer,CategoryInitializer,ConfigurationLoader,AppSelector,CDKAtmosphereIntegration,Gen2MigrationExecutor,EnvironmentDetector,DirectoryManager,FileManager,AWSProfileManager,Logger,types/index.ts, plus unit tests and e2e tests for each. Total: ~4,400 lines.After:
Three files:
App(owns the full lifecycle),Git(commit/checkout),Logger. Total: ~650 lines. The migration workflow incli.tsreads as a flat sequence of method calls:Atmosphere support removed
The
CDKAtmosphereIntegrationclass and all atmosphere-related code paths are removed. We haven't determined whether atmosphere will actually be used in the migration workflow, and carrying its maintenance overhead — wiring, configuration surface, test coverage — isn't justified while that decision is still open. The code is in git history and can be restored straightforwardly if we decide to adopt it.E2E test ordering
testGen1andtestGen2now run immediately afterrefactor, beforegitCheckoutGen2/postRefactor. This validates both environments right after the refactor completes.Migration Apps (
amplify-migration-apps/)3. Replace custom test scripts with Jest suites
Before:
Each app had three files for testing:
test-utils.ts(a factory that created test functions and an orchestrator),gen1-test-script.ts, andgen2-test-script.ts. The gen1/gen2 scripts were nearly identical — they differed only in which config file they imported. The test-utils files were 300-700 lines each, with a customTestRunnerclass, manual console logging, andprocess.exit(1)on failure. A shared_test-common/directory providedsignup.tsandrunner.tsthat all apps imported via relative paths.After:
Each app has a
tests/directory with standard Jest test files (api.test.ts,storage.test.ts, etc.) and its ownsignup.tstailored to its auth config. The same test files run against both Gen1 and Gen2 — theAPP_CONFIG_PATHenv var controls which config is loaded. npm scriptstest:gen1andtest:gen2set this variable. The shared_test-common/directory is removed.4. Expose migration scripts as npm scripts
Before:
The e2e system dynamically imported each app's
post-generate.tsandpost-refactor.tsfrom the source app directory usingimport(). The scripts accepted an options object ({ appPath, envName }) and the e2e system had to know the function signature.After:
Each app's
package.jsondefines npm scripts (post-generate,post-refactor,post-push) that the e2e system runs vianpm run <script>. Apps that don't need a step set it to"true". The e2e system doesn't need to know anything about the scripts' internals.5. Restructure backend assets
Backend source files (GraphQL schemas, Lambda function code, configure scripts) moved from the app root into a
backend/subdirectory. This separates backend assets from test files, migration scripts, and app source code.CLI Fixes (
amplify-cli)Fix
lockfailing to find GraphQL API (#14560)fetchGraphQLApiIdmatched APIs by name using the pattern${appName}-${envName}, which isn't guaranteed to match the actual AppSync API name. When it didn't match, the API was silently missed and DynamoDB model tables never got deletion protection enabled. The newfindGraphQLApiIdreads the API ID directly fromamplify-meta.json(the source of truth) instead of scanningListGraphqlApiswith a name heuristic.Fix
refactordeleting user pool triggers (#14750)The
update()method passedTags: []toUpdateStackCommand, which wiped out existing stack tags and triggered an UPDATE event on every resource in the template. For Cognito user pools, this was destructive:LambdaConfig(auth triggers) is defined in a separate stack and not present in the auth stack template, and Cognito'sUpdateUserPoolAPI doesn't support PATCH semantics — it replaces the entire configuration. The combination caused auth triggers to be silently deleted. The fix removes theTags: []parameter. Additionally, stack updates now useExecuteChangeSetinstead ofUpdateStackCommand, so a truly empty change set results in a no-op rather than a spurious update.Fix
refactor --rollbackfailing with false duplicate-resource error (#14751)refactor --rollbackfailed with "Resource already exists in Gen2 stack" becauseRollbackCategoryRefactorer.afterMove()checked whether the target stack already contained the resource being moved. This check ran during planning, before any moves had executed — so the resource was naturally still present in the Gen2 stack. The fix removes the premature check from the rollback planner and moves the duplicate-resource guard intoCfn.refactor(), which runs at execution time when the stack state is current. The bug was introduced in PR #14698 and wasn't caught because snapshot tests don't exercise rollback scenarios.Cursor restore on SIGINT
SpinningLoggernow registers a SIGINT handler that stops the spinner before exiting. Previously, Ctrl-C during a spinner-active migration step left the terminal cursor hidden.Output formatting
Minor improvements to
assess,lock, andrefactorCLI output (spacing, colors).Issue #, if available
#14560, #14750, #14751
Description of how you validated changes
Ran the full e2e migration workflow against all migration apps.
Checklist
yarn testpassesBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.