@auto-engineer/server-generator-apollo-emmett 0.9.13 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/src/commands/generate-server.d.ts +14 -33
- package/dist/src/commands/generate-server.d.ts.map +1 -1
- package/dist/src/commands/generate-server.js +73 -42
- package/dist/src/commands/generate-server.js.map +1 -1
- package/dist/src/index.d.ts +1 -10
- package/dist/src/index.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/commands/generate-server.ts +94 -71
package/package.json
CHANGED
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"graphql-type-json": "^0.3.2",
|
|
29
29
|
"uuid": "^10.0.0",
|
|
30
30
|
"web-streams-polyfill": "^4.1.0",
|
|
31
|
-
"@auto-engineer/
|
|
32
|
-
"@auto-engineer/
|
|
31
|
+
"@auto-engineer/message-bus": "0.10.1",
|
|
32
|
+
"@auto-engineer/flow": "0.10.1"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/ejs": "^3.1.5",
|
|
39
39
|
"@types/fs-extra": "^11.0.4",
|
|
40
|
-
"@auto-engineer/cli": "0.
|
|
40
|
+
"@auto-engineer/cli": "0.10.1"
|
|
41
41
|
},
|
|
42
|
-
"version": "0.
|
|
42
|
+
"version": "0.10.1",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"generate:server": "tsx src/cli/index.ts",
|
|
45
45
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src",
|
|
@@ -5,47 +5,27 @@ import { readFile, writeFile } from 'fs/promises';
|
|
|
5
5
|
import { resolve, join } from 'path';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { generateScaffoldFilePlans, writeScaffoldFilePlans } from '../codegen/scaffoldFromSchema';
|
|
8
|
-
import { ensureDirExists } from '../codegen/utils/path';
|
|
8
|
+
import { ensureDirExists, ensureDirPath, toKebabCase } from '../codegen/utils/path';
|
|
9
9
|
import { Model } from '@auto-engineer/flow';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { dirname } from 'path';
|
|
12
12
|
import { execa } from 'execa';
|
|
13
13
|
import createDebug from 'debug';
|
|
14
|
-
import { defineCommandHandler } from '@auto-engineer/message-bus';
|
|
14
|
+
import { defineCommandHandler, Command, Event } from '@auto-engineer/message-bus';
|
|
15
15
|
|
|
16
16
|
const debug = createDebug('auto:generate-server');
|
|
17
|
-
const
|
|
17
|
+
const debugModel = createDebug('auto:generate-server:schema');
|
|
18
18
|
const debugFiles = createDebug('auto:generate-server:files');
|
|
19
19
|
const debugDeps = createDebug('auto:generate-server:deps');
|
|
20
20
|
const debugScaffold = createDebug('auto:generate-server:scaffold');
|
|
21
21
|
|
|
22
|
-
type DefaultRecord = Record<string, unknown>;
|
|
23
|
-
export type Command<CommandType extends string = string, CommandData extends DefaultRecord = DefaultRecord> = Readonly<{
|
|
24
|
-
type: CommandType;
|
|
25
|
-
data: Readonly<CommandData>;
|
|
26
|
-
timestamp?: Date;
|
|
27
|
-
requestId?: string;
|
|
28
|
-
correlationId?: string;
|
|
29
|
-
}>;
|
|
30
|
-
export type Event<EventType extends string = string, EventData extends DefaultRecord = DefaultRecord> = Readonly<{
|
|
31
|
-
type: EventType;
|
|
32
|
-
data: Readonly<EventData>;
|
|
33
|
-
timestamp?: Date;
|
|
34
|
-
requestId?: string;
|
|
35
|
-
correlationId?: string;
|
|
36
|
-
}>;
|
|
37
|
-
export type CommandHandler<TCommand extends Command = Command, TResult = unknown> = {
|
|
38
|
-
name: string;
|
|
39
|
-
handle: (command: TCommand) => Promise<TResult>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
43
23
|
const __dirname = dirname(__filename);
|
|
44
24
|
|
|
45
25
|
export type GenerateServerCommand = Command<
|
|
46
26
|
'GenerateServer',
|
|
47
27
|
{
|
|
48
|
-
|
|
28
|
+
modelPath: string;
|
|
49
29
|
destination: string; // the project root where "server" directory will be created
|
|
50
30
|
}
|
|
51
31
|
>;
|
|
@@ -53,7 +33,7 @@ export type GenerateServerCommand = Command<
|
|
|
53
33
|
export type ServerGeneratedEvent = Event<
|
|
54
34
|
'ServerGenerated',
|
|
55
35
|
{
|
|
56
|
-
|
|
36
|
+
modelPath: string;
|
|
57
37
|
destination: string;
|
|
58
38
|
serverDir: string;
|
|
59
39
|
contextSchemaGraphQL?: string;
|
|
@@ -63,23 +43,35 @@ export type ServerGeneratedEvent = Event<
|
|
|
63
43
|
export type ServerGenerationFailedEvent = Event<
|
|
64
44
|
'ServerGenerationFailed',
|
|
65
45
|
{
|
|
66
|
-
|
|
46
|
+
modelPath: string;
|
|
67
47
|
destination: string;
|
|
68
48
|
error: string;
|
|
69
49
|
}
|
|
70
50
|
>;
|
|
71
51
|
|
|
72
|
-
export type
|
|
52
|
+
export type SliceGeneratedEvent = Event<
|
|
53
|
+
'SliceGenerated',
|
|
54
|
+
{
|
|
55
|
+
flowName: string;
|
|
56
|
+
sliceName: string;
|
|
57
|
+
sliceType: string;
|
|
58
|
+
schemaPath: string;
|
|
59
|
+
slicePath: string;
|
|
60
|
+
}
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
export type GenerateServerEvents = ServerGeneratedEvent | ServerGenerationFailedEvent | SliceGeneratedEvent;
|
|
73
64
|
|
|
74
|
-
export const commandHandler = defineCommandHandler
|
|
65
|
+
export const commandHandler = defineCommandHandler({
|
|
75
66
|
name: 'GenerateServer',
|
|
76
67
|
alias: 'generate:server',
|
|
77
|
-
description: 'Generate server from
|
|
68
|
+
description: 'Generate server from model',
|
|
78
69
|
category: 'generate',
|
|
79
70
|
icon: 'server',
|
|
71
|
+
events: ['ServerGenerated', 'ServerGenerationFailed', 'SliceGenerated'],
|
|
80
72
|
fields: {
|
|
81
|
-
|
|
82
|
-
description: 'Path to
|
|
73
|
+
modelPath: {
|
|
74
|
+
description: 'Path to the json model file',
|
|
83
75
|
required: true,
|
|
84
76
|
},
|
|
85
77
|
destination: {
|
|
@@ -87,30 +79,34 @@ export const commandHandler = defineCommandHandler<GenerateServerCommand>({
|
|
|
87
79
|
required: true,
|
|
88
80
|
},
|
|
89
81
|
},
|
|
90
|
-
examples: ['$ auto generate:server --
|
|
91
|
-
handle: async (command:
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
examples: ['$ auto generate:server --model-path=.context/model.json --destination=.'],
|
|
83
|
+
handle: async (command: Command): Promise<GenerateServerEvents | GenerateServerEvents[]> => {
|
|
84
|
+
const typedCommand = command as GenerateServerCommand;
|
|
85
|
+
const result = await handleGenerateServerCommandInternal(typedCommand);
|
|
86
|
+
const events = Array.isArray(result) ? result : [result];
|
|
87
|
+
const finalEvent = events[events.length - 1];
|
|
88
|
+
if (finalEvent.type === 'ServerGenerated') {
|
|
89
|
+
debug('Server generated at %s', finalEvent.data.serverDir);
|
|
90
|
+
} else if (finalEvent.type === 'ServerGenerationFailed') {
|
|
91
|
+
debug('Failed to generate server: %s', finalEvent.data.error);
|
|
97
92
|
}
|
|
93
|
+
|
|
98
94
|
return result;
|
|
99
95
|
},
|
|
100
96
|
});
|
|
101
97
|
|
|
102
|
-
async function
|
|
103
|
-
|
|
98
|
+
async function validateModelFile(
|
|
99
|
+
absModel: string,
|
|
104
100
|
command: GenerateServerCommand,
|
|
105
101
|
): Promise<ServerGenerationFailedEvent | null> {
|
|
106
|
-
if (!existsSync(
|
|
107
|
-
debug('
|
|
102
|
+
if (!existsSync(absModel)) {
|
|
103
|
+
debug('Model file not found at %s', absModel);
|
|
108
104
|
return {
|
|
109
105
|
type: 'ServerGenerationFailed',
|
|
110
106
|
data: {
|
|
111
|
-
|
|
107
|
+
modelPath: command.data.modelPath,
|
|
112
108
|
destination: command.data.destination,
|
|
113
|
-
error: `
|
|
109
|
+
error: `Model file not found at ${absModel}`,
|
|
114
110
|
},
|
|
115
111
|
timestamp: new Date(),
|
|
116
112
|
requestId: command.requestId,
|
|
@@ -120,17 +116,17 @@ async function validateSchemaFile(
|
|
|
120
116
|
return null;
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
async function
|
|
124
|
-
|
|
125
|
-
const content = await readFile(
|
|
119
|
+
async function readAndParseModel(absModel: string): Promise<Model> {
|
|
120
|
+
debugModel('Reading model file from %s', absModel);
|
|
121
|
+
const content = await readFile(absModel, 'utf8');
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
debugModel('Model content length: %d bytes', content.length);
|
|
128
124
|
const spec = JSON.parse(content) as Model;
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
debugModel('Parsed model:');
|
|
127
|
+
debugModel(' Flows: %d', spec.flows?.length || 0);
|
|
128
|
+
debugModel(' Messages: %d', spec.messages?.length || 0);
|
|
129
|
+
debugModel(' Integrations: %d', spec.integrations?.length ?? 0);
|
|
134
130
|
|
|
135
131
|
logFlowDetails(spec);
|
|
136
132
|
return spec;
|
|
@@ -138,14 +134,14 @@ async function readAndParseSchema(absSchema: string): Promise<Model> {
|
|
|
138
134
|
|
|
139
135
|
function logFlowDetails(spec: Model): void {
|
|
140
136
|
if (spec.flows !== undefined && spec.flows.length > 0) {
|
|
141
|
-
|
|
137
|
+
debugModel(
|
|
142
138
|
'Flow names: %o',
|
|
143
139
|
spec.flows.map((f) => f.name),
|
|
144
140
|
);
|
|
145
141
|
spec.flows.forEach((flow) => {
|
|
146
|
-
|
|
142
|
+
debugModel(' Flow "%s" has %d slices', flow.name, flow.slices?.length || 0);
|
|
147
143
|
flow.slices?.forEach((slice) => {
|
|
148
|
-
|
|
144
|
+
debugModel(' Slice: %s (type: %s)', slice.name, slice.type);
|
|
149
145
|
});
|
|
150
146
|
});
|
|
151
147
|
}
|
|
@@ -207,7 +203,7 @@ function createServerSuccessEvent(
|
|
|
207
203
|
return {
|
|
208
204
|
type: 'ServerGenerated',
|
|
209
205
|
data: {
|
|
210
|
-
|
|
206
|
+
modelPath: command.data.modelPath,
|
|
211
207
|
destination: command.data.destination,
|
|
212
208
|
serverDir,
|
|
213
209
|
contextSchemaGraphQL: join(absDest, '.context', 'schema.graphql'),
|
|
@@ -223,7 +219,7 @@ function createServerFailureEvent(command: GenerateServerCommand, error: unknown
|
|
|
223
219
|
return {
|
|
224
220
|
type: 'ServerGenerationFailed',
|
|
225
221
|
data: {
|
|
226
|
-
|
|
222
|
+
modelPath: command.data.modelPath,
|
|
227
223
|
destination: command.data.destination,
|
|
228
224
|
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
229
225
|
},
|
|
@@ -233,29 +229,28 @@ function createServerFailureEvent(command: GenerateServerCommand, error: unknown
|
|
|
233
229
|
};
|
|
234
230
|
}
|
|
235
231
|
|
|
236
|
-
async function handleGenerateServerCommandInternal(
|
|
232
|
+
export async function handleGenerateServerCommandInternal(
|
|
237
233
|
command: GenerateServerCommand,
|
|
238
|
-
): Promise<
|
|
239
|
-
const {
|
|
234
|
+
): Promise<GenerateServerEvents[]> {
|
|
235
|
+
const { modelPath, destination } = command.data;
|
|
236
|
+
const events: GenerateServerEvents[] = [];
|
|
240
237
|
|
|
241
238
|
debug('Starting server generation');
|
|
242
|
-
debug('
|
|
239
|
+
debug('Model path: %s', modelPath);
|
|
243
240
|
debug('Destination: %s', destination);
|
|
244
241
|
|
|
245
242
|
try {
|
|
246
243
|
const absDest = resolve(destination);
|
|
247
|
-
const
|
|
244
|
+
const absModel = resolve(modelPath);
|
|
248
245
|
|
|
249
246
|
debug('Resolved paths:');
|
|
250
247
|
debug(' Absolute destination: %s', absDest);
|
|
251
|
-
debug(' Absolute
|
|
248
|
+
debug(' Absolute model: %s', absModel);
|
|
252
249
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (validationError) return validationError;
|
|
250
|
+
const validationError = await validateModelFile(absModel, command);
|
|
251
|
+
if (validationError) return [validationError];
|
|
256
252
|
|
|
257
|
-
|
|
258
|
-
const spec = await readAndParseSchema(absSchema);
|
|
253
|
+
const spec = await readAndParseModel(absModel);
|
|
259
254
|
|
|
260
255
|
// Setup server directory
|
|
261
256
|
const serverDir = join(absDest, 'server');
|
|
@@ -265,9 +260,33 @@ async function handleGenerateServerCommandInternal(
|
|
|
265
260
|
await ensureDirExists(serverDir);
|
|
266
261
|
debugFiles('Created server directory: %s', serverDir);
|
|
267
262
|
|
|
268
|
-
// Generate scaffold files
|
|
269
263
|
await generateAndWriteScaffold(spec, serverDir);
|
|
270
264
|
|
|
265
|
+
if (Array.isArray(spec.flows) && spec.flows.length > 0) {
|
|
266
|
+
for (const flow of spec.flows) {
|
|
267
|
+
if (Array.isArray(flow.slices) && flow.slices.length > 0) {
|
|
268
|
+
for (const slice of flow.slices) {
|
|
269
|
+
if (slice.type === 'experience') continue; // skip experience slices
|
|
270
|
+
const sliceEvent: SliceGeneratedEvent = {
|
|
271
|
+
type: 'SliceGenerated',
|
|
272
|
+
data: {
|
|
273
|
+
flowName: flow.name,
|
|
274
|
+
sliceName: slice.name,
|
|
275
|
+
sliceType: slice.type,
|
|
276
|
+
schemaPath: command.data.modelPath,
|
|
277
|
+
slicePath: ensureDirPath('./server/src/domain/flows', toKebabCase(flow.name), toKebabCase(slice.name)),
|
|
278
|
+
},
|
|
279
|
+
timestamp: new Date(),
|
|
280
|
+
requestId: command.requestId,
|
|
281
|
+
correlationId: command.correlationId,
|
|
282
|
+
};
|
|
283
|
+
events.push(sliceEvent);
|
|
284
|
+
debug('SliceGenerated: %s.%s', flow.name, slice.name);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
271
290
|
// Copy files
|
|
272
291
|
await copyAllFiles(serverDir);
|
|
273
292
|
|
|
@@ -281,9 +300,13 @@ async function handleGenerateServerCommandInternal(
|
|
|
281
300
|
debug('Server generation completed successfully');
|
|
282
301
|
debug('Server directory: %s', serverDir);
|
|
283
302
|
|
|
284
|
-
|
|
303
|
+
// Add final success event
|
|
304
|
+
events.push(createServerSuccessEvent(command, serverDir, absDest));
|
|
305
|
+
return events;
|
|
285
306
|
} catch (error) {
|
|
286
|
-
|
|
307
|
+
// Add failure event to events array if any events were already added
|
|
308
|
+
events.push(createServerFailureEvent(command, error));
|
|
309
|
+
return events;
|
|
287
310
|
}
|
|
288
311
|
}
|
|
289
312
|
|