@kapeta/local-cluster-service 0.31.0 → 0.32.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 +14 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/src/RepositoryWatcher.js +30 -6
- package/dist/cjs/src/ai/aiClient.d.ts +20 -0
- package/dist/cjs/src/ai/aiClient.js +74 -0
- package/dist/cjs/src/ai/routes.d.ts +7 -0
- package/dist/cjs/src/ai/routes.js +37 -0
- package/dist/cjs/src/ai/transform.d.ts +11 -0
- package/dist/cjs/src/ai/transform.js +239 -0
- package/dist/cjs/src/ai/types.d.ts +40 -0
- package/dist/cjs/src/ai/types.js +2 -0
- package/dist/cjs/src/assetManager.js +5 -2
- package/dist/cjs/src/codeGeneratorManager.js +4 -3
- package/dist/cjs/src/containerManager.js +7 -1
- package/dist/cjs/src/definitionsManager.js +2 -2
- package/dist/cjs/src/instanceManager.js +2 -2
- package/dist/cjs/src/repositoryManager.js +1 -1
- package/dist/cjs/src/utils/utils.d.ts +1 -0
- package/dist/cjs/src/utils/utils.js +7 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/src/RepositoryWatcher.js +30 -6
- package/dist/esm/src/ai/aiClient.d.ts +20 -0
- package/dist/esm/src/ai/aiClient.js +74 -0
- package/dist/esm/src/ai/routes.d.ts +7 -0
- package/dist/esm/src/ai/routes.js +37 -0
- package/dist/esm/src/ai/transform.d.ts +11 -0
- package/dist/esm/src/ai/transform.js +239 -0
- package/dist/esm/src/ai/types.d.ts +40 -0
- package/dist/esm/src/ai/types.js +2 -0
- package/dist/esm/src/assetManager.js +5 -2
- package/dist/esm/src/codeGeneratorManager.js +4 -3
- package/dist/esm/src/containerManager.js +7 -1
- package/dist/esm/src/definitionsManager.js +2 -2
- package/dist/esm/src/instanceManager.js +2 -2
- package/dist/esm/src/repositoryManager.js +1 -1
- package/dist/esm/src/utils/utils.d.ts +1 -0
- package/dist/esm/src/utils/utils.js +7 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/RepositoryWatcher.ts +34 -6
- package/src/ai/aiClient.ts +93 -0
- package/src/ai/routes.ts +39 -0
- package/src/ai/transform.ts +275 -0
- package/src/ai/types.ts +45 -0
- package/src/assetManager.ts +5 -1
- package/src/codeGeneratorManager.ts +6 -4
- package/src/containerManager.ts +7 -1
- package/src/definitionsManager.ts +2 -2
- package/src/instanceManager.ts +2 -2
- package/src/repositoryManager.ts +1 -1
- package/src/utils/utils.ts +6 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.32.1](https://github.com/kapetacom/local-cluster-service/compare/v0.32.0...v0.32.1) (2023-12-23)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Several smaller fixes ([4036345](https://github.com/kapetacom/local-cluster-service/commit/40363457597d36c97481e94e646a6b45d113bb09))
|
7
|
+
|
8
|
+
# [0.32.0](https://github.com/kapetacom/local-cluster-service/compare/v0.31.0...v0.32.0) (2023-12-20)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Adds AI routes for generating plans from prompts ([#102](https://github.com/kapetacom/local-cluster-service/issues/102)) ([db73996](https://github.com/kapetacom/local-cluster-service/commit/db739965cd8c58252651e0522b11c062f00488af))
|
14
|
+
|
1
15
|
# [0.31.0](https://github.com/kapetacom/local-cluster-service/compare/v0.30.1...v0.31.0) (2023-12-18)
|
2
16
|
|
3
17
|
|
package/dist/cjs/index.js
CHANGED
@@ -49,6 +49,7 @@ const routes_8 = __importDefault(require("./src/providers/routes"));
|
|
49
49
|
const routes_9 = __importDefault(require("./src/attachments/routes"));
|
50
50
|
const routes_10 = __importDefault(require("./src/tasks/routes"));
|
51
51
|
const api_1 = __importDefault(require("./src/api"));
|
52
|
+
const routes_11 = __importDefault(require("./src/ai/routes"));
|
52
53
|
const utils_1 = require("./src/utils/utils");
|
53
54
|
const request_1 = __importDefault(require("request"));
|
54
55
|
const repositoryManager_1 = require("./src/repositoryManager");
|
@@ -87,6 +88,7 @@ function createServer() {
|
|
87
88
|
app.use('/attachments', routes_9.default);
|
88
89
|
app.use('/tasks', routes_10.default);
|
89
90
|
app.use('/api', api_1.default);
|
91
|
+
app.use('/ai', routes_11.default);
|
90
92
|
app.get('/status', async (req, res) => {
|
91
93
|
res.send({
|
92
94
|
ok: true,
|
@@ -20,6 +20,24 @@ const cacheManager_1 = require("./cacheManager");
|
|
20
20
|
const node_events_1 = require("node:events");
|
21
21
|
const assetManager_1 = require("./assetManager");
|
22
22
|
const KAPETA_YML_RX = /^kapeta.ya?ml$/;
|
23
|
+
let definitions;
|
24
|
+
let definitionTimeout;
|
25
|
+
function getDefinitionsDebounced() {
|
26
|
+
if (definitionTimeout) {
|
27
|
+
clearTimeout(definitionTimeout);
|
28
|
+
definitionTimeout = undefined;
|
29
|
+
}
|
30
|
+
if (!definitions) {
|
31
|
+
definitions = local_cluster_config_1.default.getDefinitions();
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
definitionTimeout = setTimeout(() => {
|
35
|
+
definitionTimeout = undefined;
|
36
|
+
definitions = undefined;
|
37
|
+
}, 500);
|
38
|
+
}
|
39
|
+
return definitions;
|
40
|
+
}
|
23
41
|
class RepositoryWatcher extends node_events_1.EventEmitter {
|
24
42
|
watcher;
|
25
43
|
disabled = false;
|
@@ -186,7 +204,7 @@ class RepositoryWatcher extends node_events_1.EventEmitter {
|
|
186
204
|
}
|
187
205
|
async checkForChange(assetIdentity, sourceOfChange) {
|
188
206
|
const ymlPath = node_path_1.default.join(this.getRepositoryPath(assetIdentity), 'kapeta.yml');
|
189
|
-
const newDefinitions =
|
207
|
+
const newDefinitions = getDefinitionsDebounced();
|
190
208
|
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
191
209
|
let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
|
192
210
|
const ymlExists = await this.exists(ymlPath);
|
@@ -265,11 +283,17 @@ class RepositoryWatcher extends node_events_1.EventEmitter {
|
|
265
283
|
}
|
266
284
|
catch (e) { }
|
267
285
|
if (symbolicLink) {
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
286
|
+
try {
|
287
|
+
const realPath = node_path_1.default.join(await fs_extra_1.default.realpath(path), 'kapeta.yml');
|
288
|
+
if (await this.exists(realPath)) {
|
289
|
+
//console.log('Watching symlink target %s => %s', path, realPath);
|
290
|
+
this.watcher?.add(realPath);
|
291
|
+
this.symbolicLinks[path] = realPath;
|
292
|
+
}
|
293
|
+
}
|
294
|
+
catch (e) {
|
295
|
+
// Remove the symlink - it's broken
|
296
|
+
await fs_extra_1.default.remove(path);
|
273
297
|
}
|
274
298
|
}
|
275
299
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { PlanContext } from './transform';
|
2
|
+
export type PromptResult = {
|
3
|
+
explanation: string;
|
4
|
+
response: string;
|
5
|
+
context?: PlanContext;
|
6
|
+
};
|
7
|
+
export interface AIMessage {
|
8
|
+
content: string;
|
9
|
+
role: 'user' | 'assistant';
|
10
|
+
}
|
11
|
+
export interface AIRequest {
|
12
|
+
messages: AIMessage[];
|
13
|
+
}
|
14
|
+
declare class AIClient {
|
15
|
+
private readonly _baseUrl;
|
16
|
+
constructor();
|
17
|
+
sendPrompt(handle: string, body: AIRequest): Promise<PromptResult>;
|
18
|
+
}
|
19
|
+
export declare const aiClient: AIClient;
|
20
|
+
export {};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.aiClient = void 0;
|
7
|
+
/**
|
8
|
+
* Copyright 2023 Kapeta Inc.
|
9
|
+
* SPDX-License-Identifier: BUSL-1.1
|
10
|
+
*/
|
11
|
+
const request_1 = __importDefault(require("request"));
|
12
|
+
const transform_1 = require("./transform");
|
13
|
+
const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
|
14
|
+
const utils_1 = require("../utils/utils");
|
15
|
+
class AIClient {
|
16
|
+
_baseUrl;
|
17
|
+
constructor() {
|
18
|
+
this._baseUrl = (0, utils_1.getRemoteUrl)('ai-service', 'https://ai.kapeta.com');
|
19
|
+
}
|
20
|
+
async sendPrompt(handle, body) {
|
21
|
+
const url = `${this._baseUrl}/v1/plan?type=chat`;
|
22
|
+
const headers = {};
|
23
|
+
const api = new nodejs_api_client_1.KapetaAPI();
|
24
|
+
if (api.hasToken()) {
|
25
|
+
headers['Authorization'] = `Bearer ${await api.getAccessToken()}`;
|
26
|
+
}
|
27
|
+
const options = {
|
28
|
+
url,
|
29
|
+
method: 'POST',
|
30
|
+
json: true,
|
31
|
+
body,
|
32
|
+
headers,
|
33
|
+
};
|
34
|
+
return new Promise((resolve, reject) => {
|
35
|
+
(0, request_1.default)(options, async (error, response, application) => {
|
36
|
+
if (error) {
|
37
|
+
console.error(error);
|
38
|
+
reject(error);
|
39
|
+
}
|
40
|
+
if (response.statusCode !== 200) {
|
41
|
+
console.log('Prompt failed', response.statusCode, response.body);
|
42
|
+
reject(new Error(`Invalid response code: ${response.statusCode}`));
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
try {
|
46
|
+
if (application?.name) {
|
47
|
+
const planContext = await (0, transform_1.transformToPlan)(handle, application);
|
48
|
+
resolve({
|
49
|
+
explanation: application.explanation,
|
50
|
+
response: application.response ?? application.explanation ?? 'Plan was generated',
|
51
|
+
context: planContext,
|
52
|
+
});
|
53
|
+
}
|
54
|
+
else {
|
55
|
+
resolve({
|
56
|
+
explanation: application.explanation,
|
57
|
+
response: application.response ??
|
58
|
+
application.explanation ??
|
59
|
+
'I did not understand your request. Please rephrase.',
|
60
|
+
});
|
61
|
+
}
|
62
|
+
}
|
63
|
+
catch (err) {
|
64
|
+
console.error(err);
|
65
|
+
resolve({
|
66
|
+
explanation: '',
|
67
|
+
response: 'I did not understand your request. Please rephrase.',
|
68
|
+
});
|
69
|
+
}
|
70
|
+
});
|
71
|
+
});
|
72
|
+
}
|
73
|
+
}
|
74
|
+
exports.aiClient = new AIClient();
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* Copyright 2023 Kapeta Inc.
|
4
|
+
* SPDX-License-Identifier: BUSL-1.1
|
5
|
+
*/
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
|
+
};
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
10
|
+
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
11
|
+
const cors_1 = require("../middleware/cors");
|
12
|
+
const stringBody_1 = require("../middleware/stringBody");
|
13
|
+
const aiClient_1 = require("./aiClient");
|
14
|
+
const yaml_1 = __importDefault(require("yaml"));
|
15
|
+
const router = (0, express_promise_router_1.default)();
|
16
|
+
router.use('/', cors_1.corsHandler);
|
17
|
+
router.use('/', stringBody_1.stringBody);
|
18
|
+
router.post('/prompt/:handle', async (req, res) => {
|
19
|
+
const handle = req.params.handle;
|
20
|
+
try {
|
21
|
+
const aiRequest = JSON.parse(req.stringBody ?? '{}');
|
22
|
+
const result = await aiClient_1.aiClient.sendPrompt(handle, aiRequest);
|
23
|
+
if (req.accepts('application/yaml')) {
|
24
|
+
res.set('Content-Type', 'application/yaml');
|
25
|
+
res.send(yaml_1.default.stringify(result));
|
26
|
+
}
|
27
|
+
else {
|
28
|
+
res.json(result);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
catch (err) {
|
32
|
+
console.error('Failed to send prompt', err);
|
33
|
+
res.status(400).send({ error: err.message });
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
});
|
37
|
+
exports.default = router;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import { BlockDefinition, Plan } from '@kapeta/schemas';
|
6
|
+
import { Application } from './types';
|
7
|
+
export type PlanContext = {
|
8
|
+
plan: Plan;
|
9
|
+
blocks: BlockDefinition[];
|
10
|
+
};
|
11
|
+
export declare const transformToPlan: (handle: string, application: Application) => Promise<PlanContext>;
|
@@ -0,0 +1,239 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.transformToPlan = void 0;
|
7
|
+
const definitionsManager_1 = require("../definitionsManager");
|
8
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
9
|
+
const node_uuid_1 = __importDefault(require("node-uuid"));
|
10
|
+
async function getFreeName(name) {
|
11
|
+
let currentName = name;
|
12
|
+
let iteration = 1;
|
13
|
+
do {
|
14
|
+
const found = await definitionsManager_1.definitionsManager.getLatestDefinition(currentName);
|
15
|
+
if (!found) {
|
16
|
+
return currentName;
|
17
|
+
}
|
18
|
+
currentName = name + '_' + iteration++;
|
19
|
+
} while (true);
|
20
|
+
}
|
21
|
+
const transformToPlan = async (handle, application) => {
|
22
|
+
const blockTypeService = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-service');
|
23
|
+
const blockTypeFrontend = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/block-type-frontend');
|
24
|
+
const mongoDbResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-mongodb');
|
25
|
+
const postgresResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-postgresql');
|
26
|
+
const webPageResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-web-page');
|
27
|
+
const webFragmentResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-web-fragment');
|
28
|
+
const restApiResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-rest-api');
|
29
|
+
const restClientResource = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/resource-type-rest-client');
|
30
|
+
const javaLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
|
31
|
+
const nodejsLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
|
32
|
+
const reactLanguage = await definitionsManager_1.definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
|
33
|
+
if (!blockTypeService ||
|
34
|
+
!blockTypeFrontend ||
|
35
|
+
!mongoDbResource ||
|
36
|
+
!postgresResource ||
|
37
|
+
!javaLanguage ||
|
38
|
+
!nodejsLanguage ||
|
39
|
+
!reactLanguage ||
|
40
|
+
!webPageResource ||
|
41
|
+
!restApiResource ||
|
42
|
+
!restClientResource ||
|
43
|
+
!webFragmentResource) {
|
44
|
+
throw new Error('Missing definitions');
|
45
|
+
}
|
46
|
+
const plan = {
|
47
|
+
kind: 'core/plan',
|
48
|
+
metadata: {
|
49
|
+
name: await getFreeName(`${handle}/${application.name}`),
|
50
|
+
title: application.title,
|
51
|
+
description: application.description,
|
52
|
+
visibility: 'private',
|
53
|
+
},
|
54
|
+
spec: {
|
55
|
+
blocks: [],
|
56
|
+
connections: [],
|
57
|
+
},
|
58
|
+
};
|
59
|
+
const blocks = [];
|
60
|
+
const addToPlan = (ref, name) => {
|
61
|
+
const top = 100 + Math.floor(plan.spec.blocks.length / 3) * 300;
|
62
|
+
const left = 200 + (plan.spec.blocks.length % 3) * 450;
|
63
|
+
plan.spec.blocks.push({
|
64
|
+
block: {
|
65
|
+
ref,
|
66
|
+
},
|
67
|
+
name,
|
68
|
+
id: node_uuid_1.default.v4(),
|
69
|
+
dimensions: {
|
70
|
+
top,
|
71
|
+
left,
|
72
|
+
width: -1,
|
73
|
+
height: -1,
|
74
|
+
},
|
75
|
+
});
|
76
|
+
};
|
77
|
+
const nameMapper = new Map();
|
78
|
+
for (const backend of application.backends) {
|
79
|
+
const blockName = `${handle}/${backend.name}`;
|
80
|
+
const blockRealName = await getFreeName(blockName);
|
81
|
+
nameMapper.set(blockName, blockRealName);
|
82
|
+
const language = backend.targetLanguage === 'java' ? javaLanguage : nodejsLanguage;
|
83
|
+
const databaseInfo = backend.databases?.[0];
|
84
|
+
const database = databaseInfo?.type === 'mongodb' ? mongoDbResource : postgresResource;
|
85
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockRealName + ':local');
|
86
|
+
let targetOptions = {};
|
87
|
+
if (backend.targetLanguage === 'java') {
|
88
|
+
targetOptions = {
|
89
|
+
basePackage: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
|
90
|
+
groupId: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
|
91
|
+
artifactId: backend.name.toLowerCase().replace(/-/g, '_'),
|
92
|
+
};
|
93
|
+
}
|
94
|
+
blocks.push({
|
95
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeService.definition.metadata.name}:${blockTypeService.version}`),
|
96
|
+
metadata: {
|
97
|
+
name: blockRealName,
|
98
|
+
title: backend.title,
|
99
|
+
description: backend.description,
|
100
|
+
visibility: 'private',
|
101
|
+
},
|
102
|
+
spec: {
|
103
|
+
target: {
|
104
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${language.definition.metadata.name}:${language.version}`),
|
105
|
+
options: targetOptions,
|
106
|
+
},
|
107
|
+
consumers: [
|
108
|
+
{
|
109
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${database.definition.metadata.name}:${database.version}`),
|
110
|
+
metadata: {
|
111
|
+
name: (databaseInfo?.name ?? 'main').replace(/-/g, ''),
|
112
|
+
},
|
113
|
+
spec: {
|
114
|
+
port: {
|
115
|
+
type: database.definition.spec.ports[0].type,
|
116
|
+
},
|
117
|
+
},
|
118
|
+
},
|
119
|
+
],
|
120
|
+
providers: [
|
121
|
+
{
|
122
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${restApiResource.definition.metadata.name}:${restApiResource.version}`),
|
123
|
+
metadata: {
|
124
|
+
name: 'main',
|
125
|
+
},
|
126
|
+
spec: {
|
127
|
+
port: {
|
128
|
+
type: restApiResource.definition.spec.ports[0].type,
|
129
|
+
},
|
130
|
+
},
|
131
|
+
},
|
132
|
+
],
|
133
|
+
},
|
134
|
+
});
|
135
|
+
addToPlan(blockRef, backend.name);
|
136
|
+
}
|
137
|
+
for (const frontend of application.frontends) {
|
138
|
+
const blockName = `${handle}/${frontend.name}`;
|
139
|
+
const blockRealName = await getFreeName(blockName);
|
140
|
+
nameMapper.set(blockName, blockRealName);
|
141
|
+
const language = reactLanguage;
|
142
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockRealName + ':local');
|
143
|
+
blocks.push({
|
144
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
|
145
|
+
metadata: {
|
146
|
+
name: blockRealName,
|
147
|
+
title: frontend.title,
|
148
|
+
description: frontend.description,
|
149
|
+
visibility: 'private',
|
150
|
+
},
|
151
|
+
spec: {
|
152
|
+
target: {
|
153
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${language.definition.metadata.name}:${language.version}`),
|
154
|
+
options: {},
|
155
|
+
},
|
156
|
+
consumers: [],
|
157
|
+
providers: [
|
158
|
+
{
|
159
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${webPageResource.definition.metadata.name}:${webPageResource.version}`),
|
160
|
+
metadata: {
|
161
|
+
name: 'main',
|
162
|
+
},
|
163
|
+
spec: {
|
164
|
+
port: {
|
165
|
+
type: webPageResource.definition.spec.ports[0].type,
|
166
|
+
},
|
167
|
+
},
|
168
|
+
},
|
169
|
+
],
|
170
|
+
},
|
171
|
+
});
|
172
|
+
addToPlan(blockRef, frontend.name);
|
173
|
+
}
|
174
|
+
application.connections?.forEach((connection) => {
|
175
|
+
const fullProviderName = nameMapper.get(`${handle}/${connection.provider.name}`);
|
176
|
+
const fullConsumerName = nameMapper.get(`${handle}/${connection.consumer.name}`);
|
177
|
+
const consumerResourceName = connection.provider.name;
|
178
|
+
const providerRef = (0, nodejs_utils_1.normalizeKapetaUri)(`${fullProviderName}:local`);
|
179
|
+
const consumerRef = (0, nodejs_utils_1.normalizeKapetaUri)(`${fullConsumerName}:local`);
|
180
|
+
const instanceProvider = plan.spec.blocks.find((b) => b.block.ref === providerRef);
|
181
|
+
const instanceConsumer = plan.spec.blocks.find((b) => b.block.ref === consumerRef);
|
182
|
+
const consumerBlock = blocks.find((block) => block.metadata.name === fullConsumerName);
|
183
|
+
const providerBlock = blocks.find((block) => block.metadata.name === fullProviderName);
|
184
|
+
if (!consumerBlock) {
|
185
|
+
throw new Error('Missing consumer block: ' + fullConsumerName);
|
186
|
+
}
|
187
|
+
if (!providerBlock) {
|
188
|
+
throw new Error('Missing provider block: ' + fullProviderName);
|
189
|
+
}
|
190
|
+
const portType = (0, nodejs_utils_1.parseKapetaUri)(providerBlock.kind).fullName === 'kapeta/block-type-service' ? 'rest' : 'web';
|
191
|
+
if (portType === 'rest') {
|
192
|
+
consumerBlock.spec.consumers.push({
|
193
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${restClientResource.definition.metadata.name}:${restClientResource.version}`),
|
194
|
+
metadata: {
|
195
|
+
name: consumerResourceName,
|
196
|
+
},
|
197
|
+
spec: {
|
198
|
+
port: {
|
199
|
+
type: 'rest',
|
200
|
+
},
|
201
|
+
},
|
202
|
+
});
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
consumerBlock.spec.consumers.push({
|
206
|
+
kind: (0, nodejs_utils_1.normalizeKapetaUri)(`${webFragmentResource.definition.metadata.name}:${webFragmentResource.version}`),
|
207
|
+
metadata: {
|
208
|
+
name: consumerResourceName,
|
209
|
+
},
|
210
|
+
spec: {
|
211
|
+
port: {
|
212
|
+
type: 'web',
|
213
|
+
},
|
214
|
+
},
|
215
|
+
});
|
216
|
+
}
|
217
|
+
plan.spec.connections.push({
|
218
|
+
provider: {
|
219
|
+
blockId: instanceProvider.id,
|
220
|
+
resourceName: 'main',
|
221
|
+
port: {
|
222
|
+
type: portType,
|
223
|
+
},
|
224
|
+
},
|
225
|
+
consumer: {
|
226
|
+
blockId: instanceConsumer.id,
|
227
|
+
resourceName: consumerResourceName,
|
228
|
+
port: {
|
229
|
+
type: portType,
|
230
|
+
},
|
231
|
+
},
|
232
|
+
});
|
233
|
+
});
|
234
|
+
return {
|
235
|
+
plan,
|
236
|
+
blocks,
|
237
|
+
};
|
238
|
+
};
|
239
|
+
exports.transformToPlan = transformToPlan;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
export interface Database {
|
6
|
+
name: string;
|
7
|
+
type: 'mongodb' | 'postgres';
|
8
|
+
}
|
9
|
+
export interface BackendService {
|
10
|
+
name: string;
|
11
|
+
title: string;
|
12
|
+
description: string;
|
13
|
+
targetLanguage: 'java' | 'node';
|
14
|
+
databases: Database[] | null;
|
15
|
+
}
|
16
|
+
export interface FrontendService {
|
17
|
+
name: string;
|
18
|
+
title: string;
|
19
|
+
description: string;
|
20
|
+
targetLanguage: 'react';
|
21
|
+
}
|
22
|
+
export interface Endpoint {
|
23
|
+
type: 'backend' | 'frontend';
|
24
|
+
name: string;
|
25
|
+
}
|
26
|
+
export interface Connection {
|
27
|
+
provider: Endpoint;
|
28
|
+
consumer: Endpoint;
|
29
|
+
}
|
30
|
+
export interface Application {
|
31
|
+
kind: 'core/plan';
|
32
|
+
name: string;
|
33
|
+
title: string;
|
34
|
+
description: string;
|
35
|
+
backends: BackendService[];
|
36
|
+
frontends: FrontendService[];
|
37
|
+
connections: Connection[];
|
38
|
+
explanation: string;
|
39
|
+
response: string;
|
40
|
+
}
|
@@ -24,10 +24,13 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
24
24
|
const CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
25
25
|
const UPGRADE_CHECK_INTERVAL = 10 * 60 * 1000; // 10 minutes
|
26
26
|
const toKey = (ref) => `assetManager:asset:${ref}`;
|
27
|
+
function filterExists(asset) {
|
28
|
+
return fs_extra_1.default.existsSync(asset.path);
|
29
|
+
}
|
27
30
|
function enrichAsset(asset) {
|
28
31
|
return {
|
29
32
|
ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
|
30
|
-
editable: asset.version === 'local',
|
33
|
+
editable: asset.version === 'local', //Only local versions are editable
|
31
34
|
exists: true,
|
32
35
|
version: asset.version,
|
33
36
|
kind: asset.definition.kind,
|
@@ -81,7 +84,7 @@ class AssetManager {
|
|
81
84
|
assetKinds.push('core/plan');
|
82
85
|
}
|
83
86
|
const assets = await definitionsManager_1.definitionsManager.getDefinitions(assetKinds);
|
84
|
-
return assets.map(enrichAsset);
|
87
|
+
return assets.filter(filterExists).map(enrichAsset);
|
85
88
|
}
|
86
89
|
async getPlans() {
|
87
90
|
return this.getAssets(['core/plan']);
|
@@ -58,13 +58,14 @@ class CodeGeneratorManager {
|
|
58
58
|
});
|
59
59
|
}
|
60
60
|
async canGenerateCode(yamlContent) {
|
61
|
-
if (!yamlContent.spec
|
61
|
+
if (!yamlContent.spec?.target?.kind || !yamlContent.kind) {
|
62
62
|
//Not all block types have targets
|
63
63
|
return false;
|
64
64
|
}
|
65
|
+
const kindUri = (0, nodejs_utils_1.parseKapetaUri)(yamlContent.kind);
|
65
66
|
const blockTypes = await definitionsManager_1.definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
|
66
|
-
const blockTypeKinds = blockTypes.map((blockType) => blockType.definition.metadata.name
|
67
|
-
return
|
67
|
+
const blockTypeKinds = blockTypes.map((blockType) => (0, nodejs_utils_1.parseKapetaUri)(blockType.definition.metadata.name).fullName);
|
68
|
+
return blockTypeKinds.includes(kindUri.fullName);
|
68
69
|
}
|
69
70
|
async generate(yamlFile, yamlContent) {
|
70
71
|
if (!yamlContent.spec.target?.kind) {
|
@@ -98,11 +98,17 @@ class ContainerManager {
|
|
98
98
|
];
|
99
99
|
for (const opts of connectOptions) {
|
100
100
|
try {
|
101
|
+
const testClient = new dockerode_1.default({
|
102
|
+
...opts,
|
103
|
+
timeout: 1000, // 1 secs should be enough for a ping
|
104
|
+
});
|
105
|
+
await testClient.ping();
|
106
|
+
// If we get here - we have a working connection
|
107
|
+
// Now create a client with a longer timeout for all other operations
|
101
108
|
const client = new dockerode_1.default({
|
102
109
|
...opts,
|
103
110
|
timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
|
104
111
|
});
|
105
|
-
await client.ping();
|
106
112
|
this._docker = client;
|
107
113
|
const versionInfo = await client.version();
|
108
114
|
this._version = versionInfo.Server?.Version ?? versionInfo.Version;
|
@@ -131,9 +131,9 @@ class DefinitionsManager {
|
|
131
131
|
const definitions = await this.getDefinitions();
|
132
132
|
return definitions.find((d) => {
|
133
133
|
if (!uri.version) {
|
134
|
-
return d.definition.metadata.name === uri.fullName;
|
134
|
+
return d.definition.metadata.name.toLowerCase() === uri.fullName.toLowerCase();
|
135
135
|
}
|
136
|
-
return (0, nodejs_utils_1.parseKapetaUri)(`${d.definition.metadata.name}:${d.version}`).
|
136
|
+
return (0, nodejs_utils_1.parseKapetaUri)(`${d.definition.metadata.name}:${d.version}`).equals(uri);
|
137
137
|
});
|
138
138
|
}
|
139
139
|
async getLatestDefinition(name) {
|
@@ -25,7 +25,7 @@ const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
|
25
25
|
const definitionsManager_1 = require("./definitionsManager");
|
26
26
|
const taskManager_1 = require("./taskManager");
|
27
27
|
const CHECK_INTERVAL = 5000;
|
28
|
-
const DEFAULT_HEALTH_PORT_TYPE = '
|
28
|
+
const DEFAULT_HEALTH_PORT_TYPE = 'http';
|
29
29
|
const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
|
30
30
|
class InstanceManager {
|
31
31
|
_interval = undefined;
|
@@ -198,7 +198,7 @@ class InstanceManager {
|
|
198
198
|
}
|
199
199
|
getHealthUrl(info, address) {
|
200
200
|
let healthUrl = null;
|
201
|
-
let health = info.health;
|
201
|
+
let health = info.health ?? '/.kapeta/health';
|
202
202
|
if (health) {
|
203
203
|
if (health.startsWith('/')) {
|
204
204
|
health = health.substring(1);
|
@@ -183,7 +183,7 @@ class RepositoryManager extends node_events_1.EventEmitter {
|
|
183
183
|
if (await definitionsManager_1.definitionsManager.exists(ref)) {
|
184
184
|
return;
|
185
185
|
}
|
186
|
-
throw new Error(`Failed to
|
186
|
+
throw new Error(`Failed to find asset after installation: ${ref}. Please try again`);
|
187
187
|
};
|
188
188
|
};
|
189
189
|
const tasks = [];
|
@@ -5,6 +5,7 @@
|
|
5
5
|
import { EntityList } from '@kapeta/schemas';
|
6
6
|
import { AnyMap } from '../types';
|
7
7
|
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string;
|
8
|
+
export declare function getRemoteUrl(id: string, defautValue: string): any;
|
8
9
|
export declare function readYML(path: string): any;
|
9
10
|
export declare function isWindows(): boolean;
|
10
11
|
export declare function isMac(): boolean;
|