Skip to content

Commit 77f6761

Browse files
authored
fix(cli): assess transformer version in data assessor (#14896)
fix(cli-internal): assess transformer version in data assessor DataAssessor now checks graphQLTransformer.transformerVersion via FeatureFlags. If the version is not 2, the resource is recorded as unsupported with a clear note. This prevents Gen2 migration from proceeding on Transformer V1 projects. Snapshot tests (generate, refactor) initialize FeatureFlags from the app's cli.json via MigrationApp.run, so no mocking is needed there. Unit tests use a strict jest.spyOn that only responds to the expected flag name and throws on any other call. --- Prompt: Look at the changes I made and write a unit test in data.assessor.test.ts
1 parent 8a14a65 commit 77f6761

4 files changed

Lines changed: 56 additions & 5 deletions

File tree

docs/packages/amplify-cli/src/commands/gen2-migration/assess.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Produced by `Gen1App.discover()`. The `key` field is a typed `category:service`
7676
| auth | Cognito-UserPool-Groups |||
7777
| storage | S3 |||
7878
| storage | DynamoDB |||
79-
| api | AppSync | | n/a |
79+
| api | AppSync |(V2 only) | n/a |
8080
| api | API Gateway || n/a |
8181
| analytics | Kinesis |||
8282
| function | Lambda | ✔ (Node.js only) | n/a |
@@ -89,5 +89,5 @@ Produced by `Gen1App.discover()`. The `key` field is a typed `category:service`
8989
- Adding a new resource type: add the pair to `KNOWN_RESOURCE_KEYS` in `gen1-app.ts`, create an assessor, handle the case in `assess.ts`, and in the generate/refactor steps. The compiler enforces exhaustiveness.
9090
- The `Assessment` is also used by generate and refactor steps for validation — `validFor(step)` returns false if any resource or feature is unsupported for that step. The `of(resource, step)` method returns the `Support` for a specific resource, used by the generate orchestrator to skip unsupported resources before instantiating generators.
9191
- Feature detection (overrides, custom policies, conflict resolution) is assessor-specific. Each assessor checks for files in the cloud backend directory via `gen1App.fileExists()` or reads `cli-inputs.json` via `gen1App.cliInputs()`.
92-
- `DataAssessor` detects conflict resolution (DataStore) by reading `cli-inputs.json` for the API resource. If `serviceConfiguration.conflictResolution` is present and non-empty, both generate and refactor are marked unsupported because Gen2 does not support DataStore conflict resolution.
92+
- `DataAssessor` checks the GraphQL transformer version via `FeatureFlags.getNumber('graphQLTransformer.transformerVersion')`. If the version is not 2 (i.e., Transformer V1), the resource is marked unsupported for generate. It also detects conflict resolution (DataStore) by reading `cli-inputs.json` for the API resource. If `serviceConfiguration.conflictResolution` is present and non-empty, both generate and refactor are marked unsupported because Gen2 does not support DataStore conflict resolution.
9393
- `FunctionAssessor` reads the Lambda runtime from the local CloudFormation template (`function/<name>/<name>-cloudformation-template.json`). Non-Node.js runtimes are marked as unsupported for generate. Function refactor is always not-applicable since functions are stateless.

packages/amplify-cli/src/__tests__/commands/gen2-migration/_framework/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { SpinningLogger } from '../../../../commands/gen2-migration/_common/spin
77
import { Gen1App } from '../../../../commands/gen2-migration/_common/gen1-app';
88
import { AwsFetcher } from '../../../../commands/gen2-migration/_common/aws-fetcher';
99
import { AwsClients } from '../../../../commands/gen2-migration/_common/aws-clients';
10-
import { JSONUtilities } from '@aws-amplify/amplify-cli-core';
10+
import { JSONUtilities, FeatureFlags } from '@aws-amplify/amplify-cli-core';
1111
import { Snapshot } from './snapshot';
1212
import { DEFAULT_STATEFUL_RESOURCES } from '../../../../commands/gen2-migration/_common/resource-types';
1313

@@ -398,6 +398,7 @@ export class MigrationApp {
398398

399399
process.chdir(workDir);
400400
try {
401+
await FeatureFlags.initialize({ getCurrentEnvName: () => app.environmentName });
401402
await callback(app);
402403
} finally {
403404
process.chdir(cwd);

packages/amplify-cli/src/__tests__/commands/gen2-migration/assess/api/data.assessor.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DataAssessor } from '../../../../../commands/gen2-migration/assess/api/data.assessor';
22
import { Assessment } from '../../../../../commands/gen2-migration/assess/assessment';
33
import { Gen1App, DiscoveredResource } from '../../../../../commands/gen2-migration/_common/gen1-app';
4+
import { FeatureFlags } from '@aws-amplify/amplify-cli-core';
45

56
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- test helper for untyped cli-inputs.json
67
function mockGen1App(existingFiles: string[] = [], cliInputsData: any = {}): Gen1App {
@@ -12,10 +13,22 @@ function mockGen1App(existingFiles: string[] = [], cliInputsData: any = {}): Gen
1213
} as unknown as Gen1App;
1314
}
1415

16+
function mockTransformerVersion(version: number): void {
17+
jest.spyOn(FeatureFlags, 'getNumber').mockImplementation((flagName: string) => {
18+
if (flagName === 'graphQLTransformer.transformerVersion') return version;
19+
throw new Error(`Unexpected FeatureFlags.getNumber call: ${flagName}`);
20+
});
21+
}
22+
1523
const RESOURCE: DiscoveredResource = { category: 'api', resourceName: 'myApi', service: 'AppSync', key: 'api:AppSync' };
1624

1725
describe('DataAssessor', () => {
26+
afterEach(() => {
27+
jest.restoreAllMocks();
28+
});
29+
1830
it('records resource as supported', () => {
31+
mockTransformerVersion(2);
1932
const assessment = new Assessment('app', 'dev');
2033
new DataAssessor(mockGen1App(), RESOURCE).record(assessment);
2134

@@ -25,6 +38,7 @@ describe('DataAssessor', () => {
2538
});
2639

2740
it('detects override.ts', () => {
41+
mockTransformerVersion(2);
2842
const assessment = new Assessment('app', 'dev');
2943
new DataAssessor(mockGen1App(['api/myApi/override.ts']), RESOURCE).record(assessment);
3044

@@ -37,13 +51,15 @@ describe('DataAssessor', () => {
3751
});
3852

3953
it('records no features when override.ts is absent', () => {
54+
mockTransformerVersion(2);
4055
const assessment = new Assessment('app', 'dev');
4156
new DataAssessor(mockGen1App(), RESOURCE).record(assessment);
4257

4358
expect(assessment.features).toHaveLength(0);
4459
});
4560

4661
it('detects conflict resolution (DataStore) as unsupported', () => {
62+
mockTransformerVersion(2);
4763
const cliInputs = {
4864
version: 1,
4965
serviceConfiguration: {
@@ -66,6 +82,7 @@ describe('DataAssessor', () => {
6682
});
6783

6884
it('does not record conflict resolution when it is absent from cli-inputs.json', () => {
85+
mockTransformerVersion(2);
6986
const cliInputs = {
7087
version: 1,
7188
serviceConfiguration: {
@@ -81,6 +98,7 @@ describe('DataAssessor', () => {
8198
});
8299

83100
it('does not record conflict resolution when conflictResolution is an empty object', () => {
101+
mockTransformerVersion(2);
84102
const cliInputs = {
85103
version: 1,
86104
serviceConfiguration: {
@@ -94,4 +112,25 @@ describe('DataAssessor', () => {
94112

95113
expect(assessment.features).toHaveLength(0);
96114
});
115+
116+
it('records resource as unsupported when transformer version is not 2', () => {
117+
mockTransformerVersion(1);
118+
const assessment = new Assessment('app', 'dev');
119+
new DataAssessor(mockGen1App(), RESOURCE).record(assessment);
120+
121+
const entry = assessment.resources[0];
122+
expect(entry!.generate.level).toBe('unsupported');
123+
expect(entry!.generate).toHaveProperty('note', 'Transformer V1 is not supported in Gen2');
124+
expect(entry!.refactor.level).toBe('not-applicable');
125+
});
126+
127+
it('records resource as supported when transformer version is 2', () => {
128+
mockTransformerVersion(2);
129+
const assessment = new Assessment('app', 'dev');
130+
new DataAssessor(mockGen1App(), RESOURCE).record(assessment);
131+
132+
const entry = assessment.resources[0];
133+
expect(entry!.generate.level).toBe('supported');
134+
expect(entry!.refactor.level).toBe('not-applicable');
135+
});
97136
});

packages/amplify-cli/src/commands/gen2-migration/assess/api/data.assessor.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Assessor } from '../assessor';
22
import { Assessment, supported, unsupported, notApplicable } from '../assessment';
33
import { Gen1App, DiscoveredResource, KNOWN_FEATURES } from '../../_common/gen1-app';
4+
import { FeatureFlags } from '@aws-amplify/amplify-cli-core';
45

56
/**
67
* Assesses migration readiness for an AppSync GraphQL API resource.
7-
* Detects overrides.ts usage and conflict resolution (DataStore).
8+
* Rejects Transformer V1 projects, detects overrides.ts usage and conflict resolution (DataStore).
89
*/
910
export class DataAssessor implements Assessor {
1011
public constructor(private readonly gen1App: Gen1App, private readonly resource: DiscoveredResource) {}
@@ -14,7 +15,17 @@ export class DataAssessor implements Assessor {
1415
*/
1516
public record(assessment: Assessment): void {
1617
this.gen1App.ensureCliInputs(this.resource.category, this.resource.resourceName);
17-
assessment.recordResource({ resource: this.resource, generate: supported(), refactor: notApplicable() });
18+
19+
const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion');
20+
if (transformerVersion !== 2) {
21+
assessment.recordResource({
22+
resource: this.resource,
23+
generate: unsupported('Transformer V1 is not supported in Gen2'),
24+
refactor: notApplicable(),
25+
});
26+
} else {
27+
assessment.recordResource({ resource: this.resource, generate: supported(), refactor: notApplicable() });
28+
}
1829

1930
const overridesPath = `api/${this.resource.resourceName}/override.ts`;
2031

0 commit comments

Comments
 (0)