@kapeta/local-cluster-service 0.14.4 → 0.15.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.15.1](https://github.com/kapetacom/local-cluster-service/compare/v0.15.0...v0.15.1) (2023-08-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Move providers into TS file - compile doesnt copy json ([1e2681a](https://github.com/kapetacom/local-cluster-service/commit/1e2681ab85592366e6eb99aab72a514b4a1503c4))
7
+
8
+ # [0.15.0](https://github.com/kapetacom/local-cluster-service/compare/v0.14.4...v0.15.0) (2023-08-09)
9
+
10
+
11
+ ### Features
12
+
13
+ * auto-install core providers and cli when starting ([6495dce](https://github.com/kapetacom/local-cluster-service/commit/6495dcea33218fb214ee9df682ef327b91ebf817))
14
+
1
15
  ## [0.14.4](https://github.com/kapetacom/local-cluster-service/compare/v0.14.3...v0.14.4) (2023-08-09)
2
16
 
3
17
 
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
  }
@@ -18,6 +18,20 @@ const definitionsManager_1 = require("./definitionsManager");
18
18
  const taskManager_1 = require("./taskManager");
19
19
  const utils_1 = require("./utils/utils");
20
20
  const assetManager_1 = require("./assetManager");
21
+ const DEFAULT_PROVIDERS = [
22
+ 'kapeta/block-type-service',
23
+ 'kapeta/block-type-frontend',
24
+ 'kapeta/block-type-gateway-http',
25
+ 'kapeta/resource-type-rest-api',
26
+ 'kapeta/resource-type-rest-client',
27
+ 'kapeta/resource-type-web-page',
28
+ 'kapeta/resource-type-web-fragment',
29
+ 'kapeta/resource-type-mongodb',
30
+ 'kapeta/resource-type-postgresql',
31
+ 'kapeta/language-target-react-ts',
32
+ 'kapeta/language-target-nodejs',
33
+ 'kapeta/language-target-java-spring-boot',
34
+ ];
21
35
  const INSTALL_ATTEMPTED = {};
22
36
  class RepositoryManager {
23
37
  changeEventsEnabled;
@@ -107,7 +121,10 @@ class RepositoryManager {
107
121
  this.watcher();
108
122
  this.watcher = undefined;
109
123
  }
110
- async _install(refs) {
124
+ ensureDefaultProviders() {
125
+ this._install(DEFAULT_PROVIDERS);
126
+ }
127
+ _install(refs) {
111
128
  //We make sure to only install one asset at a time - otherwise unexpected things might happen
112
129
  const createInstaller = (ref) => {
113
130
  return async () => {
@@ -117,7 +134,7 @@ class RepositoryManager {
117
134
  if (definitionsManager_1.definitionsManager.exists(ref)) {
118
135
  return;
119
136
  }
120
- console.log(`Installing asset: ${ref}`);
137
+ //console.log(`Installing asset: ${ref}`);
121
138
  INSTALL_ATTEMPTED[ref] = true;
122
139
  //Auto-install missing asset
123
140
  try {
@@ -127,12 +144,16 @@ class RepositoryManager {
127
144
  this.setChangeEventsEnabled(false);
128
145
  await nodejs_registry_utils_1.Actions.install(progressListener_1.progressListener, [ref], {});
129
146
  }
147
+ catch (e) {
148
+ console.error(`Failed to install asset: ${ref}`, e);
149
+ throw e;
150
+ }
130
151
  finally {
131
152
  this.setChangeEventsEnabled(true);
132
153
  }
133
154
  definitionsManager_1.definitionsManager.clearCache();
134
155
  assetManager_1.assetManager.clearCache();
135
- console.log(`Asset installed: ${ref}`);
156
+ //console.log(`Asset installed: ${ref}`);
136
157
  };
137
158
  };
138
159
  const tasks = [];
@@ -150,7 +171,7 @@ class RepositoryManager {
150
171
  }
151
172
  const task = taskManager_1.taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
152
173
  name: `Installing ${ref}`,
153
- group: 'asset:install:',
174
+ group: 'asset:install:', //Group prevents multiple tasks from running at the same time
154
175
  });
155
176
  tasks.push(task);
156
177
  }
@@ -189,17 +210,17 @@ class RepositoryManager {
189
210
  this._cache[ref] = true;
190
211
  let tasks = undefined;
191
212
  if (!installedAsset) {
192
- tasks = await this._install([ref]);
213
+ tasks = this._install([ref]);
193
214
  }
194
215
  else {
195
216
  //Ensure dependencies are installed
196
217
  const refs = assetVersion.dependencies.map((dep) => dep.name);
197
218
  if (refs.length > 0) {
198
- tasks = await this._install(refs);
219
+ tasks = this._install(refs);
199
220
  }
200
221
  }
201
222
  if (tasks && wait) {
202
- await Promise.all(tasks.map((t) => t.future.promise));
223
+ await Promise.all(tasks.map((t) => t.wait()));
203
224
  }
204
225
  return tasks;
205
226
  }
@@ -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
  }
@@ -12,6 +12,20 @@ import { definitionsManager } from './definitionsManager';
12
12
  import { taskManager } from './taskManager';
13
13
  import { normalizeKapetaUri } from './utils/utils';
14
14
  import { assetManager } from './assetManager';
15
+ const DEFAULT_PROVIDERS = [
16
+ 'kapeta/block-type-service',
17
+ 'kapeta/block-type-frontend',
18
+ 'kapeta/block-type-gateway-http',
19
+ 'kapeta/resource-type-rest-api',
20
+ 'kapeta/resource-type-rest-client',
21
+ 'kapeta/resource-type-web-page',
22
+ 'kapeta/resource-type-web-fragment',
23
+ 'kapeta/resource-type-mongodb',
24
+ 'kapeta/resource-type-postgresql',
25
+ 'kapeta/language-target-react-ts',
26
+ 'kapeta/language-target-nodejs',
27
+ 'kapeta/language-target-java-spring-boot',
28
+ ];
15
29
  const INSTALL_ATTEMPTED = {};
16
30
  class RepositoryManager {
17
31
  changeEventsEnabled;
@@ -101,7 +115,10 @@ class RepositoryManager {
101
115
  this.watcher();
102
116
  this.watcher = undefined;
103
117
  }
104
- async _install(refs) {
118
+ ensureDefaultProviders() {
119
+ this._install(DEFAULT_PROVIDERS);
120
+ }
121
+ _install(refs) {
105
122
  //We make sure to only install one asset at a time - otherwise unexpected things might happen
106
123
  const createInstaller = (ref) => {
107
124
  return async () => {
@@ -111,7 +128,7 @@ class RepositoryManager {
111
128
  if (definitionsManager.exists(ref)) {
112
129
  return;
113
130
  }
114
- console.log(`Installing asset: ${ref}`);
131
+ //console.log(`Installing asset: ${ref}`);
115
132
  INSTALL_ATTEMPTED[ref] = true;
116
133
  //Auto-install missing asset
117
134
  try {
@@ -121,12 +138,16 @@ class RepositoryManager {
121
138
  this.setChangeEventsEnabled(false);
122
139
  await Actions.install(progressListener, [ref], {});
123
140
  }
141
+ catch (e) {
142
+ console.error(`Failed to install asset: ${ref}`, e);
143
+ throw e;
144
+ }
124
145
  finally {
125
146
  this.setChangeEventsEnabled(true);
126
147
  }
127
148
  definitionsManager.clearCache();
128
149
  assetManager.clearCache();
129
- console.log(`Asset installed: ${ref}`);
150
+ //console.log(`Asset installed: ${ref}`);
130
151
  };
131
152
  };
132
153
  const tasks = [];
@@ -144,7 +165,7 @@ class RepositoryManager {
144
165
  }
145
166
  const task = taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
146
167
  name: `Installing ${ref}`,
147
- group: 'asset:install:',
168
+ group: 'asset:install:', //Group prevents multiple tasks from running at the same time
148
169
  });
149
170
  tasks.push(task);
150
171
  }
@@ -183,17 +204,17 @@ class RepositoryManager {
183
204
  this._cache[ref] = true;
184
205
  let tasks = undefined;
185
206
  if (!installedAsset) {
186
- tasks = await this._install([ref]);
207
+ tasks = this._install([ref]);
187
208
  }
188
209
  else {
189
210
  //Ensure dependencies are installed
190
211
  const refs = assetVersion.dependencies.map((dep) => dep.name);
191
212
  if (refs.length > 0) {
192
- tasks = await this._install(refs);
213
+ tasks = this._install(refs);
193
214
  }
194
215
  }
195
216
  if (tasks && wait) {
196
- await Promise.all(tasks.map((t) => t.future.promise));
217
+ await Promise.all(tasks.map((t) => t.wait()));
197
218
  }
198
219
  return tasks;
199
220
  }
@@ -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.1",
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
 
@@ -14,6 +14,21 @@ import { Task, taskManager } from './taskManager';
14
14
  import { normalizeKapetaUri } from './utils/utils';
15
15
  import { assetManager } from './assetManager';
16
16
 
17
+ const DEFAULT_PROVIDERS = [
18
+ 'kapeta/block-type-service',
19
+ 'kapeta/block-type-frontend',
20
+ 'kapeta/block-type-gateway-http',
21
+ 'kapeta/resource-type-rest-api',
22
+ 'kapeta/resource-type-rest-client',
23
+ 'kapeta/resource-type-web-page',
24
+ 'kapeta/resource-type-web-fragment',
25
+ 'kapeta/resource-type-mongodb',
26
+ 'kapeta/resource-type-postgresql',
27
+ 'kapeta/language-target-react-ts',
28
+ 'kapeta/language-target-nodejs',
29
+ 'kapeta/language-target-java-spring-boot',
30
+ ];
31
+
17
32
  const INSTALL_ATTEMPTED: { [p: string]: boolean } = {};
18
33
 
19
34
  class RepositoryManager {
@@ -114,7 +129,11 @@ class RepositoryManager {
114
129
  this.watcher = undefined;
115
130
  }
116
131
 
117
- private async _install(refs: string[]): Promise<Task[]> {
132
+ public ensureDefaultProviders(): void {
133
+ this._install(DEFAULT_PROVIDERS);
134
+ }
135
+
136
+ private _install(refs: string[]): Task[] {
118
137
  //We make sure to only install one asset at a time - otherwise unexpected things might happen
119
138
  const createInstaller = (ref: string) => {
120
139
  return async () => {
@@ -125,7 +144,7 @@ class RepositoryManager {
125
144
  if (definitionsManager.exists(ref)) {
126
145
  return;
127
146
  }
128
- console.log(`Installing asset: ${ref}`);
147
+ //console.log(`Installing asset: ${ref}`);
129
148
  INSTALL_ATTEMPTED[ref] = true;
130
149
  //Auto-install missing asset
131
150
  try {
@@ -134,12 +153,15 @@ class RepositoryManager {
134
153
  //Disable change events while installing
135
154
  this.setChangeEventsEnabled(false);
136
155
  await Actions.install(progressListener, [ref], {});
156
+ } catch (e) {
157
+ console.error(`Failed to install asset: ${ref}`, e);
158
+ throw e;
137
159
  } finally {
138
160
  this.setChangeEventsEnabled(true);
139
161
  }
140
162
  definitionsManager.clearCache();
141
163
  assetManager.clearCache();
142
- console.log(`Asset installed: ${ref}`);
164
+ //console.log(`Asset installed: ${ref}`);
143
165
  };
144
166
  };
145
167
 
@@ -162,7 +184,7 @@ class RepositoryManager {
162
184
 
163
185
  const task = taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
164
186
  name: `Installing ${ref}`,
165
- group: 'asset:install:',
187
+ group: 'asset:install:', //Group prevents multiple tasks from running at the same time
166
188
  });
167
189
 
168
190
  tasks.push(task);
@@ -216,17 +238,17 @@ class RepositoryManager {
216
238
  this._cache[ref] = true;
217
239
  let tasks: Task[] | undefined = undefined;
218
240
  if (!installedAsset) {
219
- tasks = await this._install([ref]);
241
+ tasks = this._install([ref]);
220
242
  } else {
221
243
  //Ensure dependencies are installed
222
244
  const refs = assetVersion.dependencies.map((dep: Dependency) => dep.name);
223
245
  if (refs.length > 0) {
224
- tasks = await this._install(refs);
246
+ tasks = this._install(refs);
225
247
  }
226
248
  }
227
249
 
228
250
  if (tasks && wait) {
229
- await Promise.all(tasks.map((t) => t.future.promise));
251
+ await Promise.all(tasks.map((t) => t.wait()));
230
252
  }
231
253
 
232
254
  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
+ }