Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: merge noinit-modelgen into main to support executing amplify codegen models without an initialized amplify app #698

Merged
merged 4 commits into from
Sep 13, 2023
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
15 changes: 13 additions & 2 deletions .codebuild/e2e_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,26 @@ batch:
CLI_REGION: ap-northeast-1
depend-on:
- publish_to_local_registry
- identifier: build_app_ts
- identifier: >-
build_app_ts_uninitialized_project_modelgen_android_uninitialized_project_modelgen_flutter_uninitialized_project_modelgen_ios
buildspec: .codebuild/run_e2e_tests.yml
env:
compute-type: BUILD_GENERAL1_LARGE
variables:
TEST_SUITE: src/__tests__/build-app-ts.test.ts
TEST_SUITE: >-
src/__tests__/build-app-ts.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts|src/__tests__/uninitialized-project-modelgen-flutter.test.ts|src/__tests__/uninitialized-project-modelgen-ios.test.ts
CLI_REGION: ap-southeast-1
depend-on:
- publish_to_local_registry
- identifier: uninitialized_project_modelgen_js
buildspec: .codebuild/run_e2e_tests.yml
env:
compute-type: BUILD_GENERAL1_LARGE
variables:
TEST_SUITE: src/__tests__/uninitialized-project-modelgen-js.test.ts
CLI_REGION: ap-southeast-2
depend-on:
- publish_to_local_registry
- identifier: cleanup_e2e_resources
buildspec: .codebuild/cleanup_e2e_resources.yml
env:
Expand Down
10 changes: 10 additions & 0 deletions packages/amplify-codegen-e2e-core/src/categories/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ export function generateModels(cwd: string, outputDir?: string, settings: { errM
});
}

export const generateModelsWithOptions = (cwd: string, options: Record<string, any>): Promise<void> => new Promise((resolve, reject) => {
spawn(getCLIPath(), ['codegen', 'models', ...(Object.entries(options).flat())], { cwd, stripColors: true }).run((err: Error) => {
if (!err) {
resolve();
} else {
reject(err);
}
});
});

export function generateStatementsAndTypes(cwd: string) : Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(), ['codegen'], { cwd, stripColors: true })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum AmplifyFrontend {
javascript = 'javascript',
typescript = 'typescript',
ios = 'ios',
android = 'android',
flutter = 'flutter'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createNewProjectDir, DEFAULT_ANDROID_CONFIG, deleteProjectDir } from '@aws-amplify/amplify-codegen-e2e-core';
import { testUninitializedCodegenModels } from '../codegen-tests-base';
import * as path from 'path';

const schemaName = 'modelgen/model_gen_schema_with_aws_scalars.graphql';

describe('Uninitialized Project Modelgen tests - Android', () => {
let projectRoot: string;

beforeEach(async () => {
projectRoot = await createNewProjectDir('uninitializedProjectModelgenAndroid');
});

afterEach(() => deleteProjectDir(projectRoot));

it(`should generate files at desired location and not delete src files`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_ANDROID_CONFIG,
projectRoot,
schemaName,
outputDir: path.join('app', 'src', 'main', 'guava'),
shouldSucceed: true,
expectedFilenames: [
'AmplifyModelProvider.java',
'Attration.java',
'Comment.java',
'License.java',
'Person.java',
'Post.java',
'Status.java',
'User.java',
],
});
});

it(`should not generate files at desired location and not delete src files if no output dir is specified`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_ANDROID_CONFIG,
projectRoot,
schemaName,
shouldSucceed: false,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createNewProjectDir, DEFAULT_FLUTTER_CONFIG, deleteProjectDir } from '@aws-amplify/amplify-codegen-e2e-core';
import { testUninitializedCodegenModels } from '../codegen-tests-base';
import * as path from 'path';

const schemaName = 'modelgen/model_gen_schema_with_aws_scalars.graphql';

describe('Uninitialized Project Modelgen tests - Flutter', () => {
let projectRoot: string;

beforeEach(async () => {
projectRoot = await createNewProjectDir('uninitializedProjectModelgenFlutter');
});

afterEach(() => deleteProjectDir(projectRoot));

it(`should generate files at desired location and not delete src files`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_FLUTTER_CONFIG,
projectRoot,
schemaName,
outputDir: path.join('lib', 'blueprints'),
shouldSucceed: true,
expectedFilenames: [
'Attration.dart',
'Comment.dart',
'License.dart',
'ModelProvider.dart',
'Person.dart',
'Post.dart',
'Status.dart',
'User.dart',
],
});
});

it(`should not generate files at desired location and not delete src files if no output dir is specified`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_FLUTTER_CONFIG,
projectRoot,
schemaName,
shouldSucceed: false,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createNewProjectDir, DEFAULT_IOS_CONFIG, deleteProjectDir } from '@aws-amplify/amplify-codegen-e2e-core';
import { testUninitializedCodegenModels } from '../codegen-tests-base';
import * as path from 'path';

const schemaName = 'modelgen/model_gen_schema_with_aws_scalars.graphql';

