@kapeta/local-cluster-service 0.15.2 → 0.16.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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/src/RepositoryWatcher.d.ts +22 -0
  3. package/dist/cjs/src/RepositoryWatcher.js +273 -0
  4. package/dist/cjs/src/assetManager.js +33 -20
  5. package/dist/cjs/src/containerManager.js +0 -1
  6. package/dist/cjs/src/identities/routes.js +2 -1
  7. package/dist/cjs/src/instanceManager.d.ts +0 -2
  8. package/dist/cjs/src/instanceManager.js +16 -35
  9. package/dist/cjs/src/progressListener.d.ts +5 -6
  10. package/dist/cjs/src/progressListener.js +54 -36
  11. package/dist/cjs/src/repositoryManager.d.ts +4 -4
  12. package/dist/cjs/src/repositoryManager.js +20 -93
  13. package/dist/cjs/src/socketManager.d.ts +18 -6
  14. package/dist/cjs/src/socketManager.js +35 -1
  15. package/dist/esm/src/RepositoryWatcher.d.ts +22 -0
  16. package/dist/esm/src/RepositoryWatcher.js +266 -0
  17. package/dist/esm/src/assetManager.js +35 -22
  18. package/dist/esm/src/containerManager.js +0 -1
  19. package/dist/esm/src/identities/routes.js +2 -1
  20. package/dist/esm/src/instanceManager.d.ts +0 -2
  21. package/dist/esm/src/instanceManager.js +16 -35
  22. package/dist/esm/src/progressListener.d.ts +5 -6
  23. package/dist/esm/src/progressListener.js +53 -36
  24. package/dist/esm/src/repositoryManager.d.ts +4 -4
  25. package/dist/esm/src/repositoryManager.js +20 -93
  26. package/dist/esm/src/socketManager.d.ts +18 -6
  27. package/dist/esm/src/socketManager.js +34 -0
  28. package/package.json +2 -2
  29. package/src/RepositoryWatcher.ts +304 -0
  30. package/src/assetManager.ts +39 -25
  31. package/src/containerManager.ts +0 -5
  32. package/src/identities/routes.ts +2 -1
  33. package/src/instanceManager.ts +22 -35
  34. package/src/progressListener.ts +59 -39
  35. package/src/repositoryManager.ts +26 -100
  36. package/src/socketManager.ts +44 -5
@@ -1,19 +1,38 @@
1
1
  import { spawn } from '@kapeta/nodejs-process';
