@kapeta/local-cluster-service 0.54.11 → 0.55.0
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 +21 -0
- package/definitions.d.ts +11 -0
- package/dist/cjs/src/storm/archetype.d.ts +12 -0
- package/dist/cjs/src/storm/archetype.js +98 -0
- package/dist/cjs/src/storm/codegen.d.ts +2 -0
- package/dist/cjs/src/storm/codegen.js +28 -4
- package/dist/cjs/src/storm/event-parser.d.ts +7 -4
- package/dist/cjs/src/storm/event-parser.js +190 -160
- package/dist/cjs/src/storm/events.d.ts +1 -0
- package/dist/cjs/src/storm/predefined.d.ts +27 -0
- package/dist/cjs/src/storm/predefined.js +64 -0
- package/dist/cjs/src/storm/routes.js +23 -15
- package/dist/cjs/test/storm/codegen.test.d.ts +5 -0
- package/dist/cjs/test/storm/codegen.test.js +41 -0
- package/dist/cjs/test/storm/event-parser.test.d.ts +25 -1
- package/dist/cjs/test/storm/event-parser.test.js +34 -15
- package/dist/cjs/test/storm/predefined-user-events.json +13 -0
- package/dist/esm/src/storm/archetype.d.ts +12 -0
- package/dist/esm/src/storm/archetype.js +98 -0
- package/dist/esm/src/storm/codegen.d.ts +2 -0
- package/dist/esm/src/storm/codegen.js +28 -4
- package/dist/esm/src/storm/event-parser.d.ts +7 -4
- package/dist/esm/src/storm/event-parser.js +190 -160
- package/dist/esm/src/storm/events.d.ts +1 -0
- package/dist/esm/src/storm/predefined.d.ts +27 -0
- package/dist/esm/src/storm/predefined.js +64 -0
- package/dist/esm/src/storm/routes.js +23 -15
- package/dist/esm/test/storm/codegen.test.d.ts +5 -0
- package/dist/esm/test/storm/codegen.test.js +41 -0
- package/dist/esm/test/storm/event-parser.test.d.ts +25 -1
- package/dist/esm/test/storm/event-parser.test.js +34 -15
- package/dist/esm/test/storm/predefined-user-events.json +13 -0
- package/package.json +6 -1
- package/src/storm/archetype.ts +85 -0
- package/src/storm/codegen.ts +34 -4
- package/src/storm/event-parser.ts +200 -159
- package/src/storm/events.ts +1 -0
- package/src/storm/predefined.ts +52 -0
- package/src/storm/routes.ts +25 -18
- package/test/storm/codegen.test.ts +46 -0
- package/test/storm/event-parser.test.ts +32 -10
- package/test/storm/predefined-user-events.json +13 -0
@@ -7,9 +7,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
8
|
};
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
10
|
+
exports.parserOptions = void 0;
|
10
11
|
const event_parser_1 = require("../../src/storm/event-parser");
|
11
12
|
const simple_blog_events_json_1 = __importDefault(require("./simple-blog-events.json"));
|
12
|
-
const
|
13
|
+
const predefined_user_events_json_1 = __importDefault(require("./predefined-user-events.json"));
|
14
|
+
exports.parserOptions = {
|
13
15
|
serviceKind: 'kapeta/block-service:local',
|
14
16
|
serviceLanguage: 'kapeta/language-target-nodejs-ts:local',
|
15
17
|
frontendKind: 'kapeta/block-type-frontend:local',
|
@@ -129,10 +131,12 @@ const events = [
|
|
129
131
|
},
|
130
132
|
];
|
131
133
|
describe('event-parser', () => {
|
132
|
-
it('it can parse events into a plan and blocks with proper layout', () => {
|
133
|
-
const parser = new event_parser_1.StormEventParser(parserOptions);
|
134
|
-
|
135
|
-
|
134
|
+
it('it can parse events into a plan and blocks with proper layout', async () => {
|
135
|
+
const parser = new event_parser_1.StormEventParser(exports.parserOptions);
|
136
|
+
for (const event of events) {
|
137
|
+
await parser.processEvent('kapeta', event);
|
138
|
+
}
|
139
|
+
const result = await parser.toResult('kapeta');
|
136
140
|
expect(result.plan.metadata.name).toBe('kapeta/my-plan');
|
137
141
|
expect(result.plan.metadata.description).toBe('my plan description');
|
138
142
|
expect(result.blocks.length).toBe(2);
|
@@ -146,10 +150,10 @@ describe('event-parser', () => {
|
|
146
150
|
expect(clientResource).toBeDefined();
|
147
151
|
expect(dbResource).toBeDefined();
|
148
152
|
expect(pageResource).toBeDefined();
|
149
|
-
expect(apiResource?.kind).toBe(parserOptions.apiKind);
|
150
|
-
expect(clientResource?.kind).toBe(parserOptions.clientKind);
|
151
|
-
expect(dbResource?.kind).toBe(parserOptions.databaseKind);
|
152
|
-
expect(pageResource?.kind).toBe(parserOptions.webPageKind);
|
153
|
+
expect(apiResource?.kind).toBe(exports.parserOptions.apiKind);
|
154
|
+
expect(clientResource?.kind).toBe(exports.parserOptions.clientKind);
|
155
|
+
expect(dbResource?.kind).toBe(exports.parserOptions.databaseKind);
|
156
|
+
expect(pageResource?.kind).toBe(exports.parserOptions.webPageKind);
|
153
157
|
expect(apiResource?.spec).toEqual(clientResource?.spec);
|
154
158
|
expect(dbResource?.spec.source.value).toContain('type Entity');
|
155
159
|
const serviceBlockInstance = result.plan.spec.blocks[0];
|
@@ -162,18 +166,33 @@ describe('event-parser', () => {
|
|
162
166
|
expect(result.plan.spec.connections[0].provider.blockId).toBe(serviceBlockInstance.id);
|
163
167
|
expect(result.plan.spec.connections[0].provider.resourceName).toBe(apiResource?.metadata.name);
|
164
168
|
});
|
165
|
-
it('it will split api into correct provider', () => {
|
169
|
+
it('it will split api into correct provider', async () => {
|
166
170
|
const events = simple_blog_events_json_1.default;
|
167
|
-
const parser = new event_parser_1.StormEventParser(parserOptions);
|
168
|
-
|
169
|
-
|
171
|
+
const parser = new event_parser_1.StormEventParser(exports.parserOptions);
|
172
|
+
for (const event of events) {
|
173
|
+
await parser.processEvent('kapeta', event);
|
174
|
+
}
|
175
|
+
const result = await parser.toResult('kapeta');
|
170
176
|
const blogService = result.blocks.find((block) => block.aiName === 'blog-service');
|
171
177
|
expect(blogService).toBeDefined();
|
172
178
|
expect(blogService?.content).toBeDefined();
|
173
179
|
const apiProviders = blogService?.content?.spec?.providers?.filter((provider) => provider.kind === 'kapeta/block-type-api:local');
|
174
180
|
expect(apiProviders).toBeDefined();
|
175
181
|
expect(apiProviders.length).toBe(2);
|
176
|
-
expect(apiProviders[
|
177
|
-
expect(apiProviders[
|
182
|
+
expect(apiProviders['0'].spec.source.value).not.toBe('');
|
183
|
+
expect(apiProviders['1'].spec.source.value).not.toBe('');
|
184
|
+
});
|
185
|
+
it('predefined components', async () => {
|
186
|
+
const events = predefined_user_events_json_1.default;
|
187
|
+
const parser = new event_parser_1.StormEventParser(exports.parserOptions);
|
188
|
+
for (const event of events) {
|
189
|
+
await parser.processEvent('kapeta', event);
|
190
|
+
}
|
191
|
+
const result = await parser.toResult('kapeta');
|
192
|
+
expect(result.blocks.length).toBe(1);
|
193
|
+
expect(result.blocks[0].content.metadata.title).toBe('user-service');
|
194
|
+
expect(result.blocks[0].content.metadata.title).toBe('user-service');
|
195
|
+
expect(result.blocks[0].content.metadata.name).toBe('kapeta/user-service');
|
196
|
+
expect(result.blocks[0].archetype).toBeDefined();
|
178
197
|
});
|
179
198
|
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"type": "CREATE_BLOCK",
|
4
|
+
"reason": "Handles user registration, authentication, and authorization.",
|
5
|
+
"payload": {
|
6
|
+
"name": "user-service",
|
7
|
+
"resources": [],
|
8
|
+
"type": "BACKEND",
|
9
|
+
"archetype": "USER_SERVICE"
|
10
|
+
},
|
11
|
+
"created": 1720597268038
|
12
|
+
}
|
13
|
+
]
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.55.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -67,14 +67,17 @@
|
|
67
67
|
"async-lock": "^1.4.0",
|
68
68
|
"chokidar": "^3.5.3",
|
69
69
|
"dockerode": "^3.3.5",
|
70
|
+
"download-git-repo": "^3.0.2",
|
70
71
|
"express": "4.17.1",
|
71
72
|
"express-promise-router": "^4.1.1",
|
72
73
|
"fs-extra": "^11.1.0",
|
73
74
|
"glob": "^7.1.6",
|
74
75
|
"gunzip-maybe": "^1.4.2",
|
76
|
+
"js-yaml": "^4.1.0",
|
75
77
|
"lodash": "^4.17.15",
|
76
78
|
"md5": "2.2.1",
|
77
79
|
"node-cache": "^5.1.2",
|
80
|
+
"node-fetch": "^3.3.2",
|
78
81
|
"node-uuid": "^1.4.8",
|
79
82
|
"parse-data-uri": "^0.2.0",
|
80
83
|
"qs": "^6.11.2",
|
@@ -96,9 +99,11 @@
|
|
96
99
|
"@types/glob": "^8.1.0",
|
97
100
|
"@types/gunzip-maybe": "^1.4.0",
|
98
101
|
"@types/jest": "^29.5.4",
|
102
|
+
"@types/js-yaml": "^4.0.9",
|
99
103
|
"@types/lodash": "^4.14.195",
|
100
104
|
"@types/md5": "^2.3.2",
|
101
105
|
"@types/node": "^20.5.8",
|
106
|
+
"@types/node-fetch": "^2.6.11",
|
102
107
|
"@types/node-uuid": "^0.0.29",
|
103
108
|
"@types/request": "^2.48.8",
|
104
109
|
"@types/tar-stream": "^2.2.2",
|
@@ -0,0 +1,85 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
|
6
|
+
import download from 'download-git-repo';
|
7
|
+
import { PredefinedBlock } from './predefined';
|
8
|
+
import * as fs from 'node:fs';
|
9
|
+
import path from 'path';
|
10
|
+
import { GeneratedFile, GeneratedResult } from '@kapeta/codegen';
|
11
|
+
|
12
|
+
export class Archetype {
|
13
|
+
public async cloneRepository(
|
14
|
+
predefinedBlock: PredefinedBlock,
|
15
|
+
targetDir: string,
|
16
|
+
basePath: string
|
17
|
+
): Promise<GeneratedResult> {
|
18
|
+
const downloadDirectory = `${targetDir}/download`;
|
19
|
+
|
20
|
+
await this.downloadRepo(
|
21
|
+
predefinedBlock.getGitRepo().owner + '/' + predefinedBlock.getGitRepo().repo,
|
22
|
+
downloadDirectory
|
23
|
+
);
|
24
|
+
await this.copyToBasePathAndRemoveDownload(downloadDirectory, predefinedBlock.getGitRepo().path, basePath);
|
25
|
+
|
26
|
+
return this.convertToGeneratedResult(basePath);
|
27
|
+
}
|
28
|
+
|
29
|
+
private async copyToBasePathAndRemoveDownload(downloadDirectory: string, subPath: string, targetDir: string) {
|
30
|
+
const fullSourcePath = path.resolve(`${downloadDirectory}/${subPath}`);
|
31
|
+
const fullDestinationPath = path.resolve(targetDir);
|
32
|
+
|
33
|
+
try {
|
34
|
+
await fs.promises.cp(fullSourcePath, fullDestinationPath, { recursive: true });
|
35
|
+
console.log(`Directory copied: ${fullSourcePath} -> ${fullDestinationPath}`);
|
36
|
+
} catch (err) {
|
37
|
+
console.error(`Error copying directory: ${err}`);
|
38
|
+
} finally {
|
39
|
+
await fs.promises.rm(downloadDirectory, { recursive: true, force: true });
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
private async convertToGeneratedResult(basePath: string): Promise<GeneratedResult> {
|
44
|
+
const generatedFiles: GeneratedFile[] = [];
|
45
|
+
|
46
|
+
async function traverse(currentPath: string): Promise<void> {
|
47
|
+
const files = await fs.promises.readdir(currentPath, { withFileTypes: true });
|
48
|
+
|
49
|
+
for (const file of files) {
|
50
|
+
const fullPath = path.join(currentPath, file.name);
|
51
|
+
|
52
|
+
if (file.isFile()) {
|
53
|
+
const stats = await fs.promises.stat(fullPath);
|
54
|
+
const mode = stats.mode.toString(8);
|
55
|
+
const permissions = stats.mode.toString(8).slice(-3);
|
56
|
+
const content = await fs.promises.readFile(fullPath);
|
57
|
+
const generatedFile: GeneratedFile = {
|
58
|
+
filename: file.name,
|
59
|
+
content: content.toString(),
|
60
|
+
mode: mode,
|
61
|
+
permissions: permissions,
|
62
|
+
};
|
63
|
+
generatedFiles.push(generatedFile);
|
64
|
+
} else if (file.isDirectory()) {
|
65
|
+
await traverse(fullPath);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
await traverse(basePath);
|
71
|
+
return { files: generatedFiles } as GeneratedResult;
|
72
|
+
}
|
73
|
+
|
74
|
+
private downloadRepo(url: string, dest: string): Promise<void> {
|
75
|
+
return new Promise<void>((resolve, reject) => {
|
76
|
+
download(url, dest, function (err: Error) {
|
77
|
+
if (err) {
|
78
|
+
reject(err);
|
79
|
+
} else {
|
80
|
+
resolve();
|
81
|
+
}
|
82
|
+
});
|
83
|
+
});
|
84
|
+
}
|
85
|
+
}
|
package/src/storm/codegen.ts
CHANGED
@@ -36,6 +36,9 @@ 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 { PREDEFINED_BLOCKS } from './predefined';
|
40
|
+
import { Archetype } from './archetype';
|
41
|
+
import _ from 'lodash';
|
39
42
|
|
40
43
|
type ImplementationGenerator<T = StormFileImplementationPrompt> = (
|
41
44
|
prompt: T,
|
@@ -116,7 +119,11 @@ export class StormCodegen {
|
|
116
119
|
|
117
120
|
public async process() {
|
118
121
|
const promises = this.blocks.map((block) => {
|
119
|
-
|
122
|
+
if (block.archetype) {
|
123
|
+
return this.cloneArchetype(block);
|
124
|
+
} else {
|
125
|
+
return this.processBlockCode(block);
|
126
|
+
}
|
120
127
|
});
|
121
128
|
await Promise.all(promises);
|
122
129
|
this.out.end();
|
@@ -480,14 +487,17 @@ export class StormCodegen {
|
|
480
487
|
|
481
488
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.QA);
|
482
489
|
|
483
|
-
/* TODO: temporarily disabled - enable again when codegen is more stable
|
490
|
+
/* TODO: temporarily disabled - enable again when codegen is more stable
|
484
491
|
const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
|
485
492
|
const codeGenerator = new BlockCodeGenerator(blockDefinition);
|
486
493
|
*/
|
487
494
|
|
488
495
|
this.emitBlockStatus(blockUri, block.aiName, StormEventBlockStatusType.BUILDING);
|
489
|
-
// await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
496
|
+
// await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
|
497
|
+
this.emitBlockStatusDone(basePath, block, blockRef);
|
498
|
+
}
|
490
499
|
|
500
|
+
private emitBlockStatusDone(basePath: string, block: BlockDefinitionInfo, blockRef: string) {
|
491
501
|
this.out.emit('data', {
|
492
502
|
type: 'BLOCK_READY',
|
493
503
|
reason: 'Block ready',
|
@@ -977,7 +987,7 @@ export class StormCodegen {
|
|
977
987
|
/**
|
978
988
|
* Generates the code using codegen for a given block.
|
979
989
|
*/
|
980
|
-
private async generateBlock(yamlContent: Definition) {
|
990
|
+
private async generateBlock(yamlContent: Definition): Promise<GeneratedResult | undefined> {
|
981
991
|
if (this.isAborted()) {
|
982
992
|
return;
|
983
993
|
}
|
@@ -998,6 +1008,26 @@ export class StormCodegen {
|
|
998
1008
|
return generatedResult;
|
999
1009
|
}
|
1000
1010
|
|
1011
|
+
private async cloneArchetype(block: BlockDefinitionInfo): Promise<void> {
|
1012
|
+
const predefinedBlock = PREDEFINED_BLOCKS.get(block.archetype!)!;
|
1013
|
+
|
1014
|
+
let blockDefinition = await predefinedBlock.getBlockDefinition();
|
1015
|
+
const kapetaURI = new KapetaURI(block.uri);
|
1016
|
+
_.set(blockDefinition!, ['metadata', 'name'], kapetaURI.fullName);
|
1017
|
+
_.set(blockDefinition!, ['metadata', 'title'], kapetaURI.name);
|
1018
|
+
|
1019
|
+
const basePath = this.getBasePath(blockDefinition.metadata.name);
|
1020
|
+
|
1021
|
+
let archetype = new Archetype();
|
1022
|
+
|
1023
|
+
const generatedResult = await archetype.cloneRepository(predefinedBlock, this.tmpDir, basePath);
|
1024
|
+
|
1025
|
+
await this.emitStaticFiles(kapetaURI, block.aiName, this.toStormFiles(generatedResult));
|
1026
|
+
|
1027
|
+
const blockRef = block.uri;
|
1028
|
+
this.emitBlockStatusDone(basePath, block, blockRef);
|
1029
|
+
}
|
1030
|
+
|
1001
1031
|
abort() {
|
1002
1032
|
this.out.abort();
|
1003
1033
|
}
|