@kapeta/local-cluster-service 0.15.3 → 0.16.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 +14 -0
- package/dist/cjs/src/RepositoryWatcher.d.ts +23 -0
- package/dist/cjs/src/RepositoryWatcher.js +269 -0
- package/dist/cjs/src/assetManager.d.ts +4 -6
- package/dist/cjs/src/assetManager.js +34 -44
- package/dist/cjs/src/assets/routes.js +2 -2
- package/dist/cjs/src/cacheManager.d.ts +16 -0
- package/dist/cjs/src/cacheManager.js +38 -0
- package/dist/cjs/src/definitionsManager.d.ts +2 -4
- package/dist/cjs/src/definitionsManager.js +7 -21
- package/dist/cjs/src/instanceManager.js +4 -1
- package/dist/cjs/src/repositoryManager.d.ts +9 -4
- package/dist/cjs/src/repositoryManager.js +18 -91
- package/dist/cjs/src/types.d.ts +2 -0
- package/dist/cjs/src/utils/utils.js +1 -1
- package/dist/esm/src/RepositoryWatcher.d.ts +23 -0
- package/dist/esm/src/RepositoryWatcher.js +262 -0
- package/dist/esm/src/assetManager.d.ts +4 -6
- package/dist/esm/src/assetManager.js +35 -45
- package/dist/esm/src/assets/routes.js +2 -2
- package/dist/esm/src/cacheManager.d.ts +16 -0
- package/dist/esm/src/cacheManager.js +31 -0
- package/dist/esm/src/definitionsManager.d.ts +2 -4
- package/dist/esm/src/definitionsManager.js +7 -21
- package/dist/esm/src/instanceManager.js +4 -1
- package/dist/esm/src/repositoryManager.d.ts +9 -4
- package/dist/esm/src/repositoryManager.js +18 -91
- package/dist/esm/src/types.d.ts +2 -0
- package/dist/esm/src/utils/utils.js +1 -1
- package/package.json +2 -2
- package/src/RepositoryWatcher.ts +302 -0
- package/src/assetManager.ts +51 -55
- package/src/assets/routes.ts +2 -2
- package/src/cacheManager.ts +45 -0
- package/src/definitionsManager.ts +9 -33
- package/src/identities/routes.ts +0 -1
- package/src/instanceManager.ts +4 -1
- package/src/repositoryManager.ts +20 -95
- package/src/types.ts +2 -2
- package/src/utils/utils.ts +1 -1
@@ -0,0 +1,31 @@
|
|
1
|
+
import NodeCache from 'node-cache';
|
2
|
+
const DEFAULT_CACHE_TTL = 60 * 1000; // 1 min
|
3
|
+
class CacheManager {
|
4
|
+
cache = new NodeCache();
|
5
|
+
flush() {
|
6
|
+
this.cache.flushAll();
|
7
|
+
}
|
8
|
+
doCached(key, getter, ttl = DEFAULT_CACHE_TTL) {
|
9
|
+
const data = this.cache.get(key);
|
10
|
+
if (data !== undefined) {
|
11
|
+
return data;
|
12
|
+
}
|
13
|
+
const result = getter();
|
14
|
+
this.cache.set(key, result, ttl);
|
15
|
+
return result;
|
16
|
+
}
|
17
|
+
get(key) {
|
18
|
+
return this.cache.get(key);
|
19
|
+
}
|
20
|
+
set(key, data, ttl = DEFAULT_CACHE_TTL) {
|
21
|
+
this.cache.set(key, data, ttl);
|
22
|
+
}
|
23
|
+
has(key) {
|
24
|
+
return this.cache.has(key);
|
25
|
+
}
|
26
|
+
remove(key) {
|
27
|
+
return this.cache.del(key);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
export const cacheManager = new CacheManager();
|
31
|
+
export const doCached = (key, getter, ttl = DEFAULT_CACHE_TTL) => cacheManager.doCached(key, getter, ttl);
|
@@ -1,9 +1,7 @@
|
|
1
1
|
import { DefinitionInfo } from '@kapeta/local-cluster-config';
|
2
2
|
declare class DefinitionsManager {
|
3
|
-
private
|
4
|
-
private
|
5
|
-
clearCache(): void;
|
6
|
-
private doCached;
|
3
|
+
private getHash;
|
4
|
+
private getFullKey;
|
7
5
|
getDefinitions(kindFilter?: string | string[]): DefinitionInfo[];
|
8
6
|
exists(ref: string): boolean;
|
9
7
|
getProviderDefinitions(): DefinitionInfo[];
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
2
2
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
3
|
-
|
3
|
+
import { doCached } from './cacheManager';
|
4
4
|
class DefinitionsManager {
|
5
|
-
|
6
|
-
getKey(kindFilter) {
|
5
|
+
getHash(kindFilter) {
|
7
6
|
if (kindFilter) {
|
8
7
|
if (Array.isArray(kindFilter)) {
|
9
8
|
return kindFilter.join(',');
|
@@ -12,31 +11,18 @@ class DefinitionsManager {
|
|
12
11
|
}
|
13
12
|
return 'none';
|
14
13
|
}
|
15
|
-
|
16
|
-
this.
|
17
|
-
}
|
18
|
-
doCached(key, getter) {
|
19
|
-
if (this.cache[key]) {
|
20
|
-
if (this.cache[key].expires > Date.now()) {
|
21
|
-
return this.cache[key].definitions;
|
22
|
-
}
|
23
|
-
delete this.cache[key];
|
24
|
-
}
|
25
|
-
this.cache[key] = {
|
26
|
-
expires: Date.now() + CACHE_TTL,
|
27
|
-
definitions: getter(),
|
28
|
-
};
|
29
|
-
return this.cache[key].definitions;
|
14
|
+
getFullKey(kindFilter) {
|
15
|
+
return `DefinitionsManager:${this.getHash(kindFilter)}`;
|
30
16
|
}
|
31
17
|
getDefinitions(kindFilter) {
|
32
|
-
const key = this.
|
33
|
-
return
|
18
|
+
const key = this.getFullKey(kindFilter);
|
19
|
+
return doCached(key, () => ClusterConfiguration.getDefinitions(kindFilter));
|
34
20
|
}
|
35
21
|
exists(ref) {
|
36
22
|
return !!this.getDefinition(ref);
|
37
23
|
}
|
38
24
|
getProviderDefinitions() {
|
39
|
-
return
|
25
|
+
return doCached('providers', () => ClusterConfiguration.getProviderDefinitions());
|
40
26
|
}
|
41
27
|
getDefinition(ref) {
|
42
28
|
const uri = parseKapetaUri(ref);
|
@@ -51,7 +51,10 @@ export class InstanceManager {
|
|
51
51
|
return [];
|
52
52
|
}
|
53
53
|
const plan = planInfo.definition;
|
54
|
-
|
54
|
+
if (!plan?.spec?.blocks) {
|
55
|
+
return [];
|
56
|
+
}
|
57
|
+
const instanceIds = plan.spec?.blocks?.map((block) => block.id) || [];
|
55
58
|
return this._instances.filter((instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId));
|
56
59
|
}
|
57
60
|
getInstance(systemId, instanceId) {
|
@@ -1,13 +1,18 @@
|
|
1
1
|
import { Task } from './taskManager';
|
2
|
+
import { SourceOfChange } from './types';
|
2
3
|
declare class RepositoryManager {
|
3
|
-
private changeEventsEnabled;
|
4
4
|
private _registryService;
|
5
5
|
private _cache;
|
6
|
-
private watcher
|
6
|
+
private watcher;
|
7
7
|
constructor();
|
8
|
-
setChangeEventsEnabled(enabled: boolean): void;
|
9
8
|
listenForChanges(): void;
|
10
|
-
stopListening(): void
|
9
|
+
stopListening(): Promise<void>;
|
10
|
+
/**
|
11
|
+
* Setting the source of change helps us know
|
12
|
+
* how to react to changes in the UI.
|
13
|
+
*/
|
14
|
+
setSourceOfChangeFor(file: string, source: SourceOfChange): Promise<void>;
|
15
|
+
clearSourceOfChangeFor(file: string): Promise<void>;
|
11
16
|
ensureDefaultProviders(): void;
|
12
17
|
private _install;
|
13
18
|
ensureAsset(handle: string, name: string, version: string, wait?: boolean): Promise<undefined | Task[]>;
|
@@ -1,17 +1,12 @@
|
|
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
3
|
import { Actions, Config, RegistryService } from '@kapeta/nodejs-registry-utils';
|
10
4
|
import { definitionsManager } from './definitionsManager';
|
11
5
|
import { taskManager } from './taskManager';
|
12
6
|
import { normalizeKapetaUri } from './utils/utils';
|
13
|
-
import { assetManager } from './assetManager';
|
14
7
|
import { ProgressListener } from './progressListener';
|
8
|
+
import { RepositoryWatcher } from './RepositoryWatcher';
|
9
|
+
import { cacheManager } from './cacheManager';
|
15
10
|
const EVENT_DEFAULT_PROVIDERS_START = 'default-providers-start';
|
16
11
|
const EVENT_DEFAULT_PROVIDERS_END = 'default-providers-end';
|
17
12
|
const DEFAULT_PROVIDERS = [
|
@@ -30,92 +25,30 @@ const DEFAULT_PROVIDERS = [
|
|
30
25
|
];
|
31
26
|
const INSTALL_ATTEMPTED = {};
|
32
27
|
class RepositoryManager {
|
33
|
-
changeEventsEnabled;
|
34
28
|
_registryService;
|
35
29
|
_cache;
|
36
30
|
watcher;
|
37
31
|
constructor() {
|
38
|
-
this.changeEventsEnabled = true;
|
39
|
-
this.listenForChanges();
|
40
32
|
this._registryService = new RegistryService(Config.data.registry.url);
|
41
33
|
this._cache = {};
|
42
|
-
|
43
|
-
|
44
|
-
this.changeEventsEnabled = enabled;
|
34
|
+
this.watcher = new RepositoryWatcher();
|
35
|
+
this.listenForChanges();
|
45
36
|
}
|
46
37
|
listenForChanges() {
|
47
|
-
|
48
|
-
if (!FS.existsSync(baseDir)) {
|
49
|
-
FSExtra.mkdirpSync(baseDir);
|
50
|
-
}
|
51
|
-
let allDefinitions = ClusterConfiguration.getDefinitions();
|
52
|
-
console.log('Watching local repository for provider changes: %s', baseDir);
|
53
|
-
try {
|
54
|
-
this.watcher = watch(baseDir, (filename) => {
|
55
|
-
if (!filename) {
|
56
|
-
return;
|
57
|
-
}
|
58
|
-
const [handle, name, version] = filename.toString().split(/\//g);
|
59
|
-
if (!name || !version) {
|
60
|
-
return;
|
61
|
-
}
|
62
|
-
if (!this.changeEventsEnabled) {
|
63
|
-
return;
|
64
|
-
}
|
65
|
-
const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
|
66
|
-
const newDefinitions = ClusterConfiguration.getDefinitions();
|
67
|
-
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
68
|
-
let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
|
69
|
-
const ymlExists = FS.existsSync(ymlPath);
|
70
|
-
let type;
|
71
|
-
if (ymlExists) {
|
72
|
-
if (currentDefinition) {
|
73
|
-
type = 'updated';
|
74
|
-
}
|
75
|
-
else if (newDefinition) {
|
76
|
-
type = 'added';
|
77
|
-
currentDefinition = newDefinition;
|
78
|
-
}
|
79
|
-
else {
|
80
|
-
//Other definition was added / updated - ignore
|
81
|
-
return;
|
82
|
-
}
|
83
|
-
}
|
84
|
-
else {
|
85
|
-
if (currentDefinition) {
|
86
|
-
const ref = parseKapetaUri(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
|
87
|
-
delete INSTALL_ATTEMPTED[ref];
|
88
|
-
//Something was removed
|
89
|
-
type = 'removed';
|
90
|
-
}
|
91
|
-
else {
|
92
|
-
//Other definition was removed - ignore
|
93
|
-
return;
|
94
|
-
}
|
95
|
-
}
|
96
|
-
const payload = {
|
97
|
-
type,
|
98
|
-
definition: currentDefinition?.definition,
|
99
|
-
asset: { handle, name, version },
|
100
|
-
};
|
101
|
-
allDefinitions = newDefinitions;
|
102
|
-
socketManager.emit(`assets`, 'changed', payload);
|
103
|
-
definitionsManager.clearCache();
|
104
|
-
});
|
105
|
-
}
|
106
|
-
catch (e) {
|
107
|
-
// Fallback to run without watch mode due to potential platform issues.
|
108
|
-
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
109
|
-
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
110
|
-
return;
|
111
|
-
}
|
38
|
+
this.watcher.watch();
|
112
39
|
}
|
113
|
-
stopListening() {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
40
|
+
async stopListening() {
|
41
|
+
return this.watcher.unwatch();
|
42
|
+
}
|
43
|
+
/**
|
44
|
+
* Setting the source of change helps us know
|
45
|
+
* how to react to changes in the UI.
|
46
|
+
*/
|
47
|
+
setSourceOfChangeFor(file, source) {
|
48
|
+
return this.watcher.setSourceOfChangeFor(file, source);
|
49
|
+
}
|
50
|
+
clearSourceOfChangeFor(file) {
|
51
|
+
return this.watcher.clearSourceOfChangeFor(file);
|
119
52
|
}
|
120
53
|
ensureDefaultProviders() {
|
121
54
|
socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, { providers: DEFAULT_PROVIDERS });
|
@@ -140,19 +73,13 @@ class RepositoryManager {
|
|
140
73
|
try {
|
141
74
|
//We change to a temp dir to avoid issues with the current working directory
|
142
75
|
process.chdir(os.tmpdir());
|
143
|
-
//Disable change events while installing
|
144
|
-
this.setChangeEventsEnabled(false);
|
145
76
|
await Actions.install(new ProgressListener(), [ref], {});
|
146
77
|
}
|
147
78
|
catch (e) {
|
148
79
|
console.error(`Failed to install asset: ${ref}`, e);
|
149
80
|
throw e;
|
150
81
|
}
|
151
|
-
|
152
|
-
this.setChangeEventsEnabled(true);
|
153
|
-
}
|
154
|
-
definitionsManager.clearCache();
|
155
|
-
assetManager.clearCache();
|
82
|
+
cacheManager.flush();
|
156
83
|
//console.log(`Asset installed: ${ref}`);
|
157
84
|
};
|
158
85
|
};
|
package/dist/esm/src/types.d.ts
CHANGED
@@ -8,6 +8,8 @@ export type StringMap = {
|
|
8
8
|
export type AnyMap = {
|
9
9
|
[key: string]: any;
|
10
10
|
};
|
11
|
+
export type SourceOfChange = 'user' | 'filesystem';
|
12
|
+
export type WatchEventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
11
13
|
export type LogLevel = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE' | 'FATAL';
|
12
14
|
export type LogSource = 'stdout' | 'stderr';
|
13
15
|
export type EnvironmentType = 'docker' | 'process';
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.16.1",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -53,6 +53,7 @@
|
|
53
53
|
"@kapeta/sdk-config": "<2",
|
54
54
|
"@kapeta/web-microfrontend": "^0.2.1",
|
55
55
|
"async-lock": "^1.4.0",
|
56
|
+
"chokidar": "^3.5.3",
|
56
57
|
"express": "4.17.1",
|
57
58
|
"express-promise-router": "^4.1.1",
|
58
59
|
"fs-extra": "^11.1.0",
|
@@ -62,7 +63,6 @@
|
|
62
63
|
"node-cache": "^5.1.2",
|
63
64
|
"node-docker-api": "1.1.22",
|
64
65
|
"node-uuid": "^1.4.8",
|
65
|
-
"recursive-watch": "^1.1.4",
|
66
66
|
"request": "2.88.2",
|
67
67
|
"request-promise": "4.2.6",
|
68
68
|
"socket.io": "^4.5.2",
|
@@ -0,0 +1,302 @@
|
|
1
|
+
import chokidar, { FSWatcher } from 'chokidar';
|
2
|
+
import ClusterConfiguration, { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
|
3
|
+
import FS from 'fs-extra';
|
4
|
+
import Path from 'node:path';
|
5
|
+
import YAML from 'yaml';
|
6
|
+
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
7
|
+
import _ from 'lodash';
|
8
|
+
import { socketManager } from './socketManager';
|
9
|
+
import { definitionsManager } from './definitionsManager';
|
10
|
+
import { assetManager } from './assetManager';
|
11
|
+
import { SourceOfChange, WatchEventName } from './types';
|
12
|
+
import { cacheManager } from './cacheManager';
|
13
|
+
|
14
|
+
interface AssetIdentity {
|
15
|
+
handle: string;
|
16
|
+
name: string;
|
17
|
+
version: string;
|
18
|
+
}
|
19
|
+
const KAPETA_YML_RX = /^kapeta.ya?ml$/;
|
20
|
+
export class RepositoryWatcher {
|
21
|
+
private watcher?: FSWatcher;
|
22
|
+
private disabled: boolean = false;
|
23
|
+
private readonly baseDir: string;
|
24
|
+
private allDefinitions: DefinitionInfo[] = [];
|
25
|
+
private symbolicLinks: { [link: string]: string } = {};
|
26
|
+
private sourceOfChange: Map<string, SourceOfChange> = new Map();
|
27
|
+
constructor() {
|
28
|
+
this.baseDir = ClusterConfiguration.getRepositoryBasedir();
|
29
|
+
}
|
30
|
+
|
31
|
+
setDisabled(disabled: boolean) {
|
32
|
+
this.disabled = disabled;
|
33
|
+
}
|
34
|
+
public watch() {
|
35
|
+
if (!FS.existsSync(this.baseDir)) {
|
36
|
+
FS.mkdirpSync(this.baseDir);
|
37
|
+
}
|
38
|
+
|
39
|
+
this.allDefinitions = ClusterConfiguration.getDefinitions();
|
40
|
+
|
41
|
+
try {
|
42
|
+
this.watcher = chokidar.watch(this.baseDir, {
|
43
|
+
followSymlinks: false,
|
44
|
+
ignorePermissionErrors: true,
|
45
|
+
disableGlobbing: true,
|
46
|
+
persistent: true,
|
47
|
+
depth: 2,
|
48
|
+
ignored: (path) => this.ignoreFile(path),
|
49
|
+
});
|
50
|
+
this.watcher.on('all', this.handleFileChange.bind(this));
|
51
|
+
this.watcher.on('error', (error) => {
|
52
|
+
console.log('Error watching repository', error);
|
53
|
+
});
|
54
|
+
this.watcher.on('ready', () => {
|
55
|
+
console.log('Watching local repository for provider changes: %s', this.baseDir);
|
56
|
+
});
|
57
|
+
} catch (e) {
|
58
|
+
// Fallback to run without watch mode due to potential platform issues.
|
59
|
+
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
60
|
+
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
async setSourceOfChangeFor(file: string, source: SourceOfChange) {
|
66
|
+
this.sourceOfChange.set(file, source);
|
67
|
+
const realPath = await FS.realpath(file);
|
68
|
+
if (realPath !== file) {
|
69
|
+
this.sourceOfChange.set(realPath, source);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
async clearSourceOfChangeFor(file: string) {
|
74
|
+
this.sourceOfChange.delete(file);
|
75
|
+
const realPath = await FS.realpath(file);
|
76
|
+
if (realPath !== file) {
|
77
|
+
this.sourceOfChange.delete(realPath);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
public async unwatch() {
|
82
|
+
if (!this.watcher) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
this.symbolicLinks = {};
|
86
|
+
await this.watcher.close();
|
87
|
+
this.watcher = undefined;
|
88
|
+
}
|
89
|
+
|
90
|
+
private async getAssetIdentity(path: string): Promise<AssetIdentity | undefined> {
|
91
|
+
const baseName = Path.basename(path);
|
92
|
+
let handle, name, version;
|
93
|
+
if (path.startsWith(this.baseDir)) {
|
94
|
+
const relativePath = Path.relative(this.baseDir, path);
|
95
|
+
// Inside the repo we can use the path to determine the handle, name and version
|
96
|
+
[handle, name, version] = relativePath.split(/\//g);
|
97
|
+
if (!handle || !name || !version) {
|
98
|
+
// Do nothing with this
|
99
|
+
return;
|
100
|
+
}
|
101
|
+
|
102
|
+
return {
|
103
|
+
handle,
|
104
|
+
name,
|
105
|
+
version,
|
106
|
+
};
|
107
|
+
}
|
108
|
+
|
109
|
+
if (!KAPETA_YML_RX.test(baseName)) {
|
110
|
+
// Do nothing with this
|
111
|
+
return;
|
112
|
+
}
|
113
|
+
// Outside the repo we need to use the file content to determine the handle, name
|
114
|
+
// Version is always 'local'
|
115
|
+
version = 'local';
|
116
|
+
|
117
|
+
try {
|
118
|
+
const definition: Definition = YAML.parse((await FS.readFile(path)).toString());
|
119
|
+
const uri = parseKapetaUri(definition.metadata.name);
|
120
|
+
handle = uri.handle;
|
121
|
+
name = uri.name;
|
122
|
+
return {
|
123
|
+
handle,
|
124
|
+
name,
|
125
|
+
version,
|
126
|
+
};
|
127
|
+
} catch (e) {
|
128
|
+
// Ignore issues in the YML file
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
private async handleFileChange(eventName: WatchEventName, path: string) {
|
134
|
+
if (!path) {
|
135
|
+
return;
|
136
|
+
}
|
137
|
+
|
138
|
+
//console.log('File changed', eventName, path);
|
139
|
+
|
140
|
+
const assetIdentity = await this.getAssetIdentity(path);
|
141
|
+
if (!assetIdentity) {
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
145
|
+
if (this.disabled) {
|
146
|
+
return;
|
147
|
+
}
|
148
|
+
|
149
|
+
// If this is false it's because we're watching a symlink target
|
150
|
+
const withinRepo = path.startsWith(this.baseDir);
|
151
|
+
if (withinRepo && assetIdentity.version === 'local' && path.endsWith('/local')) {
|
152
|
+
// This is likely a symlink target
|
153
|
+
if (eventName === 'add') {
|
154
|
+
//console.log('Checking if we should add symlink target', handle, name, version, path);
|
155
|
+
await this.addSymlinkTarget(path);
|
156
|
+
}
|
157
|
+
|
158
|
+
if (eventName === 'unlink') {
|
159
|
+
await this.removeSymlinkTarget(path);
|
160
|
+
}
|
161
|
+
|
162
|
+
if (eventName === 'change') {
|
163
|
+
await this.updateSymlinkTarget(path);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
const sourceOfChange = this.sourceOfChange.get(path) ?? 'filesystem';
|
168
|
+
await this.checkForChange(assetIdentity, sourceOfChange);
|
169
|
+
|
170
|
+
// We consume the sourceOfChange when the file is changed
|
171
|
+
this.sourceOfChange.delete(path);
|
172
|
+
}
|
173
|
+
|
174
|
+
private async checkForChange(assetIdentity: AssetIdentity, sourceOfChange: SourceOfChange) {
|
175
|
+
const ymlPath = Path.join(
|
176
|
+
this.baseDir,
|
177
|
+
assetIdentity.handle,
|
178
|
+
assetIdentity.name,
|
179
|
+
assetIdentity.version,
|
180
|
+
'kapeta.yml'
|
181
|
+
);
|
182
|
+
const newDefinitions = ClusterConfiguration.getDefinitions();
|
183
|
+
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
184
|
+
let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
|
185
|
+
const ymlExists = await this.exists(ymlPath);
|
186
|
+
let type;
|
187
|
+
if (ymlExists) {
|
188
|
+
if (currentDefinition) {
|
189
|
+
if (newDefinition && _.isEqual(currentDefinition, newDefinition)) {
|
190
|
+
//Definition was not changed
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
type = 'updated';
|
194
|
+
} else if (newDefinition) {
|
195
|
+
type = 'added';
|
196
|
+
currentDefinition = newDefinition;
|
197
|
+
} else {
|
198
|
+
//Other definition was added / updated - ignore
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
} else {
|
202
|
+
if (currentDefinition) {
|
203
|
+
const ref = parseKapetaUri(
|
204
|
+
`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
|
205
|
+
).id;
|
206
|
+
//Something was removed
|
207
|
+
type = 'removed';
|
208
|
+
} else {
|
209
|
+
//Other definition was removed - ignore
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
const payload = {
|
215
|
+
type,
|
216
|
+
definition: newDefinition?.definition ?? currentDefinition?.definition,
|
217
|
+
asset: assetIdentity,
|
218
|
+
sourceOfChange,
|
219
|
+
};
|
220
|
+
|
221
|
+
this.allDefinitions = newDefinitions;
|
222
|
+
|
223
|
+
//console.log('Asset changed', payload);
|
224
|
+
socketManager.emitGlobal('asset-change', payload);
|
225
|
+
|
226
|
+
cacheManager.flush();
|
227
|
+
}
|
228
|
+
|
229
|
+
private async exists(path: string): Promise<boolean> {
|
230
|
+
try {
|
231
|
+
await FS.access(path);
|
232
|
+
return true;
|
233
|
+
} catch (e) {
|
234
|
+
return false;
|
235
|
+
}
|
236
|
+
}
|
237
|
+
private async removeSymlinkTarget(path: string) {
|
238
|
+
if (this.symbolicLinks[path]) {
|
239
|
+
//console.log('Unwatching symlink target %s => %s', path, this.symbolicLinks[path]);
|
240
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
241
|
+
delete this.symbolicLinks[path];
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
private async updateSymlinkTarget(path: string) {
|
246
|
+
if (this.symbolicLinks[path]) {
|
247
|
+
//console.log('Updating symlink target %s => %s', path, this.symbolicLinks[path]);
|
248
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
249
|
+
delete this.symbolicLinks[path];
|
250
|
+
await this.addSymlinkTarget(path);
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
private async addSymlinkTarget(path: string) {
|
255
|
+
try {
|
256
|
+
// Make sure we're not watching the symlink target
|
257
|
+
await this.removeSymlinkTarget(path);
|
258
|
+
const stat = await FS.lstat(path);
|
259
|
+
if (stat.isSymbolicLink()) {
|
260
|
+
const realPath = `${await FS.realpath(path)}/kapeta.yml`;
|
261
|
+
if (await this.exists(realPath)) {
|
262
|
+
//console.log('Watching symlink target %s => %s', path, realPath);
|
263
|
+
this.watcher?.add(realPath);
|
264
|
+
this.symbolicLinks[path] = realPath;
|
265
|
+
}
|
266
|
+
}
|
267
|
+
} catch (e) {
|
268
|
+
// Ignore
|
269
|
+
console.warn('Failed to check local symlink target', e);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
private ignoreFile(path: string) {
|
274
|
+
if (!path.startsWith(this.baseDir)) {
|
275
|
+
return false;
|
276
|
+
}
|
277
|
+
if (path.includes('/node_modules/')) {
|
278
|
+
return true;
|
279
|
+
}
|
280
|
+
|
281
|
+
const filename = Path.basename(path);
|
282
|
+
if (filename.startsWith('.')) {
|
283
|
+
return true;
|
284
|
+
}
|
285
|
+
|
286
|
+
const relativePath = Path.relative(this.baseDir, path).split(Path.sep);
|
287
|
+
|
288
|
+
try {
|
289
|
+
if (FS.statSync(path).isDirectory()) {
|
290
|
+
if (relativePath.length > 3) {
|
291
|
+
return true;
|
292
|
+
}
|
293
|
+
return false;
|
294
|
+
}
|
295
|
+
} catch (e) {
|
296
|
+
// Didn't exist - dont ignore
|
297
|
+
return false;
|
298
|
+
}
|
299
|
+
|
300
|
+
return !/^kapeta\.ya?ml$/.test(filename);
|
301
|
+
}
|
302
|
+
}
|