2
- import { SocketManager, socketManager } from './socketManager';
3
- class ProgressListener {
4
- private socketManager: SocketManager;
2
+ import { socketManager } from './socketManager';
3
+ import { LogEntry } from './types';
4
+ import { format } from 'node:util';
5
5
 
6
- constructor(socketManager: SocketManager) {
7
- this.socketManager = socketManager;
6
+ export class ProgressListener {
7
+ private readonly systemId: string | undefined;
8
+ private readonly instanceId: string | undefined;
9
+
10
+ constructor(systemId?: string, instanceId?: string) {
11
+ this.systemId = systemId;
12
+ this.instanceId = instanceId;
8
13
  }
9
14
 
10
- run(command: string, directory?: string): Promise<{ exit: number; signal: NodeJS.Signals | null; output: string }> {
11
- this.socketManager.emit(`install`, 'install:log', {
12
- type: 'info',
13
- message: `Running command "${command}"`,
14
- });
15
+ private emitLog(payload: Omit<LogEntry, 'time' | 'source'>) {
16
+ const logEntry: LogEntry = {
17
+ ...payload,
18
+ source: 'stdout',
19
+ time: Date.now(),
20
+ };
21
+ if (this.systemId && this.instanceId) {
22
+ socketManager.emitInstanceLog(this.systemId, this.instanceId, logEntry);
23
+ return;
24
+ }
25
+
26
+ if (this.systemId) {
27
+ socketManager.emitSystemLog(this.systemId, logEntry);
28
+ return;
29
+ }
30
+
31
+ socketManager.emitGlobalLog(logEntry);
32
+ }
15
33
 
16
- const firstCommand = command.split(' ')[0];
34
+ run(command: string, directory?: string): Promise<{ exit: number; signal: NodeJS.Signals | null; output: string }> {
35
+ this.info(`Running command "${command}"`);
17
36
 
18
37
  return new Promise(async (resolve, reject) => {
19
38
  try {
@@ -25,7 +44,10 @@ class ProgressListener {
25
44
  });
26
45
 
27
46
  child.onData((data) => {
28
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: data.line });
47
+ this.emitLog({
48
+ level: data.type === 'stdout' ? 'INFO' : 'WARN',
49
+ message: data.line,
50
+ });
29
51
  });
30
52
 
31
53
  if (child.process.stdout) {
@@ -36,25 +58,16 @@ class ProgressListener {
36
58
 
37
59
  child.process.on('exit', (exit, signal) => {
38
60
  if (exit !== 0) {
39
- this.socketManager.emit(`install`, 'install:log', {
40
- type: 'info',
41
- message: `"${command}" failed: "${exit}"`,
42
- });
61
+ this.warn(`Command "${command}" failed: ${exit}`);
43
62
  reject(new Error(`Command "${command}" exited with code ${exit}`));
44
63
  } else {
45
- this.socketManager.emit(`install`, 'install:log', {
46
- type: 'info',
47
- message: `Command OK: "${command}"`,
48
- });
64
+ this.info(`Command OK: "${command}"`);
49
65
  resolve({ exit, signal, output: Buffer.concat(chunks).toString() });
50
66
  }
51
67
  });
52
68
 
53
69
  child.process.on('error', (err) => {
54
- this.socketManager.emit(`install`, 'install:log', {
55
- type: 'info',
56
- message: `"${command}" failed: "${err.message}"`,
57
- });
70
+ this.warn(`"${command}" failed: "${err.message}"`);
58
71
  reject(err);
59
72
  });
60
73
 
@@ -66,48 +79,55 @@ class ProgressListener {
66
79
  }
67
80
 
68
81
  async progress(label: string, callback: () => void | Promise<void>) {
69
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: `${label}: started` });
82
+ this.info(`${label}: started`);
70
83
  try {
71
84
  const result = await callback();
72
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: `${label}: done` });
85
+ this.info(`${label}: done`);
73
86
  return result;
74
87
  } catch (e: any) {
75
- this.socketManager.emit(`install`, 'install:log', {
76
- type: 'info',
77
- message: `${label}: failed. ${e.message}`,
78
- });
88
+ this.warn(`${label}: failed. ${e.message}`);
79
89
  throw e;
80
90
  }
81
91
  }
82
92
 
83
93
  async check(message: string, ok: boolean | Promise<boolean> | (() => Promise<boolean>)) {
84
94
  const wasOk = await ok;
85
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: `${message}: ${wasOk}` });
95
+ this.info(`${message}: ${wasOk}`);
86
96
  }
87
97
 
88
98
  start(label: string) {
89
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: label });
99
+ this.info(label);
90
100
  }
91
101
 
92
102
  showValue(label: string, value: any) {
93
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: `${label}: ${value}` });
103
+ this.info(`${label}: ${value}`);
94
104
  }
95
105
 
96
106
  error(msg: string, ...args: any[]) {
97
- this.socketManager.emit(`install`, 'install:log', { type: 'error', message: msg });
107
+ this.emitLog({
108
+ message: format(msg, args),
109
+ level: 'ERROR',
110
+ });
98
111
  }
99
112
 
100
113
  warn(msg: string, ...args: any[]) {
101
- this.socketManager.emit(`install`, 'install:log', { type: 'warn', message: msg });
114
+ this.emitLog({
115
+ message: format(msg, args),
116
+ level: 'WARN',
117
+ });
102
118
  }
103
119
 
104
120
  info(msg: string, ...args: any[]) {
105
- this.socketManager.emit(`install`, 'install:log', { type: 'info', message: msg });
121
+ this.emitLog({
122
+ message: format(msg, args),
123
+ level: 'INFO',
124
+ });
106
125
  }
107
126
 
108
127
  debug(msg: string, ...args: any[]) {
109
- this.socketManager.emit(`install`, 'install:log', { type: 'debug', message: msg });
128
+ this.emitLog({
129
+ message: format(msg, args),
130
+ level: 'DEBUG',
131
+ });
110
132
  }
111
133
  }
112
-
113
- export const progressListener = new ProgressListener(socketManager);
@@ -1,21 +1,22 @@
1
- import FS from 'node:fs';
2
1
  import os from 'node:os';
3
- import Path from 'node:path';
4
- import watch from 'recursive-watch';
5
- import FSExtra from 'fs-extra';
6
- import ClusterConfiguration from '@kapeta/local-cluster-config';
7
- import { parseKapetaUri } from '@kapeta/nodejs-utils';
8
2
  import { socketManager } from './socketManager';
9
- import { progressListener } from './progressListener';
10
3
  import { Dependency } from '@kapeta/schemas';
11
4
  import { Actions, Config, RegistryService } from '@kapeta/nodejs-registry-utils';
12
5
  import { definitionsManager } from './definitionsManager';
13
6
  import { Task, taskManager } from './taskManager';
