@aws-amplify/cli-internal
Version:
Amplify CLI
248 lines • 12.2 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeoResourceGenerator = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs/promises"));
const cdk_from_cfn = __importStar(require("cdk-from-cfn"));
const cfn_condition_resolver_1 = require("../../../refactor/resolvers/cfn-condition-resolver");
const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
const amplify_cli_core_1 = require("@aws-amplify/amplify-cli-core");
const ts_1 = require("../../ts");
const geo_resource_renderer_1 = require("./geo-resource.renderer");
class GeoResourceGenerator {
constructor(gen1App, outputDir, resource, geoGenerator, logger) {
this.renderer = new geo_resource_renderer_1.GeoResourceRenderer();
this.gen1App = gen1App;
this.outputDir = outputDir;
this.resource = resource;
this.geoGenerator = geoGenerator;
this.logger = logger;
}
async plan() {
const resourceName = this.resource.resourceName;
const resourceMeta = this.gen1App.resourceMeta(this.resource);
const geoDir = path.join(this.outputDir, 'amplify', 'geo');
return [
{
resource: this.resource,
validate: () => undefined,
describe: async () => [`Generate amplify/geo/${resourceName}/resource.ts`],
execute: async () => {
const { props, parameters } = await this.generateBase(resourceMeta.providerMetadata.logicalId);
const resource = this.addResource(props, parameters);
this.logger.info(`Rendering geo/${resourceName}/resource.ts`);
const nodes = this.renderer.render(resource);
const content = ts_1.TS.printNodes(nodes);
const resourceDir = path.join(geoDir, resourceName);
await fs.mkdir(resourceDir, { recursive: true });
await fs.writeFile(path.join(resourceDir, 'resource.ts'), content, 'utf-8');
},
},
];
}
async generateBase(nestedStackLogicalId) {
var _a;
const constructFileName = `${this.resource.resourceName}-construct`;
const filePath = path.join(this.outputDir, 'amplify', 'geo', this.resource.resourceName, `${constructFileName}.ts`);
const template = this.gen1App.json(`geo/${this.resource.resourceName}/${this.resource.resourceName}-cloudformation-template.json`);
this.logger.debug(`Fetching nested stack parameters for geo resource '${this.resource.resourceName}'`);
const parameters = await this.getNestedStackParameters(nestedStackLogicalId);
const finalTemplate = await this.preTransmute(template, nestedStackLogicalId);
const tsFile = cdk_from_cfn.transmute(JSON.stringify(finalTemplate), 'typescript', nestedStackLogicalId, 'construct');
const fixedTsFile = this.postTransmute(tsFile);
await fs.mkdir(path.dirname(filePath), { recursive: true });
const prettier = await Promise.resolve().then(() => __importStar(require('prettier')));
const formatted = prettier.format(fixedTsFile, {
parser: 'typescript',
singleQuote: true,
tabWidth: 2,
printWidth: 80,
});
const fileWriter = async (content, fp) => {
await fs.mkdir(path.dirname(fp), { recursive: true });
await fs.writeFile(fp, content, 'utf-8');
};
await fileWriter(formatted, filePath);
const classNameMatch = fixedTsFile.match(/export class (\w+) extends/);
if (!classNameMatch) {
throw new amplify_cli_core_1.AmplifyFault('RegexMatchingFault', {
message: `Failed to extract class name from generated construct for geo resource: ${this.resource.resourceName}`,
});
}
const constructClassName = classNameMatch[1];
const paramMap = new Map(parameters
.filter((p) => p.ParameterKey !== undefined && p.ParameterValue !== undefined)
.map((p) => [p.ParameterKey, p.ParameterValue]));
let userPoolIdParamName = '';
const groupRoles = [];
for (const [key] of paramMap) {
if (key.startsWith('auth') && key.endsWith('UserPoolId')) {
userPoolIdParamName = key;
}
else if (key.startsWith('authuserPoolGroups') && key.endsWith('GroupRole')) {
const groupName = key.slice('authuserPoolGroups'.length, -'GroupRole'.length);
groupRoles.push({ paramName: key, groupName });
}
}
const gen1ResourceName = `${this.resource.resourceName}-${this.gen1App.envName}`;
const gen1Actions = extractGen1Actions(template);
const base = {
constructClassName,
constructFileName,
resourceName: this.resource.resourceName,
userPoolIdParamName,
groupRoles,
isDefault: (_a = paramMap.get('isDefault')) !== null && _a !== void 0 ? _a : 'false',
needsAuthAndUnauthRoles: false,
serviceSpecificProps: [],
gen1ResourceName,
gen1Actions,
serviceName: 'Map',
};
return { props: base, parameters: paramMap };
}
async getNestedStackPhysicalName(logicalId) {
const rootStackName = this.gen1App.rootStackName;
if (!rootStackName)
return undefined;
return this.gen1App.aws.findResourcePhysicalId(rootStackName, logicalId);
}
async getNestedStackParameters(logicalId) {
var _a, _b, _c;
const cfnClient = this.gen1App.clients.cloudFormation;
const rootStackName = this.gen1App.rootStackName;
if (!cfnClient || !rootStackName)
return [];
const nestedStackName = await this.getNestedStackPhysicalName(logicalId);
if (!nestedStackName)
return [];
const response = await cfnClient.send(new client_cloudformation_1.DescribeStacksCommand({ StackName: nestedStackName }));
return (_c = (_b = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Parameters) !== null && _c !== void 0 ? _c : [];
}
async preTransmute(template, logicalId) {
var _a;
const result = JSON.parse(JSON.stringify(template));
if ((_a = result.Parameters) === null || _a === void 0 ? void 0 : _a.env) {
result.Parameters['branchName'] = result.Parameters.env;
delete result.Parameters.env;
}
const updateRefs = (obj) => {
if (typeof obj === 'object' && obj !== null) {
const record = obj;
if (record.Ref === 'env') {
record.Ref = 'branchName';
}
Object.values(record).forEach(updateRefs);
}
};
updateRefs(result.Resources);
if (result.Parameters) {
const groupRoleParams = Object.keys(result.Parameters).filter((key) => key.startsWith('authuserPoolGroups') && key.endsWith('GroupRole'));
const replaceGroupRoleJoins = (obj) => {
if (typeof obj !== 'object' || obj === null)
return;
const record = obj;
for (const [key, value] of Object.entries(record)) {
if (typeof value === 'object' && value !== null && 'Fn::Join' in value) {
const joinValue = value;
const [separator, parts] = joinValue['Fn::Join'];
if (separator === '-' &&
Array.isArray(parts) &&
parts.length === 2 &&
typeof parts[0] === 'object' &&
parts[0] !== null &&
'Ref' in parts[0] &&
typeof parts[1] === 'string' &&
parts[1].endsWith('GroupRole')) {
const groupRoleSuffix = parts[1];
const matchingParam = groupRoleParams.find((p) => p.endsWith(groupRoleSuffix));
if (matchingParam) {
record[key] = { Ref: matchingParam };
}
}
}
else {
replaceGroupRoleJoins(value);
}
}
};
replaceGroupRoleJoins(result.Resources);
}
const parameters = await this.getNestedStackParameters(logicalId);
if (parameters.length > 0) {
const resolved = (0, cfn_condition_resolver_1.resolveConditions)(result, parameters);
delete resolved.Conditions;
return resolved;
}
return result;
}
postTransmute(tsCode) {
const mappingVarNames = [];
let result = tsCode.replace(/const (\w+):\s*Record<string,\s*Record<string,\s*string>>\s*=\s*\{([\s\S]*?)\n(\s*)\};/g, (_match, varName, mappingBody, indent) => {
mappingVarNames.push(varName);
const constructId = varName.charAt(0).toUpperCase() + varName.slice(1);
return `const ${varName} = new cdk.CfnMapping(this, '${constructId}', {\n${indent} mapping: {${mappingBody}\n${indent} },\n${indent}});`;
});
for (const varName of mappingVarNames) {
const lookupRegex = new RegExp(`${varName}\\[([^\\]]+)\\]\\[(['"])([^'"]+)\\2\\]`, 'g');
result = result.replace(lookupRegex, (_match, expr, quote, key) => {
return `${varName}.findInMap(${expr}, ${quote}${key}${quote})`;
});
}
return result;
}
}
exports.GeoResourceGenerator = GeoResourceGenerator;
const GEO_LIFECYCLE_ACTIONS = new Set([
'geo:CreateMap',
'geo:UpdateMap',
'geo:DeleteMap',
'geo:CreatePlaceIndex',
'geo:UpdatePlaceIndex',
'geo:DeletePlaceIndex',
'geo:CreateGeofenceCollection',
'geo:UpdateGeofenceCollection',
'geo:DeleteGeofenceCollection',
]);
function extractGen1Actions(template) {
var _a, _b, _c, _d;
const resources = (_a = template.Resources) !== null && _a !== void 0 ? _a : {};
for (const resource of Object.values(resources)) {
const res = resource;
if (res.Type !== 'AWS::IAM::Policy')
continue;
const statements = (_d = (_c = (_b = res.Properties) === null || _b === void 0 ? void 0 : _b.PolicyDocument) === null || _c === void 0 ? void 0 : _c.Statement) !== null && _d !== void 0 ? _d : [];
for (const stmt of statements) {
const actions = Array.isArray(stmt.Action) ? stmt.Action : [stmt.Action];
const userFacing = actions.filter((a) => typeof a === 'string' && !GEO_LIFECYCLE_ACTIONS.has(a));
if (userFacing.length > 0 && userFacing.length === actions.length) {
return userFacing;
}
}
}
return [];
}
//# sourceMappingURL=geo-resource.generator.js.map