@kapeta/local-cluster-service 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/src/assetManager.d.ts +2 -1
  3. package/dist/cjs/src/assetManager.js +3 -2
  4. package/dist/cjs/src/containerManager.d.ts +6 -3
  5. package/dist/cjs/src/containerManager.js +96 -18
  6. package/dist/cjs/src/instanceManager.js +15 -8
  7. package/dist/cjs/src/operatorManager.d.ts +3 -2
  8. package/dist/cjs/src/progressListener.d.ts +8 -1
  9. package/dist/cjs/src/progressListener.js +12 -1
  10. package/dist/cjs/src/repositoryManager.js +3 -2
  11. package/dist/cjs/src/taskManager.d.ts +2 -0
  12. package/dist/cjs/src/taskManager.js +9 -0
  13. package/dist/cjs/src/types.d.ts +0 -19
  14. package/dist/cjs/src/utils/BlockInstanceRunner.js +15 -3
  15. package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
  16. package/dist/cjs/src/utils/commandLineUtils.js +7 -1
  17. package/dist/cjs/src/utils/utils.d.ts +3 -3
  18. package/dist/cjs/src/utils/utils.js +2 -1
  19. package/dist/esm/src/assetManager.d.ts +2 -1
  20. package/dist/esm/src/assetManager.js +3 -2
  21. package/dist/esm/src/containerManager.d.ts +6 -3
  22. package/dist/esm/src/containerManager.js +96 -18
  23. package/dist/esm/src/instanceManager.js +15 -8
  24. package/dist/esm/src/operatorManager.d.ts +3 -2
  25. package/dist/esm/src/progressListener.d.ts +8 -1
  26. package/dist/esm/src/progressListener.js +12 -1
  27. package/dist/esm/src/repositoryManager.js +3 -2
  28. package/dist/esm/src/taskManager.d.ts +2 -0
  29. package/dist/esm/src/taskManager.js +9 -0
  30. package/dist/esm/src/types.d.ts +0 -19
  31. package/dist/esm/src/utils/BlockInstanceRunner.js +15 -3
  32. package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
  33. package/dist/esm/src/utils/commandLineUtils.js +7 -1
  34. package/dist/esm/src/utils/utils.d.ts +3 -3
  35. package/dist/esm/src/utils/utils.js +2 -1
  36. package/package.json +14 -13
  37. package/src/assetManager.ts +5 -4
  38. package/src/containerManager.ts +110 -23
  39. package/src/instanceManager.ts +16 -10
  40. package/src/operatorManager.ts +2 -3
  41. package/src/progressListener.ts +15 -1
  42. package/src/repositoryManager.ts +5 -3
  43. package/src/taskManager.ts +11 -0
  44. package/src/types.ts +0 -19
  45. package/src/utils/BlockInstanceRunner.ts +21 -15
  46. package/src/utils/commandLineUtils.ts +10 -2
  47. package/src/utils/utils.ts +4 -4
@@ -14,12 +14,12 @@ import ClusterConfiguration from '@kapeta/local-cluster-config';
14
14
  import uuid from 'node-uuid';
15
15
  import md5 from 'md5';
16
16
  import { getBlockInstanceContainerName } from './utils/utils';
17
- import { Health, InstanceInfo, LogEntry, LogSource } from './types';
17
+ import { InstanceInfo, LogEntry, LogSource } from './types';
18
18
  import { KapetaAPI } from '@kapeta/nodejs-api-client';
19
19
  import { taskManager, Task } from './taskManager';
20
20
  import { EventEmitter } from 'node:events';
21
21
  import StreamValues from 'stream-json/streamers/StreamValues';
22
- import { Stream } from 'stream';
22
+ import { LocalInstanceHealth } from '@kapeta/schemas';
23
23
 
24
24
  type StringMap = { [key: string]: string };
25
25
 