14
7
  import { normalizeKapetaUri } from './utils/utils';
8
+
9
+ import { ProgressListener } from './progressListener';
10
+ import { RepositoryWatcher } from './RepositoryWatcher';
15
11
  import { assetManager } from './assetManager';
16
12
 
17
- const EVENT_DEFAULT_PROVIDERS_START= 'default-providers-start';
18
- const EVENT_DEFAULT_PROVIDERS_END= 'default-providers-end';
13
+ function clearAllCaches() {
14
+ definitionsManager.clearCache();
15
+ assetManager.clearCache();
16
+ }
17
+
18
+ const EVENT_DEFAULT_PROVIDERS_START = 'default-providers-start';
19
+ const EVENT_DEFAULT_PROVIDERS_END = 'default-providers-end';
19
20
 
20
21
  const DEFAULT_PROVIDERS = [
21
22
  'kapeta/block-type-service',
@@ -35,107 +36,37 @@ const DEFAULT_PROVIDERS = [
35
36
  const INSTALL_ATTEMPTED: { [p: string]: boolean } = {};
36
37
 
37
38
  class RepositoryManager {
38
- private changeEventsEnabled: boolean;
39
39
  private _registryService: RegistryService;
40
40
  private _cache: { [key: string]: boolean };
41
- private watcher?: () => void;
41
+ private watcher: RepositoryWatcher;
42
42
 
43
43
  constructor() {
44
- this.changeEventsEnabled = true;
45
- this.listenForChanges();
46
44
  this._registryService = new RegistryService(Config.data.registry.url);
47
45
  this._cache = {};
48
- }
49
-
50
- setChangeEventsEnabled(enabled: boolean) {
51
- this.changeEventsEnabled = enabled;
46
+ this.watcher = new RepositoryWatcher();
47
+ this.listenForChanges();
52
48
  }
53
49
 
54
50
  listenForChanges() {
55
- const baseDir = ClusterConfiguration.getRepositoryBasedir();
56
- if (!FS.existsSync(baseDir)) {
57
- FSExtra.mkdirpSync(baseDir);
58
- }
59
-
60
- let allDefinitions = ClusterConfiguration.getDefinitions();
61
-
62
- console.log('Watching local repository for provider changes: %s', baseDir);
63
- try {
64
- this.watcher = watch(baseDir, (filename: string) => {
65
- if (!filename) {
66
- return;
67
- }
68
-
69
- const [handle, name, version] = filename.toString().split(/\//g);
70
- if (!name || !version) {
71
- return;
72
- }
73
-
74
- if (!this.changeEventsEnabled) {
75
- return;
76
- }
77
-
78
- const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
79
- const newDefinitions = ClusterConfiguration.getDefinitions();
80
-
81
- const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
82
- let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
83
- const ymlExists = FS.existsSync(ymlPath);
84
- let type;
85
- if (ymlExists) {
86
- if (currentDefinition) {
87
- type = 'updated';
88
- } else if (newDefinition) {
89
- type = 'added';
90
- currentDefinition = newDefinition;
91
- } else {
92
- //Other definition was added / updated - ignore
93
- return;
94
- }
95
- } else {
96
- if (currentDefinition) {
97
- const ref = parseKapetaUri(
98
- `${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
99
- ).id;
100
- delete INSTALL_ATTEMPTED[ref];
101
- //Something was removed
102
- type = 'removed';
103
- } else {
104
- //Other definition was removed - ignore
105
- return;
106
- }
107
- }
51
+ this.watcher.watch();
52
+ }
108
53
 
109
- const payload = {
110
- type,
111
- definition: currentDefinition?.definition,
112
- asset: { handle, name, version },
113
- };
54
+ async stopListening() {
55
+ return this.watcher.unwatch();
56
+ }
114
57
 
115
- allDefinitions = newDefinitions;
116
- socketManager.emit(`assets`, 'changed', payload);
117
- definitionsManager.clearCache();
118
- });
119
- } catch (e) {
120
- // Fallback to run without watch mode due to potential platform issues.
121
- // https://nodejs.org/docs/latest/api/fs.html#caveats
122
- console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
123
- return;
124
- }
58
+ ignoreChangesFor(file: string) {
59
+ return this.watcher.ignoreChangesFor(file);
125
60
  }
126
61
 
127
- stopListening() {
128
- if (!this.watcher) {
129
- return;
130
- }
131
- this.watcher();
132
- this.watcher = undefined;
62
+ resumeChangedFor(file: string) {
63
+ return this.watcher.resumeChangedFor(file);
133
64
  }
134
65
 
135
66
  public ensureDefaultProviders(): void {
136
- socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, {providers:DEFAULT_PROVIDERS});
67
+ socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, { providers: DEFAULT_PROVIDERS });
137
68
  const tasks = this._install(DEFAULT_PROVIDERS);
138
- Promise.allSettled(tasks.map(t => t.wait())).then(() => {
69
+ Promise.allSettled(tasks.map((t) => t.wait())).then(() => {
139
70
  socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_END, {});
140
71
  });
141
72
  }
@@ -157,17 +88,12 @@ class RepositoryManager {
157
88
  try {
158
89
  //We change to a temp dir to avoid issues with the current working directory
159
90
  process.chdir(os.tmpdir());
160
- //Disable change events while installing
161
- this.setChangeEventsEnabled(false);
162
- await Actions.install(progressListener, [ref], {});
91
+ await Actions.install(new ProgressListener(), [ref], {});
163
92
  } catch (e) {
164
93
  console.error(`Failed to install asset: ${ref}`, e);
165
94
  throw e;
166
- } finally {
167
- this.setChangeEventsEnabled(true);
168
95
  }
169
- definitionsManager.clearCache();
170
- assetManager.clearCache();
96
+ clearAllCaches();
171
97
  //console.log(`Asset installed: ${ref}`);
172
98
  };
173
99
  };
@@ -1,9 +1,18 @@
1
1
  import _ from 'lodash';
2
2
  import { Socket, Server } from 'socket.io';
3
+ import { normalizeKapetaUri } from './utils/utils';
4
+ import { LogEntry } from './types';
5
+ export const EVENT_STATUS_CHANGED = 'status-changed';
6
+ export const EVENT_INSTANCE_CREATED = 'instance-created';
7
+ export const EVENT_INSTANCE_EXITED = 'instance-exited';
8
+ export const EVENT_INSTANCE_LOG = 'instance-log';
9
+
10
+ export const EVENT_SYSTEM_LOG = 'system-log';
11
+ export const EVENT_LOG = 'log';
3
12
 
4
13
  export class SocketManager {
5
14
  private _io: Server | null;
6
- private _sockets: Socket[];
15
+ private readonly _sockets: Socket[];
7
16
 
8
17
  constructor() {
9
18
  this._io = null;
@@ -35,16 +44,46 @@ export class SocketManager {
35
44
  this.io.emit(type, payload);
36
45
  }
37
46
 
38
- _bindIO() {
47
+ emitSystemEvent(systemId: string, type: string, payload: any) {
48
+ systemId = normalizeKapetaUri(systemId);
49
+ try {
50
+ socketManager.emit(`${systemId}/instances`, type, payload);
51
+ } catch (e: any) {
52
+ console.warn('Failed to emit instance event: %s', e.message);
53
+ }
54
+ }
55
+
56
+ emitInstanceLog(systemId: string, instanceId: string, payload: LogEntry) {
57
+ this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, payload);
58
+ }
59
+
60
+ emitSystemLog(systemId: string, payload: LogEntry) {
61
+ this.emitSystemEvent(systemId, EVENT_SYSTEM_LOG, payload);
62
+ }
63
+
64
+ emitGlobalLog(payload: LogEntry) {
65
+ this.emitGlobal(EVENT_LOG, payload);
66
+ }
67
+
68
+ emitInstanceEvent(systemId: string, instanceId: string, type: string, payload: any) {
69
+ systemId = normalizeKapetaUri(systemId);
70
+ try {
71
+ socketManager.emit(`${systemId}/instances/${instanceId}`, type, payload);
72
+ } catch (e: any) {
73
+ console.warn('Failed to emit instance event: %s', e.message);
74
+ }
75
+ }
76
+
77
+ private _bindIO() {
39
78
  this.io.on('connection', (socket) => this._handleSocketCreated(socket));
40
79
  }
41
80
 
42
- _handleSocketCreated(socket: Socket) {
81
+ private _handleSocketCreated(socket: Socket) {
43
82
  this._bindSocket(socket);
44
83
  this._sockets.push(socket);
45
84
  }
46
85
 
47
- _bindSocket(socket: Socket) {
86
+ private _bindSocket(socket: Socket) {
48
87
  socket.on('disconnect', () => this._handleSocketDestroyed(socket));
49
88
  socket.on('join', (id) => {
50
89
  socket.join(id);
@@ -54,7 +93,7 @@ export class SocketManager {
54
93
  });
55
94
  }
56
95
 
57
- _handleSocketDestroyed(socket: Socket) {
96
+ private _handleSocketDestroyed(socket: Socket) {
58
97
  _.pull(this._sockets, socket);
59
98
  }
60
99
  }