@aws-amplify/cli-internal
Version:
Amplify CLI
457 lines • 27.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cfn = exports.MIGRATION_PLACEHOLDER_RESOURCE = exports.HOLDING_STACK_FORWARD_MAPPINGS_METADATA_KEY = exports.MIGRATION_PLACEHOLDER_LOGICAL_ID = exports.VALID_HOLDING_STACK_STATUSES = exports.HOLDING_STACK_NAME_SUFFIX = exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY = void 0;
const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
const client_s3_1 = require("@aws-sdk/client-s3");
const amplify_cli_core_1 = require("@aws-amplify/amplify-cli-core");
const utils_1 = require("./utils");
const drift_formatter_1 = require("../../drift/services/drift-formatter");
const chalk_1 = __importDefault(require("chalk"));
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const MAX_WAIT_TIME_SECONDS = 900;
const NO_UPDATES_MESSAGE = 'No updates are to be performed';
const CFN_IAM_CAPABILITY = 'CAPABILITY_NAMED_IAM';
exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY = '.amplify/gen2-migration/refactor.operations';
const EMPTY_HOLDING_TEMPLATE = {
AWSTemplateFormatVersion: '2010-09-09',
Description: 'Temporary holding stack for Gen2 migration',
Resources: {},
Outputs: {},
};
exports.HOLDING_STACK_NAME_SUFFIX = '-holding';
exports.VALID_HOLDING_STACK_STATUSES = ['UPDATE_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE', 'CREATE_COMPLETE'];
exports.MIGRATION_PLACEHOLDER_LOGICAL_ID = 'MigrationPlaceholder';
exports.HOLDING_STACK_FORWARD_MAPPINGS_METADATA_KEY = 'ForwardMappings';
exports.MIGRATION_PLACEHOLDER_RESOURCE = { Type: 'AWS::CloudFormation::WaitConditionHandle', Properties: {} };
class Cfn {
constructor(gen1App, logger) {
this.gen1App = gen1App;
this.logger = logger;
this.updateStackClaims = new Set();
if (!fs.existsSync(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY)) {
fs.mkdirSync(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, { recursive: true });
}
}
isUpdateClaimed(stackName) {
return this.updateStackClaims.has(stackName);
}
claimUpdate(stackName) {
this.updateStackClaims.add(stackName);
}
async update(params) {
const { stackName, parameters, templateBody, resource, snapshotPrefix } = params;
try {
const templateUrl = await this.uploadTemplate(JSON.stringify(templateBody), stackName, 'update');
const input = {
TemplateURL: templateUrl,
Parameters: parameters,
StackName: stackName,
Capabilities: [CFN_IAM_CAPABILITY],
};
writeUpdateSnapshot({ stackName, templateBody: JSON.stringify(templateBody), parameters, prefix: snapshotPrefix !== null && snapshotPrefix !== void 0 ? snapshotPrefix : 'update' });
this.info(`Updating stack: ${(0, utils_1.extractStackNameFromId)(stackName)}`, resource);
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.UpdateStackCommand(input));
}
catch (e) {
if (e && typeof e === 'object' && 'message' in e && typeof e.message === 'string' && e.message.includes(NO_UPDATES_MESSAGE)) {
return;
}
throw e;
}
this.info(`Waiting for stack update to complete: ${(0, utils_1.extractStackNameFromId)(stackName)}`, resource);
await (0, client_cloudformation_1.waitUntilStackUpdateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: stackName });
}
async refactor(resourceMappings, resource, pre) {
var _a;
const sourceStackId = resourceMappings[0].Source.StackName;
const targetStackId = resourceMappings[0].Destination.StackName;
const sourceStackName = (0, utils_1.extractStackNameFromId)(sourceStackId);
const targetStackName = (0, utils_1.extractStackNameFromId)(targetStackId);
this.info(`Refactoring ${sourceStackName} → ${targetStackName}`, resource);
const targetStack = await this.findStack(targetStackId);
const sourceStack = await this.findStack(sourceStackId);
if (!targetStack && !targetStackName.endsWith(exports.HOLDING_STACK_NAME_SUFFIX)) {
throw new amplify_cli_core_1.AmplifyError('StackNotFoundError', { message: `Target stack ${targetStackName} does not exist` });
}
if (!sourceStack) {
throw new amplify_cli_core_1.AmplifyError('StackNotFoundError', { message: `Source stack ${sourceStackName} does not exist` });
}
const sourceTemplate = await this.fetchTemplate(sourceStackId);
const sourceTemplateClone = JSON.parse(JSON.stringify(sourceTemplate));
const targetTemplate = targetStack ? await this.fetchTemplate(targetStackId) : JSON.parse(JSON.stringify(EMPTY_HOLDING_TEMPLATE));
for (const mapping of resourceMappings) {
if (mapping.Destination.LogicalResourceId in targetTemplate.Resources) {
throw new amplify_cli_core_1.AmplifyError('ResourceMappingError', {
message: `Unable to create stack refactor. Resource ${mapping.Destination.LogicalResourceId} already exists in stack ${targetStackName}`,
});
}
targetTemplate.Resources[mapping.Destination.LogicalResourceId] = sourceTemplate.Resources[mapping.Source.LogicalResourceId];
delete sourceTemplate.Resources[mapping.Source.LogicalResourceId];
}
if (Object.keys(sourceTemplate.Resources).length === 0) {
sourceTemplateClone.Resources[exports.MIGRATION_PLACEHOLDER_LOGICAL_ID] = exports.MIGRATION_PLACEHOLDER_RESOURCE;
sourceTemplate.Resources[exports.MIGRATION_PLACEHOLDER_LOGICAL_ID] = exports.MIGRATION_PLACEHOLDER_RESOURCE;
this.info(`Adding placeholder resource to source stack '${sourceStackName}'`);
await this.update({
stackName: sourceStackId,
templateBody: sourceTemplateClone,
parameters: (_a = sourceStack.Parameters) !== null && _a !== void 0 ? _a : [],
resource,
});
this.info(`Finished adding placeholder to source stack '${sourceStackName}'`);
}
if (pre) {
await pre(targetTemplate);
}
const sourceTemplateUrl = await this.uploadTemplate(JSON.stringify(sourceTemplate), sourceStackName, 'refactor');
const targetTemplateUrl = await this.uploadTemplate(JSON.stringify(targetTemplate), targetStackName, 'refactor');
const input = {
StackDefinitions: [
{ StackName: sourceStackName, TemplateURL: sourceTemplateUrl },
{ StackName: targetStackName, TemplateURL: targetTemplateUrl },
],
ResourceMappings: resourceMappings,
EnableStackCreation: true,
};
input.Description = buildRefactorDescription(input);
writeRefactorSnapshot(input, JSON.stringify(sourceTemplate), JSON.stringify(targetTemplate));
this.info(`Creating stack refactor: ${(0, utils_1.extractStackNameFromId)(sourceStackId)} → ${(0, utils_1.extractStackNameFromId)(targetStackId)}`, resource);
const { StackRefactorId } = await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.CreateStackRefactorCommand(input));
if (!StackRefactorId) {
throw new amplify_cli_core_1.AmplifyError('StackStateError', {
message: 'CreateStackRefactor returned no StackRefactorId',
});
}
this.info(`Waiting for stack refactor creation to complete: ${StackRefactorId}`, resource);
await (0, client_cloudformation_1.waitUntilStackRefactorCreateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackRefactorId });
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.ExecuteStackRefactorCommand({ StackRefactorId }));
this.info(`Waiting for stack refactor execution to complete: ${StackRefactorId}`, resource);
await (0, client_cloudformation_1.waitUntilStackRefactorExecuteComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackRefactorId });
this.info(`Waiting for source stack update: ${(0, utils_1.extractStackNameFromId)(sourceStackId)}`, resource);
await (0, client_cloudformation_1.waitUntilStackUpdateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: sourceStackId });
this.info(`Waiting for destination stack: ${(0, utils_1.extractStackNameFromId)(targetStackId)}`, resource);
if (targetStack) {
await (0, client_cloudformation_1.waitUntilStackUpdateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: targetStackId });
}
else {
await (0, client_cloudformation_1.waitUntilStackCreateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: targetStackId });
}
this.info(`Finished refactoring ${sourceStackName} → ${targetStackName}`, resource);
}
async createChangeSet(params) {
var _a;
const { stackName, parameters, templateBody } = params;
const changeSetName = `gen2-migration-${Date.now()}`;
const templateUrl = await this.uploadTemplate(JSON.stringify(templateBody), stackName, 'create-change-set');
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.CreateChangeSetCommand({
StackName: stackName,
ChangeSetName: changeSetName,
TemplateURL: templateUrl,
Parameters: parameters,
Capabilities: [CFN_IAM_CAPABILITY],
}));
try {
await (0, client_cloudformation_1.waitUntilChangeSetCreateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: 120 }, { StackName: stackName, ChangeSetName: changeSetName });
}
catch (e) {
if ((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes(`The submitted information didn't contain changes`)) {
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.DeleteChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName }));
return undefined;
}
throw e;
}
return await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.DescribeChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName, IncludePropertyValues: true }));
}
async executeChangeSet(params) {
var _a, _b;
const { changeSet, templateBody, resource } = params;
const displayName = (0, utils_1.extractStackNameFromId)(changeSet.StackName);
if ((_a = params.captureSnapshot) !== null && _a !== void 0 ? _a : true) {
writeUpdateSnapshot({
stackName: changeSet.StackName,
templateBody: JSON.stringify(templateBody),
parameters: (_b = changeSet.Parameters) !== null && _b !== void 0 ? _b : [],
prefix: 'update',
});
}
this.info(`Executing change set for stack: ${displayName}`, resource);
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.ExecuteChangeSetCommand({ StackName: changeSet.StackName, ChangeSetName: changeSet.ChangeSetName }));
this.info(`Waiting for stack update to complete: ${displayName}`, resource);
await (0, client_cloudformation_1.waitUntilStackUpdateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: changeSet.StackName });
}
async orphan(params) {
var _a;
const { stackName, logicalIds, resource } = params;
const displayName = (0, utils_1.extractStackNameFromId)(stackName);
const template = await this.fetchTemplate(stackName);
const missingRetain = logicalIds.filter((id) => id in template.Resources && template.Resources[id].DeletionPolicy !== 'Retain');
if (missingRetain.length > 0) {
throw new amplify_cli_core_1.AmplifyError('MigrationError', {
message: `Cannot orphan resources from '${displayName}': ${missingRetain.join(', ')} ` +
`missing 'DeletionPolicy: Retain' - orphaning would delete the physical resources.`,
});
}
const stack = await this.describeStack(stackName);
for (const id of logicalIds) {
delete template.Resources[id];
}
writeOrphanSnapshot({ stackName, logicalIds, prefix: 'orphan' });
await this.update({
stackName,
templateBody: template,
parameters: (_a = stack.Parameters) !== null && _a !== void 0 ? _a : [],
resource,
snapshotPrefix: 'orphan',
});
}
async importResources(params) {
const { stackName, templateAdditions, resourcesToImport, resource } = params;
const displayName = (0, utils_1.extractStackNameFromId)(stackName);
const changeSetName = `gen2-migration-import-${Date.now()}`;
const templateBody = await this.fetchTemplate(stackName);
for (const [logicalId, r] of Object.entries(templateAdditions)) {
templateBody.Resources[logicalId] = r;
}
writeImportSnapshot({
stackName,
templateBody: JSON.stringify(templateBody),
parameters: [],
resourcesToImport,
prefix: 'import',
});
this.info(`Creating import changeset for ${displayName} (${resourcesToImport.length} resource(s))`, resource);
const templateUrl = await this.uploadTemplate(JSON.stringify(templateBody), stackName, 'import');
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.CreateChangeSetCommand({
StackName: stackName,
ChangeSetName: changeSetName,
ChangeSetType: 'IMPORT',
TemplateURL: templateUrl,
ResourcesToImport: resourcesToImport,
Capabilities: [CFN_IAM_CAPABILITY],
}));
this.info(`Waiting for import changeset creation: ${displayName}`, resource);
await (0, client_cloudformation_1.waitUntilChangeSetCreateComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: 120 }, { StackName: stackName, ChangeSetName: changeSetName });
this.info(`Executing import changeset: ${displayName}`, resource);
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.ExecuteChangeSetCommand({ StackName: stackName, ChangeSetName: changeSetName }));
this.info(`Waiting for import to complete: ${displayName}`, resource);
await (0, client_cloudformation_1.waitUntilStackImportComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: MAX_WAIT_TIME_SECONDS }, { StackName: stackName });
this.info(`Import complete: ${displayName}`, resource);
}
async deleteChangeSet(changeSet) {
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.DeleteChangeSetCommand({ StackName: changeSet.StackName, ChangeSetName: changeSet.ChangeSetName }));
}
async findStack(stackName) {
var _a, _b;
try {
const response = await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.DescribeStacksCommand({ StackName: stackName }));
const stack = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0];
if (stack && stack.StackStatus !== 'DELETE_COMPLETE') {
return stack;
}
return null;
}
catch (error) {
if (error instanceof client_cloudformation_1.CloudFormationServiceException &&
error.name === 'ValidationError' &&
((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('does not exist'))) {
return null;
}
throw error;
}
}
async describeStack(stackName) {
const stack = await this.findStack(stackName);
if (!stack) {
throw new amplify_cli_core_1.AmplifyError('StackNotFoundError', {
message: `Stack '${(0, utils_1.extractStackNameFromId)(stackName)}' does not exist`,
});
}
return stack;
}
async fetchTemplate(stackName) {
const response = await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.GetTemplateCommand({ StackName: stackName, TemplateStage: 'Original' }));
if (!response.TemplateBody) {
throw new amplify_cli_core_1.AmplifyError('InvalidStackError', {
message: `Stack '${(0, utils_1.extractStackNameFromId)(stackName)}' returned an empty template`,
});
}
return JSON.parse(response.TemplateBody);
}
async deleteStack(stackName, resource) {
var _a;
try {
this.info(`Deleting stack: ${(0, utils_1.extractStackNameFromId)(stackName)}`, resource);
await this.gen1App.clients.cloudFormation.send(new client_cloudformation_1.DeleteStackCommand({ StackName: stackName }));
this.info(`Waiting for stack deletion: ${(0, utils_1.extractStackNameFromId)(stackName)}`, resource);
await (0, client_cloudformation_1.waitUntilStackDeleteComplete)({ client: this.gen1App.clients.cloudFormation, maxWaitTime: 300 }, { StackName: stackName });
}
catch (error) {
if (error instanceof client_cloudformation_1.CloudFormationServiceException &&
error.name === 'ValidationError' &&
((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('does not exist'))) {
return;
}
throw error;
}
}
renderChangeSet(changeSet) {
var _a, _b, _c, _d, _e;
const changes = (_a = changeSet.Changes) !== null && _a !== void 0 ? _a : [];
if (changes.length === 0)
return 'No changes';
const colorAction = (action) => {
if (action === 'Add')
return chalk_1.default.green(action);
if (action === 'Remove')
return chalk_1.default.red(action);
return chalk_1.default.yellow(action);
};
const lines = [];
if (changeSet.ChangeSetId) {
const consoleUrl = (0, drift_formatter_1.cfnChangesetConsoleUrl)(changeSet.ChangeSetId, changeSet.StackId);
if (consoleUrl) {
lines.push(chalk_1.default.dim(consoleUrl));
}
}
for (const change of changes) {
const rc = change.ResourceChange;
if (!rc)
continue;
const action = (_b = rc.Action) !== null && _b !== void 0 ? _b : 'Unknown';
const logicalId = (_c = rc.LogicalResourceId) !== null && _c !== void 0 ? _c : 'Unknown';
const resourceType = (_d = rc.ResourceType) !== null && _d !== void 0 ? _d : 'Unknown';
const replacement = rc.Replacement;
const displayAction = action === 'Modify' && replacement === 'True'
? 'Replace'
: action === 'Modify' && replacement === 'Conditional'
? 'Replace (conditional)'
: action;
const isReplace = displayAction === 'Replace' || displayAction === 'Replace (conditional)';
const header = isReplace
? chalk_1.default.bold.red(`${logicalId} (${resourceType}) ${displayAction}`)
: [chalk_1.default.bold(logicalId), chalk_1.default.dim(`(${resourceType})`), colorAction(displayAction)].join(' ');
lines.push('');
lines.push(header);
const details = ((_e = rc.Details) !== null && _e !== void 0 ? _e : []).filter((d) => { var _a; return !!((_a = d.Target) === null || _a === void 0 ? void 0 : _a.Attribute); });
const paths = details.map((d) => {
const { Attribute: attribute, Path: targetPath, Name: targetName } = d.Target;
if (targetPath)
return targetPath;
if (attribute === 'Properties')
return targetName ? `/Properties/${targetName}` : '/Properties';
return `/${attribute}`;
});
const pathWidth = Math.max(0, ...paths.map((p) => p.length));
details.forEach((detail, i) => {
const path = paths[i];
const before = detail.Target.BeforeValue;
const after = detail.Target.AfterValue;
const paddedPath = path.padEnd(pathWidth);
if (before && after) {
lines.push(` ${paddedPath} ${chalk_1.default.red(`(-) ${before}`)} ${chalk_1.default.dim('→')} ${chalk_1.default.green(`(+) ${after}`)}`);
}
else if (after) {
lines.push(` ${paddedPath} ${chalk_1.default.green(`(+) ${after}`)}`);
}
else if (before) {
lines.push(` ${paddedPath} ${chalk_1.default.red(`(-) ${before}`)}`);
}
else {
lines.push(` ${paddedPath} ${chalk_1.default.dim('(changed)')}`);
}
});
}
return lines.join('\n').trimStart();
}
async uploadTemplate(templateBody, stackNameOrArn, operation) {
const key = `gen2-migration/${operation}.${(0, utils_1.extractStackNameFromId)(stackNameOrArn)}.${Date.now()}.template.json`;
await this.gen1App.clients.s3.send(new client_s3_1.PutObjectCommand({
Bucket: this.gen1App.deploymentBucket,
Key: key,
Body: templateBody,
ContentType: 'application/json',
}));
const region = await this.gen1App.clients.s3.config.region();
return `https://s3.${region}.amazonaws.com/${this.gen1App.deploymentBucket}/${key}`;
}
info(message, resource) {
const prefix = resource ? `[${resource.category}/${resource.resourceName}] ` : '';
this.logger.info(`${prefix}${message}`);
}
}
exports.Cfn = Cfn;
function buildRefactorDescription(input) {
const logicalIds = input.ResourceMappings.map((m) => { var _a; return (_a = m.Source) === null || _a === void 0 ? void 0 : _a.LogicalResourceId; }).join(', ');
const source = (0, utils_1.extractStackNameFromId)(input.StackDefinitions[0].StackName);
const dest = (0, utils_1.extractStackNameFromId)(input.StackDefinitions[1].StackName);
return `Move [${logicalIds}] from ${source} to ${dest}`;
}
function formatTemplateBody(templateBody) {
return JSON.stringify(JSON.parse(templateBody), null, 2) + '\n';
}
function writeImportSnapshot(input) {
const stackName = (0, utils_1.extractStackNameFromId)(input.stackName);
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.template.json`), formatTemplateBody(input.templateBody));
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.parameters.json`), JSON.stringify(input.parameters, null, 2) + '\n');
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.resources.json`), JSON.stringify(input.resourcesToImport, null, 2) + '\n');
}
function writeUpdateSnapshot(input) {
const stackName = (0, utils_1.extractStackNameFromId)(input.stackName);
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.template.json`), formatTemplateBody(input.templateBody));
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.parameters.json`), JSON.stringify(input.parameters, null, 2) + '\n');
}
function writeOrphanSnapshot(input) {
const stackName = (0, utils_1.extractStackNameFromId)(input.stackName);
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${input.prefix}.${stackName}.logicalIds.json`), JSON.stringify(input.logicalIds, null, 2) + '\n');
}
function writeRefactorSnapshot(input, sourceTemplateBody, targetTemplateBody) {
var _a;
const source = input.StackDefinitions[0];
const target = input.StackDefinitions[1];
const sourceStackName = (0, utils_1.extractStackNameFromId)(source.StackName);
const targetStackName = (0, utils_1.extractStackNameFromId)(target.StackName);
const description = `refactor.__from__.${sourceStackName}.__to__.${targetStackName}`;
const basePath = path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, description);
writeRefactorSnapshotFile(`${basePath}.source.template.json`, formatTemplateBody(sourceTemplateBody));
writeRefactorSnapshotFile(`${basePath}.target.template.json`, formatTemplateBody(targetTemplateBody));
writeRefactorSnapshotFile(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, `${description}.mappings.json`), JSON.stringify((_a = input.ResourceMappings) !== null && _a !== void 0 ? _a : [], null, 2) + '\n');
}
const FILENAME_MAPPING_FILE = 'filename-mapping.json';
function writeRefactorSnapshotFile(filename, content) {
const hash = crypto.createHash('sha256').update(filename).digest('hex').slice(0, 10);
const hashedFilename = `${hash}.json`;
fs.writeFileSync(path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, hashedFilename), content);
const mappingPath = path.join(exports.REFACTOR_SNAPSHOT_OUTPUT_DIRECTORY, FILENAME_MAPPING_FILE);
const mapping = fs.existsSync(mappingPath) ? JSON.parse(fs.readFileSync(mappingPath, 'utf-8')) : {};
mapping[hash] = path.basename(filename);
fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2) + '\n');
}
//# sourceMappingURL=cfn.js.map