@@ -100,7 +100,7 @@ enum DockerPullEventTypes {
100
100
 
101
101
  type DockerPullEventType = DockerPullEventTypes | string;
102
102
 
103
- const processJsonStream = <T>(purpose: string, stream: Stream, handler: (d: JSONMessage<T>) => void) =>
103
+ const processJsonStream = <T>(purpose: string, stream: NodeJS.ReadableStream, handler: (d: JSONMessage<T>) => void) =>
104
104
  new Promise<void>((resolve, reject) => {
105
105
  const jsonStream = StreamValues.withParser();
106
106
  jsonStream.on('data', (data: any) => {
@@ -296,15 +296,8 @@ class ContainerManager {
296
296
  }
297
297
 
298
298
  async getContainerByName(containerName: string): Promise<ContainerInfo | undefined> {
299
- const containers = await this.docker().listContainers({ all: true });
300
- const out = containers.find((container) => {
301
- return container.Names.indexOf(`/${containerName}`) > -1;
302
- });
303
-
304
- if (out) {
305
- return this.get(out.Id);
306
- }
307
- return undefined;
299
+ // The container can be fetched by name or by id using the same API call
300
+ return this.get(containerName);
308
301
  }
309
302
 
310
303
  async pull(image: string) {
@@ -398,6 +391,11 @@ class ContainerManager {
398
391
 
399
392
  const chunk = chunks[data.id];
400
393
 
394
+ if (data.stream) {
395
+ // Emit raw output to the task log
396
+ task.addLog(data.stream);
397
+ }
398
+
401
399
  switch (data.status) {
402
400
  case DockerPullEventTypes.PreparingPhase:
403
401
  case DockerPullEventTypes.WaitingPhase:
@@ -529,7 +527,7 @@ class ContainerManager {
529
527
  return Mounts;
530
528
  }
531
529
 
532
- toDockerHealth(health: Health) {
530
+ toDockerHealth(health: LocalInstanceHealth) {
533
531
  return {
534
532
  Test: ['CMD-SHELL', health.cmd],
535
533
  Interval: health.interval ? health.interval * NANO_SECOND : 5000 * NANO_SECOND,
@@ -679,7 +677,7 @@ class ContainerManager {
679
677
  let dockerContainer = null;
680
678
 
681
679
  try {
682
- dockerContainer = await this.docker().getContainer(name);
680
+ dockerContainer = this.docker().getContainer(name);
683
681
  await dockerContainer.stats();
684
682
  } catch (err) {
685
683
  //Ignore
@@ -707,7 +705,7 @@ class ContainerManager {
707
705
  ];
708
706
  }
709
707
 
710
- return containerInfo.getLogs();
708
+ return await containerInfo.getLogs();
711
709
  }
712
710
 
713
711
  async stopLogListening(systemId: string, instanceId: string) {
@@ -782,6 +780,34 @@ class ContainerManager {
782
780
  // Ignore
783
781
  }
784
782
  }
783
+
784
+ buildDockerImage(dockerFile: string, imageName: string) {
785
+ const taskName = `Building docker image: ${imageName}`;
786
+ const processor = async (task: Task) => {
787
+ const timeStarted = Date.now();
788
+ const stream = await this.docker().buildImage(
789
+ {
790
+ context: Path.dirname(dockerFile),
791
+ src: [Path.basename(dockerFile)],
792
+ },
793
+ {
794
+ t: imageName,
795
+ dockerfile: Path.basename(dockerFile),
796
+ }
797
+ );
798
+
799
+ await processJsonStream<string>(`image:build:${imageName}`, stream, (data) => {
800
+ if (data.stream) {
801
+ // Emit raw output to the task log
802
+ task.addLog(data.stream);
803
+ }
804
+ });
805
+ };
806
+
807
+ return taskManager.add(`docker:image:build:${imageName}`, processor, {
808
+ name: taskName,
809
+ });
810
+ }
785
811
  }
786
812
 
787
813
  function readLogBuffer(logBuffer: Buffer) {
@@ -1038,17 +1064,78 @@ export class ContainerInfo {
1038
1064
  });
1039
1065
 
1040
1066
  const out = readLogBuffer(logs);
1067
+ if (out.length > 0) {
1068
+ return out;
1069
+ }
1041
1070
 
1042
- if (out.length === 0) {
1043
- out.push({
1044
- time: Date.now(),
1045
- message: 'No logs found for container',
1046
- level: 'INFO',
1047
- source: 'stdout',
1048
- });
1071
+ const status = await this.status();
1072
+ const healthLogs: LogEntry[] = status?.Health?.Log
1073
+ ? status?.Health?.Log.map((log) => {
1074
+ return {
1075
+ source: 'stdout',
1076
+ level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
1077
+ time: Date.now(),
1078
+ message: 'Health check: ' + log.Output,
1079
+ };
1080
+ })
1081
+ : [];
1082
+
1083
+ if (status?.Running) {
1084
+ return [
1085
+ {
1086
+ source: 'stdout',
1087
+ level: 'INFO',
1088
+ time: Date.now(),
1089
+ message: 'Container is starting...',
1090
+ },
1091
+ ...healthLogs,
1092
+ ];
1049
1093
  }
1050
1094
 
1051
- return out;
1095
+ if (status?.Restarting) {
1096
+ return [
1097
+ {
1098
+ source: 'stdout',
1099
+ level: 'INFO',
1100
+ time: Date.now(),
1101
+ message: 'Container is restarting...',
1102
+ },
1103
+ ...healthLogs,
1104
+ ];
1105
+ }
1106
+ if (status?.Paused) {
1107
+ return [
1108
+ {
1109
+ source: 'stdout',
1110
+ level: 'INFO',
1111
+ time: Date.now(),
1112
+ message: 'Container is paused...',
1113
+ },
1114
+ ...healthLogs,
1115
+ ];
1116
+ }
1117
+
1118
+ if (status?.Error) {
1119
+ return [
1120
+ {
1121
+ source: 'stderr',
1122
+ level: 'ERROR',
1123
+ time: Date.now(),
1124
+ message: 'Container failed to start:\n' + status.Error,
1125
+ },
1126
+ ...healthLogs,
1127
+ ];
1128
+ }
1129
+
1130
+ return [
1131
+ {
1132
+ source: 'stdout',
1133
+ level: 'INFO',
1134
+ time: Date.now(),
1135
+ message: 'Container not running',
1136
+ ...healthLogs,
1137
+ },
1138
+ ];
1052
1139
  }
1053
1140
  }
1054
1141
 
@@ -28,12 +28,11 @@ import {
28
28
  KIND_BLOCK_TYPE_EXECUTABLE,
29
29
  KIND_BLOCK_TYPE_OPERATOR,
30
30
  KIND_RESOURCE_OPERATOR,
31
- LocalImageOptions,
32
31
  LogEntry,
33
32
  OperatorInstanceInfo,
34
33
  OperatorInstancePort,
35
34
  } from './types';
36
- import { BlockDefinitionSpec, BlockInstance, Plan } from '@kapeta/schemas';
35
+ import { BlockDefinitionSpec, LocalInstance, Plan } from '@kapeta/schemas';
37
36
  import { getBlockInstanceContainerName, getResolvedConfiguration } from './utils/utils';
38
37
  import { operatorManager } from './operatorManager';
39
38
  import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
@@ -293,7 +292,9 @@ export class InstanceManager {
293
292
  systemId = normalizeKapetaUri(systemId);
294
293
  const instance = _.find(this._instances, { systemId, instanceId });
295
294
  if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
296
- instance.status = InstanceStatus.STOPPED;
295
+ if (instance.status != InstanceStatus.FAILED) {
296
+ instance.status = InstanceStatus.STOPPED;
297
+ }
297
298
  instance.pid = null;
298
299
  instance.health = null;
299
300
  socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
@@ -385,7 +386,7 @@ export class InstanceManager {
385
386
  throw new Error(`Operator block has no local definition: ${blockRef}`);
386
387
  }
387
388
 
388
- const localConfig = operatorDefinition.definition.spec.local as LocalImageOptions;
389
+ const localConfig = operatorDefinition.definition.spec.local as LocalInstance;
389
390
 
390
391
  let instance = await this.start(systemId, instanceId);
391
392
  if (instance instanceof Task) {
@@ -666,8 +667,7 @@ export class InstanceManager {
666
667
 
667
668
  const out = await this.saveInternalInstance({
668
669
  ...instance,
669
- type: InstanceType.UNKNOWN,
670
- pid: null,
670
+ type: InstanceType.DOCKER,
671
671
  health: null,
672
672
  portType: DEFAULT_HEALTH_PORT_TYPE,
673
673
  status: InstanceStatus.FAILED,
@@ -811,7 +811,6 @@ export class InstanceManager {
811
811
  if (instance.status !== newStatus) {
812
812
  const oldStatus = instance.status;
813
813
  const skipUpdate =
814
- (newStatus === InstanceStatus.STOPPED && instance.status === InstanceStatus.FAILED) ||
815
814
  ([InstanceStatus.READY, InstanceStatus.UNHEALTHY].includes(newStatus) &&
816
815
  instance.status === InstanceStatus.STOPPING) ||
817
816
  (newStatus === InstanceStatus.STOPPED &&
@@ -835,7 +834,7 @@ export class InstanceManager {
835
834
 
836
835
  if (
837
836
  instance.desiredStatus === DesiredInstanceStatus.RUN &&
838
- [InstanceStatus.STOPPED, InstanceStatus.FAILED, InstanceStatus.STOPPING].includes(newStatus)
837
+ [InstanceStatus.STOPPED, InstanceStatus.STOPPING].includes(newStatus)
839
838
  ) {
840
839
  //If the instance is stopped but we want it to run, start it
841
840
  try {
@@ -928,11 +927,18 @@ export class InstanceManager {
928
927
  }
929
928
 
930
929
  if (statusType === 'created') {
930
+ if (state.ExitCode !== 0) {
931
+ // Failed during creation
932
+ return InstanceStatus.FAILED;
933
+ }
931
934
  return InstanceStatus.STARTING;
932
935
  }
933
936
 
934
937
  if (statusType === 'exited' || statusType === 'dead') {
935
- return InstanceStatus.STOPPED;
938
+ if (state.ExitCode === 0) {
939
+ return InstanceStatus.STOPPED;
940
+ }
941
+ return InstanceStatus.FAILED;
936
942
  }
937
943
 
938
944
  if (statusType === 'removing') {
@@ -1013,7 +1019,7 @@ export class InstanceManager {
1013
1019
  }
1014
1020
 
1015
1021
  if (parseKapetaUri(provider.kind).fullName === KIND_BLOCK_TYPE_OPERATOR) {
1016
- const localConfig = provider.data.spec.local as LocalImageOptions;
1022
+ const localConfig = provider.data.spec.local as LocalInstance;
1017
1023
  return localConfig.singleton ?? false;
1018
1024
  }
1019
1025
 
@@ -21,11 +21,10 @@ import {
21
21
  EnvironmentType,
22
22
  KIND_BLOCK_TYPE_OPERATOR,
23
23
  KIND_RESOURCE_OPERATOR,
24
- LocalImageOptions,
25
24
  OperatorInfo,
26
25
  StringMap,
27
26
  } from './types';
28
- import { BlockInstance, Resource } from '@kapeta/schemas';
27
+ import { BlockInstance, LocalInstance, Resource } from '@kapeta/schemas';
29
28
  import { definitionsManager } from './definitionsManager';
30
29
  import { getBindHost, toPortInfo } from './utils/utils';
31
30
  import { parseKapetaUri, normalizeKapetaUri } from '@kapeta/nodejs-utils';
@@ -42,7 +41,7 @@ class Operator {
42
41
  this._data = data;
43
42
  }
44
43
 
45
- getLocalData(): LocalImageOptions {
44
+ getLocalData(): LocalInstance {
46
45
  return this._data.definition.spec.local;
47
46
  }
48
47
 
@@ -7,6 +7,7 @@ import { spawn } from '@kapeta/nodejs-process';
7
7
  import { socketManager } from './socketManager';
8
8
  import { LogEntry } from './types';
9
9
  import { format } from 'node:util';
10
+ import { Task } from './taskManager';
10
11
 
11
12
  export class ProgressListener {
12
13
  private readonly systemId: string | undefined;
@@ -17,7 +18,7 @@ export class ProgressListener {
17
18
  this.instanceId = instanceId;
18
19
  }
19
20
 
20
- private emitLog(payload: Omit<LogEntry, 'time' | 'source'>) {
21
+ protected emitLog(payload: Omit<LogEntry, 'time' | 'source'>) {
21
22
  const logEntry: LogEntry = {
22
23
  ...payload,
23
24
  source: 'stdout',
@@ -135,3 +136,16 @@ export class ProgressListener {
135
136
  });
136
137
  }
137
138
  }
139
+
140
+ export class TaskProgressListener extends ProgressListener {
141
+ private readonly task: Task;
142
+
143
+ constructor(task: Task) {
144
+ super();
145
+ this.task = task;
146
+ }
147
+
148
+ protected emitLog(payload: Omit<LogEntry, 'time' | 'source'>) {
149
+ this.task.addLog(payload.message, payload.level);
150
+ }
151
+ }
@@ -10,7 +10,7 @@ import { Actions, AssetVersion, Config, RegistryService } from '@kapeta/nodejs-r
10
10
  import { definitionsManager } from './definitionsManager';
11
11
  import { Task, taskManager } from './taskManager';
12
12
  import { normalizeKapetaUri, parseKapetaUri, parseVersion } from '@kapeta/nodejs-utils';
13
- import { ProgressListener } from './progressListener';
13
+ import { TaskProgressListener } from './progressListener';
14
14
  import { RepositoryWatcher } from './RepositoryWatcher';
15
15
  import { SourceOfChange } from './types';
16
16
  import { cacheManager } from './cacheManager';
@@ -191,16 +191,18 @@ class RepositoryManager extends EventEmitter {
191
191
  private async scheduleInstallation(refs: string[]): Promise<Task[]> {
192
192
  //We make sure to only install one asset at a time - otherwise unexpected things might happen
193
193
  const createInstaller = (ref: string) => {
194
- return async () => {
194
+ return async (task: Task) => {
195
195
  if (await definitionsManager.exists(ref)) {
196
196
  return;
197
197
  }
198
+
199
+ const progressListener = new TaskProgressListener(task);
198
200
  //console.log(`Installing asset: ${ref}`);
199
201
  //Auto-install missing asset
200
202
  try {
201
203
  //We change to a temp dir to avoid issues with the current working directory
202
204
  process.chdir(os.tmpdir());
203
- await Actions.install(new ProgressListener(), [ref], {});
205
+ await Actions.install(progressListener, [ref], {});
204
206
  } catch (e) {
205
207
  console.error(`Failed to install asset: ${ref}`, e);
206
208
  throw e;
@@ -7,10 +7,12 @@
7
7
  * Class that handles processing background tasks.
8
8
  */
9
9
  import { socketManager } from './socketManager';
10
+ import { LogLevel } from './types';
10
11
 
11
12
  const EVENT_TASK_UPDATED = 'task-updated';
12
13
  const EVENT_TASK_ADDED = 'task-added';
13
14
  const EVENT_TASK_REMOVED = 'task-removed';
15
+ const EVENT_TASK_LOG = 'task-log';
14
16
 
15
17
  export type TaskRunner<T> = (task: Task<T>) => Promise<T>;
16
18
 
@@ -94,6 +96,15 @@ export class Task<T = void> implements TaskData<T> {
94
96
  socketManager.emitGlobal(EVENT_TASK_UPDATED, this.toData());
95
97
  }
96
98
 
99
+ public addLog(log: string, level: LogLevel = 'INFO') {
100
+ socketManager.emitGlobal(EVENT_TASK_LOG, {
101
+ id: this.id,
102
+ message: log,
103
+ level,
104
+ time: Date.now(),
105
+ });
106
+ }
107
+
97
108
  async wait(): Promise<T> {
98
109
  return this.future.promise;
99
110
  }
package/src/types.ts CHANGED
@@ -73,20 +73,6 @@ export interface Health {
73
73
  retries?: number;
74
74
  }
75
75
 
76
- export type PortInfo = { port: number; type: 'tcp' | 'udp' } | number | string;
77
-
78
- export type LocalImageOptions<Credentials = AnyMap, Options = AnyMap> = {
79
- image: string;
80
- ports: { [key: string]: PortInfo };
81
- credentials?: Credentials;
82
- options?: Options;
83
- cmd?: string;
84
- env?: AnyMap;
85
- health?: Health;
86
- singleton?: boolean;
87
- mounts?: { [key: string]: string };
88
- };
89
-
90
76
  export type InstanceInfo = {
91
77
  systemId: string;
92
78
  instanceId: string;
@@ -105,11 +91,6 @@ export type InstanceInfo = {
105
91
  portType?: string;
106
92
  };
107
93
 
108
- interface ResourceRef {
109
- blockId: string;
110
- resourceName: string;
111
- }
112
-
113
94
  export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response, info: ProxyRequestInfo) => void;
114
95
 
115
96
  export interface OperatorInstancePort {
@@ -18,19 +18,13 @@ import {
18
18
  } from '../containerManager';
19
19
  import { LogData } from './LogData';
20
20
  import { clusterService } from '../clusterService';
21
- import {
22
- AnyMap,
23
- BlockProcessParams,
24
- InstanceType,
25
- KIND_BLOCK_TYPE_OPERATOR,
26
- LocalImageOptions,
27
- ProcessInfo,
28
- StringMap,
29
- } from '../types';
21
+ import { AnyMap, BlockProcessParams, InstanceType, KIND_BLOCK_TYPE_OPERATOR, ProcessInfo, StringMap } from '../types';
30
22
  import { definitionsManager } from '../definitionsManager';
31
23
  import Docker from 'dockerode';
32
24
  import OS from 'node:os';
25
+ import Path from 'node:path';
33
26
  import { taskManager } from '../taskManager';
27
+ import { LocalDevContainer, LocalInstance } from '@kapeta/schemas';
34
28
 
35
29
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
36
30
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
@@ -189,6 +183,8 @@ export class BlockInstanceRunner {
189
183
  throw new Error('Missing target kind in block definition');
190
184
  }
191
185
 
186
+ const realLocalPath = await FSExtra.realpath(baseDir);
187
+
192
188
  const kindUri = parseKapetaUri(assetVersion.definition.kind);
193
189
 
194
190
  const providerVersion = await getProvider(kindUri);
@@ -205,17 +201,29 @@ export class BlockInstanceRunner {
205
201
  throw new Error(`Target not found: ${targetKindUri.id}`);
206
202
  }
207
203
 
208
- const localContainer = targetVersion.definition.spec.local;
204
+ const localContainer = targetVersion.definition.spec.local as LocalDevContainer;
209
205
 
210
206
  if (!localContainer) {
211
207
  throw new Error(`Missing local container information from target: ${targetKindUri.id}`);
212
208
  }
213
209
 
214
- const dockerImage = localContainer.image;
215
- if (!dockerImage) {
210
+ let dockerImage = localContainer.image;
211
+ const isDockerImage = !localContainer.type || localContainer.type.toLowerCase() === 'docker';
212
+ const isDockerFile = Boolean(localContainer.type && localContainer.type.toLowerCase() === 'dockerfile');
213
+ if (isDockerImage && !dockerImage) {
216
214
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
217
215
  }
218
216
 
217
+ if (isDockerFile) {
218
+ dockerImage = blockInfo.fullName + ':local';
219
+ const dockerFile = Path.join(realLocalPath, localContainer.file ?? 'Dockerfile');
220
+ if (!FSExtra.existsSync(dockerFile)) {
221
+ throw new Error(`Dockerfile not found at: ${dockerFile}`);
222
+ }
223
+ const task = containerManager.buildDockerImage(dockerFile, blockInfo.fullName + ':local');
224
+ await task.wait();
225
+ }
226
+
219
227
  const containerName = await getBlockInstanceContainerName(this._systemId, blockInstance.id, targetKindUri.id);
220
228
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
221
229
  const dockerOpts = localContainer.options ?? {};
@@ -242,8 +250,6 @@ export class BlockInstanceRunner {
242
250
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
243
251
  }
244
252
 
245
- const realLocalPath = await FSExtra.realpath(baseDir);
246
-
247
253
  const Mounts = containerManager.toDockerMounts({
248
254
  [workingDir]: toLocalBindVolume(realLocalPath),
249
255
  });
@@ -391,7 +397,7 @@ export class BlockInstanceRunner {
391
397
  throw new Error(`Provider did not have local image: ${providerRef}`);
392
398
  }
393
399
 
394
- const local = spec.local as LocalImageOptions;
400
+ const local = spec.local as LocalInstance;
395
401
 
396
402
  const dockerImage = local.image;
397
403
  const operatorUri = local.singleton ? parseKapetaUri(providerRef) : blockUri;
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { spawn, hasApp } from '@kapeta/nodejs-process';
7
- import { taskManager } from '../taskManager';
7
+ import { Task, taskManager } from '../taskManager';
8
8
 
9
9
  export async function hasCLI() {
10
10
  return hasApp('kap');
@@ -17,11 +17,19 @@ export async function ensureCLI() {
17
17
 
18
18
  return taskManager.add(
19
19
  `cli:install`,
20
- () => {
20
+ (task: Task) => {
21
21
  const process = spawn('npm', ['install', '-g', '@kapeta/kap'], {
22
22
  shell: true,
23
23
  });
24
24
 
25
+ process.process.stdout?.on('data', (data: any) => {
26
+ task.addLog(data.toString(), 'INFO');
27
+ });
28
+
29
+ process.process.stderr?.on('data', (data: any) => {
30
+ task.addLog(data.toString(), 'ERROR');
31
+ });
32
+
25
33
  return process.wait();
26
34
  },
27
35
  {
@@ -6,9 +6,9 @@
6
6
  import FS from 'node:fs';
7
7
  import YAML from 'yaml';
8
8
  import md5 from 'md5';
9
- import { EntityList } from '@kapeta/schemas';
9
+ import { EntityList, LocalInstancePort, LocalInstancePortType } from '@kapeta/schemas';
10
10
  import _ from 'lodash';
11
- import { AnyMap, KIND_BLOCK_TYPE_OPERATOR, PortInfo } from '../types';
11
+ import { AnyMap, KIND_BLOCK_TYPE_OPERATOR } from '../types';
12
12
  import ClusterConfiguration from '@kapeta/local-cluster-config';
13
13
  import { definitionsManager } from '../definitionsManager';
14
14
  import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
@@ -40,13 +40,13 @@ export async function getBlockInstanceContainerName(systemId: string, instanceId
40
40
  return `kapeta-block-instance-${md5(normalizeKapetaUri(systemId) + instanceId)}`;
41
41
  }
42
42
 
43
- export function toPortInfo(port: PortInfo) {
43
+ export function toPortInfo(port: LocalInstancePort) {
44
44
  if (typeof port === 'number' || typeof port === 'string') {
45
45
  return { port: parseInt(`${port}`), type: 'tcp' };
46
46
  }
47
47
 
48
48
  if (!port.type) {
49
- port.type = 'tcp';
49
+ port.type = LocalInstancePortType.TCP;
50
50
  }
51
51
 
52
52
  return port;