@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/definitions.d.ts +11 -0
  3. package/dist/cjs/src/storm/archetype.d.ts +12 -0
  4. package/dist/cjs/src/storm/archetype.js +98 -0
  5. package/dist/cjs/src/storm/codegen.d.ts +2 -0
  6. package/dist/cjs/src/storm/codegen.js +28 -4
  7. package/dist/cjs/src/storm/event-parser.d.ts +7 -4
  8. package/dist/cjs/src/storm/event-parser.js +190 -160
  9. package/dist/cjs/src/storm/events.d.ts +1 -0
  10. package/dist/cjs/src/storm/predefined.d.ts +27 -0
  11. package/dist/cjs/src/storm/predefined.js +64 -0
  12. package/dist/cjs/src/storm/routes.js +23 -15
  13. package/dist/cjs/test/storm/codegen.test.d.ts +5 -0
  14. package/dist/cjs/test/storm/codegen.test.js +41 -0
  15. package/dist/cjs/test/storm/event-parser.test.d.ts +25 -1
  16. package/dist/cjs/test/storm/event-parser.test.js +34 -15
  17. package/dist/cjs/test/storm/predefined-user-events.json +13 -0
  18. package/dist/esm/src/storm/archetype.d.ts +12 -0
  19. package/dist/esm/src/storm/archetype.js +98 -0
  20. package/dist/esm/src/storm/codegen.d.ts +2 -0
  21. package/dist/esm/src/storm/codegen.js +28 -4
  22. package/dist/esm/src/storm/event-parser.d.ts +7 -4
  23. package/dist/esm/src/storm/event-parser.js +190 -160
  24. package/dist/esm/src/storm/events.d.ts +1 -0
  25. package/dist/esm/src/storm/predefined.d.ts +27 -0
  26. package/dist/esm/src/storm/predefined.js +64 -0
  27. package/dist/esm/src/storm/routes.js +23 -15
  28. package/dist/esm/test/storm/codegen.test.d.ts +5 -0
  29. package/dist/esm/test/storm/codegen.test.js +41 -0
  30. package/dist/esm/test/storm/event-parser.test.d.ts +25 -1
  31. package/dist/esm/test/storm/event-parser.test.js +34 -15
  32. package/dist/esm/test/storm/predefined-user-events.json +13 -0
  33. package/package.json +6 -1
  34. package/src/storm/archetype.ts +85 -0
  35. package/src/storm/codegen.ts +34 -4
  36. package/src/storm/event-parser.ts +200 -159
  37. package/src/storm/events.ts +1 -0
  38. package/src/storm/predefined.ts +52 -0
  39. package/src/storm/routes.ts +25 -18
  40. package/test/storm/codegen.test.ts +46 -0
  41. package/test/storm/event-parser.test.ts +32 -10
  42. 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 parserOptions = {
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
- events.forEach((event) => parser.processEvent('kapeta', event));
135
- const result = parser.toResult('kapeta');
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
- events.forEach((event) => parser.processEvent('kapeta', event));
169
- const result = parser.toResult('kapeta');
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["0"].spec.source.value).not.toBe('');
177
- expect(apiProviders["1"].spec.source.value).not.toBe('');
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.54.11",
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
+ }
@@ -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
- return this.processBlockCode(block);
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
  }