Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/toolkit-lib-metadata-null-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-deployer': patch
---

Map the `@aws-cdk/toolkit-lib` `countAssemblyResults` crash (`TypeError: Cannot convert undefined or null to object` when a synthesized stack has no metadata) to a fault instead of a misleading backend `SyntaxError`.
46 changes: 46 additions & 0 deletions packages/backend-deployer/src/cdk_error_mapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,49 @@ void describe('Node version error per deployment type', () => {
);
});
});

void describe('toolkit-lib countAssemblyResults metadata crash', () => {
const cdkErrorMapper = new CdkErrorMapper(formatterStub);

// The TypeError @aws-cdk/toolkit-lib throws when a stack has no metadata.
// Refs aws-amplify/amplify-backend#3316.
const buildCountAssemblyResultsError = () => {
const error = new TypeError('Cannot convert undefined or null to object');
error.stack = [
'TypeError: Cannot convert undefined or null to object',
' at Object.values (<anonymous>)',
' at node_modules/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.js:12:30',
' at Array.flatMap (<anonymous>)',
' at countAssemblyResults (node_modules/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.js:12:10)',
' at synthAndMeasure (node_modules/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.js:1709:59)',
].join('\n');
return error;
};

void it('classifies the crash as a fault, not a user error', () => {
const error = cdkErrorMapper.getAmplifyError(
buildCountAssemblyResultsError(),
);
assert.equal(error.name, 'CDKSynthAssemblyMetadataFault');
// Must be classified as a FAULT (internal/library bug), not a user ERROR
// that would tell the customer to fix their own backend code.
assert.equal(error.classification, 'FAULT');
assert.match(error.message, /@aws-cdk\/toolkit-lib/);
});

void it('preserves the original TypeError as the cause', () => {
const original = buildCountAssemblyResultsError();
const error = cdkErrorMapper.getAmplifyError(original);
assert.equal(error.cause, original);
});

void it('does not intercept unrelated TypeErrors (still a backend SyntaxError)', () => {
const unrelated = new TypeError('foo is not a function');
unrelated.stack =
'TypeError: foo is not a function\n at amplify/backend.ts:3:1';
assert.throws(
() => cdkErrorMapper.getAmplifyError(unrelated),
(thrown: Error) => thrown.name === 'SyntaxError',
);
});
});
29 changes: 29 additions & 0 deletions packages/backend-deployer/src/cdk_error_mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,34 @@ export class CdkErrorMapper {
},
error,
);
} else if (
error.name === 'TypeError' &&
/Cannot convert undefined or null to object/.test(error.message) &&
/countAssemblyResults/.test(error.stack ?? '')
) {
// @aws-cdk/toolkit-lib bug: countAssemblyResults calls
// Object.values(stack.metadata) with no null guard and throws when a
// synthesized stack has no metadata. This is a library fault, not a
// customer backend error, so it must not fall through to the generic
// TypeError branch below. Refs aws-amplify/amplify-backend#3316.
// This match relies on the TypeError reaching the mapper without being
// wrapped by an intermediate layer: the countAssemblyResults stack frame
// is the stable anchor, while the error.name check would silently miss
// if a layer wraps the error into a new type before it gets here.
// TODO(aws-amplify/amplify-backend#3316): the upstream null guard is
// merged (aws/aws-cdk-cli#1952). Once a @aws-cdk/toolkit-lib release ships
// it and this package bumps its pin, countAssemblyResults no longer throws
// and this branch becomes dead code — remove it as part of that bump.
return new AmplifyFault(
'CDKSynthAssemblyMetadataFault',
{
message:
'Unable to synthesize the Amplify backend due to a bug in the CDK toolkit library (@aws-cdk/toolkit-lib) that fails when a synthesized stack has no metadata.',
resolution:
'This is a known upstream bug in @aws-cdk/toolkit-lib, not an issue in your backend code; retrying is unlikely to help. Track aws-amplify/amplify-backend#3316 for a fix.',
},
error,
);
} else if (
['TypeError', 'TransformError', 'ReferenceError', 'SyntaxError'].some(
(errorName) => errorName === error.name,
Expand Down Expand Up @@ -628,6 +656,7 @@ export type CDKDeploymentError =
| 'CDKAssetBundleError'
| 'CDKNotFoundError'
| 'CDKResolveAWSAccountError'
| 'CDKSynthAssemblyMetadataFault'
| 'CDKVersionMismatchError'
| 'CFNUpdateNotSupportedError'
| 'CloudformationResourceCircularDependencyError'
Expand Down
Loading