@kapeta/local-cluster-service 0.54.3 → 0.54.5
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 +17 -0
- package/dist/cjs/src/storm/codegen.js +50 -20
- package/dist/cjs/src/storm/events.d.ts +7 -1
- package/dist/esm/src/storm/codegen.js +50 -20
- package/dist/esm/src/storm/events.d.ts +7 -1
- package/package.json +1 -1
- package/src/storm/codegen.ts +62 -33
- package/src/storm/events.ts +8 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## [0.54.5](https://github.com/kapetacom/local-cluster-service/compare/v0.54.4...v0.54.5) (2024-06-21)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* enable filesToBeFixed inclusion list in codefix ([4eaf272](https://github.com/kapetacom/local-cluster-service/commit/4eaf2720bc49b5709c170f88a85614b1a1a8bb8b))
|
7
|
+
* forward FILE_FAILED events from streams ([fc22b68](https://github.com/kapetacom/local-cluster-service/commit/fc22b68e129546166c117ea5d13efeebf4bd9d72))
|
8
|
+
|
9
|
+
## [0.54.4](https://github.com/kapetacom/local-cluster-service/compare/v0.54.3...v0.54.4) (2024-06-20)
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* merge error ([4c29cab](https://github.com/kapetacom/local-cluster-service/commit/4c29cab33c311a3ad21913b974d92207968a15db))
|
15
|
+
* minors ([c36e825](https://github.com/kapetacom/local-cluster-service/commit/c36e8258cf0d7a3fb0915080f64d524ffd925929))
|
16
|
+
* processTemplate will possibly return a list of files for each file when enhancing services ([3f186a3](https://github.com/kapetacom/local-cluster-service/commit/3f186a3afbb6b527c933ff051d2659e07ab39bbd))
|
17
|
+
|
1
18
|
## [0.54.3](https://github.com/kapetacom/local-cluster-service/compare/v0.54.2...v0.54.3) (2024-06-19)
|
2
19
|
|
3
20
|
|
@@ -117,16 +117,13 @@ class StormCodegen {
|
|
117
117
|
getStream() {
|
118
118
|
return this.out;
|
119
119
|
}
|
120
|
-
handleTemplateFileOutput(blockUri, aiName,
|
120
|
+
handleTemplateFileOutput(blockUri, aiName, data) {
|
121
121
|
if (this.handleFileEvents(blockUri, aiName, data)) {
|
122
122
|
return;
|
123
123
|
}
|
124
124
|
switch (data.type) {
|
125
125
|
case 'FILE_DONE':
|
126
|
-
|
127
|
-
template.content = data.payload.content;
|
128
|
-
this.handleFileDoneOutput(blockUri, aiName, data);
|
129
|
-
break;
|
126
|
+
return this.handleFileDoneOutput(blockUri, aiName, data);
|
130
127
|
}
|
131
128
|
}
|
132
129
|
handleUiOutput(blockUri, blockName, data) {
|
@@ -198,6 +195,17 @@ class StormCodegen {
|
|
198
195
|
},
|
199
196
|
});
|
200
197
|
return true;
|
198
|
+
case 'FILE_FAILED':
|
199
|
+
this.out.emit('data', {
|
200
|
+
...data,
|
201
|
+
payload: {
|
202
|
+
...data.payload,
|
203
|
+
path: (0, path_2.join)(basePath, data.payload.filename),
|
204
|
+
blockName,
|
205
|
+
blockRef,
|
206
|
+
instanceId,
|
207
|
+
},
|
208
|
+
});
|
201
209
|
}
|
202
210
|
return false;
|
203
211
|
}
|
@@ -229,8 +237,10 @@ class StormCodegen {
|
|
229
237
|
if (this.isAborted()) {
|
230
238
|
return;
|
231
239
|
}
|
240
|
+
const kapetaYaml = yaml_1.default.stringify(block.content);
|
241
|
+
const blockDefinition = block.content;
|
232
242
|
// Generate the code for the block using the standard codegen templates
|
233
|
-
const generatedResult = await this.generateBlock(
|
243
|
+
const generatedResult = await this.generateBlock(blockDefinition);
|
234
244
|
if (!generatedResult) {
|
235
245
|
return;
|
236
246
|
}
|
@@ -321,7 +331,7 @@ class StormCodegen {
|
|
321
331
|
if (this.isAborted()) {
|
322
332
|
return;
|
323
333
|
}
|
324
|
-
const basePath = this.getBasePath(
|
334
|
+
const basePath = this.getBasePath(blockDefinition.metadata.name);
|
325
335
|
const screenFilesConverted = screenFiles.map((screenFile) => {
|
326
336
|
return {
|
327
337
|
filename: screenFile.payload.filename,
|
@@ -342,7 +352,7 @@ class StormCodegen {
|
|
342
352
|
};
|
343
353
|
const stream = await stormClient_1.stormClient.generateCode(payload);
|
344
354
|
stream.on('data', (evt) => {
|
345
|
-
this.handleTemplateFileOutput(blockUri, block.aiName,
|
355
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, evt);
|
346
356
|
});
|
347
357
|
this.out.on('aborted', () => {
|
348
358
|
stream.abort();
|
@@ -352,36 +362,37 @@ class StormCodegen {
|
|
352
362
|
// Gather the context files for implementation. These will be all be passed to the AI
|
353
363
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
354
364
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
355
|
-
|
365
|
+
let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
356
366
|
if (serviceFiles.length > 0) {
|
357
|
-
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
367
|
+
serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
358
368
|
}
|
359
369
|
if (this.isAborted()) {
|
360
370
|
return;
|
361
371
|
}
|
372
|
+
for (const contextFile of contextFiles) {
|
373
|
+
const filePath = (0, path_2.join)(basePath, contextFile.filename);
|
374
|
+
await (0, promises_1.writeFile)(filePath, contextFile.content);
|
375
|
+
}
|
362
376
|
for (const screenFile of screenFilesConverted) {
|
363
377
|
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
364
378
|
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
365
379
|
}
|
380
|
+
// this might decide to overwrite a file that is also a context file
|
366
381
|
for (const serviceFile of serviceFiles) {
|
367
382
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
368
383
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
369
384
|
}
|
370
|
-
for (const serviceFile of contextFiles) {
|
371
|
-
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
372
|
-
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
373
|
-
}
|
374
385
|
// Write again after modifications
|
375
386
|
for (const webRouterFile of webRouters) {
|
376
387
|
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
377
388
|
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
378
389
|
}
|
379
390
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
380
|
-
await (0, promises_1.writeFile)(kapetaYmlPath,
|
391
|
+
await (0, promises_1.writeFile)(kapetaYmlPath, kapetaYaml);
|
381
392
|
const blockRef = block.uri;
|
382
393
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
383
394
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
384
|
-
const codeGenerator = new codegen_1.BlockCodeGenerator(
|
395
|
+
const codeGenerator = new codegen_1.BlockCodeGenerator(blockDefinition);
|
385
396
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.BUILDING);
|
386
397
|
await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
387
398
|
this.out.emit('data', {
|
@@ -428,8 +439,9 @@ class StormCodegen {
|
|
428
439
|
if (errors.size > 0) {
|
429
440
|
this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIXING);
|
430
441
|
const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
|
431
|
-
|
432
|
-
|
442
|
+
if (filesToBeFixed.some((file) => file.filename === filename)) {
|
443
|
+
return this.tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator);
|
444
|
+
}
|
433
445
|
});
|
434
446
|
await Promise.all(promises);
|
435
447
|
}
|
@@ -676,6 +688,7 @@ class StormCodegen {
|
|
676
688
|
*/
|
677
689
|
async processTemplates(blockUri, aiName, generator, templates, contextFiles) {
|
678
690
|
const promises = templates.map(async (templateFile) => {
|
691
|
+
let changedFiles = [];
|
679
692
|
const stream = await generator({
|
680
693
|
context: contextFiles,
|
681
694
|
template: templateFile,
|
@@ -685,11 +698,28 @@ class StormCodegen {
|
|
685
698
|
stream.abort();
|
686
699
|
});
|
687
700
|
stream.on('data', (evt) => {
|
688
|
-
this.handleTemplateFileOutput(blockUri, aiName,
|
701
|
+
let changedFile = this.handleTemplateFileOutput(blockUri, aiName, evt);
|
702
|
+
if (changedFile) {
|
703
|
+
changedFiles.push(...changedFiles, changedFile);
|
704
|
+
}
|
689
705
|
});
|
690
706
|
await stream.waitForDone();
|
707
|
+
return changedFiles;
|
691
708
|
});
|
692
|
-
await Promise.all(promises);
|
709
|
+
const changedFiles = await Promise.all(promises);
|
710
|
+
let allFiles = templates.concat(contextFiles);
|
711
|
+
let result = [];
|
712
|
+
for (const changedFile of changedFiles.flat()) {
|
713
|
+
const find = allFiles.find((file) => file.filename === changedFile.payload.filename);
|
714
|
+
if (find) {
|
715
|
+
result.push({ type: find.type, filename: find.filename, mode: find.mode, permissions: find.permissions,
|
716
|
+
content: changedFile.payload.content });
|
717
|
+
}
|
718
|
+
else {
|
719
|
+
console.warn("processTemplates: AI changed a file that wasn't in the input [" + changedFile.payload.filename + "]");
|
720
|
+
}
|
721
|
+
}
|
722
|
+
return result;
|
693
723
|
}
|
694
724
|
/**
|
695
725
|
* Converts the generated files to a format that can be sent to the AI
|
@@ -200,6 +200,12 @@ export interface StormEventFileDone extends StormEventFileBase {
|
|
200
200
|
content: string;
|
201
201
|
};
|
202
202
|
}
|
203
|
+
export interface StormEventFileFailed extends StormEventFileBase {
|
204
|
+
type: 'FILE_FAILED';
|
205
|
+
payload: StormEventFileBasePayload & {
|
206
|
+
error: string;
|
207
|
+
};
|
208
|
+
}
|
203
209
|
export interface StormEventFileChunk extends StormEventFileBase {
|
204
210
|
type: 'FILE_CHUNK';
|
205
211
|
payload: StormEventFileBasePayload & {
|
@@ -259,4 +265,4 @@ export interface StormEventPhases {
|
|
259
265
|
phaseType: StormEventPhaseType;
|
260
266
|
};
|
261
267
|
}
|
262
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry;
|
268
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry;
|
@@ -117,16 +117,13 @@ class StormCodegen {
|
|
117
117
|
getStream() {
|
118
118
|
return this.out;
|
119
119
|
}
|
120
|
-
handleTemplateFileOutput(blockUri, aiName,
|
120
|
+
handleTemplateFileOutput(blockUri, aiName, data) {
|
121
121
|
if (this.handleFileEvents(blockUri, aiName, data)) {
|
122
122
|
return;
|
123
123
|
}
|
124
124
|
switch (data.type) {
|
125
125
|
case 'FILE_DONE':
|
126
|
-
|
127
|
-
template.content = data.payload.content;
|
128
|
-
this.handleFileDoneOutput(blockUri, aiName, data);
|
129
|
-
break;
|
126
|
+
return this.handleFileDoneOutput(blockUri, aiName, data);
|
130
127
|
}
|
131
128
|
}
|
132
129
|
handleUiOutput(blockUri, blockName, data) {
|
@@ -198,6 +195,17 @@ class StormCodegen {
|
|
198
195
|
},
|
199
196
|
});
|
200
197
|
return true;
|
198
|
+
case 'FILE_FAILED':
|
199
|
+
this.out.emit('data', {
|
200
|
+
...data,
|
201
|
+
payload: {
|
202
|
+
...data.payload,
|
203
|
+
path: (0, path_2.join)(basePath, data.payload.filename),
|
204
|
+
blockName,
|
205
|
+
blockRef,
|
206
|
+
instanceId,
|
207
|
+
},
|
208
|
+
});
|
201
209
|
}
|
202
210
|
return false;
|
203
211
|
}
|
@@ -229,8 +237,10 @@ class StormCodegen {
|
|
229
237
|
if (this.isAborted()) {
|
230
238
|
return;
|
231
239
|
}
|
240
|
+
const kapetaYaml = yaml_1.default.stringify(block.content);
|
241
|
+
const blockDefinition = block.content;
|
232
242
|
// Generate the code for the block using the standard codegen templates
|
233
|
-
const generatedResult = await this.generateBlock(
|
243
|
+
const generatedResult = await this.generateBlock(blockDefinition);
|
234
244
|
if (!generatedResult) {
|
235
245
|
return;
|
236
246
|
}
|
@@ -321,7 +331,7 @@ class StormCodegen {
|
|
321
331
|
if (this.isAborted()) {
|
322
332
|
return;
|
323
333
|
}
|
324
|
-
const basePath = this.getBasePath(
|
334
|
+
const basePath = this.getBasePath(blockDefinition.metadata.name);
|
325
335
|
const screenFilesConverted = screenFiles.map((screenFile) => {
|
326
336
|
return {
|
327
337
|
filename: screenFile.payload.filename,
|
@@ -342,7 +352,7 @@ class StormCodegen {
|
|
342
352
|
};
|
343
353
|
const stream = await stormClient_1.stormClient.generateCode(payload);
|
344
354
|
stream.on('data', (evt) => {
|
345
|
-
this.handleTemplateFileOutput(blockUri, block.aiName,
|
355
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, evt);
|
346
356
|
});
|
347
357
|
this.out.on('aborted', () => {
|
348
358
|
stream.abort();
|
@@ -352,36 +362,37 @@ class StormCodegen {
|
|
352
362
|
// Gather the context files for implementation. These will be all be passed to the AI
|
353
363
|
const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN, codegen_1.AIFileTypes.WEB_ROUTER].includes(file.type));
|
354
364
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
355
|
-
|
365
|
+
let serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
356
366
|
if (serviceFiles.length > 0) {
|
357
|
-
await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
367
|
+
serviceFiles = await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
358
368
|
}
|
359
369
|
if (this.isAborted()) {
|
360
370
|
return;
|
361
371
|
}
|
372
|
+
for (const contextFile of contextFiles) {
|
373
|
+
const filePath = (0, path_2.join)(basePath, contextFile.filename);
|
374
|
+
await (0, promises_1.writeFile)(filePath, contextFile.content);
|
375
|
+
}
|
362
376
|
for (const screenFile of screenFilesConverted) {
|
363
377
|
const filePath = (0, path_2.join)(basePath, screenFile.filename);
|
364
378
|
await (0, promises_1.writeFile)(filePath, screenFile.content);
|
365
379
|
}
|
380
|
+
// this might decide to overwrite a file that is also a context file
|
366
381
|
for (const serviceFile of serviceFiles) {
|
367
382
|
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
368
383
|
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
369
384
|
}
|
370
|
-
for (const serviceFile of contextFiles) {
|
371
|
-
const filePath = (0, path_2.join)(basePath, serviceFile.filename);
|
372
|
-
await (0, promises_1.writeFile)(filePath, serviceFile.content);
|
373
|
-
}
|
374
385
|
// Write again after modifications
|
375
386
|
for (const webRouterFile of webRouters) {
|
376
387
|
const filePath = (0, path_2.join)(basePath, webRouterFile.filename);
|
377
388
|
await (0, promises_1.writeFile)(filePath, webRouterFile.content);
|
378
389
|
}
|
379
390
|
const kapetaYmlPath = (0, path_2.join)(basePath, 'kapeta.yml');
|
380
|
-
await (0, promises_1.writeFile)(kapetaYmlPath,
|
391
|
+
await (0, promises_1.writeFile)(kapetaYmlPath, kapetaYaml);
|
381
392
|
const blockRef = block.uri;
|
382
393
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
|
383
394
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
384
|
-
const codeGenerator = new codegen_1.BlockCodeGenerator(
|
395
|
+
const codeGenerator = new codegen_1.BlockCodeGenerator(blockDefinition);
|
385
396
|
this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.BUILDING);
|
386
397
|
await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
387
398
|
this.out.emit('data', {
|
@@ -428,8 +439,9 @@ class StormCodegen {
|
|
428
439
|
if (errors.size > 0) {
|
429
440
|
this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIXING);
|
430
441
|
const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
|
431
|
-
|
432
|
-
|
442
|
+
if (filesToBeFixed.some((file) => file.filename === filename)) {
|
443
|
+
return this.tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator);
|
444
|
+
}
|
433
445
|
});
|
434
446
|
await Promise.all(promises);
|
435
447
|
}
|
@@ -676,6 +688,7 @@ class StormCodegen {
|
|
676
688
|
*/
|
677
689
|
async processTemplates(blockUri, aiName, generator, templates, contextFiles) {
|
678
690
|
const promises = templates.map(async (templateFile) => {
|
691
|
+
let changedFiles = [];
|
679
692
|
const stream = await generator({
|
680
693
|
context: contextFiles,
|
681
694
|
template: templateFile,
|
@@ -685,11 +698,28 @@ class StormCodegen {
|
|
685
698
|
stream.abort();
|
686
699
|
});
|
687
700
|
stream.on('data', (evt) => {
|
688
|
-
this.handleTemplateFileOutput(blockUri, aiName,
|
701
|
+
let changedFile = this.handleTemplateFileOutput(blockUri, aiName, evt);
|
702
|
+
if (changedFile) {
|
703
|
+
changedFiles.push(...changedFiles, changedFile);
|
704
|
+
}
|
689
705
|
});
|
690
706
|
await stream.waitForDone();
|
707
|
+
return changedFiles;
|
691
708
|
});
|
692
|
-
await Promise.all(promises);
|
709
|
+
const changedFiles = await Promise.all(promises);
|
710
|
+
let allFiles = templates.concat(contextFiles);
|
711
|
+
let result = [];
|
712
|
+
for (const changedFile of changedFiles.flat()) {
|
713
|
+
const find = allFiles.find((file) => file.filename === changedFile.payload.filename);
|
714
|
+
if (find) {
|
715
|
+
result.push({ type: find.type, filename: find.filename, mode: find.mode, permissions: find.permissions,
|
716
|
+
content: changedFile.payload.content });
|
717
|
+
}
|
718
|
+
else {
|
719
|
+
console.warn("processTemplates: AI changed a file that wasn't in the input [" + changedFile.payload.filename + "]");
|
720
|
+
}
|
721
|
+
}
|
722
|
+
return result;
|
693
723
|
}
|
694
724
|
/**
|
695
725
|
* Converts the generated files to a format that can be sent to the AI
|
@@ -200,6 +200,12 @@ export interface StormEventFileDone extends StormEventFileBase {
|
|
200
200
|
content: string;
|
201
201
|
};
|
202
202
|
}
|
203
|
+
export interface StormEventFileFailed extends StormEventFileBase {
|
204
|
+
type: 'FILE_FAILED';
|
205
|
+
payload: StormEventFileBasePayload & {
|
206
|
+
error: string;
|
207
|
+
};
|
208
|
+
}
|
203
209
|
export interface StormEventFileChunk extends StormEventFileBase {
|
204
210
|
type: 'FILE_CHUNK';
|
205
211
|
payload: StormEventFileBasePayload & {
|
@@ -259,4 +265,4 @@ export interface StormEventPhases {
|
|
259
265
|
phaseType: StormEventPhaseType;
|
260
266
|
};
|
261
267
|
}
|
262
|
-
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry;
|
268
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry;
|
package/package.json
CHANGED
package/src/storm/codegen.ts
CHANGED
@@ -36,7 +36,6 @@ import Path, { join } from 'path';
|
|
36
36
|
import os from 'node:os';
|
37
37
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
38
38
|
import YAML from 'yaml';
|
39
|
-
import assert from 'assert';
|
40
39
|
|
41
40
|
type ImplementationGenerator<T = StormFileImplementationPrompt> = (
|
42
41
|
prompt: T,
|
@@ -134,19 +133,15 @@ export class StormCodegen {
|
|
134
133
|
private handleTemplateFileOutput(
|
135
134
|
blockUri: KapetaURI,
|
136
135
|
aiName: string,
|
137
|
-
template: StormFileInfo,
|
138
136
|
data: StormEvent
|
139
|
-
):
|
137
|
+
): StormEventFileDone | undefined {
|
140
138
|
if (this.handleFileEvents(blockUri, aiName, data)) {
|
141
139
|
return;
|
142
140
|
}
|
143
141
|
|
144
142
|
switch (data.type) {
|
145
143
|
case 'FILE_DONE':
|
146
|
-
|
147
|
-
template.content = data.payload.content;
|
148
|
-
this.handleFileDoneOutput(blockUri, aiName, data);
|
149
|
-
break;
|
144
|
+
return this.handleFileDoneOutput(blockUri, aiName, data);
|
150
145
|
}
|
151
146
|
}
|
152
147
|
|
@@ -222,12 +217,23 @@ export class StormCodegen {
|
|
222
217
|
},
|
223
218
|
});
|
224
219
|
return true;
|
220
|
+
case 'FILE_FAILED':
|
221
|
+
this.out.emit('data', {
|
222
|
+
...data,
|
223
|
+
payload: {
|
224
|
+
...data.payload,
|
225
|
+
path: join(basePath, data.payload.filename),
|
226
|
+
blockName,
|
227
|
+
blockRef,
|
228
|
+
instanceId,
|
229
|
+
},
|
230
|
+
});
|
225
231
|
}
|
226
232
|
|
227
233
|
return false;
|
228
234
|
}
|
229
235
|
|
230
|
-
private handleFileDoneOutput(blockUri: KapetaURI, aiName: string, data: StormEvent) {
|
236
|
+
private handleFileDoneOutput(blockUri: KapetaURI, aiName: string, data: StormEvent): StormEventFileDone | undefined {
|
231
237
|
switch (data.type) {
|
232
238
|
case 'FILE_DONE':
|
233
239
|
const ref = blockUri.toNormalizedString();
|
@@ -257,8 +263,12 @@ export class StormCodegen {
|
|
257
263
|
if (this.isAborted()) {
|
258
264
|
return;
|
259
265
|
}
|
266
|
+
|
267
|
+
const kapetaYaml = YAML.stringify(block.content);
|
268
|
+
const blockDefinition = block.content;
|
269
|
+
|
260
270
|
// Generate the code for the block using the standard codegen templates
|
261
|
-
const generatedResult = await this.generateBlock(
|
271
|
+
const generatedResult = await this.generateBlock(blockDefinition);
|
262
272
|
if (!generatedResult) {
|
263
273
|
return;
|
264
274
|
}
|
@@ -374,7 +384,7 @@ export class StormCodegen {
|
|
374
384
|
if (this.isAborted()) {
|
375
385
|
return;
|
376
386
|
}
|
377
|
-
const basePath = this.getBasePath(
|
387
|
+
const basePath = this.getBasePath(blockDefinition.metadata.name);
|
378
388
|
|
379
389
|
const screenFilesConverted = screenFiles.map((screenFile) => {
|
380
390
|
return {
|
@@ -400,7 +410,7 @@ export class StormCodegen {
|
|
400
410
|
const stream = await stormClient.generateCode(payload);
|
401
411
|
|
402
412
|
stream.on('data', (evt) => {
|
403
|
-
this.handleTemplateFileOutput(blockUri, block.aiName,
|
413
|
+
this.handleTemplateFileOutput(blockUri, block.aiName, evt);
|
404
414
|
});
|
405
415
|
|
406
416
|
this.out.on('aborted', () => {
|
@@ -417,9 +427,9 @@ export class StormCodegen {
|
|
417
427
|
);
|
418
428
|
|
419
429
|
// Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
|
420
|
-
|
430
|
+
let serviceFiles: StormFileInfo[] = allFiles.filter((file) => file.type === AIFileTypes.SERVICE);
|
421
431
|
if (serviceFiles.length > 0) {
|
422
|
-
await this.processTemplates(
|
432
|
+
serviceFiles = await this.processTemplates(
|
423
433
|
blockUri,
|
424
434
|
block.aiName,
|
425
435
|
stormClient.createServiceImplementation.bind(stormClient),
|
@@ -432,21 +442,22 @@ export class StormCodegen {
|
|
432
442
|
return;
|
433
443
|
}
|
434
444
|
|
445
|
+
for (const contextFile of contextFiles) {
|
446
|
+
const filePath = join(basePath, contextFile.filename);
|
447
|
+
await writeFile(filePath, contextFile.content);
|
448
|
+
}
|
449
|
+
|
435
450
|
for (const screenFile of screenFilesConverted) {
|
436
451
|
const filePath = join(basePath, screenFile.filename);
|
437
452
|
await writeFile(filePath, screenFile.content);
|
438
453
|
}
|
439
454
|
|
455
|
+
// this might decide to overwrite a file that is also a context file
|
440
456
|
for (const serviceFile of serviceFiles) {
|
441
457
|
const filePath = join(basePath, serviceFile.filename);
|
442
458
|
await writeFile(filePath, serviceFile.content);
|
443
459
|
}
|
444
460
|
|
445
|
-
for (const serviceFile of contextFiles) {
|
446
|
-
const filePath = join(basePath, serviceFile.filename);
|
447
|
-
await writeFile(filePath, serviceFile.content);
|
448
|
-
}
|
449
|
-
|
450
461
|
// Write again after modifications
|
451
462
|
for (const webRouterFile of webRouters) {
|
452
463
|
const filePath = join(basePath, webRouterFile.filename);
|
@@ -454,14 +465,14 @@ export class StormCodegen {
|
|
454
465
|
}
|
455
466
|
|
456
467
|
const kapetaYmlPath = join(basePath, 'kapeta.yml');
|
457
|
-
await writeFile(kapetaYmlPath,
|
468
|
+
await writeFile(kapetaYmlPath, kapetaYaml);
|
458
469
|
|
459
470
|
const blockRef = block.uri;
|
460
471
|
|
461
472
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
|
462
473
|
|
463
474
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
464
|
-
const codeGenerator = new BlockCodeGenerator(
|
475
|
+
const codeGenerator = new BlockCodeGenerator(blockDefinition);
|
465
476
|
|
466
477
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.BUILDING);
|
467
478
|
await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
@@ -524,16 +535,17 @@ export class StormCodegen {
|
|
524
535
|
this.emitBlockStatus(blockUri, blockName, StormEventBlockStatusType.FIXING);
|
525
536
|
|
526
537
|
const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
538
|
+
if (filesToBeFixed.some((file) => file.filename === filename)) {
|
539
|
+
return this.tryToFixFile(
|
540
|
+
blockUri,
|
541
|
+
blockName,
|
542
|
+
basePath,
|
543
|
+
filename,
|
544
|
+
fileErrors,
|
545
|
+
allFiles,
|
546
|
+
codeGenerator
|
547
|
+
);
|
548
|
+
}
|
537
549
|
});
|
538
550
|
|
539
551
|
await Promise.all(promises);
|
@@ -864,8 +876,9 @@ export class StormCodegen {
|
|
864
876
|
generator: ImplementationGenerator,
|
865
877
|
templates: StormFileInfo[],
|
866
878
|
contextFiles: StormFileInfo[]
|
867
|
-
): Promise<
|
879
|
+
): Promise<StormFileInfo[]> {
|
868
880
|
const promises = templates.map(async (templateFile) => {
|
881
|
+
let changedFiles: StormEventFileDone[] = [];
|
869
882
|
const stream = await generator({
|
870
883
|
context: contextFiles,
|
871
884
|
template: templateFile,
|
@@ -877,13 +890,29 @@ export class StormCodegen {
|
|
877
890
|
});
|
878
891
|
|
879
892
|
stream.on('data', (evt) => {
|
880
|
-
this.handleTemplateFileOutput(blockUri, aiName,
|
893
|
+
let changedFile = this.handleTemplateFileOutput(blockUri, aiName, evt);
|
894
|
+
if (changedFile) {
|
895
|
+
changedFiles.push(...changedFiles, changedFile);
|
896
|
+
}
|
881
897
|
});
|
882
898
|
|
883
899
|
await stream.waitForDone();
|
900
|
+
return changedFiles;
|
884
901
|
});
|
885
902
|
|
886
|
-
await Promise.all(promises);
|
903
|
+
const changedFiles: StormEventFileDone[][] = await Promise.all(promises);
|
904
|
+
let allFiles = templates.concat(contextFiles);
|
905
|
+
let result: StormFileInfo[] = [];
|
906
|
+
for (const changedFile of changedFiles.flat()) {
|
907
|
+
const find = allFiles.find((file) => file.filename === changedFile.payload.filename);
|
908
|
+
if (find) {
|
909
|
+
result.push({ type: find.type, filename: find.filename, mode: find.mode, permissions: find.permissions,
|
910
|
+
content: changedFile.payload.content });
|
911
|
+
} else {
|
912
|
+
console.warn("processTemplates: AI changed a file that wasn't in the input [" + changedFile.payload.filename + "]");
|
913
|
+
}
|
914
|
+
}
|
915
|
+
return result;
|
887
916
|
}
|
888
917
|
|
889
918
|
/**
|
package/src/storm/events.ts
CHANGED
@@ -241,6 +241,13 @@ export interface StormEventFileDone extends StormEventFileBase {
|
|
241
241
|
};
|
242
242
|
}
|
243
243
|
|
244
|
+
export interface StormEventFileFailed extends StormEventFileBase {
|
245
|
+
type: 'FILE_FAILED';
|
246
|
+
payload: StormEventFileBasePayload & {
|
247
|
+
error: string;
|
248
|
+
};
|
249
|
+
}
|
250
|
+
|
244
251
|
export interface StormEventFileChunk extends StormEventFileBase {
|
245
252
|
type: 'FILE_CHUNK';
|
246
253
|
payload: StormEventFileBasePayload & {
|
@@ -322,6 +329,7 @@ export type StormEvent =
|
|
322
329
|
| StormEventFileLogical
|
323
330
|
| StormEventFileState
|
324
331
|
| StormEventFileDone
|
332
|
+
| StormEventFileFailed
|
325
333
|
| StormEventFileChunk
|
326
334
|
| StormEventDone
|
327
335
|
| StormEventDefinitionChange
|