@kapeta/local-cluster-service 0.0.67 → 0.0.68

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.0.67",
3
+ "version": "0.0.68",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -24,6 +24,7 @@
24
24
  "@kapeta/local-cluster-config": "<2",
25
25
  "@kapeta/local-cluster-executor": "<2",
26
26
  "@kapeta/nodejs-api-client": "<2",
27
+ "@kapeta/nodejs-registry-utils": "^0.0.2",
27
28
  "@kapeta/nodejs-utils": "<2",
28
29
  "@kapeta/sdk-config": "<2",
29
30
  "express": "4.17.1",
@@ -36,6 +37,7 @@
36
37
  "node-docker-api": "1.1.22",
37
38
  "node-uuid": "^1.4.8",
38
39
  "request": "2.88.2",
40
+ "request-promise": "4.2.6",
39
41
  "socket.io": "^4.5.2",
40
42
  "yaml": "^1.6.0"
41
43
  },
@@ -3,13 +3,11 @@ const FS = require('node:fs');
3
3
  const FSExtra = require('fs-extra');
4
4
  const YAML = require('yaml');
5
5
  const ClusterConfiguration = require('@kapeta/local-cluster-config');
6
+ const {Actions} = require('@kapeta/nodejs-registry-utils');
6
7
  const codeGeneratorManager = require('./codeGeneratorManager');
7
- const socketManager = require('./socketManager');
8
-
9
- function makeSymLink(directory, versionTarget) {
10
- FSExtra.mkdirpSync(Path.dirname(versionTarget));
11
- FSExtra.createSymlinkSync(directory, versionTarget);
12
- }
8
+ const progressListener = require('./progressListener');
9
+ const {parseKapetaUri} = require("@kapeta/nodejs-utils");
10
+ const repositoryManager = require("./repositoryManager");
13
11
 