describe('Uninitialized Project Modelgen tests - IOS', () => {
let projectRoot: string;

beforeEach(async () => {
projectRoot = await createNewProjectDir('uninitializedProjectModelgenIOS');
});

afterEach(() => deleteProjectDir(projectRoot));

it(`should generate files at desired location and not delete src files`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_IOS_CONFIG,
projectRoot,
schemaName,
outputDir: path.join('amplification', 'manufactured', 'models'),
shouldSucceed: true,
expectedFilenames: [
'AmplifyModels.swift',
'Attration+Schema.swift',
'Attration.swift',
'Comment+Schema.swift',
'Comment.swift',
'License+Schema.swift',
'License.swift',
'Person+Schema.swift',
'Person.swift',
'Post+Schema.swift',
'Post.swift',
'Status.swift',
'User+Schema.swift',
'User.swift',
],
});
});

it(`should not generate files at desired location and not delete src files if no output dir is specified`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_IOS_CONFIG,
projectRoot,
schemaName,
shouldSucceed: false,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AmplifyFrontend, createNewProjectDir, DEFAULT_JS_CONFIG, deleteProjectDir } from '@aws-amplify/amplify-codegen-e2e-core';
import { testUninitializedCodegenModels } from '../codegen-tests-base';
import * as path from 'path';

const schemaName = 'modelgen/model_gen_schema_with_aws_scalars.graphql';

describe('Uninitialized Project Modelgen tests - JS', () => {
let projectRoot: string;

beforeEach(async () => {
projectRoot = await createNewProjectDir('uninitializedProjectModelgenJS');
});

afterEach(() => deleteProjectDir(projectRoot));

it(`should generate files at desired location and not delete src files`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_JS_CONFIG,
projectRoot,
schemaName,
outputDir: path.join('src', 'backmodels'),
shouldSucceed: true,
expectedFilenames: [
'index.d.ts',
'index.js',
'schema.d.ts',
'schema.js',
],
});
});

it(`should generate files at desired location and not delete src files for typescript variant`, async () => {
await testUninitializedCodegenModels({
config: {
...DEFAULT_JS_CONFIG,
frontendType: AmplifyFrontend.typescript,
},
projectRoot,
schemaName,
outputDir: path.join('src', 'backmodels'),
shouldSucceed: true,
expectedFilenames: [
'index.ts',
'schema.ts',
],
});
});

it(`should not generate files at desired location and not delete src files if no output dir is specified`, async () => {
await testUninitializedCodegenModels({
config: DEFAULT_JS_CONFIG,
projectRoot,
schemaName,
shouldSucceed: false,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './configure-codegen';
export * from './remove-codegen';
export * from './datastore-modelgen';
export * from './push-codegen';
export * from './uninitialized-modelgen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { generateModelsWithOptions, AmplifyFrontendConfig, AmplifyFrontend, getSchemaPath } from '@aws-amplify/amplify-codegen-e2e-core';
import { existsSync, writeFileSync, readFileSync, readdirSync } from 'fs';
import { isNotEmptyDir, generateSourceCode } from '../utils';
import { createPubspecLockFile } from './datastore-modelgen';
import path from 'path';

export type TestUninitializedCodegenModelsProps = {
config: AmplifyFrontendConfig;
projectRoot: string;
schemaName: string;
shouldSucceed: boolean;
outputDir?: string;
featureFlags?: Record<string, any>;
expectedFilenames?: Array<string>;
};

export const testUninitializedCodegenModels = async ({
config,
projectRoot,
schemaName,
outputDir,
shouldSucceed,
featureFlags,
expectedFilenames,
}: TestUninitializedCodegenModelsProps): Promise<void> => {
// generate pre existing user file
const userSourceCodePath = generateSourceCode(projectRoot, config.srcDir);

// Write Schema File to Schema Path
const schemaPath = getSchemaPath(schemaName);
const schema = readFileSync(schemaPath).toString();
const modelSchemaPath = path.join(config.srcDir, 'schema.graphql');
writeFileSync(path.join(projectRoot, modelSchemaPath), schema);

// For flutter frontend, we need to have a pubspec lock file with supported dart version
if (config?.frontendType === AmplifyFrontend.flutter) {
createPubspecLockFile(projectRoot);
}

// generate models
try {
await generateModelsWithOptions(projectRoot, {
'--target': config.frontendType,
'--model-schema': modelSchemaPath,
'--output-dir': outputDir,
...(featureFlags ? Object.entries(featureFlags).map(([ffName, ffVal]) => [`--feature-flag:${ffName}`, ffVal]).flat() : []),
});
} catch (_) {
// This is temporarily expected to throw, since the post-modelgen hook in amplify cli fails, even though modelgen succeeds.
}

// pre-existing file should still exist
expect(existsSync(userSourceCodePath)).toBe(true);

// datastore models are generated at correct location
const partialDirToCheck = outputDir
? path.join(projectRoot, outputDir)
: path.join(projectRoot, config.modelgenDir);
const dirToCheck = config.frontendType === AmplifyFrontend.android
? path.join(partialDirToCheck, 'com', 'amplifyframework', 'datastore', 'generated', 'model')
: partialDirToCheck;

expect(isNotEmptyDir(dirToCheck)).toBe(shouldSucceed);

if (expectedFilenames) {
const foundFiles = new Set(readdirSync(dirToCheck));
console.log(`Comparing written files: ${JSON.stringify(Array.from(foundFiles))} to expected files: ${JSON.stringify(expectedFilenames)}`);
expectedFilenames.forEach((expectedFilename) => expect(foundFiles.has(expectedFilename)).toBe(true));
}
};
Loading