@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
@@ -4,20 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.repositoryManager = void 0;
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
8
7
|
const node_os_1 = __importDefault(require("node:os"));
|
9
|
-
const node_path_1 = __importDefault(require("node:path"));
|
10
|
-
const recursive_watch_1 = __importDefault(require("recursive-watch"));
|
11
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
12
|
-
const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
|
13
|
-
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
14
8
|
const socketManager_1 = require("./socketManager");
|
15
9
|
const nodejs_registry_utils_1 = require("@kapeta/nodejs-registry-utils");
|
16
10
|
const definitionsManager_1 = require("./definitionsManager");
|
17
11
|
const taskManager_1 = require("./taskManager");
|
18
12
|
const utils_1 = require("./utils/utils");
|
19
|
-
const assetManager_1 = require("./assetManager");
|
20
13
|
const progressListener_1 = require("./progressListener");
|
14
|
+
const RepositoryWatcher_1 = require("./RepositoryWatcher");
|
15
|
+
const cacheManager_1 = require("./cacheManager");
|
21
16
|
const EVENT_DEFAULT_PROVIDERS_START = 'default-providers-start';
|
22
17
|
const EVENT_DEFAULT_PROVIDERS_END = 'default-providers-end';
|
23
18
|
const DEFAULT_PROVIDERS = [
|
@@ -36,92 +31,30 @@ const DEFAULT_PROVIDERS = [
|
|
36
31
|
];
|
37
32
|
const INSTALL_ATTEMPTED = {};
|
38
33
|
class RepositoryManager {
|
39
|
-
changeEventsEnabled;
|
40
34
|
_registryService;
|
41
35
|
_cache;
|
42
36
|
watcher;
|
43
37
|
constructor() {
|
44
|
-
this.changeEventsEnabled = true;
|
45
|
-
this.listenForChanges();
|
46
38
|
this._registryService = new nodejs_registry_utils_1.RegistryService(nodejs_registry_utils_1.Config.data.registry.url);
|
47
39
|
this._cache = {};
|
48
|
-
|
49
|
-
|
50
|
-
this.changeEventsEnabled = enabled;
|
40
|
+
this.watcher = new RepositoryWatcher_1.RepositoryWatcher();
|
41
|
+
this.listenForChanges();
|
51
42
|
}
|
52
43
|
listenForChanges() {
|
53
|
-
|
54
|
-
if (!node_fs_1.default.existsSync(baseDir)) {
|
55
|
-
fs_extra_1.default.mkdirpSync(baseDir);
|
56
|
-
}
|
57
|
-
let allDefinitions = local_cluster_config_1.default.getDefinitions();
|
58
|
-
console.log('Watching local repository for provider changes: %s', baseDir);
|
59
|
-
try {
|
60
|
-
this.watcher = (0, recursive_watch_1.default)(baseDir, (filename) => {
|
61
|
-
if (!filename) {
|
62
|
-
return;
|
63
|
-
}
|
64
|
-
const [handle, name, version] = filename.toString().split(/\//g);
|
65
|
-
if (!name || !version) {
|
66
|
-
return;
|
67
|
-
}
|
68
|
-
if (!this.changeEventsEnabled) {
|
69
|
-
return;
|
70
|
-
}
|
71
|
-
const ymlPath = node_path_1.default.join(baseDir, handle, name, version, 'kapeta.yml');
|
72
|
-
const newDefinitions = local_cluster_config_1.default.getDefinitions();
|
73
|
-
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
74
|
-
let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
|
75
|
-
const ymlExists = node_fs_1.default.existsSync(ymlPath);
|
76
|
-
let type;
|
77
|
-
if (ymlExists) {
|
78
|
-
if (currentDefinition) {
|
79
|
-
type = 'updated';
|
80
|
-
}
|
81
|
-
else if (newDefinition) {
|
82
|
-
type = 'added';
|
83
|
-
currentDefinition = newDefinition;
|
84
|
-
}
|
85
|
-
else {
|
86
|
-
//Other definition was added / updated - ignore
|
87
|
-
return;
|
88
|
-
}
|
89
|
-
}
|
90
|
-
else {
|
91
|
-
if (currentDefinition) {
|
92
|
-
const ref = (0, nodejs_utils_1.parseKapetaUri)(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
|
93
|
-
delete INSTALL_ATTEMPTED[ref];
|
94
|
-
//Something was removed
|
95
|
-
type = 'removed';
|
96
|
-
}
|
97
|
-
else {
|
98
|
-
//Other definition was removed - ignore
|
99
|
-
return;
|
100
|
-
}
|
101
|
-
}
|
102
|
-
const payload = {
|
103
|
-
type,
|
104
|
-
definition: currentDefinition?.definition,
|
105
|
-
asset: { handle, name, version },
|
106
|
-
};
|
107
|
-
allDefinitions = newDefinitions;
|
108
|
-
socketManager_1.socketManager.emit(`assets`, 'changed', payload);
|
109
|
-
definitionsManager_1.definitionsManager.clearCache();
|
110
|
-
});
|
111
|
-
}
|
112
|
-
catch (e) {
|
113
|
-
// Fallback to run without watch mode due to potential platform issues.
|
114
|
-
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
115
|
-
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
116
|
-
return;
|
117
|
-
}
|
44
|
+
this.watcher.watch();
|
118
45
|
}
|
119
|
-
stopListening() {
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
46
|
+
async stopListening() {
|
47
|
+
return this.watcher.unwatch();
|
48
|
+
}
|
49
|
+
/**
|
50
|
+
* Setting the source of change helps us know
|
51
|
+
* how to react to changes in the UI.
|
52
|
+
*/
|
53
|
+
setSourceOfChangeFor(file, source) {
|
54
|
+
return this.watcher.setSourceOfChangeFor(file, source);
|
55
|
+
}
|
56
|
+
clearSourceOfChangeFor(file) {
|
57
|
+
return this.watcher.clearSourceOfChangeFor(file);
|
125
58
|
}
|
126
59
|
ensureDefaultProviders() {
|
127
60
|
socketManager_1.socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, { providers: DEFAULT_PROVIDERS });
|
@@ -146,19 +79,13 @@ class RepositoryManager {
|
|
146
79
|
try {
|
147
80
|
//We change to a temp dir to avoid issues with the current working directory
|
148
81
|
process.chdir(node_os_1.default.tmpdir());
|
149
|
-
//Disable change events while installing
|
150
|
-
this.setChangeEventsEnabled(false);
|
151
82
|
await nodejs_registry_utils_1.Actions.install(new progressListener_1.ProgressListener(), [ref], {});
|
152
83
|
}
|
153
84
|
catch (e) {
|
154
85
|
console.error(`Failed to install asset: ${ref}`, e);
|
155
86
|
throw e;
|
156
87
|
}
|
157
|
-
|
158
|
-
this.setChangeEventsEnabled(true);
|
159
|
-
}
|
160
|
-
definitionsManager_1.definitionsManager.clearCache();
|
161
|
-
assetManager_1.assetManager.clearCache();
|
88
|
+
cacheManager_1.cacheManager.flush();
|
162
89
|
//console.log(`Asset installed: ${ref}`);
|
163
90
|
};
|
164
91
|
};
|
package/dist/cjs/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';
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { SourceOfChange } from './types';
|
2
|
+
export declare class RepositoryWatcher {
|
3
|
+
private watcher?;
|
4
|
+
private disabled;
|
5
|
+
private readonly baseDir;
|
6
|
+
private allDefinitions;
|
7
|
+
private symbolicLinks;
|
8
|
+
private sourceOfChange;
|
9
|
+
constructor();
|
10
|
+
setDisabled(disabled: boolean): void;
|
11
|
+
watch(): void;
|
12
|
+
setSourceOfChangeFor(file: string, source: SourceOfChange): Promise<void>;
|
13
|
+
clearSourceOfChangeFor(file: string): Promise<void>;
|
14
|
+
unwatch(): Promise<void>;
|
15
|
+
private getAssetIdentity;
|
16
|
+
private handleFileChange;
|
17
|
+
private checkForChange;
|
18
|
+
private exists;
|
19
|
+
private removeSymlinkTarget;
|
20
|
+
private updateSymlinkTarget;
|
21
|
+
private addSymlinkTarget;
|
22
|
+
private ignoreFile;
|
23
|
+
}
|
@@ -0,0 +1,262 @@
|
|
1
|
+
import chokidar from 'chokidar';
|
2
|
+
import ClusterConfiguration 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 { cacheManager } from './cacheManager';
|
10
|
+
const KAPETA_YML_RX = /^kapeta.ya?ml$/;
|
11
|
+
export class RepositoryWatcher {
|
12
|
+
watcher;
|
13
|
+
disabled = false;
|
14
|
+
baseDir;
|
15
|
+
allDefinitions = [];
|
16
|
+
symbolicLinks = {};
|
17
|
+
sourceOfChange = new Map();
|
18
|
+
constructor() {
|
19
|
+
this.baseDir = ClusterConfiguration.getRepositoryBasedir();
|
20
|
+
}
|
21
|
+
setDisabled(disabled) {
|
22
|
+
this.disabled = disabled;
|
23
|
+
}
|
24
|
+
watch() {
|
25
|
+
if (!FS.existsSync(this.baseDir)) {
|
26
|
+
FS.mkdirpSync(this.baseDir);
|
27
|
+
}
|
28
|
+
this.allDefinitions = ClusterConfiguration.getDefinitions();
|
29
|
+
try {
|
30
|
+
this.watcher = chokidar.watch(this.baseDir, {
|
31
|
+
followSymlinks: false,
|
32
|
+
ignorePermissionErrors: true,
|
33
|
+
disableGlobbing: true,
|
34
|
+
persistent: true,
|
35
|
+
depth: 2,
|
36
|
+
ignored: (path) => this.ignoreFile(path),
|
37
|
+
});
|
38
|
+
this.watcher.on('all', this.handleFileChange.bind(this));
|
39
|
+
this.watcher.on('error', (error) => {
|
40
|
+
console.log('Error watching repository', error);
|
41
|
+
});
|
42
|
+
this.watcher.on('ready', () => {
|
43
|
+
console.log('Watching local repository for provider changes: %s', this.baseDir);
|
44
|
+
});
|
45
|
+
}
|
46
|
+
catch (e) {
|
47
|
+
// Fallback to run without watch mode due to potential platform issues.
|
48
|
+
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
49
|
+
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
async setSourceOfChangeFor(file, source) {
|
54
|
+
this.sourceOfChange.set(file, source);
|
55
|
+
const realPath = await FS.realpath(file);
|
56
|
+
if (realPath !== file) {
|
57
|
+
this.sourceOfChange.set(realPath, source);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
async clearSourceOfChangeFor(file) {
|
61
|
+
this.sourceOfChange.delete(file);
|
62
|
+
const realPath = await FS.realpath(file);
|
63
|
+
if (realPath !== file) {
|
64
|
+
this.sourceOfChange.delete(realPath);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
async unwatch() {
|
68
|
+
if (!this.watcher) {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
this.symbolicLinks = {};
|
72
|
+
await this.watcher.close();
|
73
|
+
this.watcher = undefined;
|
74
|
+
}
|
75
|
+
async getAssetIdentity(path) {
|
76
|
+
const baseName = Path.basename(path);
|
77
|
+
let handle, name, version;
|
78
|
+
if (path.startsWith(this.baseDir)) {
|
79
|
+
const relativePath = Path.relative(this.baseDir, path);
|
80
|
+
// Inside the repo we can use the path to determine the handle, name and version
|
81
|
+
[handle, name, version] = relativePath.split(/\//g);
|
82
|
+
if (!handle || !name || !version) {
|
83
|
+
// Do nothing with this
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
return {
|
87
|
+
handle,
|
88
|
+
name,
|
89
|
+
version,
|
90
|
+
};
|
91
|
+
}
|
92
|
+
if (!KAPETA_YML_RX.test(baseName)) {
|
93
|
+
// Do nothing with this
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
// Outside the repo we need to use the file content to determine the handle, name
|
97
|
+
// Version is always 'local'
|
98
|
+
version = 'local';
|
99
|
+
try {
|
100
|
+
const definition = YAML.parse((await FS.readFile(path)).toString());
|
101
|
+
const uri = parseKapetaUri(definition.metadata.name);
|
102
|
+
handle = uri.handle;
|
103
|
+
name = uri.name;
|
104
|
+
return {
|
105
|
+
handle,
|
106
|
+
name,
|
107
|
+
version,
|
108
|
+
};
|
109
|
+
}
|
110
|
+
catch (e) {
|
111
|
+
// Ignore issues in the YML file
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
async handleFileChange(eventName, path) {
|
116
|
+
if (!path) {
|
117
|
+
return;
|
118
|
+
}
|
119
|
+
//console.log('File changed', eventName, path);
|
120
|
+
const assetIdentity = await this.getAssetIdentity(path);
|
121
|
+
if (!assetIdentity) {
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
if (this.disabled) {
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
// If this is false it's because we're watching a symlink target
|
128
|
+
const withinRepo = path.startsWith(this.baseDir);
|
129
|
+
if (withinRepo && assetIdentity.version === 'local' && path.endsWith('/local')) {
|
130
|
+
// This is likely a symlink target
|
131
|
+
if (eventName === 'add') {
|
132
|
+
//console.log('Checking if we should add symlink target', handle, name, version, path);
|
133
|
+
await this.addSymlinkTarget(path);
|
134
|
+
}
|
135
|
+
if (eventName === 'unlink') {
|
136
|
+
await this.removeSymlinkTarget(path);
|
137
|
+
}
|
138
|
+
if (eventName === 'change') {
|
139
|
+
await this.updateSymlinkTarget(path);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
const sourceOfChange = this.sourceOfChange.get(path) ?? 'filesystem';
|
143
|
+
await this.checkForChange(assetIdentity, sourceOfChange);
|
144
|
+
// We consume the sourceOfChange when the file is changed
|
145
|
+
this.sourceOfChange.delete(path);
|
146
|
+
}
|
147
|
+
async checkForChange(assetIdentity, sourceOfChange) {
|
148
|
+
const ymlPath = Path.join(this.baseDir, assetIdentity.handle, assetIdentity.name, assetIdentity.version, 'kapeta.yml');
|
149
|
+
const newDefinitions = ClusterConfiguration.getDefinitions();
|
150
|
+
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
151
|
+
let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
|
152
|
+
const ymlExists = await this.exists(ymlPath);
|
153
|
+
let type;
|
154
|
+
if (ymlExists) {
|
155
|
+
if (currentDefinition) {
|
156
|
+
if (newDefinition && _.isEqual(currentDefinition, newDefinition)) {
|
157
|
+
//Definition was not changed
|
158
|
+
return;
|
159
|
+
}
|
160
|
+
type = 'updated';
|
161
|
+
}
|
162
|
+
else if (newDefinition) {
|
163
|
+
type = 'added';
|
164
|
+
currentDefinition = newDefinition;
|
165
|
+
}
|
166
|
+
else {
|
167
|
+
//Other definition was added / updated - ignore
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
else {
|
172
|
+
if (currentDefinition) {
|
173
|
+
const ref = parseKapetaUri(`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`).id;
|
174
|
+
//Something was removed
|
175
|
+
type = 'removed';
|
176
|
+
}
|
177
|
+
else {
|
178
|
+
//Other definition was removed - ignore
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
}
|
182
|
+
const payload = {
|
183
|
+
type,
|
184
|
+
definition: newDefinition?.definition ?? currentDefinition?.definition,
|
185
|
+
asset: assetIdentity,
|
186
|
+
sourceOfChange,
|
187
|
+
};
|
188
|
+
this.allDefinitions = newDefinitions;
|
189
|
+
//console.log('Asset changed', payload);
|
190
|
+
socketManager.emitGlobal('asset-change', payload);
|
191
|
+
cacheManager.flush();
|
192
|
+
}
|
193
|
+
async exists(path) {
|
194
|
+
try {
|
195
|
+
await FS.access(path);
|
196
|
+
return true;
|
197
|
+
}
|
198
|
+
catch (e) {
|
199
|
+
return false;
|
200
|
+
}
|
201
|
+
}
|
202
|
+
async removeSymlinkTarget(path) {
|
203
|
+
if (this.symbolicLinks[path]) {
|
204
|
+
//console.log('Unwatching symlink target %s => %s', path, this.symbolicLinks[path]);
|
205
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
206
|
+
delete this.symbolicLinks[path];
|
207
|
+
}
|
208
|
+
}
|
209
|
+
async updateSymlinkTarget(path) {
|
210
|
+
if (this.symbolicLinks[path]) {
|
211
|
+
//console.log('Updating symlink target %s => %s', path, this.symbolicLinks[path]);
|
212
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
213
|
+
delete this.symbolicLinks[path];
|
214
|
+
await this.addSymlinkTarget(path);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
async addSymlinkTarget(path) {
|
218
|
+
try {
|
219
|
+
// Make sure we're not watching the symlink target
|
220
|
+
await this.removeSymlinkTarget(path);
|
221
|
+
const stat = await FS.lstat(path);
|
222
|
+
if (stat.isSymbolicLink()) {
|
223
|
+
const realPath = `${await FS.realpath(path)}/kapeta.yml`;
|
224
|
+
if (await this.exists(realPath)) {
|
225
|
+
//console.log('Watching symlink target %s => %s', path, realPath);
|
226
|
+
this.watcher?.add(realPath);
|
227
|
+
this.symbolicLinks[path] = realPath;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}
|
231
|
+
catch (e) {
|
232
|
+
// Ignore
|
233
|
+
console.warn('Failed to check local symlink target', e);
|
234
|
+
}
|
235
|
+
}
|
236
|
+
ignoreFile(path) {
|
237
|
+
if (!path.startsWith(this.baseDir)) {
|
238
|
+
return false;
|
239
|
+
}
|
240
|
+
if (path.includes('/node_modules/')) {
|
241
|
+
return true;
|
242
|
+
}
|
243
|
+
const filename = Path.basename(path);
|
244
|
+
if (filename.startsWith('.')) {
|
245
|
+
return true;
|
246
|
+
}
|
247
|
+
const relativePath = Path.relative(this.baseDir, path).split(Path.sep);
|
248
|
+
try {
|
249
|
+
if (FS.statSync(path).isDirectory()) {
|
250
|
+
if (relativePath.length > 3) {
|
251
|
+
return true;
|
252
|
+
}
|
253
|
+
return false;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
catch (e) {
|
257
|
+
// Didn't exist - dont ignore
|
258
|
+
return false;
|
259
|
+
}
|
260
|
+
return !/^kapeta\.ya?ml$/.test(filename);
|
261
|
+
}
|
262
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { Definition } from '@kapeta/local-cluster-config';
|
2
2
|
import { BlockDefinition } from '@kapeta/schemas';
|
3
|
+
import { SourceOfChange } from './types';
|
3
4
|
export interface EnrichedAsset {
|
4
5
|
ref: string;
|
5
6
|
editable: boolean;
|
@@ -11,9 +12,6 @@ export interface EnrichedAsset {
|
|
11
12
|
ymlPath: string;
|
12
13
|
}
|
13
14
|
declare class AssetManager {
|
14
|
-
private cache;
|
15
|
-
constructor();
|
16
|
-
clearCache(): void;
|
17
15
|
/**
|
18
16
|
*
|
19
17
|
* @param {string[]} [assetKinds]
|
@@ -23,12 +21,12 @@ declare class AssetManager {
|
|
23
21
|
getPlans(): EnrichedAsset[];
|
24
22
|
getPlan(ref: string, noCache?: boolean): Promise<Definition>;
|
25
23
|
getAsset(ref: string, noCache?: boolean, autoFetch?: boolean): Promise<EnrichedAsset | undefined>;
|
26
|
-
createAsset(path: string, yaml: BlockDefinition): Promise<EnrichedAsset[]>;
|
27
|
-
updateAsset(ref: string, yaml: BlockDefinition): Promise<void>;
|
28
|
-
private maybeGenerateCode;
|
24
|
+
createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange): Promise<EnrichedAsset[]>;
|
25
|
+
updateAsset(ref: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange): Promise<void>;
|
29
26
|
importFile(filePath: string): Promise<EnrichedAsset[]>;
|
30
27
|
unregisterAsset(ref: string): Promise<void>;
|
31
28
|
installAsset(ref: string): Promise<import("./taskManager").Task<void>[] | undefined>;
|
29
|
+
private maybeGenerateCode;
|
32
30
|
}
|
33
31
|
export declare const assetManager: AssetManager;
|
34
32
|
export {};
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import Path from 'node:path';
|
2
|
-
import FS from '
|
3
|
-
import FSExtra from 'fs-extra';
|
2
|
+
import FS from 'fs-extra';
|
4
3
|
import YAML from 'yaml';
|
5
|
-
import NodeCache from 'node-cache';
|
6
4
|
import { codeGeneratorManager } from './codeGeneratorManager';
|
7
5
|
import { ProgressListener } from './progressListener';
|
8
6
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
@@ -11,6 +9,8 @@ import { Actions } from '@kapeta/nodejs-registry-utils';
|
|
11
9
|
import { definitionsManager } from './definitionsManager';
|
12
10
|
import { normalizeKapetaUri } from './utils/utils';
|
13
11
|
import { taskManager } from './taskManager';
|
12
|
+
import { cacheManager } from './cacheManager';
|
13
|
+
const CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
14
14
|
function enrichAsset(asset) {
|
15
15
|
return {
|
16
16
|
ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
|
@@ -36,15 +36,6 @@ function parseRef(ref) {
|
|
36
36
|
return [out[0].toLowerCase(), out[1].toLowerCase()];
|
37
37
|
}
|
38
38
|
class AssetManager {
|
39
|
-
cache;
|
40
|
-
constructor() {
|
41
|
-
this.cache = new NodeCache({
|
42
|
-
stdTTL: 60 * 60, // 1 hour
|
43
|
-
});
|
44
|
-
}
|
45
|
-
clearCache() {
|
46
|
-
this.cache.flushAll();
|
47
|
-
}
|
48
39
|
/**
|
49
40
|
*
|
50
41
|
* @param {string[]} [assetKinds]
|
@@ -77,8 +68,8 @@ class AssetManager {
|
|
77
68
|
async getAsset(ref, noCache = false, autoFetch = true) {
|
78
69
|
ref = normalizeKapetaUri(ref);
|
79
70
|
const cacheKey = `getAsset:${ref}`;
|
80
|
-
if (!noCache &&
|
81
|
-
return
|
71
|
+
if (!noCache && cacheManager.has(cacheKey)) {
|
72
|
+
return cacheManager.get(cacheKey);
|
82
73
|
}
|
83
74
|
const uri = parseKapetaUri(ref);
|
84
75
|
if (autoFetch) {
|
@@ -92,29 +83,27 @@ class AssetManager {
|
|
92
83
|
throw new Error('Asset not found: ' + ref);
|
93
84
|
}
|
94
85
|
if (asset) {
|
95
|
-
|
86
|
+
cacheManager.set(cacheKey, asset, CACHE_TTL);
|
96
87
|
}
|
97
88
|
return asset;
|
98
89
|
}
|
99
|
-
async createAsset(path, yaml) {
|
100
|
-
if (FS.
|
90
|
+
async createAsset(path, yaml, sourceOfChange = 'filesystem') {
|
91
|
+
if (await FS.pathExists(path)) {
|
101
92
|
throw new Error('File already exists: ' + path);
|
102
93
|
}
|
103
94
|
const dirName = Path.dirname(path);
|
104
|
-
if (!FS.
|
105
|
-
|
95
|
+
if (!(await FS.pathExists(dirName))) {
|
96
|
+
await FS.mkdirp(dirName);
|
106
97
|
}
|
107
|
-
|
108
|
-
FS.
|
98
|
+
await repositoryManager.setSourceOfChangeFor(path, sourceOfChange);
|
99
|
+
await FS.writeFile(path, YAML.stringify(yaml));
|
109
100
|
const asset = await this.importFile(path);
|
110
|
-
|
111
|
-
this.cache.flushAll();
|
112
|
-
definitionsManager.clearCache();
|
101
|
+
cacheManager.flush();
|
113
102
|
const ref = `kapeta://${yaml.metadata.name}:local`;
|
114
103
|
this.maybeGenerateCode(ref, path, yaml);
|
115
104
|
return asset;
|
116
105
|
}
|
117
|
-
async updateAsset(ref, yaml) {
|
106
|
+
async updateAsset(ref, yaml, sourceOfChange = 'filesystem') {
|
118
107
|
const asset = await this.getAsset(ref, true, false);
|
119
108
|
if (!asset) {
|
120
109
|
throw new Error('Attempted to update unknown asset: ' + ref);
|
@@ -125,37 +114,25 @@ class AssetManager {
|
|
125
114
|
if (!asset.ymlPath) {
|
126
115
|
throw new Error('Attempted to update corrupted asset: ' + ref);
|
127
116
|
}
|
117
|
+
await repositoryManager.setSourceOfChangeFor(asset.ymlPath, sourceOfChange);
|
118
|
+
await FS.writeFile(asset.ymlPath, YAML.stringify(yaml));
|
128
119
|
console.log('Wrote to ' + asset.ymlPath);
|
129
|
-
|
130
|
-
this.cache.flushAll();
|
131
|
-
definitionsManager.clearCache();
|
120
|
+
cacheManager.flush();
|
132
121
|
this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
|
133
122
|
}
|
134
|
-
maybeGenerateCode(ref, ymlPath, block) {
|
135
|
-
ref = normalizeKapetaUri(ref);
|
136
|
-
if (codeGeneratorManager.canGenerateCode(block)) {
|
137
|
-
const assetTitle = block.metadata.title ? block.metadata.title : parseKapetaUri(block.metadata.name).name;
|
138
|
-
taskManager.add(`codegen:${ref}`, async () => {
|
139
|
-
await codeGeneratorManager.generate(ymlPath, block);
|
140
|
-
}, {
|
141
|
-
name: `Generating code for ${assetTitle}`,
|
142
|
-
});
|
143
|
-
return true;
|
144
|
-
}
|
145
|
-
return false;
|
146
|
-
}
|
147
123
|
async importFile(filePath) {
|
148
124
|
if (filePath.startsWith('file://')) {
|
149
125
|
filePath = filePath.substring('file://'.length);
|
150
126
|
}
|
151
|
-
if (!FS.
|
127
|
+
if (!(await FS.pathExists(filePath))) {
|
152
128
|
throw new Error('File not found: ' + filePath);
|
153
129
|
}
|
154
|
-
const
|
130
|
+
const content = await FS.readFile(filePath);
|
131
|
+
const assetInfos = YAML.parseAllDocuments(content.toString()).map((doc) => doc.toJSON());
|
155
132
|
await Actions.link(new ProgressListener(), Path.dirname(filePath));
|
156
133
|
const version = 'local';
|
157
134
|
const refs = assetInfos.map((assetInfo) => `kapeta://${assetInfo.metadata.name}:${version}`);
|
158
|
-
|
135
|
+
cacheManager.flush();
|
159
136
|
return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
|
160
137
|
}
|
161
138
|
async unregisterAsset(ref) {
|
@@ -163,7 +140,7 @@ class AssetManager {
|
|
163
140
|
if (!asset) {
|
164
141
|
throw new Error('Asset does not exists: ' + ref);
|
165
142
|
}
|
166
|
-
|
143
|
+
cacheManager.flush();
|
167
144
|
await Actions.uninstall(new ProgressListener(), [asset.ref]);
|
168
145
|
}
|
169
146
|
async installAsset(ref) {
|
@@ -175,5 +152,18 @@ class AssetManager {
|
|
175
152
|
console.log('Installing %s', ref);
|
176
153
|
return await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, false);
|
177
154
|
}
|
155
|
+
maybeGenerateCode(ref, ymlPath, block) {
|
156
|
+
ref = normalizeKapetaUri(ref);
|
157
|
+
if (codeGeneratorManager.canGenerateCode(block)) {
|
158
|
+
const assetTitle = block.metadata.title ? block.metadata.title : parseKapetaUri(block.metadata.name).name;
|
159
|
+
taskManager.add(`codegen:${ref}`, async () => {
|
160
|
+
await codeGeneratorManager.generate(ymlPath, block);
|
161
|
+
}, {
|
162
|
+
name: `Generating code for ${assetTitle}`,
|
163
|
+
});
|
164
|
+
return true;
|
165
|
+
}
|
166
|
+
return false;
|
167
|
+
}
|
178
168
|
}
|
179
169
|
export const assetManager = new AssetManager();
|
@@ -58,7 +58,7 @@ router.post('/create', async (req, res) => {
|
|
58
58
|
}
|
59
59
|
const content = parseBody(req);
|
60
60
|
try {
|
61
|
-
const assets = await assetManager.createAsset(req.query.path, content);
|
61
|
+
const assets = await assetManager.createAsset(req.query.path, content, 'user');
|
62
62
|
res.status(200).send(assets);
|
63
63
|
}
|
64
64
|
catch (err) {
|
@@ -76,7 +76,7 @@ router.put('/update', async (req, res) => {
|
|
76
76
|
}
|
77
77
|
const content = parseBody(req);
|
78
78
|
try {
|
79
|
-
await assetManager.updateAsset(req.query.ref, content);
|
79
|
+
await assetManager.updateAsset(req.query.ref, content, 'user');
|
80
80
|
res.sendStatus(204);
|
81
81
|
}
|
82
82
|
catch (err) {
|
@@ -0,0 +1,16 @@
|
|
1
|
+
export interface CacheEntry<T = any> {
|
2
|
+
expires: number;
|
3
|
+
data: T;
|
4
|
+
}
|
5
|
+
declare class CacheManager {
|
6
|
+
private cache;
|
7
|
+
flush(): void;
|
8
|
+
doCached<T>(key: string, getter: () => T, ttl?: number): T;
|
9
|
+
get<T>(key: string): T | undefined;
|
10
|
+
set<T>(key: string, data: T, ttl?: number): void;
|
11
|
+
has(key: string): boolean;
|
12
|
+
remove(key: string): number;
|
13
|
+
}
|
14
|
+
export declare const cacheManager: CacheManager;
|
15
|
+
export declare const doCached: <T>(key: string, getter: () => T, ttl?: number) => T;
|
16
|
+
export {};
|