Skip to content

Commit dbe12ab

Browse files
author
Dane Pilcher
authored
feat: graphql generator (#690)
1 parent ad25eb2 commit dbe12ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+14298
-324
lines changed

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ module.exports = {
5656
"utils",
5757
"aws",
5858
"sdk",
59+
"javascript",
60+
"graphql",
61+
"scala",
5962
],
6063
"skipIfMatch": [
6164
"http://[^s]*",

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package-lock.json
1010
.vscode/*
1111
!.vscode/settings.json
1212
!.vscode/extensions.json
13+
packages/graphql-generator/lib
1314
packages/appsync-modelgen-plugin/lib
1415
packages/graphql-docs-generator/lib
1516
packages/graphql-types-generator/lib

packages/amplify-codegen/package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121
"extract-api": "ts-node ../../scripts/extract-api.ts"
2222
},
2323
"dependencies": {
24-
"@aws-amplify/appsync-modelgen-plugin": "2.6.0",
25-
"@aws-amplify/graphql-docs-generator": "4.1.0",
26-
"@aws-amplify/graphql-types-generator": "3.3.0",
24+
"@aws-amplify/graphql-generator": "^0.0.1",
25+
"@aws-amplify/graphql-types-generator": "^3.2.0",
2726
"@graphql-codegen/core": "2.6.6",
2827
"chalk": "^3.0.0",
2928
"fs-extra": "^8.1.0",
@@ -46,9 +45,9 @@
4645
"collectCoverage": true,
4746
"coverageThreshold": {
4847
"global": {
49-
"branches": 56,
50-
"functions": 68,
51-
"lines": 73
48+
"branches": 54,
49+
"functions": 65,
50+
"lines": 72
5251
}
5352
},
5453
"testURL": "http://localhost",

packages/amplify-codegen/src/commands/models.js

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ const fs = require('fs-extra');
33
const { parse } = require('graphql');
44
const glob = require('glob-all');
55
const { FeatureFlags, pathManager } = require('@aws-amplify/amplify-cli-core');
6-
const gqlCodeGen = require('@graphql-codegen/core');
7-
const appSyncDataStoreCodeGen = require('@aws-amplify/appsync-modelgen-plugin');
8-
const { version: packageVersion } = require('../../package.json');
6+
const { generateModels: generateModelsHelper } = require('@aws-amplify/graphql-generator');
97
const { validateAmplifyFlutterMinSupportedVersion } = require('../utils/validateAmplifyFlutterMinSupportedVersion');
108

119
const platformToLanguageMap = {
1210
android: 'java',
1311
ios: 'swift',
1412
flutter: 'dart',
1513
javascript: 'javascript',
14+
typescript: 'typescript',
1615
};
1716

1817
/**
@@ -91,9 +90,7 @@ async function generateModels(context, generateOptions = null) {
9190
});
9291

9392
const schemaContent = loadSchema(apiResourcePath);
94-
95-
const baseOutputDir = path.join(projectRoot, getModelOutputPath(context));
96-
const schema = parse(schemaContent);
93+
const baseOutputDir = overrideOutputDir || path.join(projectRoot, getModelOutputPath(context));
9794
const projectConfig = context.amplify.getProjectConfig();
9895

9996
if (!isIntrospection && projectConfig.frontend === 'flutter' && !validateAmplifyFlutterMinSupportedVersion(projectRoot)) {
@@ -104,64 +101,42 @@ Amplify Flutter versions prior to 0.6.0 are no longer supported by codegen. Plea
104101

105102
const generateIndexRules = readFeatureFlag('codegen.generateIndexRules');
106103
const emitAuthProvider = readFeatureFlag('codegen.emitAuthProvider');
107-
const usePipelinedTransformer = readFeatureFlag('graphQLTransformer.useExperimentalPipelinedTransformer');
104+
const useExperimentalPipelinedTransformer = readFeatureFlag('graphQLTransformer.useExperimentalPipelinedTransformer');
108105
const transformerVersion = readNumericFeatureFlag('graphQLTransformer.transformerVersion');
109106
const respectPrimaryKeyAttributesOnConnectionField = readFeatureFlag('graphQLTransformer.respectPrimaryKeyAttributesOnConnectionField');
110107
const generateModelsForLazyLoadAndCustomSelectionSet = readFeatureFlag('codegen.generateModelsForLazyLoadAndCustomSelectionSet');
111108
const improvePluralization = readFeatureFlag('graphQLTransformer.improvePluralization');
112-
113-
let isTimestampFieldsAdded = readFeatureFlag('codegen.addTimestampFields');
109+
const addTimestampFields = readFeatureFlag('codegen.addTimestampFields');
114110

115111
const handleListNullabilityTransparently = readFeatureFlag('codegen.handleListNullabilityTransparently');
116-
const appsyncLocalConfig = await appSyncDataStoreCodeGen.preset.buildGeneratesSection({
117-
baseOutputDir,
118-
schema,
119-
config: {
120-
target: isIntrospection ? 'introspection' : platformToLanguageMap[projectConfig.frontend] || projectConfig.frontend,
121-
directives: directiveDefinitions,
122-
isTimestampFieldsAdded,
123-
emitAuthProvider,
124-
generateIndexRules,
125-
handleListNullabilityTransparently,
126-
usePipelinedTransformer,
127-
transformerVersion,
128-
respectPrimaryKeyAttributesOnConnectionField,
129-
improvePluralization,
130-
generateModelsForLazyLoadAndCustomSelectionSet,
131-
codegenVersion: packageVersion,
132-
overrideOutputDir, // This needs to live under `config` in order for the GraphQL types to work out.
133-
},
134-
});
135112

136-
const codeGenPromises = appsyncLocalConfig.map(cfg => {
137-
return gqlCodeGen.codegen({
138-
...cfg,
139-
plugins: [
140-
{
141-
appSyncLocalCodeGen: {},
142-
},
143-
],
144-
pluginMap: {
145-
appSyncLocalCodeGen: appSyncDataStoreCodeGen,
146-
},
147-
});
113+
const generatedCode = await generateModelsHelper({
114+
schema: schemaContent,
115+
directives: directiveDefinitions,
116+
target: isIntrospection ? 'introspection' : platformToLanguageMap[projectConfig.frontend],
117+
generateIndexRules,
118+
emitAuthProvider,
119+
useExperimentalPipelinedTransformer,
120+
transformerVersion,
121+
respectPrimaryKeyAttributesOnConnectionField,
122+
improvePluralization,
123+
generateModelsForLazyLoadAndCustomSelectionSet,
124+
addTimestampFields,
125+
handleListNullabilityTransparently,
148126
});
149127

150-
const generatedCode = await Promise.all(codeGenPromises);
151-
152128
if (writeToDisk) {
153-
appsyncLocalConfig.forEach((cfg, idx) => {
154-
const outPutPath = cfg.filename;
155-
fs.ensureFileSync(outPutPath);
156-
fs.writeFileSync(outPutPath, generatedCode[idx]);
129+
Object.entries(generatedCode).forEach(([filepath, contents]) => {
130+
fs.outputFileSync(path.resolve(path.join(baseOutputDir, filepath)), contents);
157131
});
158132

133+
// TODO: move to @aws-amplify/graphql-generator
159134
generateEslintIgnore(context);
160135

161-
context.print.info(`Successfully generated models. Generated models can be found in ${overrideOutputDir ?? baseOutputDir}`);
136+
context.print.info(`Successfully generated models. Generated models can be found in ${baseOutputDir}`);
162137
}
163138

164-
return generatedCode;
139+
return Object.values(generatedCode);
165140
}
166141

167142
async function validateSchema(context) {
@@ -196,9 +171,12 @@ function getModelOutputPath(context) {
196171
const projectConfig = context.amplify.getProjectConfig();
197172
switch (projectConfig.frontend) {
198173
case 'javascript':
199-
return projectConfig.javascript && projectConfig.javascript.config && projectConfig.javascript.config.SourceDir
200-
? path.normalize(projectConfig.javascript.config.SourceDir)
201-
: 'src';
174+
return path.join(
175+
projectConfig.javascript && projectConfig.javascript.config && projectConfig.javascript.config.SourceDir
176+
? path.normalize(projectConfig.javascript.config.SourceDir)
177+
: 'src',
178+
'models',
179+
);
202180
case 'android':
203181
return projectConfig.android && projectConfig.android.config && projectConfig.android.config.ResDir
204182
? path.normalize(path.join(projectConfig.android.config.ResDir, '..', 'java'))

packages/amplify-codegen/src/commands/statements.js

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@ const Ora = require('ora');
44

55
const { loadConfig } = require('../codegen-config');
66
const constants = require('../constants');
7-
const {
8-
ensureIntrospectionSchema,
9-
getFrontEndHandler,
10-
getAppSyncAPIDetails,
11-
readSchemaFromFile,
12-
GraphQLStatementsFormatter,
13-
} = require('../utils');
7+
const { ensureIntrospectionSchema, getFrontEndHandler, getAppSyncAPIDetails, readSchemaFromFile } = require('../utils');
148
const { generateGraphQLDocuments } = require('@aws-amplify/graphql-docs-generator');
9+
const { generateStatements: generateStatementsHelper } = require('@aws-amplify/graphql-generator');
1510

1611
async function generateStatements(context, forceDownloadSchema, maxDepth, withoutInit = false, decoupleFrontend = '') {
1712
try {
@@ -56,31 +51,29 @@ async function generateStatements(context, forceDownloadSchema, maxDepth, withou
5651
frontend = decoupleFrontend;
5752
}
5853
const language = frontend === 'javascript' ? cfg.amplifyExtension.codeGenTarget : 'graphql';
59-
if (!Object.keys(FILE_EXTENSION_MAP).includes(language)) {
60-
throw new Error(`Language ${language} not supported`);
61-
}
6254

6355
const opsGenSpinner = new Ora(constants.INFO_MESSAGE_OPS_GEN);
6456
opsGenSpinner.start();
6557

6658
try {
67-
fs.ensureDirSync(opsGenDirectory);
6859
const schemaData = readSchemaFromFile(schemaPath);
69-
const generatedOps = generateGraphQLDocuments(schemaData, {
60+
const relativeTypesPath = cfg.amplifyExtension.generatedFileName
61+
? path.relative(opsGenDirectory, cfg.amplifyExtension.generatedFileName)
62+
: null;
63+
const generatedOps = generateStatementsHelper({
64+
schema: schemaData,
65+
target: language,
7066
maxDepth: maxDepth || cfg.amplifyExtension.maxDepth,
7167
useExternalFragmentForS3Object: language === 'graphql',
7268
// default typenameIntrospection to true when not set
7369
typenameIntrospection:
7470
cfg.amplifyExtension.typenameIntrospection === undefined ? true : !!cfg.amplifyExtension.typenameIntrospection,
75-
includeMetaData: true,
71+
relativeTypesPath,
7672
});
7773
if (!generatedOps) {
7874
context.print.warning('No GraphQL statements are generated. Check if the introspection schema has GraphQL operations defined.');
7975
} else {
80-
const relativeTypesPath = cfg.amplifyExtension.generatedFileName
81-
? path.relative(opsGenDirectory, cfg.amplifyExtension.generatedFileName)
82-
: null;
83-
await writeGeneratedDocuments(language, generatedOps, opsGenDirectory, relativeTypesPath);
76+
await writeGeneratedDocuments(language, generatedOps, opsGenDirectory);
8477
opsGenSpinner.succeed(constants.INFO_MESSAGE_OPS_GEN_SUCCESS + path.relative(path.resolve('.'), opsGenDirectory));
8578
}
8679
} finally {
@@ -89,35 +82,10 @@ async function generateStatements(context, forceDownloadSchema, maxDepth, withou
8982
}
9083
}
9184

92-
async function writeGeneratedDocuments(language, generatedStatements, outputPath, relativeTypesPath) {
93-
const fileExtension = FILE_EXTENSION_MAP[language];
94-
95-
['queries', 'mutations', 'subscriptions'].forEach(op => {
96-
const ops = generatedStatements[op];
97-
if (ops && ops.size) {
98-
const formattedStatements = new GraphQLStatementsFormatter(language, op, relativeTypesPath).format(ops);
99-
const outputFile = path.resolve(path.join(outputPath, `${op}.${fileExtension}`));
100-
fs.writeFileSync(outputFile, formattedStatements);
101-
}
85+
function writeGeneratedDocuments(language, generatedStatements, outputPath) {
86+
Object.entries(generatedStatements).forEach(([filepath, contents]) => {
87+
fs.outputFileSync(path.resolve(path.join(outputPath, filepath)), contents);
10288
});
103-
104-
if (fileExtension === 'graphql') {
105-
// External Fragments are rendered only for GraphQL targets
106-
const fragments = generatedStatements['fragments'];
107-
if (fragments.size) {
108-
const formattedStatements = new GraphQLStatementsFormatter(language).format(fragments);
109-
const outputFile = path.resolve(path.join(outputPath, `fragments.${fileExtension}`));
110-
fs.writeFileSync(outputFile, formattedStatements);
111-
}
112-
}
11389
}
11490

115-
const FILE_EXTENSION_MAP = {
116-
javascript: 'js',
117-
graphql: 'graphql',
118-
flow: 'js',
119-
typescript: 'ts',
120-
angular: 'graphql',
121-
};
122-
12391
module.exports = generateStatements;

packages/amplify-codegen/src/commands/types.js

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
const glob = require('glob-all');
21
const path = require('path');
2+
const fs = require('fs-extra');
33
const Ora = require('ora');
4+
const glob = require('glob-all');
45

56
const constants = require('../constants');
67
const { loadConfig } = require('../codegen-config');
78
const { ensureIntrospectionSchema, getFrontEndHandler, getAppSyncAPIDetails } = require('../utils');
8-
const { generate } = require('@aws-amplify/graphql-types-generator');
9+
const { generateTypes: generateTypesHelper } = require('@aws-amplify/graphql-generator');
10+
const { extractDocumentFromJavascript } = require('@aws-amplify/graphql-types-generator');
911

1012
async function generateTypes(context, forceDownloadSchema, withoutInit = false, decoupleFrontend = '') {
1113
let frontend = decoupleFrontend;
@@ -37,39 +39,68 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false,
3739
}
3840

3941
try {
40-
projects.forEach(async cfg => {
42+
for (const cfg of projects) {
4143
const { generatedFileName } = cfg.amplifyExtension || {};
4244
const includeFiles = cfg.includes;
4345
if (!generatedFileName || generatedFileName === '' || includeFiles.length === 0) {
4446
return;
4547
}
48+
const target = cfg.amplifyExtension.codeGenTarget;
4649

4750
const excludes = cfg.excludes.map(pattern => `!${pattern}`);
48-
const queries = glob.sync([...includeFiles, ...excludes], {
49-
cwd: projectPath,
50-
absolute: true,
51-
});
52-
const schemaPath = path.join(projectPath, cfg.schema);
53-
const target = cfg.amplifyExtension.codeGenTarget;
51+
const queries = glob
52+
.sync([...includeFiles, ...excludes], {
53+
cwd: projectPath,
54+
absolute: true,
55+
})
56+
.map(queryFilePath => {
57+
const fileContents = fs.readFileSync(queryFilePath, 'utf8');
58+
if (
59+
queryFilePath.endsWith('.jsx') ||
60+
queryFilePath.endsWith('.js') ||
61+
queryFilePath.endsWith('.tsx') ||
62+
queryFilePath.endsWith('.ts')
63+
) {
64+
return extractDocumentFromJavascript(fileContents, '');
65+
}
66+
return fileContents;
67+
})
68+
.join('\n');
5469

55-
const outputPath = path.join(projectPath, generatedFileName);
70+
const schemaPath = path.join(projectPath, cfg.schema);
5671
let region;
5772
if (!withoutInit) {
5873
({ region } = cfg.amplifyExtension);
5974
await ensureIntrospectionSchema(context, schemaPath, apis[0], region, forceDownloadSchema);
6075
}
6176
const codeGenSpinner = new Ora(constants.INFO_MESSAGE_CODEGEN_GENERATE_STARTED);
6277
codeGenSpinner.start();
78+
const schema = fs.readFileSync(schemaPath, 'utf8');
79+
const introspection = path.extname(schemaPath) === '.json';
80+
6381
try {
64-
generate(queries, schemaPath, path.join(projectPath, generatedFileName), '', target, '', {
65-
addTypename: true,
66-
complexObjectSupport: 'auto',
82+
const output = await generateTypesHelper({
83+
schema,
84+
queries,
85+
target,
86+
introspection,
6787
});
88+
const outputs = Object.entries(output);
89+
90+
const outputPath = path.join(projectPath, generatedFileName);
91+
if (outputs.length === 1) {
92+
const [[, contents]] = outputs;
93+
fs.outputFileSync(path.resolve(outputPath), contents);
94+
} else {
95+
outputs.forEach(([filepath, contents]) => {
96+
fs.outputFileSync(path.resolve(path.join(outputPath, filepath)), contents);
97+
});
98+
}
6899
codeGenSpinner.succeed(`${constants.INFO_MESSAGE_CODEGEN_GENERATE_SUCCESS} ${path.relative(path.resolve('.'), outputPath)}`);
69100
} catch (err) {
70101
codeGenSpinner.fail(err.message);
71102
}
72-
});
103+
}
73104
} catch (err) {
74105
throw Error(err.message);
75106
}

packages/amplify-codegen/src/utils/getOutputFileName.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)