@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 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, () => resolve({ host, port, dockerStatus: containerManager_1.containerManager.isAlive() }));
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
  });
@@ -21,6 +21,6 @@ router.use('/registry', createAPIRoute(remoteServices.registry ?? 'https://regis
21
21
  return api.getAccessToken();
22
22
  }
23
23
  return null;
24
- }
24
+ },
25
25
  }));
26
26
  exports.default = router;
@@ -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
- return new Promise((resolve, reject) => {
17
- const child = (0, nodejs_process_1.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
- child.process.on('exit', (exit, signal) => {
26
- if (exit !== 0) {
27
- this.socketManager.emit(`install`, 'install:log', {
28
- type: 'info',
29
- message: `"${command}" failed: "${exit}"`,
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
- else {
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: `Command OK: "${command}"`,
52
+ message: `"${command}" failed: "${err.message}"`,
37
53
  });
38
- resolve({ exit, signal });
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
- reject(err);
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
- async _install(refs) {
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 = await this._install([ref]);
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 = await this._install(refs);
206
+ tasks = this._install(refs);
199
207
  }
200
208
  }
201
209
  if (tasks && wait) {
202
- await Promise.all(tasks.map((t) => t.future.promise));
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,2 @@
1
+ export declare function hasCLI(): Promise<boolean>;
2
+ export declare function ensureCLI(): Promise<import("../taskManager").Task<void> | null>;
@@ -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, () => resolve({ host, port, dockerStatus: containerManager.isAlive() }));
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
  });
@@ -1,6 +1,6 @@
1
1
  import Router from 'express-promise-router';
2
- import { corsHandler } from "./middleware/cors";
3
- import { KapetaAPI } from "@kapeta/nodejs-api-client";
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 "./taskManager";
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
- return new Promise((resolve, reject) => {
14
- const child = spawn(command, [], {
15
- cwd: directory ? directory : process.cwd(),
16
- detached: true,
17
- shell: true,
18
- });
19
- child.onData((data) => {
20
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
21
- });
22
- child.process.on('exit', (exit, signal) => {
23
- if (exit !== 0) {
24
- this.socketManager.emit(`install`, 'install:log', {
25
- type: 'info',
26
- message: `"${command}" failed: "${exit}"`,
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
- else {
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: `Command OK: "${command}"`,
49
+ message: `"${command}" failed: "${err.message}"`,
34
50
  });
35
- resolve({ exit, signal });
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
- reject(err);
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
- async _install(refs) {
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 = await this._install([ref]);
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 = await this._install(refs);
200
+ tasks = this._install(refs);
193
201
  }
194
202
  }
195
203
  if (tasks && wait) {
196
- await Promise.all(tasks.map((t) => t.future.promise));
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,2 @@
1
+ export declare function hasCLI(): Promise<boolean>;
2
+ export declare function ensureCLI(): Promise<import("../taskManager").Task<void> | null>;
@@ -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 ClusterConfiguration from "@kapeta/local-cluster-config";
24
- import {KapetaAPI} from "@kapeta/nodejs-api-client";
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
- resolve({ host, port, dockerStatus: containerManager.isAlive() })
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.14.4",
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": "^1.1.0",
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 "./middleware/cors";
3
- import {KapetaAPI} from "@kapeta/nodejs-api-client";
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 } = require('@kapeta/web-microfrontend/server');
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('/registry', createAPIRoute(remoteServices.registry ?? 'https://registry.kapeta.com', {
14
- nonce: false,
15
- userAgent: `KapetaDesktopCluster/${packageJson.version}`,
16
- tokenFetcher: () => {
17
- const api = new KapetaAPI();
18
- if (api.hasToken()) {
19
- return api.getAccessToken();
20
- }
21
- return null;
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;
@@ -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 "./taskManager";
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(`codegen:${ref}`, async () => {
188
- await codeGeneratorManager.generate(ymlPath, block);
189
- },{
190
- name: `Generating code for ${assetTitle}`,
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;
@@ -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 = accessToken && image.startsWith('docker.kapeta.com/')
256
- ? {
257
- username: 'kapeta',
258
- password: accessToken,
259
- serveraddress: 'docker.kapeta.com',
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
  }
@@ -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
- return new Promise((resolve, reject) => {
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
- child.onData((data) => {
24
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
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
- child.process.on('exit', (exit, signal) => {
28
- if (exit !== 0) {
29
- this.socketManager.emit(`install`, 'install:log', {
30
- type: 'info',
31
- message: `"${command}" failed: "${exit}"`,
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
- reject(new Error(`Command "${command}" exited with code ${exit}`));
34
- } else {
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: `Command OK: "${command}"`,
56
+ message: `"${command}" failed: "${err.message}"`,
38
57
  });
39
- resolve({ exit, signal });
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
- reject(err);
49
- });
60
+
61
+ await child.wait();
62
+ } catch (e) {
63
+ reject(e);
64
+ }
50
65
  });
51
66
  }
52
67
 
@@ -114,7 +114,12 @@ class RepositoryManager {
114
114
  this.watcher = undefined;
115
115
  }
116
116
 
117
- private async _install(refs: string[]): Promise<Task[]> {
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 = await this._install([ref]);
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 = await this._install(refs);
232
+ tasks = this._install(refs);
225
233
  }
226
234
  }
227
235
 
228
236
  if (tasks && wait) {
229
- await Promise.all(tasks.map((t) => t.future.promise));
237
+ await Promise.all(tasks.map((t) => t.wait()));
230
238
  }
231
239
 
232
240
  return tasks;
@@ -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
+ }