Skip to content

Commit 370ca61

Browse files
taeyzzzThanetpon Kultontikorn
andauthored
Refactor (#26)
* refactoring * refactor script to generate config * refactor share plugin * clean --------- Co-authored-by: Thanetpon Kultontikorn <[email protected]>
1 parent bcb4d4f commit 370ca61

File tree

7 files changed

+70
-219
lines changed

7 files changed

+70
-219
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agoda-graphql-csharp-generator",
3-
"version": "2.1.7",
3+
"version": "2.1.8",
44
"main": "dist/agoda-csharp-operation.js",
55
"exports": {
66
"./shared-types": "./dist/agoda-csharp-shared-types.js",

src/agoda-csharp-shared-types.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,19 @@
11
import {
22
visit,
3-
Kind,
43
isNonNullType,
54
isListType,
65
getNamedType,
76
typeFromAST,
87
GraphQLSchema,
9-
SelectionSetNode,
10-
VariableDefinitionNode,
118
GraphQLType,
12-
GraphQLObjectType,
13-
isObjectType,
14-
isEnumType,
15-
TypeNode
9+
TypeNode,
10+
Kind
1611
} from 'graphql';
1712
import { PluginFunction, Types } from '@graphql-codegen/plugin-helpers'
18-
import { isScalarType } from './graphqlUtils';
13+
import { isScalarType, isEnumTypeFromSchema } from './graphqlUtils';
1914
import { mapGraphQLTypeToCSharp, toPascalCase } from './naming';
2015
import { extractTypeName } from './graphqlUtils';
2116

22-
23-
// Helper function to check if a type is an enum
24-
const isEnumTypeFromSchema = (schema: GraphQLSchema, typeName: string): boolean => {
25-
try {
26-
const graphqlType = schema.getType(typeName);
27-
return !!(graphqlType && isEnumType(graphqlType));
28-
} catch (error) {
29-
return false;
30-
}
31-
};
32-
3317
// Plugin configuration interface
3418
interface AgodaCSharpSharedConfig {
3519
namespace?: string;
@@ -48,7 +32,7 @@ const convertGraphQLTypeToCSharp = (input: GraphQLType | TypeNode, schema: Graph
4832

4933
if (!convertedType) {
5034
// Fallback to old method if conversion fails
51-
const typeName = input.kind === 'NamedType' ? (input as any).name.value : 'object';
35+
const typeName = input.kind === Kind.NAMED_TYPE ? (input as any).name.value : 'object';
5236
return toPascalCase(typeName);
5337
}
5438
schemaType = convertedType;

src/core.ts

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

src/files.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,4 @@ export const getFiles = async (dir: string, extensionName: string, files: string
1212
}
1313
}
1414
return files
15-
}
16-
17-
export const deleteFiles = async (listFiles: string[]): Promise<void> => {
18-
await Promise.all(
19-
listFiles.map(async (file) => {
20-
try {
21-
await fs.unlink(file)
22-
console.log(`Deleted: ${file}`)
23-
} catch (err) {
24-
console.error(`Failed to delete ${file}:`, err)
25-
}
26-
})
27-
)
2815
}

src/generate.ts

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,35 @@ import { showManual } from './manual'
88
import { getFiles } from './files'
99

1010
// Helper function to generate namespace from file path (cross-platform)
11-
const generateNamespaceFromPath = (filePath: string, projectName?: string): string => {
11+
const generateNamespaceFromPath = (filePath: string, projectName: string): string => {
1212
const dir = path.dirname(filePath)
1313

14-
// If project name is provided, construct namespace by finding the project name in the path
15-
// and using everything after it
16-
if (projectName) {
17-
// First, normalize the path to dots
18-
const normalizedDir = path.normalize(dir)
19-
.replace(/^\.[\\/]/, '') // Remove leading ./ or .\
20-
.replace(/[\\/]+/g, '.') // Convert all path separators to dots
21-
22-
// Find the project name in the normalized path
23-
const projectIndex = normalizedDir.indexOf(projectName)
24-
if (projectIndex !== -1) {
25-
const pathAfterProject = normalizedDir.substring(projectIndex + projectName.length)
26-
// Clean up any leading dots
27-
const cleanPathAfterProject = pathAfterProject.replace(/^\.+/, '')
28-
return `${projectName}${cleanPathAfterProject ? '.' + cleanPathAfterProject : ''}`
29-
}
30-
31-
// If project name not found in path, just prepend it
32-
return `${projectName}.${normalizedDir}`
14+
// First, normalize the path to dots
15+
const normalizedDir = path.normalize(dir)
16+
.replace(/^\.[\\/]/, '') // Remove leading ./ or .\
17+
.replace(/[\\/]+/g, '.') // Convert all path separators to dots
18+
19+
// Find the project name in the normalized path
20+
const projectIndex = normalizedDir.indexOf(projectName)
21+
if (projectIndex !== -1) {
22+
const pathAfterProject = normalizedDir.substring(projectIndex + projectName.length)
23+
// Clean up any leading dots
24+
const cleanPathAfterProject = pathAfterProject.replace(/^\.+/, '')
25+
return `${projectName}${cleanPathAfterProject ? '.' + cleanPathAfterProject : ''}`
3326
}
34-
35-
// Normalize path separators and remove leading ./ or .\
36-
const normalizedDir = path.normalize(dir)
37-
.replace(/^\.[\\/]/, '') // Remove leading ./ or .\
38-
.replace(/[\\/]+/g, '.') // Convert all path separators to dots
39-
40-
return normalizedDir
27+
28+
// If project name not found in path, just prepend it
29+
return `${projectName}.${normalizedDir}`
4130
}
4231

4332
// Helper function to create dynamic config content based on GraphQL files (cross-platform)
44-
const createDynamicConfig = (schemaUrl: string, graphqlFiles: string[], graphqlDir: string, projectName?: string, headers?: string[]): string => {
33+
const createDynamicConfig = (
34+
schemaUrl: string,
35+
graphqlFiles: string[],
36+
graphqlDir: string,
37+
projectName: string,
38+
headers?: string[]
39+
): string => {
4540
let config = `# generated by agoda-graphql-csharp-generator
4641
overwrite: true
4742
schema: "${schemaUrl}"
@@ -152,21 +147,18 @@ export const run = async (): Promise<void> => {
152147
const ymlOut = getArgValue('--yml-out')
153148
const headers = getHeaderArgs()
154149

155-
if (!rawGraphqlDirectory || !schemaUrl) {
156-
console.error('Usage: script --graphql-dir <dir> --schema-url <url> [--graphql-project <project-name>] [--yml-out <output-path>]')
150+
if (!rawGraphqlDirectory || !schemaUrl || !graphqlProject) {
151+
console.error('Usage: script --graphql-dir <dir> --schema-url <url> --graphql-project <project-name> [--header <name:value>] [--yml-out <output-path>]')
157152
process.exit(1)
158153
}
159154

160-
// Determine the config file path
161-
const tempConfigPath = ymlOut ? path.resolve(ymlOut) : path.join(currentDir, 'codegen.yml')
155+
const outputConfigFilePath = ymlOut ? path.resolve(ymlOut) : path.join(currentDir, 'codegen.yml')
162156

163157
console.log('raw graphql directory: ', rawGraphqlDirectory)
164158
console.log('graphql schema url: ', schemaUrl)
165-
if (graphqlProject) {
166-
console.log('graphql project: ', graphqlProject)
167-
}
159+
console.log('graphql project: ', graphqlProject)
168160
if (ymlOut) {
169-
console.log('yml output path: ', ymlOut)
161+
console.log('yml output path: ', outputConfigFilePath)
170162
}
171163
if (headers.length > 0) {
172164
console.log('headers: ', headers)
@@ -186,9 +178,9 @@ export const run = async (): Promise<void> => {
186178
console.log('Creating dynamic config content...')
187179
const dynamicConfigContent = createDynamicConfig(schemaUrl, graphqlFiles, rawGraphqlDirectory, graphqlProject, headers)
188180

189-
const configFileName = path.basename(tempConfigPath)
190-
console.log(`Creating ${configFileName} at:`, tempConfigPath)
191-
await fs.writeFile(tempConfigPath, dynamicConfigContent, 'utf8')
181+
const configFileName = path.basename(outputConfigFilePath)
182+
console.log(`Creating ${configFileName} at:`, outputConfigFilePath)
183+
await fs.writeFile(outputConfigFilePath, dynamicConfigContent, 'utf8')
192184
console.log(`Successfully created ${configFileName}`)
193185

194186
} catch (error) {

src/graphqlUtils.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
1-
import { Kind, OperationDefinitionNode, TypeNode, visit } from "graphql";
2-
import { Types } from "@graphql-codegen/plugin-helpers";
1+
import { Kind, TypeNode, GraphQLSchema, isEnumType } from "graphql";
32
import { SCALAR_TYPES } from "./constants";
43

5-
export const getOperationsDefinitions = async (documents: Types.DocumentFile[]): Promise<OperationDefinitionNode[]> => {
6-
const parseDocumentTasks = documents.map(document => {
7-
return new Promise<OperationDefinitionNode | undefined>((resolve, reject) => {
8-
if (!document.document) return resolve(undefined);
9-
visit(document.document, {
10-
OperationDefinition: (node: OperationDefinitionNode) => {
11-
resolve(node);
12-
}
13-
});
14-
});
15-
});
16-
return (await Promise.all(parseDocumentTasks)).filter(x => x !== undefined) as OperationDefinitionNode[];
17-
}
18-
194
export const extractTypeName = (typeNode: TypeNode): string => {
205
if (typeNode.kind === Kind.NAMED_TYPE) {
216
return typeNode.name.value;
@@ -29,4 +14,13 @@ export const extractTypeName = (typeNode: TypeNode): string => {
2914

3015
export const isScalarType = (typeName: string): boolean => {
3116
return SCALAR_TYPES.includes(typeName);
17+
};
18+
19+
export const isEnumTypeFromSchema = (schema: GraphQLSchema, typeName: string): boolean => {
20+
try {
21+
const graphqlType = schema.getType(typeName);
22+
return !!(graphqlType && isEnumType(graphqlType));
23+
} catch (error) {
24+
return false;
25+
}
3226
};

src/manual.ts

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,34 @@ export const showManual = (): void => {
33
GraphQL C# Code Generator
44
55
DESCRIPTION:
6-
A tool to generate C# classes from GraphQL schema files. This script recursively
7-
searches for .graphql files in a specified directory, generates corresponding
8-
C# classes using graphql-code-generator, and post-processes the output to
9-
match Agoda's coding standards.
6+
This script generates a codegen config file that can be used to generate C# classes using @graphql-codegen/cli - gql-gen.
107
118
USAGE:
12-
npm run generate -- --graphql-dir <directory> --schema-url <url> [--graphql-project <project-name>] [--yml-out <output-path>] [--header <name:value>]
13-
node src/generate.ts --graphql-dir <directory> --schema-url <url> [--graphql-project <project-name>] [--yml-out <output-path>] [--header <name:value>]
9+
generate-codegen-config --graphql-dir <directory> --schema-url <url> [--graphql-project <project-name>] [--yml-out <output-path>] [--header <name:value>]
1410
1511
REQUIRED ARGUMENTS:
16-
--graphql-dir <directory> Directory containing .graphql files to process
17-
The script will recursively search this directory
18-
for all .graphql files
12+
--graphql-dir <directory> Directory containing .graphql files to process
13+
The script will recursively search this directory
14+
for all .graphql files
1915
20-
--schema-url <url> GraphQL schema endpoint URL
21-
Used to validate and generate types from the schema
16+
--schema-url <url> GraphQL schema endpoint URL
17+
Used to validate and generate types from the schema
2218
23-
OPTIONAL ARGUMENTS:
24-
--graphql-project <project-name> Base project name for namespace generation
25-
When provided, the namespace will be constructed as:
26-
<project-name>.<path-after-project>
27-
Example: --graphql-project "Agoda.Graphql"
19+
--graphql-project <project-name> Base project name for namespace generation
20+
When provided, the namespace will be constructed as:
21+
<project-name>.<path-after-project>
22+
Example: --graphql-project "Agoda.Graphql"
2823
29-
--yml-out <output-path> Custom output path for the generated YAML config file
30-
If not specified, defaults to 'temp-codegen.yml' in current directory
31-
Example: --yml-out "./config/codegen.yml"
24+
OPTIONAL ARGUMENTS:
25+
--yml-out <output-path> Custom output path for the generated YAML config file
26+
If not specified, defaults to 'temp-codegen.yml' in current directory
27+
Example: --yml-out "./config/codegen.yml"
28+
default is 'codegen.yml' in current directory
3229
33-
--header <name:value> HTTP header to include in GraphQL schema requests
34-
Can be specified multiple times for multiple headers
35-
Format: "Header-Name: header-value"
36-
Example: --header "API-Key: your-api-key" --header "Client-ID: xxxx"
30+
--header <name:value> HTTP header to include in GraphQL schema requests
31+
Can be specified multiple times for multiple headers
32+
Format: "Header-Name: header-value"
33+
Example: --header "API-Key: your-api-key" --header "Client-ID: xxxx"
3734
3835
--help Show this help message and exit
3936
@@ -44,19 +41,9 @@ BEHAVIOR:
4441
4542
EXAMPLES:
4643
# Basic usage
47-
graphql-csharp-generator --graphql-dir "./Agoda.Graphql" --schema-url "https://api.example.com/graphql"
48-
49-
# With project name for namespace generation
50-
graphql-csharp-generator --graphql-dir "./Agoda.Graphql" --schema-url "https://api.example.com/graphql" --graphql-project "Agoda.Graphql"
51-
52-
# With custom YAML output path
53-
graphql-csharp-generator --graphql-dir "./Agoda.Graphql" --schema-url "https://api.example.com/graphql" --yml-out "./config/codegen.yml"
54-
55-
# With custom headers
56-
graphql-csharp-generator --graphql-dir "./Agoda.Graphql" --schema-url "https://api.example.com/graphql" --header "API-Key: your-key" --header "Client-ID: xxxx"
57-
58-
OUTPUT:
59-
For each .graphql file, a corresponding .generated.cs file will be created
60-
in the same directory with the generated C# classes.
44+
generate-codegen-config --graphql-dir "./Graphql" --schema-url "https://api.example.com/graphql" --graphql-project "Graphql"
45+
46+
# With custom header
47+
generate-codegen-config --graphql-dir "./Graphql" --schema-url "https://api.example.com/graphql" --header "API-Key: your-key" --header "Client-ID: xxxx" --graphql-project "Graphql"
6148
`)
6249
}

0 commit comments

Comments
 (0)