Skip to content
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class BackendUpdater {
const [resourceName, className] = entries[i];
const deps = resourceDependencies?.get(resourceName) || [];
imports.push(this.createImport(resourceName, className));
instantiations.push(this.createInstantiation(resourceName, className, deps));
instantiations.push(this.createInstantiation(resourceName, deps.length > 0));
}

const updatedFile = this.injectIntoBackend(sourceFile, imports, instantiations);
Expand All @@ -48,15 +48,7 @@ export class BackendUpdater {
);
}

// Map Gen1 category names to Gen2 backend property names
private static readonly CATEGORY_MAP: Record<string, string> = {
function: 'functions',
api: 'data',
storage: 'storage',
auth: 'auth',
};

private createInstantiation(resourceName: string, className: string, dependencies?: string[]): ts.ExpressionStatement {
private createInstantiation(resourceName: string, hasDependencies: boolean): ts.ExpressionStatement {
const args: ts.Expression[] = [
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('backend'), 'createStack'),
Expand All @@ -66,12 +58,9 @@ export class BackendUpdater {
ts.factory.createStringLiteral(resourceName),
];

// Add dependencies as positional arguments
if (dependencies && dependencies.length > 0) {
dependencies.forEach((dep) => {
const gen2Name = BackendUpdater.CATEGORY_MAP[dep] || dep;
args.push(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('backend'), gen2Name));
});
// Pass the backend object when the resource has dependencies
if (hasDependencies) {
args.push(ts.factory.createIdentifier('backend'));
}

return ts.factory.createExpressionStatement(ts.factory.createNewExpression(ts.factory.createIdentifier(resourceName), undefined, args));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ export class AmplifyHelperTransformer {
const projectInfoVariables = new Set<string>();
// Track parameter names with AmplifyResourceProps type
const amplifyResourcePropsParams = new Set<string>();
// Track dependencies from addResourceDependency calls
const resourceDependencies = new Set<string>();
// Track whether the resource has any dependencies from addResourceDependency calls
let hasDependencies = false;
// Track variable names assigned from addResourceDependency (e.g., `dependencies`, `deps`)
const dependencyVariables = new Set<string>();
// Track identifiers imported from `amplify-dependent-resources-ref` so we can remove calls to them
const removedModuleIdentifiers = new Set<string>();

const transformer = <T extends ts.Node>(context: ts.TransformationContext) => {
return (node: T) => {
Expand All @@ -49,10 +53,23 @@ export class AmplifyHelperTransformer {
if (ts.isImportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier;
if (ts.isStringLiteral(moduleSpecifier)) {
if (
moduleSpecifier.text === '@aws-amplify/cli-extensibility-helper' ||
moduleSpecifier.text.includes('amplify-dependent-resources-ref')
) {
if (moduleSpecifier.text.includes('amplify-dependent-resources-ref')) {
// Track imported identifiers so we can remove variable declarations that call them
if (node.importClause) {
// Default import: import foo from '...'
if (node.importClause.name && ts.isIdentifier(node.importClause.name)) {
removedModuleIdentifiers.add(node.importClause.name.text);
}
// Named imports: import { foo, bar } from '...'
if (node.importClause.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
for (const specifier of node.importClause.namedBindings.elements) {
removedModuleIdentifiers.add(specifier.name.text);
}
}
}
return undefined;
}
if (moduleSpecifier.text === '@aws-amplify/cli-extensibility-helper') {
return undefined;
}
}
Expand All @@ -70,13 +87,33 @@ export class AmplifyHelperTransformer {

const isFnRef = ts.isIdentifier(expression.expression) && expression.expression.text === 'Fn';

if (
(isCdkFnRef || isFnRef) &&
node.arguments.length === 1 &&
ts.isStringLiteral(node.arguments[0]) &&
node.arguments[0].text === 'env'
) {
return ts.factory.createIdentifier('branchName');
if ((isCdkFnRef || isFnRef) && node.arguments.length === 1) {
const arg = node.arguments[0];

// Handle cdk.Fn.ref('env') → branchName
if (ts.isStringLiteral(arg) && arg.text === 'env') {
return ts.factory.createIdentifier('branchName');
}

// Handle cdk.Fn.ref(dependencies.category.resource.attribute) → backend.gen2Category.resources.gen2Path
if (ts.isPropertyAccessExpression(arg)) {
const chain = AmplifyHelperTransformer.getPropertyAccessChain(arg);
const parts = chain.split('.');
if (parts.length >= 4 && dependencyVariables.has(parts[0])) {
const gen1Category = parts[1];
const gen2Category = AmplifyHelperTransformer.CATEGORY_MAP[gen1Category] || gen1Category;
const attribute = parts[3];
const mappedAttr = AmplifyHelperTransformer.ATTRIBUTE_MAP[gen1Category]?.[attribute];
const gen2Path = mappedAttr || attribute;

if (gen1Category === 'function') {
return AmplifyHelperTransformer.createPropertyAccessFromString(
`backend.${gen2Category}.${parts[2]}.resources.${gen2Path}`,
);
}
return AmplifyHelperTransformer.createPropertyAccessFromString(`backend.${gen2Category}.resources.${gen2Path}`);
}
}
}
}
}
Expand Down Expand Up @@ -106,28 +143,72 @@ export class AmplifyHelperTransformer {
ts.isPropertyAccessExpression(callExpr.expression) && callExpr.expression.name.text === 'addResourceDependency';

if (isAddResourceDependency) {
// Extract dependencies from the call
const args = callExpr.arguments;
if (args.length >= 4 && ts.isArrayLiteralExpression(args[3])) {
args[3].elements.forEach((element) => {
if (ts.isObjectLiteralExpression(element)) {
element.properties.forEach((prop) => {
if (
ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'category' &&
ts.isStringLiteral(prop.initializer)
) {
resourceDependencies.add(prop.initializer.text);
}
});
}
});
// Track the declared variable name (e.g., `dependencies`, `deps`)
if (ts.isIdentifier(declaration.name)) {
dependencyVariables.add(declaration.name.text);
}
hasDependencies = true;
// Remove this entire variable statement
return undefined;
}
}

// Remove `const envParam = new cdk.CfnParameter(this, 'env', ...)` variable statements
if (declaration && declaration.initializer && ts.isNewExpression(declaration.initializer)) {
const newExpr = declaration.initializer;
const exprText = newExpr.expression;
const isCfnParameter =
(ts.isPropertyAccessExpression(exprText) && exprText.name.text === 'CfnParameter') ||
(ts.isIdentifier(exprText) && exprText.text === 'CfnParameter');

if (isCfnParameter && newExpr.arguments && newExpr.arguments.length >= 2) {
const secondArg = newExpr.arguments[1];
if (ts.isStringLiteral(secondArg) && secondArg.text === 'env') {
return undefined;
}
}
}

// Remove variable declarations with AmplifyDependentResourcesAttributes type annotation
// e.g., `const deps: AmplifyDependentResourcesAttributes = someExpression;`
if (declaration && declaration.type && ts.isTypeReferenceNode(declaration.type)) {
const typeName = declaration.type.typeName;
if (ts.isIdentifier(typeName) && typeName.text === 'AmplifyDependentResourcesAttributes') {
return undefined;
}
}

// Remove variable declarations whose initializer calls a function imported from `amplify-dependent-resources-ref`
// e.g., `const retVal = getAmplifyResourcesRef();`
if (
declaration &&
declaration.initializer &&
ts.isCallExpression(declaration.initializer) &&
ts.isIdentifier(declaration.initializer.expression) &&
removedModuleIdentifiers.has(declaration.initializer.expression.text)
) {
return undefined;
}

// Strip `as AmplifyDependentResourcesAttributes` type assertions while preserving the variable
// e.g., `const x = someFunc() as AmplifyDependentResourcesAttributes;` → `const x = someFunc();`
if (declaration && declaration.initializer && ts.isAsExpression(declaration.initializer)) {
const asExpr = declaration.initializer;
if (ts.isTypeReferenceNode(asExpr.type)) {
const typeName = asExpr.type.typeName;
if (ts.isIdentifier(typeName) && typeName.text === 'AmplifyDependentResourcesAttributes') {
const updatedDeclaration = ts.factory.updateVariableDeclaration(
declaration,
declaration.name,
declaration.exclamationToken,
declaration.type,
asExpr.expression,
);
const updatedDeclarationList = ts.factory.updateVariableDeclarationList(node.declarationList, [updatedDeclaration]);
return ts.factory.updateVariableStatement(node, node.modifiers, updatedDeclarationList);
}
}
}
}

// Transform property access to AmplifyHelpers.getProjectInfo().envName or .projectName
Expand Down Expand Up @@ -181,23 +262,41 @@ export class AmplifyHelperTransformer {
const fullAccess = AmplifyHelperTransformer.getPropertyAccessChain(node);
const parts = fullAccess.split('.');
// Match pattern: amplifyResources.category.resourceName.property (4+ parts)
if (parts.length >= 4 && parts[0].includes('amplifyResources')) {
if (parts.length >= 4 && (parts[0].includes('amplifyResources') || dependencyVariables.has(parts[0]))) {
const gen1Category = parts[1];
const gen2Category = AmplifyHelperTransformer.CATEGORY_MAP[gen1Category] || gen1Category;
const resourceName = parts[2];
const gen1Attribute = parts[3];
const mappedAttribute = AmplifyHelperTransformer.ATTRIBUTE_MAP[gen1Category]?.[gen1Attribute];
const gen2Property = mappedAttribute || parts.slice(3).join('.');

// Functions need resource name preserved: functions.myFunc.resources.lambda.functionArn
// Functions need resource name preserved: backend.functions.myFunc.resources.lambda.functionArn
if (gen1Category === 'function') {
return AmplifyHelperTransformer.createPropertyAccessFromString(
`${gen2Category}.${resourceName}.resources.${gen2Property}`,
`backend.${gen2Category}.${resourceName}.resources.${gen2Property}`,
);
}

// Other categories: auth.resources.userPool.userPoolId
return AmplifyHelperTransformer.createPropertyAccessFromString(`${gen2Category}.resources.${gen2Property}`);
// Other categories: backend.auth.resources.userPool.userPoolId
return AmplifyHelperTransformer.createPropertyAccessFromString(`backend.${gen2Category}.resources.${gen2Property}`);
}
}
}

// Remove `new cdk.CfnParameter(this, 'env', ...)` expression statements
if (ts.isExpressionStatement(node)) {
const expr = node.expression;
if (ts.isNewExpression(expr)) {
const exprText = expr.expression;
const isCfnParameter =
(ts.isPropertyAccessExpression(exprText) && exprText.name.text === 'CfnParameter') ||
(ts.isIdentifier(exprText) && exprText.text === 'CfnParameter');

if (isCfnParameter && expr.arguments && expr.arguments.length >= 2) {
const secondArg = expr.arguments[1];
if (ts.isStringLiteral(secondArg) && secondArg.text === 'env') {
return undefined;
}
}
}
}
Expand Down Expand Up @@ -239,25 +338,24 @@ export class AmplifyHelperTransformer {
);
}

// Transform constructor: add resource dependencies as parameters
// Transform constructor: add backend parameter if resource has dependencies
if (ts.isConstructorDeclaration(visitedNode)) {
const baseParams = visitedNode.parameters.slice(0, 2); // scope, id

// Add resource dependency parameters with Gen2 naming
const resourceParams = Array.from(resourceDependencies).map((gen1Category) => {
const gen2Category = AmplifyHelperTransformer.CATEGORY_MAP[gen1Category] || gen1Category;
return ts.factory.createParameterDeclaration(
// Add a single `backend` parameter when the resource has dependencies
if (hasDependencies) {
const backendParam = ts.factory.createParameterDeclaration(
undefined,
undefined,
gen2Category,
'backend',
undefined,
ts.factory.createTypeReferenceNode('any'),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
undefined,
);
});
baseParams.push(backendParam);
}

const newParams = [...baseParams, ...resourceParams];
return ts.factory.updateConstructorDeclaration(visitedNode, visitedNode.modifiers, newParams, visitedNode.body);
return ts.factory.updateConstructorDeclaration(visitedNode, visitedNode.modifiers, baseParams, visitedNode.body);
}

// Transform super() call: remove props argument
Expand Down
Loading