Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 107 additions & 4 deletions packages/amplify-gen2-migration-e2e-system/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<void> {
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.
*
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -497,21 +584,37 @@ 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 });

// 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;
}
}
Expand Down
Loading