@kapeta/local-cluster-service 0.14.4 → 0.15.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 +7 -0
- package/default-providers.json +14 -0
- package/dist/cjs/index.js +18 -1
- package/dist/cjs/src/api.js +1 -1
- package/dist/cjs/src/definitionsManager.js +3 -0
- package/dist/cjs/src/progressListener.d.ts +1 -0
- package/dist/cjs/src/progressListener.js +39 -26
- package/dist/cjs/src/repositoryManager.d.ts +1 -0
- package/dist/cjs/src/repositoryManager.js +15 -7
- package/dist/cjs/src/taskManager.js +4 -0
- package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -0
- package/dist/cjs/src/utils/commandLineUtils.js +23 -0
- package/dist/esm/index.js +18 -1
- package/dist/esm/src/api.js +3 -3
- package/dist/esm/src/assetManager.js +1 -1
- package/dist/esm/src/definitionsManager.js +3 -0
- package/dist/esm/src/progressListener.d.ts +1 -0
- package/dist/esm/src/progressListener.js +39 -26
- package/dist/esm/src/repositoryManager.d.ts +1 -0
- package/dist/esm/src/repositoryManager.js +15 -7
- package/dist/esm/src/taskManager.js +4 -0
- package/dist/esm/src/utils/commandLineUtils.d.ts +2 -0
- package/dist/esm/src/utils/commandLineUtils.js +18 -0
- package/index.ts +18 -7
- package/package.json +2 -2
- package/src/api.ts +18 -15
- package/src/assetManager.ts +11 -7
- package/src/containerManager.ts +8 -7
- package/src/definitionsManager.ts +3 -0
- package/src/progressListener.ts +43 -28
- package/src/repositoryManager.ts +15 -7
- package/src/taskManager.ts +5 -0
- package/src/utils/commandLineUtils.ts +26 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.15.0](https://github.com/kapetacom/local-cluster-service/compare/v0.14.4...v0.15.0) (2023-08-09)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* auto-install core providers and cli when starting ([6495dce](https://github.com/kapetacom/local-cluster-service/commit/6495dcea33218fb214ee9df682ef327b91ebf817))
|
7
|
+
|
1
8
|
## [0.14.4](https://github.com/kapetacom/local-cluster-service/compare/v0.14.3...v0.14.4) (2023-08-09)
|
2
9
|
|
3
10
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
[
|
2
|
+
"kapeta/block-type-service",
|
3
|
+
"kapeta/block-type-frontend",
|
4
|
+
"kapeta/block-type-gateway-http",
|
5
|
+
"kapeta/resource-type-rest-api",
|
6
|
+
"kapeta/resource-type-rest-client",
|
7
|
+
"kapeta/resource-type-web-page",
|
8
|
+
"kapeta/resource-type-web-fragment",
|
9
|
+
"kapeta/resource-type-mongodb",
|
10
|
+
"kapeta/resource-type-postgresql",
|
11
|
+
"kapeta/language-target-react-ts",
|
12
|
+
"kapeta/language-target-nodejs",
|
13
|
+
"kapeta/language-target-java-spring-boot"
|
14
|
+
]
|
package/dist/cjs/index.js
CHANGED
@@ -24,6 +24,8 @@ const routes_10 = __importDefault(require("./src/tasks/routes"));
|
|
24
24
|
const api_1 = __importDefault(require("./src/api"));
|
25
25
|
const utils_1 = require("./src/utils/utils");
|
26
26
|
const request_1 = __importDefault(require("request"));
|
27
|
+
const repositoryManager_1 = require("./src/repositoryManager");
|
28
|
+
const commandLineUtils_1 = require("./src/utils/commandLineUtils");
|
27
29
|
let currentServer = null;
|
28
30
|
function createServer() {
|
29
31
|
const app = (0, express_1.default)();
|
@@ -144,7 +146,22 @@ exports.default = {
|
|
144
146
|
reject(err);
|
145
147
|
});
|
146
148
|
const bindHost = (0, utils_1.getBindHost)(host);
|
147
|
-
currentServer.listen(port, bindHost, () =>
|
149
|
+
currentServer.listen(port, bindHost, () => {
|
150
|
+
try {
|
151
|
+
(0, commandLineUtils_1.ensureCLI)().catch((e) => console.error('Failed to install CLI.', e));
|
152
|
+
}
|
153
|
+
catch (e) {
|
154
|
+
console.error('Failed to install CLI.', e);
|
155
|
+
}
|
156
|
+
try {
|
157
|
+
// Start installation process for all default providers
|
158
|
+
repositoryManager_1.repositoryManager.ensureDefaultProviders();
|
159
|
+
}
|
160
|
+
catch (e) {
|
161
|
+
console.error('Failed to install default providers.', e);
|
162
|
+
}
|
163
|
+
resolve({ host, port, dockerStatus: containerManager_1.containerManager.isAlive() });
|
164
|
+
});
|
148
165
|
currentServer.host = host;
|
149
166
|
currentServer.port = port;
|
150
167
|
});
|
package/dist/cjs/src/api.js
CHANGED
@@ -47,6 +47,9 @@ class DefinitionsManager {
|
|
47
47
|
getDefinition(ref) {
|
48
48
|
const uri = (0, nodejs_utils_1.parseKapetaUri)(ref);
|
49
49
|
return this.getDefinitions().find((d) => {
|
50
|
+
if (!uri.version) {
|
51
|
+
return d.definition.metadata.name === uri.fullName;
|
52
|
+
}
|
50
53
|
return (0, nodejs_utils_1.parseKapetaUri)(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
51
54
|
});
|
52
55
|
}
|
@@ -6,6 +6,7 @@ declare class ProgressListener {
|
|
6
6
|
run(command: string, directory?: string): Promise<{
|
7
7
|
exit: number;
|
8
8
|
signal: NodeJS.Signals | null;
|
9
|
+
output: string;
|
9
10
|
}>;
|
10
11
|
progress(label: string, callback: () => void | Promise<void>): Promise<void>;
|
11
12
|
check(message: string, ok: boolean | Promise<boolean> | (() => Promise<boolean>)): Promise<void>;
|
@@ -13,38 +13,51 @@ class ProgressListener {
|
|
13
13
|
type: 'info',
|
14
14
|
message: `Running command "${command}"`,
|
15
15
|
});
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
16
|
+
const firstCommand = command.split(' ')[0];
|
17
|
+
return new Promise(async (resolve, reject) => {
|
18
|
+
try {
|
19
|
+
const chunks = [];
|
20
|
+
const child = (0, nodejs_process_1.spawn)(command, [], {
|
21
|
+
cwd: directory ? directory : process.cwd(),
|
22
|
+
detached: true,
|
23
|
+
shell: true,
|
24
|
+
});
|
25
|
+
child.onData((data) => {
|
26
|
+
this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
|
27
|
+
});
|
28
|
+
if (child.process.stdout) {
|
29
|
+
child.process.stdout.on('data', (data) => {
|
30
|
+
chunks.push(data);
|
30
31
|
});
|
31
|
-
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
32
32
|
}
|
33
|
-
|
33
|
+
child.process.on('exit', (exit, signal) => {
|
34
|
+
if (exit !== 0) {
|
35
|
+
this.socketManager.emit(`install`, 'install:log', {
|
36
|
+
type: 'info',
|
37
|
+
message: `"${command}" failed: "${exit}"`,
|
38
|
+
});
|
39
|
+
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
this.socketManager.emit(`install`, 'install:log', {
|
43
|
+
type: 'info',
|
44
|
+
message: `Command OK: "${command}"`,
|
45
|
+
});
|
46
|
+
resolve({ exit, signal, output: Buffer.concat(chunks).toString() });
|
47
|
+
}
|
48
|
+
});
|
49
|
+
child.process.on('error', (err) => {
|
34
50
|
this.socketManager.emit(`install`, 'install:log', {
|
35
51
|
type: 'info',
|
36
|
-
message: `
|
52
|
+
message: `"${command}" failed: "${err.message}"`,
|
37
53
|
});
|
38
|
-
|
39
|
-
}
|
40
|
-
});
|
41
|
-
child.process.on('error', (err) => {
|
42
|
-
this.socketManager.emit(`install`, 'install:log', {
|
43
|
-
type: 'info',
|
44
|
-
message: `"${command}" failed: "${err.message}"`,
|
54
|
+
reject(err);
|
45
55
|
});
|
46
|
-
|
47
|
-
}
|
56
|
+
await child.wait();
|
57
|
+
}
|
58
|
+
catch (e) {
|
59
|
+
reject(e);
|
60
|
+
}
|
48
61
|
});
|
49
62
|
}
|
50
63
|
async progress(label, callback) {
|
@@ -8,6 +8,7 @@ declare class RepositoryManager {
|
|
8
8
|
setChangeEventsEnabled(enabled: boolean): void;
|
9
9
|
listenForChanges(): void;
|
10
10
|
stopListening(): void;
|
11
|
+
ensureDefaultProviders(): void;
|
11
12
|
private _install;
|
12
13
|
ensureAsset(handle: string, name: string, version: string, wait?: boolean): Promise<undefined | Task[]>;
|
13
14
|
}
|
@@ -107,7 +107,11 @@ class RepositoryManager {
|
|
107
107
|
this.watcher();
|
108
108
|
this.watcher = undefined;
|
109
109
|
}
|
110
|
-
|
110
|
+
ensureDefaultProviders() {
|
111
|
+
const providers = require('../default-providers.json');
|
112
|
+
this._install(providers);
|
113
|
+
}
|
114
|
+
_install(refs) {
|
111
115
|
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
112
116
|
const createInstaller = (ref) => {
|
113
117
|
return async () => {
|
@@ -117,7 +121,7 @@ class RepositoryManager {
|
|
117
121
|
if (definitionsManager_1.definitionsManager.exists(ref)) {
|
118
122
|
return;
|
119
123
|
}
|
120
|
-
console.log(`Installing asset: ${ref}`);
|
124
|
+
//console.log(`Installing asset: ${ref}`);
|
121
125
|
INSTALL_ATTEMPTED[ref] = true;
|
122
126
|
//Auto-install missing asset
|
123
127
|
try {
|
@@ -127,12 +131,16 @@ class RepositoryManager {
|
|
127
131
|
this.setChangeEventsEnabled(false);
|
128
132
|
await nodejs_registry_utils_1.Actions.install(progressListener_1.progressListener, [ref], {});
|
129
133
|
}
|
134
|
+
catch (e) {
|
135
|
+
console.error(`Failed to install asset: ${ref}`, e);
|
136
|
+
throw e;
|
137
|
+
}
|
130
138
|
finally {
|
131
139
|
this.setChangeEventsEnabled(true);
|
132
140
|
}
|
133
141
|
definitionsManager_1.definitionsManager.clearCache();
|
134
142
|
assetManager_1.assetManager.clearCache();
|
135
|
-
console.log(`Asset installed: ${ref}`);
|
143
|
+
//console.log(`Asset installed: ${ref}`);
|
136
144
|
};
|
137
145
|
};
|
138
146
|
const tasks = [];
|
@@ -150,7 +158,7 @@ class RepositoryManager {
|
|
150
158
|
}
|
151
159
|
const task = taskManager_1.taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
|
152
160
|
name: `Installing ${ref}`,
|
153
|
-
group: 'asset:install:',
|
161
|
+
group: 'asset:install:', //Group prevents multiple tasks from running at the same time
|
154
162
|
});
|
155
163
|
tasks.push(task);
|
156
164
|
}
|
@@ -189,17 +197,17 @@ class RepositoryManager {
|
|
189
197
|
this._cache[ref] = true;
|
190
198
|
let tasks = undefined;
|
191
199
|
if (!installedAsset) {
|
192
|
-
tasks =
|
200
|
+
tasks = this._install([ref]);
|
193
201
|
}
|
194
202
|
else {
|
195
203
|
//Ensure dependencies are installed
|
196
204
|
const refs = assetVersion.dependencies.map((dep) => dep.name);
|
197
205
|
if (refs.length > 0) {
|
198
|
-
tasks =
|
206
|
+
tasks = this._install(refs);
|
199
207
|
}
|
200
208
|
}
|
201
209
|
if (tasks && wait) {
|
202
|
-
await Promise.all(tasks.map((t) => t.
|
210
|
+
await Promise.all(tasks.map((t) => t.wait()));
|
203
211
|
}
|
204
212
|
return tasks;
|
205
213
|
}
|
@@ -65,6 +65,8 @@ function createFuture() {
|
|
65
65
|
resolve = res;
|
66
66
|
reject = rej;
|
67
67
|
});
|
68
|
+
// Ignore unhandled promise rejections
|
69
|
+
promise.catch(() => { });
|
68
70
|
return {
|
69
71
|
promise,
|
70
72
|
resolve,
|
@@ -133,6 +135,7 @@ class TaskManager {
|
|
133
135
|
return;
|
134
136
|
}
|
135
137
|
}
|
138
|
+
const startTime = Date.now();
|
136
139
|
try {
|
137
140
|
task.status = TaskStatus.RUNNING;
|
138
141
|
task.emitUpdate();
|
@@ -149,6 +152,7 @@ class TaskManager {
|
|
149
152
|
}
|
150
153
|
finally {
|
151
154
|
this.remove(task.id);
|
155
|
+
console.log(`Task ${task.id} completed in ${Date.now() - startTime}ms`);
|
152
156
|
}
|
153
157
|
if (task.metadata.group) {
|
154
158
|
const nextTaskInGroup = this._tasks.find((t) => t.id !== task.id && t.metadata.group === task.metadata.group && t.status === TaskStatus.PENDING);
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.ensureCLI = exports.hasCLI = void 0;
|
4
|
+
const nodejs_process_1 = require("@kapeta/nodejs-process");
|
5
|
+
const taskManager_1 = require("../taskManager");
|
6
|
+
async function hasCLI() {
|
7
|
+
return (0, nodejs_process_1.hasApp)('kap');
|
8
|
+
}
|
9
|
+
exports.hasCLI = hasCLI;
|
10
|
+
async function ensureCLI() {
|
11
|
+
if (await hasCLI()) {
|
12
|
+
return null;
|
13
|
+
}
|
14
|
+
return taskManager_1.taskManager.add(`cli:install`, () => {
|
15
|
+
const process = (0, nodejs_process_1.spawn)('npm', ['install', '-g', '@kapeta/kap'], {
|
16
|
+
shell: true,
|
17
|
+
});
|
18
|
+
return process.wait();
|
19
|
+
}, {
|
20
|
+
name: `Installing Kapeta CLI`,
|
21
|
+
});
|
22
|
+
}
|
23
|
+
exports.ensureCLI = ensureCLI;
|
package/dist/esm/index.js
CHANGED
@@ -19,6 +19,8 @@ import TaskRoutes from './src/tasks/routes';
|
|
19
19
|
import APIRoutes from './src/api';
|
20
20
|
import { getBindHost } from './src/utils/utils';
|
21
21
|
import request from 'request';
|
22
|
+
import { repositoryManager } from './src/repositoryManager';
|
23
|
+
import { ensureCLI } from './src/utils/commandLineUtils';
|
22
24
|
let currentServer = null;
|
23
25
|
function createServer() {
|
24
26
|
const app = express();
|
@@ -139,7 +141,22 @@ export default {
|
|
139
141
|
reject(err);
|
140
142
|
});
|
141
143
|
const bindHost = getBindHost(host);
|
142
|
-
currentServer.listen(port, bindHost, () =>
|
144
|
+
currentServer.listen(port, bindHost, () => {
|
145
|
+
try {
|
146
|
+
ensureCLI().catch((e) => console.error('Failed to install CLI.', e));
|
147
|
+
}
|
148
|
+
catch (e) {
|
149
|
+
console.error('Failed to install CLI.', e);
|
150
|
+
}
|
151
|
+
try {
|
152
|
+
// Start installation process for all default providers
|
153
|
+
repositoryManager.ensureDefaultProviders();
|
154
|
+
}
|
155
|
+
catch (e) {
|
156
|
+
console.error('Failed to install default providers.', e);
|
157
|
+
}
|
158
|
+
resolve({ host, port, dockerStatus: containerManager.isAlive() });
|
159
|
+
});
|
143
160
|
currentServer.host = host;
|
144
161
|
currentServer.port = port;
|
145
162
|
});
|
package/dist/esm/src/api.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import Router from 'express-promise-router';
|
2
|
-
import { corsHandler } from
|
3
|
-
import { KapetaAPI } from
|
2
|
+
import { corsHandler } from './middleware/cors';
|
3
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
4
4
|
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
5
5
|
const { createAPIRoute } = require('@kapeta/web-microfrontend/server');
|
6
6
|
const packageJson = require('../package.json');
|
@@ -16,6 +16,6 @@ router.use('/registry', createAPIRoute(remoteServices.registry ?? 'https://regis
|
|
16
16
|
return api.getAccessToken();
|
17
17
|
}
|
18
18
|
return null;
|
19
|
-
}
|
19
|
+
},
|
20
20
|
}));
|
21
21
|
export default router;
|
@@ -10,7 +10,7 @@ import { repositoryManager } from './repositoryManager';
|
|
10
10
|
import { Actions } from '@kapeta/nodejs-registry-utils';
|
11
11
|
import { definitionsManager } from './definitionsManager';
|
12
12
|
import { normalizeKapetaUri } from './utils/utils';
|
13
|
-
import { taskManager } from
|
13
|
+
import { taskManager } from './taskManager';
|
14
14
|
function enrichAsset(asset) {
|
15
15
|
return {
|
16
16
|
ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
|
@@ -41,6 +41,9 @@ class DefinitionsManager {
|
|
41
41
|
getDefinition(ref) {
|
42
42
|
const uri = parseKapetaUri(ref);
|
43
43
|
return this.getDefinitions().find((d) => {
|
44
|
+
if (!uri.version) {
|
45
|
+
return d.definition.metadata.name === uri.fullName;
|
46
|
+
}
|
44
47
|
return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
45
48
|
});
|
46
49
|
}
|
@@ -6,6 +6,7 @@ declare class ProgressListener {
|
|
6
6
|
run(command: string, directory?: string): Promise<{
|
7
7
|
exit: number;
|
8
8
|
signal: NodeJS.Signals | null;
|
9
|
+
output: string;
|
9
10
|
}>;
|
10
11
|
progress(label: string, callback: () => void | Promise<void>): Promise<void>;
|
11
12
|
check(message: string, ok: boolean | Promise<boolean> | (() => Promise<boolean>)): Promise<void>;
|
@@ -10,38 +10,51 @@ class ProgressListener {
|
|
10
10
|
type: 'info',
|
11
11
|
message: `Running command "${command}"`,
|
12
12
|
});
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
13
|
+
const firstCommand = command.split(' ')[0];
|
14
|
+
return new Promise(async (resolve, reject) => {
|
15
|
+
try {
|
16
|
+
const chunks = [];
|
17
|
+
const child = spawn(command, [], {
|
18
|
+
cwd: directory ? directory : process.cwd(),
|
19
|
+
detached: true,
|
20
|
+
shell: true,
|
21
|
+
});
|
22
|
+
child.onData((data) => {
|
23
|
+
this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
|
24
|
+
});
|
25
|
+
if (child.process.stdout) {
|
26
|
+
child.process.stdout.on('data', (data) => {
|
27
|
+
chunks.push(data);
|
27
28
|
});
|
28
|
-
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
29
29
|
}
|
30
|
-
|
30
|
+
child.process.on('exit', (exit, signal) => {
|
31
|
+
if (exit !== 0) {
|
32
|
+
this.socketManager.emit(`install`, 'install:log', {
|
33
|
+
type: 'info',
|
34
|
+
message: `"${command}" failed: "${exit}"`,
|
35
|
+
});
|
36
|
+
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
37
|
+
}
|
38
|
+
else {
|
39
|
+
this.socketManager.emit(`install`, 'install:log', {
|
40
|
+
type: 'info',
|
41
|
+
message: `Command OK: "${command}"`,
|
42
|
+
});
|
43
|
+
resolve({ exit, signal, output: Buffer.concat(chunks).toString() });
|
44
|
+
}
|
45
|
+
});
|
46
|
+
child.process.on('error', (err) => {
|
31
47
|
this.socketManager.emit(`install`, 'install:log', {
|
32
48
|
type: 'info',
|
33
|
-
message: `
|
49
|
+
message: `"${command}" failed: "${err.message}"`,
|
34
50
|
});
|
35
|
-
|
36
|
-
}
|
37
|
-
});
|
38
|
-
child.process.on('error', (err) => {
|
39
|
-
this.socketManager.emit(`install`, 'install:log', {
|
40
|
-
type: 'info',
|
41
|
-
message: `"${command}" failed: "${err.message}"`,
|
51
|
+
reject(err);
|
42
52
|
});
|
43
|
-
|
44
|
-
}
|
53
|
+
await child.wait();
|
54
|
+
}
|
55
|
+
catch (e) {
|
56
|
+
reject(e);
|
57
|
+
}
|
45
58
|
});
|
46
59
|
}
|
47
60
|
async progress(label, callback) {
|
@@ -8,6 +8,7 @@ declare class RepositoryManager {
|
|
8
8
|
setChangeEventsEnabled(enabled: boolean): void;
|
9
9
|
listenForChanges(): void;
|
10
10
|
stopListening(): void;
|
11
|
+
ensureDefaultProviders(): void;
|
11
12
|
private _install;
|
12
13
|
ensureAsset(handle: string, name: string, version: string, wait?: boolean): Promise<undefined | Task[]>;
|
13
14
|
}
|
@@ -101,7 +101,11 @@ class RepositoryManager {
|
|
101
101
|
this.watcher();
|
102
102
|
this.watcher = undefined;
|
103
103
|
}
|
104
|
-
|
104
|
+
ensureDefaultProviders() {
|
105
|
+
const providers = require('../default-providers.json');
|
106
|
+
this._install(providers);
|
107
|
+
}
|
108
|
+
_install(refs) {
|
105
109
|
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
106
110
|
const createInstaller = (ref) => {
|
107
111
|
return async () => {
|
@@ -111,7 +115,7 @@ class RepositoryManager {
|
|
111
115
|
if (definitionsManager.exists(ref)) {
|
112
116
|
return;
|
113
117
|
}
|
114
|
-
console.log(`Installing asset: ${ref}`);
|
118
|
+
//console.log(`Installing asset: ${ref}`);
|
115
119
|
INSTALL_ATTEMPTED[ref] = true;
|
116
120
|
//Auto-install missing asset
|
117
121
|
try {
|
@@ -121,12 +125,16 @@ class RepositoryManager {
|
|
121
125
|
this.setChangeEventsEnabled(false);
|
122
126
|
await Actions.install(progressListener, [ref], {});
|
123
127
|
}
|
128
|
+
catch (e) {
|
129
|
+
console.error(`Failed to install asset: ${ref}`, e);
|
130
|
+
throw e;
|
131
|
+
}
|
124
132
|
finally {
|
125
133
|
this.setChangeEventsEnabled(true);
|
126
134
|
}
|
127
135
|
definitionsManager.clearCache();
|
128
136
|
assetManager.clearCache();
|
129
|
-
console.log(`Asset installed: ${ref}`);
|
137
|
+
//console.log(`Asset installed: ${ref}`);
|
130
138
|
};
|
131
139
|
};
|
132
140
|
const tasks = [];
|
@@ -144,7 +152,7 @@ class RepositoryManager {
|
|
144
152
|
}
|
145
153
|
const task = taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
|
146
154
|
name: `Installing ${ref}`,
|
147
|
-
group: 'asset:install:',
|
155
|
+
group: 'asset:install:', //Group prevents multiple tasks from running at the same time
|
148
156
|
});
|
149
157
|
tasks.push(task);
|
150
158
|
}
|
@@ -183,17 +191,17 @@ class RepositoryManager {
|
|
183
191
|
this._cache[ref] = true;
|
184
192
|
let tasks = undefined;
|
185
193
|
if (!installedAsset) {
|
186
|
-
tasks =
|
194
|
+
tasks = this._install([ref]);
|
187
195
|
}
|
188
196
|
else {
|
189
197
|
//Ensure dependencies are installed
|
190
198
|
const refs = assetVersion.dependencies.map((dep) => dep.name);
|
191
199
|
if (refs.length > 0) {
|
192
|
-
tasks =
|
200
|
+
tasks = this._install(refs);
|
193
201
|
}
|
194
202
|
}
|
195
203
|
if (tasks && wait) {
|
196
|
-
await Promise.all(tasks.map((t) => t.
|
204
|
+
await Promise.all(tasks.map((t) => t.wait()));
|
197
205
|
}
|
198
206
|
return tasks;
|
199
207
|
}
|
@@ -61,6 +61,8 @@ function createFuture() {
|
|
61
61
|
resolve = res;
|
62
62
|
reject = rej;
|
63
63
|
});
|
64
|
+
// Ignore unhandled promise rejections
|
65
|
+
promise.catch(() => { });
|
64
66
|
return {
|
65
67
|
promise,
|
66
68
|
resolve,
|
@@ -129,6 +131,7 @@ class TaskManager {
|
|
129
131
|
return;
|
130
132
|
}
|
131
133
|
}
|
134
|
+
const startTime = Date.now();
|
132
135
|
try {
|
133
136
|
task.status = TaskStatus.RUNNING;
|
134
137
|
task.emitUpdate();
|
@@ -145,6 +148,7 @@ class TaskManager {
|
|
145
148
|
}
|
146
149
|
finally {
|
147
150
|
this.remove(task.id);
|
151
|
+
console.log(`Task ${task.id} completed in ${Date.now() - startTime}ms`);
|
148
152
|
}
|
149
153
|
if (task.metadata.group) {
|
150
154
|
const nextTaskInGroup = this._tasks.find((t) => t.id !== task.id && t.metadata.group === task.metadata.group && t.status === TaskStatus.PENDING);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { spawn, hasApp } from '@kapeta/nodejs-process';
|
2
|
+
import { taskManager } from '../taskManager';
|
3
|
+
export async function hasCLI() {
|
4
|
+
return hasApp('kap');
|
5
|
+
}
|
6
|
+
export async function ensureCLI() {
|
7
|
+
if (await hasCLI()) {
|
8
|
+
return null;
|
9
|
+
}
|
10
|
+
return taskManager.add(`cli:install`, () => {
|
11
|
+
const process = spawn('npm', ['install', '-g', '@kapeta/kap'], {
|
12
|
+
shell: true,
|
13
|
+
});
|
14
|
+
return process.wait();
|
15
|
+
}, {
|
16
|
+
name: `Installing Kapeta CLI`,
|
17
|
+
});
|
18
|
+
}
|
package/index.ts
CHANGED
@@ -20,10 +20,8 @@ import TaskRoutes from './src/tasks/routes';
|
|
20
20
|
import APIRoutes from './src/api';
|
21
21
|
import { getBindHost } from './src/utils/utils';
|
22
22
|
import request from 'request';
|
23
|
-
import
|
24
|
-
import {
|
25
|
-
import {corsHandler} from "./src/middleware/cors";
|
26
|
-
|
23
|
+
import { repositoryManager } from './src/repositoryManager';
|
24
|
+
import { ensureCLI } from './src/utils/commandLineUtils';
|
27
25
|
|
28
26
|
export type LocalClusterService = HTTP.Server & { host?: string; port?: number };
|
29
27
|
|
@@ -175,9 +173,22 @@ export default {
|
|
175
173
|
|
176
174
|
const bindHost = getBindHost(host);
|
177
175
|
|
178
|
-
currentServer.listen(port, bindHost, () =>
|
179
|
-
|
180
|
-
|
176
|
+
currentServer.listen(port, bindHost, () => {
|
177
|
+
try {
|
178
|
+
ensureCLI().catch((e: any) => console.error('Failed to install CLI.', e));
|
179
|
+
} catch (e: any) {
|
180
|
+
console.error('Failed to install CLI.', e);
|
181
|
+
}
|
182
|
+
|
183
|
+
try {
|
184
|
+
// Start installation process for all default providers
|
185
|
+
repositoryManager.ensureDefaultProviders();
|
186
|
+
} catch (e: any) {
|
187
|
+
console.error('Failed to install default providers.', e);
|
188
|
+
}
|
189
|
+
|
190
|
+
resolve({ host, port, dockerStatus: containerManager.isAlive() });
|
191
|
+
});
|
181
192
|
currentServer.host = host;
|
182
193
|
currentServer.port = port;
|
183
194
|
});
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.15.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -46,7 +46,7 @@
|
|
46
46
|
"@kapeta/codegen": "<2",
|
47
47
|
"@kapeta/local-cluster-config": ">= 0.2.3 <2",
|
48
48
|
"@kapeta/nodejs-api-client": "<2",
|
49
|
-
"@kapeta/nodejs-process": "
|
49
|
+
"@kapeta/nodejs-process": "<2",
|
50
50
|
"@kapeta/nodejs-registry-utils": "<2",
|
51
51
|
"@kapeta/nodejs-utils": "<2",
|
52
52
|
"@kapeta/schemas": "^0.0.58",
|
package/src/api.ts
CHANGED
@@ -1,25 +1,28 @@
|
|
1
1
|
import Router from 'express-promise-router';
|
2
|
-
import {corsHandler} from
|
3
|
-
import {KapetaAPI} from
|
2
|
+
import { corsHandler } from './middleware/cors';
|
3
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
4
4
|
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
5
|
-
const { createAPIRoute }
|
5
|
+
const { createAPIRoute } = require('@kapeta/web-microfrontend/server');
|
6
6
|
const packageJson = require('../package.json');
|
7
7
|
|
8
8
|
const router = Router();
|
9
9
|
|
10
10
|
const remoteServices = ClusterConfiguration.getClusterConfig().remoteServices ?? {};
|
11
|
-
router.use('/', corsHandler)
|
11
|
+
router.use('/', corsHandler);
|
12
12
|
|
13
|
-
router.use(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
router.use(
|
14
|
+
'/registry',
|
15
|
+
createAPIRoute(remoteServices.registry ?? 'https://registry.kapeta.com', {
|
16
|
+
nonce: false,
|
17
|
+
userAgent: `KapetaDesktopCluster/${packageJson.version}`,
|
18
|
+
tokenFetcher: () => {
|
19
|
+
const api = new KapetaAPI();
|
20
|
+
if (api.hasToken()) {
|
21
|
+
return api.getAccessToken();
|
22
|
+
}
|
23
|
+
return null;
|
24
|
+
},
|
25
|
+
})
|
26
|
+
);
|
24
27
|
|
25
28
|
export default router;
|
package/src/assetManager.ts
CHANGED
@@ -12,7 +12,7 @@ import { BlockDefinition } from '@kapeta/schemas';
|
|
12
12
|
import { Actions } from '@kapeta/nodejs-registry-utils';
|
13
13
|
import { definitionsManager } from './definitionsManager';
|
14
14
|
import { normalizeKapetaUri } from './utils/utils';
|
15
|
-
import {taskManager} from
|
15
|
+
import { taskManager } from './taskManager';
|
16
16
|
|
17
17
|
export interface EnrichedAsset {
|
18
18
|
ref: string;
|
@@ -180,15 +180,19 @@ class AssetManager {
|
|
180
180
|
this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
|
181
181
|
}
|
182
182
|
|
183
|
-
private maybeGenerateCode(ref:string, ymlPath:string, block: BlockDefinition) {
|
183
|
+
private maybeGenerateCode(ref: string, ymlPath: string, block: BlockDefinition) {
|
184
184
|
ref = normalizeKapetaUri(ref);
|
185
185
|
if (codeGeneratorManager.canGenerateCode(block)) {
|
186
186
|
const assetTitle = block.metadata.title ? block.metadata.title : parseKapetaUri(block.metadata.name).name;
|
187
|
-
taskManager.add(
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
187
|
+
taskManager.add(
|
188
|
+
`codegen:${ref}`,
|
189
|
+
async () => {
|
190
|
+
await codeGeneratorManager.generate(ymlPath, block);
|
191
|
+
},
|
192
|
+
{
|
193
|
+
name: `Generating code for ${assetTitle}`,
|
194
|
+
}
|
195
|
+
);
|
192
196
|
return true;
|
193
197
|
}
|
194
198
|
return false;
|
package/src/containerManager.ts
CHANGED
@@ -252,13 +252,14 @@ class ContainerManager {
|
|
252
252
|
const api = new KapetaAPI();
|
253
253
|
const accessToken = api.hasToken() ? await api.getAccessToken() : null;
|
254
254
|
|
255
|
-
const auth =
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
255
|
+
const auth =
|
256
|
+
accessToken && image.startsWith('docker.kapeta.com/')
|
257
|
+
? {
|
258
|
+
username: 'kapeta',
|
259
|
+
password: accessToken,
|
260
|
+
serveraddress: 'docker.kapeta.com',
|
261
|
+
}
|
262
|
+
: {};
|
262
263
|
|
263
264
|
const stream = (await this.docker().image.create(auth, {
|
264
265
|
fromImage: imageName,
|
@@ -58,6 +58,9 @@ class DefinitionsManager {
|
|
58
58
|
public getDefinition(ref: string) {
|
59
59
|
const uri = parseKapetaUri(ref);
|
60
60
|
return this.getDefinitions().find((d) => {
|
61
|
+
if (!uri.version) {
|
62
|
+
return d.definition.metadata.name === uri.fullName;
|
63
|
+
}
|
61
64
|
return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
62
65
|
});
|
63
66
|
}
|
package/src/progressListener.ts
CHANGED
@@ -7,46 +7,61 @@ class ProgressListener {
|
|
7
7
|
this.socketManager = socketManager;
|
8
8
|
}
|
9
9
|
|
10
|
-
run(command: string, directory?: string): Promise<{ exit: number; signal: NodeJS.Signals | null }> {
|
10
|
+
run(command: string, directory?: string): Promise<{ exit: number; signal: NodeJS.Signals | null; output: string }> {
|
11
11
|
this.socketManager.emit(`install`, 'install:log', {
|
12
12
|
type: 'info',
|
13
13
|
message: `Running command "${command}"`,
|
14
14
|
});
|
15
15
|
|
16
|
-
|
17
|
-
const child = spawn(command, [],{
|
18
|
-
cwd: directory ? directory : process.cwd(),
|
19
|
-
detached: true,
|
20
|
-
shell: true,
|
21
|
-
});
|
16
|
+
const firstCommand = command.split(' ')[0];
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
return new Promise(async (resolve, reject) => {
|
19
|
+
try {
|
20
|
+
const chunks: Buffer[] = [];
|
21
|
+
const child = spawn(command, [], {
|
22
|
+
cwd: directory ? directory : process.cwd(),
|
23
|
+
detached: true,
|
24
|
+
shell: true,
|
25
|
+
});
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
child.onData((data) => {
|
28
|
+
this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
|
29
|
+
});
|
30
|
+
|
31
|
+
if (child.process.stdout) {
|
32
|
+
child.process.stdout.on('data', (data) => {
|
33
|
+
chunks.push(data);
|
32
34
|
});
|
33
|
-
|
34
|
-
|
35
|
+
}
|
36
|
+
|
37
|
+
child.process.on('exit', (exit, signal) => {
|
38
|
+
if (exit !== 0) {
|
39
|
+
this.socketManager.emit(`install`, 'install:log', {
|
40
|
+
type: 'info',
|
41
|
+
message: `"${command}" failed: "${exit}"`,
|
42
|
+
});
|
43
|
+
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
44
|
+
} else {
|
45
|
+
this.socketManager.emit(`install`, 'install:log', {
|
46
|
+
type: 'info',
|
47
|
+
message: `Command OK: "${command}"`,
|
48
|
+
});
|
49
|
+
resolve({ exit, signal, output: Buffer.concat(chunks).toString() });
|
50
|
+
}
|
51
|
+
});
|
52
|
+
|
53
|
+
child.process.on('error', (err) => {
|
35
54
|
this.socketManager.emit(`install`, 'install:log', {
|
36
55
|
type: 'info',
|
37
|
-
message: `
|
56
|
+
message: `"${command}" failed: "${err.message}"`,
|
38
57
|
});
|
39
|
-
|
40
|
-
}
|
41
|
-
});
|
42
|
-
|
43
|
-
child.process.on('error', (err) => {
|
44
|
-
this.socketManager.emit(`install`, 'install:log', {
|
45
|
-
type: 'info',
|
46
|
-
message: `"${command}" failed: "${err.message}"`,
|
58
|
+
reject(err);
|
47
59
|
});
|
48
|
-
|
49
|
-
|
60
|
+
|
61
|
+
await child.wait();
|
62
|
+
} catch (e) {
|
63
|
+
reject(e);
|
64
|
+
}
|
50
65
|
});
|
51
66
|
}
|
52
67
|
|
package/src/repositoryManager.ts
CHANGED
@@ -114,7 +114,12 @@ class RepositoryManager {
|
|
114
114
|
this.watcher = undefined;
|
115
115
|
}
|
116
116
|
|
117
|
-
|
117
|
+
public ensureDefaultProviders(): void {
|
118
|
+
const providers = require('../default-providers.json') as string[];
|
119
|
+
this._install(providers);
|
120
|
+
}
|
121
|
+
|
122
|
+
private _install(refs: string[]): Task[] {
|
118
123
|
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
119
124
|
const createInstaller = (ref: string) => {
|
120
125
|
return async () => {
|
@@ -125,7 +130,7 @@ class RepositoryManager {
|
|
125
130
|
if (definitionsManager.exists(ref)) {
|
126
131
|
return;
|
127
132
|
}
|
128
|
-
console.log(`Installing asset: ${ref}`);
|
133
|
+
//console.log(`Installing asset: ${ref}`);
|
129
134
|
INSTALL_ATTEMPTED[ref] = true;
|
130
135
|
//Auto-install missing asset
|
131
136
|
try {
|
@@ -134,12 +139,15 @@ class RepositoryManager {
|
|
134
139
|
//Disable change events while installing
|
135
140
|
this.setChangeEventsEnabled(false);
|
136
141
|
await Actions.install(progressListener, [ref], {});
|
142
|
+
} catch (e) {
|
143
|
+
console.error(`Failed to install asset: ${ref}`, e);
|
144
|
+
throw e;
|
137
145
|
} finally {
|
138
146
|
this.setChangeEventsEnabled(true);
|
139
147
|
}
|
140
148
|
definitionsManager.clearCache();
|
141
149
|
assetManager.clearCache();
|
142
|
-
console.log(`Asset installed: ${ref}`);
|
150
|
+
//console.log(`Asset installed: ${ref}`);
|
143
151
|
};
|
144
152
|
};
|
145
153
|
|
@@ -162,7 +170,7 @@ class RepositoryManager {
|
|
162
170
|
|
163
171
|
const task = taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
|
164
172
|
name: `Installing ${ref}`,
|
165
|
-
group: 'asset:install:',
|
173
|
+
group: 'asset:install:', //Group prevents multiple tasks from running at the same time
|
166
174
|
});
|
167
175
|
|
168
176
|
tasks.push(task);
|
@@ -216,17 +224,17 @@ class RepositoryManager {
|
|
216
224
|
this._cache[ref] = true;
|
217
225
|
let tasks: Task[] | undefined = undefined;
|
218
226
|
if (!installedAsset) {
|
219
|
-
tasks =
|
227
|
+
tasks = this._install([ref]);
|
220
228
|
} else {
|
221
229
|
//Ensure dependencies are installed
|
222
230
|
const refs = assetVersion.dependencies.map((dep: Dependency) => dep.name);
|
223
231
|
if (refs.length > 0) {
|
224
|
-
tasks =
|
232
|
+
tasks = this._install(refs);
|
225
233
|
}
|
226
234
|
}
|
227
235
|
|
228
236
|
if (tasks && wait) {
|
229
|
-
await Promise.all(tasks.map((t) => t.
|
237
|
+
await Promise.all(tasks.map((t) => t.wait()));
|
230
238
|
}
|
231
239
|
|
232
240
|
return tasks;
|
package/src/taskManager.ts
CHANGED
@@ -106,6 +106,9 @@ function createFuture<T>(): Future<T> {
|
|
106
106
|
reject = rej;
|
107
107
|
});
|
108
108
|
|
109
|
+
// Ignore unhandled promise rejections
|
110
|
+
promise.catch(() => {});
|
111
|
+
|
109
112
|
return {
|
110
113
|
promise,
|
111
114
|
resolve,
|
@@ -193,6 +196,7 @@ class TaskManager {
|
|
193
196
|
}
|
194
197
|
}
|
195
198
|
|
199
|
+
const startTime = Date.now();
|
196
200
|
try {
|
197
201
|
task.status = TaskStatus.RUNNING;
|
198
202
|
task.emitUpdate();
|
@@ -207,6 +211,7 @@ class TaskManager {
|
|
207
211
|
task.emitUpdate();
|
208
212
|
} finally {
|
209
213
|
this.remove(task.id);
|
214
|
+
console.log(`Task ${task.id} completed in ${Date.now() - startTime}ms`);
|
210
215
|
}
|
211
216
|
|
212
217
|
if (task.metadata.group) {
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { spawn, hasApp } from '@kapeta/nodejs-process';
|
2
|
+
import { taskManager } from '../taskManager';
|
3
|
+
|
4
|
+
export async function hasCLI() {
|
5
|
+
return hasApp('kap');
|
6
|
+
}
|
7
|
+
|
8
|
+
export async function ensureCLI() {
|
9
|
+
if (await hasCLI()) {
|
10
|
+
return null;
|
11
|
+
}
|
12
|
+
|
13
|
+
return taskManager.add(
|
14
|
+
`cli:install`,
|
15
|
+
() => {
|
16
|
+
const process = spawn('npm', ['install', '-g', '@kapeta/kap'], {
|
17
|
+
shell: true,
|
18
|
+
});
|
19
|
+
|
20
|
+
return process.wait();
|
21
|
+
},
|
22
|
+
{
|
23
|
+
name: `Installing Kapeta CLI`,
|
24
|
+
}
|
25
|
+
);
|
26
|
+
}
|