14
12
  function enrichAsset(asset) {
15
13
  return {
@@ -47,77 +45,6 @@ function parseRef(ref) {
47
45
 
48
46
  class AssetManager {
49
47
 
50
- constructor() {
51
- this.watcher = null;
52
- this.listenForChanges();
53
- }
54
-
55
- listenForChanges() {
56
- const baseDir = ClusterConfiguration.getRepositoryBasedir();
57
- if (!FS.existsSync(baseDir)) {
58
- FSExtra.mkdirpSync(baseDir);
59
- }
60
-
61
- let currentWebDefinitions = ClusterConfiguration
62
- .getProviderDefinitions()
63
- .filter(d => d.hasWeb);
64
-
65
- console.log('Watching local repository for provider changes: %s', baseDir);
66
- try {
67
- this.watcher = FS.watch(baseDir, { recursive: true });
68
- } catch (e) {
69
- // Fallback to run without watch mode due to potential platform issues.
70
- // https://nodejs.org/docs/latest/api/fs.html#caveats
71
- console.log('Unable to watch for changes. Changes to assets will not update automatically.');
72
- return;
73
- }
74
- this.watcher.on('change', (eventType, filename) => {
75
- const [handle, name, version] = filename.split(/\//g);
76
- if (!name || !version) {
77
- return;
78
- }
79
-
80
- const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
81
- const newWebDefinitions = ClusterConfiguration
82
- .getProviderDefinitions()
83
- .filter(d => d.hasWeb);
84
-
85
- const newWebDefinition = newWebDefinitions.find(d => d.ymlPath === ymlPath);
86
- let currentWebDefinition = currentWebDefinitions.find(d => d.ymlPath === ymlPath);
87
- const ymlExists = FS.existsSync(ymlPath);
88
- let type;
89
- if (ymlExists) {
90
- if (currentWebDefinition) {
91
- type = 'updated';
92
- } else if (newWebDefinition) {
93
- type = 'added';
94
- currentWebDefinition = newWebDefinition;
95
- } else {
96
- //Other definition was added / updated - ignore
97
- return;
98
- }
99
- } else {
100
- if (currentWebDefinition) {
101
- //Something was removed
102
- type = 'removed';
103
- } else {
104
- //Other definition was removed - ignore
105
- return;
106
- }
107
- }
108
-
109
- const payload = {type, definition: currentWebDefinition?.definition, asset: {handle, name, version} };
110
-
111
- currentWebDefinitions = newWebDefinitions
112
-
113
- socketManager.emit(`assets`, 'changed', payload);
114
- });
115
- }
116
-
117
- stopListening() {
118
- this.watcher.close();
119
- this.watcher = null;
120
- }
121
48
 
122
49
 
123
50
  /**
@@ -143,8 +70,8 @@ class AssetManager {
143
70
  return this.getAssets(['core/plan']);
144
71
  }
145
72
 
146
- getPlan(ref) {
147
- const asset = this.getAsset(ref);
73
+ async getPlan(ref) {
74
+ const asset = await this.getAsset(ref);
148
75
 
149
76
  if ('core/plan' !== asset.kind) {
150
77
  throw new Error('Asset was not a plan: ' + ref);
@@ -153,8 +80,15 @@ class AssetManager {
153
80
  return asset.data;
154
81
  }
155
82
 
156
- getAsset(ref) {
157
- const asset = this.getAssets().find(a => compareRefs(a.ref,ref));
83
+ async getAsset(ref) {
84
+ const uri = parseKapetaUri(ref);
85
+
86
+ await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version);
87
+
88
+ let asset = ClusterConfiguration.getDefinitions()
89
+ .map(enrichAsset)
90
+ .find(a => parseKapetaUri(a.ref).equals(uri));
91
+
158
92
  if (!asset) {
159
93
  throw new Error('Asset not found: ' + ref);
160
94
  }
@@ -184,7 +118,7 @@ class AssetManager {
184
118
  }
185
119
 
186
120
  async updateAsset(ref, yaml) {
187
- const asset = this.getAsset(ref);
121
+ const asset = await this.getAsset(ref);
188
122
  if (!asset) {
189
123
  throw new Error('Attempted to update unknown asset: ' + ref);
190
124
  }
@@ -206,10 +140,6 @@ class AssetManager {
206
140
  }
207
141
  }
208
142
 
209
- hasAsset(ref) {
210
- return !!this.getAsset(ref);
211
- }
212
-
213
143
  async importFile(filePath) {
214
144
  if (filePath.startsWith('file://')) {
215
145
  filePath = filePath.substring('file://'.length);
@@ -222,27 +152,21 @@ class AssetManager {
222
152
  const assetInfos = YAML.parseAllDocuments(FS.readFileSync(filePath).toString())
223
153
  .map(doc => doc.toJSON());
224
154
 
225
- const assetInfo = assetInfos[0];
226
- const version = 'local';
227
- const [handle, name] = assetInfo.metadata.name.split('/');
228
-
229
- const target = ClusterConfiguration.getRepositoryAssetPath(handle, name, version);
230
- if (!FS.existsSync(target)) {
231
- makeSymLink(Path.dirname(filePath), target);
232
- }
155
+ await Actions.link(progressListener, Path.dirname(filePath));
233
156
 
157
+ const version = 'local';
234
158
  const refs = assetInfos.map(assetInfo => `kapeta://${assetInfo.metadata.name}:${version}`);
235
159
 
236
160
  return this.getAssets().filter(a => refs.some(ref => compareRefs(ref, a.ref)));
237
161
  }
238
162
 
239
- unregisterAsset(ref) {
240
- const asset = this.getAsset(ref);
163
+ async unregisterAsset(ref) {
164
+ const asset = await this.getAsset(ref);
241
165
  if (!asset) {
242
166
  throw new Error('Asset does not exists: ' + ref);
243
167
  }
244
- //Remove from repository. If its local it is just a symlink - so no unchecked code is removed.
245
- FSExtra.removeSync(asset.path);
168
+
169
+ await Actions.uninstall(progressListener, asset.path);
246
170
  }
247
171
  }
248
172
 
@@ -34,14 +34,14 @@ router.get('/', (req, res) => {
34
34
  /**
35
35
  * Get single asset
36
36
  */
37
- router.get('/read', (req, res) => {
37
+ router.get('/read', async (req, res) => {
38
38
  if (!req.query.ref) {
39
39
  res.status(400).send({error:'Query parameter "ref" is missing'});
40
40
  return;
41
41
  }
42
42
 
43
43
  try {
44
- res.send(assetManager.getAsset(req.query.ref));
44
+ res.send(await assetManager.getAsset(req.query.ref));
45
45
  } catch(err) {
46
46
  res.status(400).send({error: err.message});
47
47
  }
@@ -228,7 +228,7 @@ class InstanceManager {
228
228
  async createProcessesForPlan(planRef) {
229
229
  await this.stopAllForPlan(planRef);
230
230
 
231
- const plan = assetManager.getPlan(planRef);
231
+ const plan = await assetManager.getPlan(planRef);
232
232
  if (!plan) {
233
233
  throw new Error('Plan not found: ' + planRef);
234
234
  }
@@ -301,7 +301,7 @@ class InstanceManager {
301
301
  * @return {Promise<PromiseInfo>}
302
302
  */
303
303
  async createProcess(planRef, instanceId) {
304
- const plan = assetManager.getPlan(planRef);
304
+ const plan = await assetManager.getPlan(planRef);
305
305
  if (!plan) {
306
306
  throw new Error('Plan not found: ' + planRef);
307
307
  }
@@ -313,7 +313,7 @@ class InstanceManager {
313
313
 
314
314
  const blockRef = blockInstance.block.ref;
315
315
 
316
- const blockAsset = assetManager.getAsset(blockRef);
316
+ const blockAsset = await assetManager.getAsset(blockRef);
317
317
 
318
318
  if (!blockAsset) {
319
319
  throw new Error('Block not found: ' + blockRef);
@@ -0,0 +1,82 @@
1
+ const {spawn} = require("child_process");
2
+ const socketManager = require("./socketManager");
3
+ /**
4
+ *
5
+ * @type {ProgressListener}
6
+ */
7
+ module.exports = {
8
+ run: (command, directory) => {
9
+ socketManager.emit(
10
+ `install`,
11
+ 'install:log',
12
+ {
13
+ type: 'info',
14
+ message: `Running command "${command}"`
15
+ }
16
+ );
17
+
18
+ return new Promise((resolve, reject) => {
19
+ const child = spawn(command, {
20
+ cwd: directory ? directory : process.cwd(),
21
+ detached: true,
22
+ shell: true
23
+ });
24
+
25
+ child.stdout.on('data', (data) => {
26
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: data.toString()});
27
+ });
28
+
29
+ child.stderr.on('data', (data) => {
30
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: data.toString()});
31
+ });
32
+
33
+ child.on('exit', (exit, signal) => {
34
+ if (exit !== 0) {
35
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `"${command}" failed: "${exit}"`});
36
+ reject(new Error(`Command "${command}" exited with code ${exit}`));
37
+ } else {
38
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `Command OK: "${command}"`});
39
+ resolve({exit, signal});
40
+ }
41
+ });
42
+
43
+ child.on('error', (err) => {
44
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `"${command}" failed: "${err.message}"`});
45
+ reject(err);
46
+ });
47
+ });
48
+ },
49
+ progress: async (label, callback) => {
50
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: started`});
51
+ try {
52
+ const result = await callback();
53
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: done`});
54
+ return result;
55
+ } catch (e) {
56
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: failed. ${e.message}`});
57
+ throw e;
58
+ }
59
+ },
60
+ check: async (message, ok) => {
61
+ const wasOk = await ok;
62
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `${message}: ${wasOk}`});
63
+ },
64
+ start: (label) => {
65
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: label});
66
+ },
67
+ showValue: (label, value) => {
68
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: ${value}`});
69
+ },
70
+ error: (msg, ...args) => {
71
+ socketManager.emit(`install`, 'install:log', {type: 'error', message: msg});
72
+ },
73
+ warn: (msg, ...args) => {
74
+ socketManager.emit(`install`, 'install:log', {type: 'warn', message: msg});
75
+ },
76
+ info: (msg, ...args) => {
77
+ socketManager.emit(`install`, 'install:log', {type: 'info', message: msg});
78
+ },
79
+ debug: (msg, ...args) => {
80
+ socketManager.emit(`install`, 'install:log', {type: 'debug', message: msg});
81
+ },
82
+ }
@@ -1,14 +1,13 @@
1
- const _ = require('lodash');
2
1
  const FS = require('fs');
3
2
  const Path = require('path');
4
- const Glob = require("glob");
5
-
3
+ const FSExtra = require('fs-extra');
4
+ const repositoryManager = require('./repositoryManager')
6
5
  const ClusterConfiguration = require('@kapeta/local-cluster-config');
7
6
 
8
7
  class ProviderManager {
9
8
 
10
9
  constructor() {
11
- this._assetCache = {};
10
+ this._webAssetCache = {};
12
11
  }
13
12
 
14
13
  getWebProviders() {
@@ -17,54 +16,32 @@ class ProviderManager {
17
16
  .filter((providerDefinition) => providerDefinition.hasWeb)
18
17
  }
19
18
 
20
- getWebAssets() {
21
- const webProviders = this.getWebProviders();
19
+ async getAsset(handle, name, version, sourceMap = false) {
20
+ const fullName = `${handle}/${name}`;
21
+ const id = `${handle}/${name}/${version}/web.js${sourceMap ? '.map' : ''}`;
22
+
23
+ if (this._webAssetCache[id] &&
24
+ await FSExtra.exists(this._webAssetCache[id])) {
25
+ return FSExtra.read(this._webAssetCache[id]);
26
+ }
22
27
 
23
- let providerFiles = [];
24
- webProviders.map((webProvider) => {
25
- return Glob.sync('web/**/*.js', {cwd: webProvider.path}).map((file) => {
26
- return {webProvider, file};
27
- });
28
- }).forEach((webFiles) => {
29
- providerFiles.push(...webFiles);
30
- });
28
+ await repositoryManager.ensureAsset(handle, name, version);
31
29
 
32
- return providerFiles;
33
- }
34
-
35
- loadAssets() {
36
- this.getWebAssets().forEach((asset) => {
37
- const providerId = asset.webProvider.definition.metadata.name;
38
- const file = asset.file;
39
- const assetId = `${providerId}/${asset.webProvider.version}/${file}`;
40
- this._assetCache[assetId] = Path.join(asset.webProvider.path, file);
30
+ const installedProvider = this.getWebProviders().find((providerDefinition) => {
31
+ return providerDefinition.definition.metadata.name === fullName &&
32
+ providerDefinition.version === version;
41
33
  })
42
- }
43
34
 
35
+ if (installedProvider) {
36
+ //Check locally installed providers
37
+ const path = Path.join(installedProvider.path, 'web', handle, `${name}.js${sourceMap ? '.map' : ''}`);
38
+ if (await FSExtra.exists(path)) {
39
+ this._webAssetCache[id] = path;
44
40
 
45
- /**
46
- * Returns all public (web) javascript for available providers.
47
- *
48
- * Provides frontend / applications with the implementation of the frontends for the
49
- * providers.
50
- *
51
- */
52
- getPublicJS() {
53
- this.loadAssets();
54
- const includes = Object.keys(this._assetCache).map((assetId) => {
55
- return `${ClusterConfiguration.getClusterServiceAddress()}/providers/asset/${assetId}`
56
- });
57
-
58
- return `Kapeta.setPluginPaths(${JSON.stringify(includes)});`
59
- }
60
-
61
- getAsset(id) {
62
- if (_.isEmpty(this._assetCache)) {
63
- this.loadAssets();
64
- }
65
- if (this._assetCache[id]) {
66
- return FS.readFileSync(this._assetCache[id]).toString();
41
+ return FSExtra.read(path);
42
+ }
67
43
  }
44
+
68
45
  return null;
69
46
  }
70
47
  }
@@ -5,16 +5,38 @@ const router = new Router();
5
5
 
6
6
  router.use('/', require('../middleware/cors'));
7
7
 
8
- /**
9
- * Get all local assets available
10
- */
11
- router.get('/all.js', (req, res) => {
12
- res.send(providerManager.getPublicJS());
8
+ router.get('/', async (req, res) => {
9
+ const result = await providerManager.getWebProviders();
10
+
11
+ res.send(result);
12
+ });
13
+
14
+ router.get('/asset/:handle/:name/:version/web.js', async (req, res) => {
15
+
16
+ const {handle, name, version} = req.params;
17
+ let result = await providerManager.getAsset(handle, name, version);
18
+
19
+ if (version !== 'local') {
20
+ res.setHeader('Cache-Control', 'max-age=31536000, immutable');
21
+ }
22
+
23
+ if (!result) {
24
+ res.status(404).send('');
25
+ } else {
26
+ res.send(result
27
+ .replace(`${name}.js.map`, 'web.js.map')
28
+ );
29
+ }
13
30
  });
14
31
 
15
- router.get('/asset/*', (req, res) => {
16
- const assetId = req.params[0];
17
- const result = providerManager.getAsset(assetId)
32
+ router.get('/asset/:handle/:name/:version/web.js.map', async (req, res) => {
33
+
34
+ const {handle, name, version} = req.params;
35
+ const result = await providerManager.getAsset(handle, name, version, true);
36
+ if (version !== 'local') {
37
+ res.setHeader('Cache-Control', 'max-age=31536000, immutable');
38
+ }
39
+
18
40
  if (!result) {
19
41
  res.status(404).send('');
20
42
  } else {
@@ -0,0 +1,201 @@
1
+ const ClusterConfiguration = require("@kapeta/local-cluster-config");
2
+ const FS = require("node:fs");
3
+ const FSExtra = require("fs-extra");
4
+ const Path = require("node:path");
5
+ const socketManager = require("./socketManager");
6
+ const {Actions, RegistryService, Config} = require("@kapeta/nodejs-registry-utils");
7
+ const progressListener = require("./progressListener");
8
+ const os = require("os");
9
+ const {parseKapetaUri} = require("@kapeta/nodejs-utils");
10
+ const INSTALL_ATTEMPTED = {};
11
+
12
+ class RepositoryManager {
13
+
14
+ constructor() {
15
+ this.watcher = null;
16
+ this.changeEventsEnabled = true;
17
+ this.listenForChanges();
18
+ this._registryService = new RegistryService(
19
+ Config.data.registry.url
20
+ );
21
+ this._cache = {};
22
+ this._installQueue = [];
23
+ }
24
+
25
+ setChangeEventsEnabled(enabled) {
26
+ this.changeEventsEnabled = enabled;
27
+ }
28
+
29
+ listenForChanges() {
30
+ const baseDir = ClusterConfiguration.getRepositoryBasedir();
31
+ if (!FS.existsSync(baseDir)) {
32
+ FSExtra.mkdirpSync(baseDir);
33
+ }
34
+
35
+ let allDefinitions = ClusterConfiguration
36
+ .getDefinitions();
37
+
38
+ console.log('Watching local repository for provider changes: %s', baseDir);
39
+ try {
40
+ this.watcher = FS.watch(baseDir, { recursive: true });
41
+ } catch (e) {
42
+ // Fallback to run without watch mode due to potential platform issues.
43
+ // https://nodejs.org/docs/latest/api/fs.html#caveats
44
+ console.log('Unable to watch for changes. Changes to assets will not update automatically.');
45
+ return;
46
+ }
47
+ this.watcher.on('change', (eventType, filename) => {
48
+ const [handle, name, version] = filename.split(/\//g);
49
+ if (!name || !version) {
50
+ return;
51
+ }
52
+
53
+ if (!this.changeEventsEnabled) {
54
+ return;
55
+ }
56
+
57
+ const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
58
+ const newDefinitions = ClusterConfiguration.getDefinitions();
59
+
60
+ const newDefinition = newDefinitions.find(d => d.ymlPath === ymlPath);
61
+ let currentDefinition = allDefinitions.find(d => d.ymlPath === ymlPath);
62
+ const ymlExists = FS.existsSync(ymlPath);
63
+ let type;
64
+ if (ymlExists) {
65
+ if (currentDefinition) {
66
+ type = 'updated';
67
+ } else if (newDefinition) {
68
+ type = 'added';
69
+ currentDefinition = newDefinition;
70
+ } else {
71
+ //Other definition was added / updated - ignore
72
+ return;
73
+ }
74
+ } else {
75
+ if (currentDefinition) {
76
+ const ref = parseKapetaUri(
77
+ `${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
78
+ ).id;
79
+ delete INSTALL_ATTEMPTED[ref];
80
+ //Something was removed
81
+ type = 'removed';
82
+ } else {
83
+ //Other definition was removed - ignore
84
+ return;
85
+ }
86
+ }
87
+
88
+ const payload = {type, definition: currentDefinition?.definition, asset: {handle, name, version} };
89
+
90
+ allDefinitions = newDefinitions
91
+ socketManager.emit(`assets`, 'changed', payload);
92
+ });
93
+ }
94
+
95
+ stopListening() {
96
+ this.watcher.close();
97
+ this.watcher = null;
98
+ }
99
+
100
+ /**
101
+ *
102
+ * @param {string[]} refs
103
+ * @return {Promise<void>}
104
+ * @private
105
+ */
106
+ async _install(refs) {
107
+ //We make sure to only install one asset at a time - otherwise unexpected things might happen
108
+ const out = new Promise((resolve, reject) => {
109
+ this._installQueue.push(async () => {
110
+ try {
111
+ const normalizedRefs = refs.map(ref => parseKapetaUri(ref).id)
112
+ const filteredRefs = normalizedRefs.filter(ref => !INSTALL_ATTEMPTED[ref]);
113
+ if (filteredRefs.length > 0) {
114
+ filteredRefs.forEach(ref => INSTALL_ATTEMPTED[ref] = true);
115
+ //Auto-install missing asset
116
+ try {
117
+ //We change to a temp dir to avoid issues with the current working directory
118
+ process.chdir(os.tmpdir());
119
+ //Disable change events while installing
120
+ this.setChangeEventsEnabled(false);
121
+ socketManager.emit(`install`, 'install:action', {type: 'start', refs});
122
+ await Actions.install(progressListener, normalizedRefs, {});
123
+ socketManager.emit(`install`, 'install:action', {type: 'done', refs});
124
+ } catch (e) {
125
+ socketManager.emit(`install`, 'install:action', {type: 'failed', refs, error: e.message});
126
+ } finally {
127
+ this.setChangeEventsEnabled(true);
128
+ }
129
+ }
130
+ resolve();
131
+ } catch (e) {
132
+ reject(e);
133
+ } finally {
134
+ this._processNext().catch(e => console.error(e));
135
+ }
136
+ })
137
+ });
138
+
139
+ this._processNext().catch(e => console.error(e));
140
+
141
+ return out;
142
+ }
143
+
144
+ async _processNext() {
145
+ if (this._processing) {
146
+ return;
147
+ }
148
+ this._processing = true;
149
+ try {
150
+ while (this._installQueue.length > 0) {
151
+ const item = this._installQueue.shift();
152
+
153
+ await item();
154
+ }
155
+ } finally {
156
+ this._processing = false;
157
+ }
158
+ }
159
+
160
+ async ensureAsset(handle, name, version) {
161
+ const fullName = `${handle}/${name}`;
162
+ const ref = `${fullName}:${version}`;
163
+
164
+ if (version === 'local') {
165
+ //TODO: Get dependencies for local asset
166
+ return null;
167
+ }
168
+
169
+ const installedAsset = ClusterConfiguration.getDefinitions().find(d =>
170
+ d.definition.metadata.name === fullName &&
171
+ d.version === version);
172
+
173
+
174
+ if (installedAsset && this._cache[ref] === true) {
175
+ return;
176
+ }
177
+
178
+ if (!installedAsset && this._cache[ref] === false) {
179
+ return;
180
+ }
181
+
182
+ const assetVersion = await this._registryService.getVersion(fullName, version);
183
+ if (!assetVersion) {
184
+ this._cache[ref] = false;
185
+ return;
186
+ }
187
+
188
+ this._cache[ref] = true;
189
+ if (!installedAsset) {
190
+ await this._install([ref]);
191
+ } else {
192
+ //Ensure dependencies are installed
193
+ const refs = assetVersion.dependencies.map((dep) => dep.name);
194
+ await this._install(refs);
195
+ }
196
+
197
+
198
+ }
199
+ }
200
+
201
+ module.exports = new RepositoryManager();