diff --git a/packages/amplify-gen2-migration-e2e-system/src/cli.ts b/packages/amplify-gen2-migration-e2e-system/src/cli.ts index 05b157f175..465f03b6f1 100644 --- a/packages/amplify-gen2-migration-e2e-system/src/cli.ts +++ b/packages/amplify-gen2-migration-e2e-system/src/cli.ts @@ -23,6 +23,7 @@ import { generateTimeBasedE2EAmplifyAppName } from './utils/math'; import path from 'path'; import os from 'os'; import fs from 'fs'; +import * as fsExtra from 'fs-extra'; import { getCLIPath } from '@aws-amplify/amplify-e2e-core'; /** Options passed to app-specific post-generate scripts */ @@ -363,6 +364,75 @@ async function runPostRefactorScript(appName: string, targetAppPath: string, env logger.info(`Post-refactor script completed for ${appName}`); } +/** + * Run the app's gen1-test-script.ts to validate the Gen1 deployment. + * + * Copies _test-common to the migration target directory so relative + * imports like ../_test-common resolve, then executes the test script + * via npx tsx from the target app directory. + */ +async function runGen1TestScript(targetAppPath: string, migrationTargetPath: string, sourceAppsBasePath: string): Promise { + const testScriptName = 'gen1-test-script.ts'; + + // Copy _test-common so ../_test-common imports resolve from the target app dir + const testCommonSource = path.join(sourceAppsBasePath, '_test-common'); + const testCommonDest = path.join(migrationTargetPath, '_test-common'); + logger.info(`Copying _test-common to ${testCommonDest}`); + await fsExtra.copy(testCommonSource, testCommonDest, { overwrite: true }); + + // Install dependencies for the test script (aws-amplify, etc.) + logger.info(`Installing dependencies in ${targetAppPath}`); + await execa('npm', ['install'], { cwd: targetAppPath }); + + // Install dependencies for _test-common + logger.info(`Installing _test-common dependencies in ${testCommonDest}`); + await execa('npm', ['install'], { cwd: testCommonDest }); + + logger.info(`Running ${testScriptName} in ${targetAppPath}`); + const result = await execa('npx', ['tsx', testScriptName], { + cwd: targetAppPath, + reject: false, + env: { ...process.env, AWS_SDK_LOAD_CONFIG: '1' }, + }); + + const stdout = result.stdout || ''; + const stderr = result.stderr || ''; + + // Partition stdout into test-result summary lines (INFO) and everything else (DEBUG) + const isTestResultLine = (line: string): boolean => { + const trimmed = line.trim(); + return ( + trimmed.startsWith('✅') || + trimmed.startsWith('❌') || + trimmed.includes('TEST SUMMARY') || + trimmed.includes('All tests passed') || + trimmed.includes('test(s) failed') + ); + }; + + if (stdout) { + const lines = stdout.split('\n'); + const debugLines = lines.filter((l) => !isTestResultLine(l)).join('\n'); + if (debugLines.trim()) { + logger.debug(`[test-script] stdout:\n${debugLines}`); + } + for (const line of lines.filter(isTestResultLine)) { + logger.info(`[test-script] ${line.trim()}`); + } + } + if (stderr) { + logger.debug(`[test-script] stderr:\n${stderr}`); + } + + if (result.exitCode !== 0) { + // Include output in the error so it's visible even without --verbose + const combinedOutput = [stdout, stderr].filter(Boolean).join('\n'); + throw new Error(`${testScriptName} failed with exit code ${result.exitCode}\n${combinedOutput}`); + } + + logger.info(`${testScriptName} completed successfully`); +} + /** * Spawn the amplify CLI directly to run amplify push --yes. * @@ -402,11 +472,13 @@ async function initializeAppFromCLI(params: InitializeAppFromCLIParams): Promise const sourceAppPath = appSelector.getAppPath(appName); logger.debug(`Source app path: ${sourceAppPath}`, context); - logger.debug(`Config app name: ${config.app.name}`, context); + // Derive sourceAppsBasePath once for all test script calls + const sourceAppsBasePath = path.dirname(sourceAppPath); + try { - // Create the target directory, where we will store our Amplify app + // Create target directory and copy source const targetAppPath = await directoryManager.createAppDirectory({ basePath: migrationTargetPath, appName: deploymentName, @@ -424,7 +496,7 @@ async function initializeAppFromCLI(params: InitializeAppFromCLIParams): Promise logger.debug(`Running amplify init in ${targetAppPath}`, context); - // Use profile-based initialization (works for both atmosphere and local environments) + // Amplify init logger.debug(`Using AWS profile '${profile}' for Amplify initialization`, context); await amplifyInitializer.initializeApp({ appPath: targetAppPath, @@ -456,11 +528,26 @@ async function initializeAppFromCLI(params: InitializeAppFromCLIParams): Promise throw new Error(`Failed to initialize ${categoryResult.errors.length} category(ies)`); } + // Run configure.sh if present (copies custom source files into amplify/backend/) + const configureScriptPath = path.join(targetAppPath, 'configure.sh'); + if (fs.existsSync(configureScriptPath)) { + logger.info(`Running configure.sh for ${deploymentName}...`, context); + await execa('bash', ['configure.sh'], { cwd: targetAppPath }); + logger.info(`Successfully ran configure.sh for ${deploymentName}`, context); + } else { + logger.debug(`No configure.sh found for ${deploymentName}, skipping`, context); + } + // Push the initialized app to AWS logger.info(`Pushing ${deploymentName} to AWS...`, context); await amplifyPush(targetAppPath); logger.info(`Successfully pushed ${deploymentName} to AWS`, context); + // Run gen1 test script to validate the Gen1 deployment + logger.info(`Running gen1 test script (post-push) for ${deploymentName}...`, context); + await runGen1TestScript(targetAppPath, migrationTargetPath, sourceAppsBasePath); + logger.info(`Gen1 test script passed (post-push) for ${deploymentName}`, context); + // Initialize git repo and commit the Gen1 state logger.info(`Initializing git repository for ${deploymentName}...`, context); await execa('git', ['init'], { cwd: targetAppPath }); @@ -497,6 +584,11 @@ async function initializeAppFromCLI(params: InitializeAppFromCLIParams): Promise await gen2MigrationExecutor.refactor(targetAppPath, gen2StackName); logger.info(`Successfully completed gen2-migration refactor for ${deploymentName}`, context); + // Run gen1 test script to validate post-refactor state + logger.info(`Running gen1 test script (post-refactor) for ${deploymentName}...`, context); + await runGen1TestScript(targetAppPath, migrationTargetPath, sourceAppsBasePath); + logger.info(`Gen1 test script passed (post-refactor) for ${deploymentName}`, context); + // Checkout back to Gen2 branch for post-refactor edits logger.info(`Checking out ${gen2BranchName} branch for post-refactor edits...`, context); await execa('git', ['checkout', gen2BranchName], { cwd: targetAppPath }); @@ -504,14 +596,25 @@ async function initializeAppFromCLI(params: InitializeAppFromCLIParams): Promise // Run app-specific post-refactor script await runPostRefactorScript(appName, targetAppPath, envName); + // Commit post-refactor changes + logger.info(`Committing post-refactor changes for ${deploymentName}...`, context); + await execa('git', ['add', '.'], { cwd: targetAppPath }); + await execa('git', ['commit', '-m', 'fix: post-refactor edits'], { cwd: targetAppPath }); + logger.info(`Post-refactor changes committed`, context); + // Redeploy Gen2 to pick up post-refactor changes logger.info(`Redeploying Gen2 app after refactor for ${deploymentName}...`, context); await gen2MigrationExecutor.deployGen2Sandbox(targetAppPath, deploymentName, gen2BranchName); logger.info(`Gen2 app redeployed successfully`, context); + // Run gen1 test script to validate final deployment + logger.info(`Running gen1 test script (post-redeployment) for ${deploymentName}...`, context); + await runGen1TestScript(targetAppPath, migrationTargetPath, sourceAppsBasePath); + logger.info(`Gen1 test script passed (post-redeployment) for ${deploymentName}`, context); + logger.info(`App ${deploymentName} fully initialized and migrated at ${targetAppPath}`, context); } catch (error) { - logger.error(`Failed to initialize/migrate ${appName}`, error as Error, context); + logger.error(`Failed to initialize ${appName}`, error as Error, context); throw error; } }