@cyberismo/data-handler 0.0.7 → 0.0.8
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/dist/commands/create.d.ts +1 -1
- package/dist/commands/create.js +15 -10
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/import.js +8 -2
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -1
- package/dist/commands/remove.js +4 -10
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/validate.d.ts +0 -8
- package/dist/commands/validate.js +0 -32
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/project/resource-collector.d.ts +2 -1
- package/dist/containers/project/resource-collector.js +33 -23
- package/dist/containers/project/resource-collector.js.map +1 -1
- package/dist/containers/project.d.ts +0 -5
- package/dist/containers/project.js +9 -17
- package/dist/containers/project.js.map +1 -1
- package/dist/exceptions/index.d.ts +20 -0
- package/dist/exceptions/index.js +16 -0
- package/dist/exceptions/index.js.map +1 -1
- package/dist/interfaces/macros.d.ts +3 -0
- package/dist/macros/base-macro.d.ts +2 -0
- package/dist/macros/base-macro.js +66 -19
- package/dist/macros/base-macro.js.map +1 -1
- package/dist/macros/graph/index.d.ts +0 -1
- package/dist/macros/graph/index.js +11 -7
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/index.d.ts +6 -0
- package/dist/macros/index.js +25 -2
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/report/index.js +18 -9
- package/dist/macros/report/index.js.map +1 -1
- package/dist/module-manager.d.ts +16 -4
- package/dist/module-manager.js +179 -55
- package/dist/module-manager.js.map +1 -1
- package/dist/project-settings.js +2 -8
- package/dist/project-settings.js.map +1 -1
- package/package.json +3 -4
- package/src/commands/create.ts +18 -10
- package/src/commands/import.ts +15 -2
- package/src/commands/remove.ts +7 -12
- package/src/commands/validate.ts +0 -35
- package/src/containers/project/resource-collector.ts +33 -14
- package/src/containers/project.ts +36 -18
- package/src/exceptions/index.ts +36 -0
- package/src/interfaces/macros.ts +3 -0
- package/src/macros/base-macro.ts +89 -25
- package/src/macros/graph/index.ts +12 -7
- package/src/macros/index.ts +29 -2
- package/src/macros/report/index.ts +19 -10
- package/src/module-manager.ts +228 -66
- package/src/project-settings.ts +2 -11
|
@@ -23,6 +23,7 @@ import type TaskQueue from '../task-queue.js';
|
|
|
23
23
|
import { ReportResource } from '../../resources/report-resource.js';
|
|
24
24
|
import { resourceName } from '../../utils/resource-utils.js';
|
|
25
25
|
import { generateReportContent } from '../../utils/report.js';
|
|
26
|
+
import { ClingoError } from '@cyberismo/node-clingo';
|
|
26
27
|
|
|
27
28
|
export interface ReportOptions extends MacroOptions {
|
|
28
29
|
name: string;
|
|
@@ -58,16 +59,24 @@ class ReportMacro extends BaseMacro {
|
|
|
58
59
|
schema: report.schema,
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
try {
|
|
63
|
+
return await generateReportContent({
|
|
64
|
+
calculate: this.calculate,
|
|
65
|
+
contentTemplate: report.contentTemplate,
|
|
66
|
+
queryTemplate: report.queryTemplate,
|
|
67
|
+
options: {
|
|
68
|
+
cardKey: context.cardKey,
|
|
69
|
+
...options,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof ClingoError) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Error running logic program in report '${options.name}':${error.details.errors.join('\n')}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
71
80
|
};
|
|
72
81
|
|
|
73
82
|
private validate(data: unknown): ReportOptions {
|
package/src/module-manager.ts
CHANGED
|
@@ -29,17 +29,24 @@ import { readJsonFile } from './utils/json.js';
|
|
|
29
29
|
import { Validate } from './commands/index.js';
|
|
30
30
|
|
|
31
31
|
const FILE_PROTOCOL = 'file:';
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const HTTPS_PROTOCOL = 'https:';
|
|
33
|
+
|
|
34
34
|
// timeout in milliseconds for git client (no stdout / stderr activity)
|
|
35
35
|
const DEFAULT_TIMEOUT = 10000;
|
|
36
36
|
|
|
37
|
+
// When dependencies are built to a map, use map that has
|
|
38
|
+
// key: module name,
|
|
39
|
+
// value: list of unique prefixes
|
|
40
|
+
type DependencyGraph = Map<string, Set<string>>;
|
|
41
|
+
|
|
37
42
|
/**
|
|
38
43
|
* Class that handles module updates and imports.
|
|
39
44
|
*/
|
|
40
45
|
export class ModuleManager {
|
|
41
46
|
private modules: ModuleSetting[] = [];
|
|
42
47
|
private tempModulesDir: string = '';
|
|
48
|
+
private defaultBranchCache: Map<string, string> = new Map();
|
|
49
|
+
|
|
43
50
|
constructor(private project: Project) {
|
|
44
51
|
this.tempModulesDir = join(this.project.paths.tempFolder, 'modules');
|
|
45
52
|
}
|
|
@@ -53,6 +60,35 @@ export class ModuleManager {
|
|
|
53
60
|
await this.project.collectModuleResources();
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
// Creates a map of what dependencies each module depend from.
|
|
64
|
+
private async buildDependencyGraph(
|
|
65
|
+
dependencies: ModuleSetting[],
|
|
66
|
+
): Promise<DependencyGraph> {
|
|
67
|
+
const dependencyNames = dependencies.map((item) => item.name);
|
|
68
|
+
const dependencyGraph = new Map<string, Set<string>>() as DependencyGraph;
|
|
69
|
+
for (const dependency of dependencyNames) {
|
|
70
|
+
dependencyGraph.set(
|
|
71
|
+
dependency,
|
|
72
|
+
await this.transientDependencies(dependency),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return dependencyGraph;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Returns 'true' if 'moduleName' can be removed.
|
|
79
|
+
private canBeRemoved(
|
|
80
|
+
dependencies: DependencyGraph,
|
|
81
|
+
moduleName: string,
|
|
82
|
+
): boolean {
|
|
83
|
+
let unused = true;
|
|
84
|
+
dependencies.forEach((transientDependencies, key) => {
|
|
85
|
+
if (key !== moduleName && transientDependencies.has(moduleName)) {
|
|
86
|
+
unused = false;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return unused;
|
|
90
|
+
}
|
|
91
|
+
|
|
56
92
|
// Handles cloning of a repository.
|
|
57
93
|
private async clone(
|
|
58
94
|
module: ModuleSetting,
|
|
@@ -67,10 +103,15 @@ export class ModuleManager {
|
|
|
67
103
|
|
|
68
104
|
let remote = module.location;
|
|
69
105
|
if (module.private) {
|
|
70
|
-
if (
|
|
106
|
+
if (
|
|
107
|
+
credentials &&
|
|
108
|
+
credentials?.username &&
|
|
109
|
+
credentials?.token &&
|
|
110
|
+
module.location.startsWith(HTTPS_PROTOCOL)
|
|
111
|
+
) {
|
|
71
112
|
if (verbose) {
|
|
72
113
|
console.log(
|
|
73
|
-
`... Using credentials '${credentials?.username}' for cloning '${module.name}'`,
|
|
114
|
+
`... Using HTTPS with credentials '${credentials?.username}' for cloning '${module.name}'`,
|
|
74
115
|
);
|
|
75
116
|
}
|
|
76
117
|
try {
|
|
@@ -83,16 +124,22 @@ export class ModuleManager {
|
|
|
83
124
|
} catch {
|
|
84
125
|
throw new Error(`Invalid repository URL: ${module.location}`);
|
|
85
126
|
}
|
|
86
|
-
} else {
|
|
127
|
+
} else if (module.location.startsWith('git@')) {
|
|
87
128
|
if (verbose) {
|
|
88
|
-
console.log(`...
|
|
129
|
+
console.log(`... Using SSH for cloning '${module.name}'`);
|
|
89
130
|
}
|
|
90
131
|
}
|
|
132
|
+
} else {
|
|
133
|
+
if (verbose) {
|
|
134
|
+
console.log(
|
|
135
|
+
`... Using HTTPS without credentials for cloning '${module.name}'`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
91
138
|
}
|
|
92
139
|
|
|
93
140
|
try {
|
|
94
141
|
await mkdir(this.tempModulesDir, { recursive: true });
|
|
95
|
-
const cloneOptions = this.setCloneOptions(module);
|
|
142
|
+
const cloneOptions = await this.setCloneOptions(module);
|
|
96
143
|
await rm(destinationPath, { recursive: true, force: true });
|
|
97
144
|
|
|
98
145
|
const git: SimpleGit = simpleGit({
|
|
@@ -110,7 +157,6 @@ export class ModuleManager {
|
|
|
110
157
|
console.log(`... Cloned '${module.name}' to a temporary folder`);
|
|
111
158
|
}
|
|
112
159
|
} catch (error) {
|
|
113
|
-
console.error(error);
|
|
114
160
|
if (error instanceof Error)
|
|
115
161
|
throw new Error(
|
|
116
162
|
`Failed to clone module '${module.name}': ${error.message}`,
|
|
@@ -143,6 +189,48 @@ export class ModuleManager {
|
|
|
143
189
|
}
|
|
144
190
|
}
|
|
145
191
|
|
|
192
|
+
// Gets the default branch for a repository from remote or cache
|
|
193
|
+
private async defaultBranch(module: ModuleSetting): Promise<string> {
|
|
194
|
+
if (this.defaultBranchCache.has(module.location)) {
|
|
195
|
+
return this.defaultBranchCache.get(module.location)!;
|
|
196
|
+
}
|
|
197
|
+
// Set the default branch if branch was not specified
|
|
198
|
+
if (!module.branch) {
|
|
199
|
+
const destinationPath = join(this.tempModulesDir, module.name);
|
|
200
|
+
// Only return path after cloning
|
|
201
|
+
if (pathExists(destinationPath)) {
|
|
202
|
+
const git: SimpleGit = simpleGit({
|
|
203
|
+
timeout: {
|
|
204
|
+
block: DEFAULT_TIMEOUT,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const options = ['--abbrev-ref', 'HEAD'];
|
|
208
|
+
const defaultBranch = await git.cwd(destinationPath).revparse(options);
|
|
209
|
+
this.defaultBranchCache.set(module.location, defaultBranch);
|
|
210
|
+
return defaultBranch;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// The actual default branch will be updated later (after cloning).
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Fetches direct dependencies of a module.
|
|
218
|
+
private async dependencies(moduleName: string): Promise<Set<string>> {
|
|
219
|
+
const allModules = await this.project.modules();
|
|
220
|
+
if (!allModules) return new Set();
|
|
221
|
+
const module = allModules.find((m) => m.name === moduleName);
|
|
222
|
+
if (!module) {
|
|
223
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
224
|
+
}
|
|
225
|
+
const modulePath = join(module.path, module.name, 'cardsConfig.json');
|
|
226
|
+
const moduleConfiguration = (await readJsonFile(
|
|
227
|
+
modulePath,
|
|
228
|
+
)) as ProjectConfiguration;
|
|
229
|
+
return moduleConfiguration.modules
|
|
230
|
+
? new Set(moduleConfiguration.modules.map((m) => m.name))
|
|
231
|
+
: new Set();
|
|
232
|
+
}
|
|
233
|
+
|
|
146
234
|
// Collects one module's dependency prefixes to 'this.modules'.
|
|
147
235
|
// Note that there can be duplicate entries.
|
|
148
236
|
private async doCollectModulePrefix(
|
|
@@ -167,16 +255,17 @@ export class ModuleManager {
|
|
|
167
255
|
|
|
168
256
|
// Updates one module that is read from local file system.
|
|
169
257
|
private async handleFileModule(module: ModuleSetting) {
|
|
170
|
-
this.
|
|
258
|
+
this.stripProtocolFromLocation(module);
|
|
171
259
|
await this.remove(module);
|
|
172
|
-
await this.importFromFolder(module);
|
|
260
|
+
await this.importFromFolder(module.location, module.name);
|
|
173
261
|
}
|
|
174
262
|
|
|
175
263
|
// Updates one module that is received from Git.
|
|
176
264
|
private async handleGitModule(module: ModuleSetting) {
|
|
177
265
|
await this.clone(module);
|
|
266
|
+
const tempLocation = join(this.tempModulesDir, module.name);
|
|
178
267
|
await this.remove(module);
|
|
179
|
-
await this.
|
|
268
|
+
await this.importFromFolder(tempLocation, module.name);
|
|
180
269
|
}
|
|
181
270
|
|
|
182
271
|
// Updates one module.
|
|
@@ -186,19 +275,11 @@ export class ModuleManager {
|
|
|
186
275
|
: this.handleFileModule(module);
|
|
187
276
|
}
|
|
188
277
|
|
|
189
|
-
//
|
|
190
|
-
private async importFromFolder(
|
|
191
|
-
await this.importFileModule(
|
|
192
|
-
console.log(
|
|
193
|
-
`... Imported module '${module.name}' to '${this.project.configuration.name}'`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Handles importing a module from '.temp' folder
|
|
198
|
-
private async importFromTemp(module: ModuleSetting) {
|
|
199
|
-
await this.importFileModule(join(this.tempModulesDir, module.name));
|
|
278
|
+
// Imports from a given folder. Is used both for .temp/<module name> and file locations.
|
|
279
|
+
private async importFromFolder(path: string, name: string) {
|
|
280
|
+
await this.importFileModule(path);
|
|
200
281
|
console.log(
|
|
201
|
-
`... Imported module '${
|
|
282
|
+
`... Imported module '${name}' to '${this.project.configuration.name}'`,
|
|
202
283
|
);
|
|
203
284
|
}
|
|
204
285
|
|
|
@@ -211,7 +292,41 @@ export class ModuleManager {
|
|
|
211
292
|
// Returns true if module is imported from git.
|
|
212
293
|
private isGitModule(module: ModuleSetting): boolean {
|
|
213
294
|
if (!module.location) return false;
|
|
214
|
-
return
|
|
295
|
+
return (
|
|
296
|
+
module.location.startsWith('https:') || module.location.startsWith('git@')
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Collect modules that could be removed from .cards/modules when
|
|
301
|
+
// 'moduleName' is removed.
|
|
302
|
+
private async orphanedModules(
|
|
303
|
+
dependencies: DependencyGraph,
|
|
304
|
+
moduleName: string,
|
|
305
|
+
): Promise<string[]> {
|
|
306
|
+
const projectModules = this.project.configuration.modules;
|
|
307
|
+
const removableTransientModules: string[] = [];
|
|
308
|
+
if (dependencies.has(moduleName)) {
|
|
309
|
+
const deps = dependencies.get(moduleName);
|
|
310
|
+
for (const dependency of deps!) {
|
|
311
|
+
const projectDependency = projectModules.some(
|
|
312
|
+
(item) => item.name === dependency,
|
|
313
|
+
);
|
|
314
|
+
if (projectDependency) continue;
|
|
315
|
+
|
|
316
|
+
let orphanModule = true;
|
|
317
|
+
dependencies.forEach((transientDependencies, key) => {
|
|
318
|
+
if (key === moduleName) return;
|
|
319
|
+
if (transientDependencies.has(dependency)) {
|
|
320
|
+
orphanModule = false;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (orphanModule) {
|
|
325
|
+
removableTransientModules.push(dependency);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return removableTransientModules;
|
|
215
330
|
}
|
|
216
331
|
|
|
217
332
|
// Prepares '.temp/modules' for cloning
|
|
@@ -231,11 +346,6 @@ export class ModuleManager {
|
|
|
231
346
|
}
|
|
232
347
|
}
|
|
233
348
|
|
|
234
|
-
// Returns whether to use git or file system for handling the module.
|
|
235
|
-
private protocol(module: ModuleSetting) {
|
|
236
|
-
return this.isFileModule(module) ? 'file' : 'git';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
349
|
// Handles removing an imported module.
|
|
240
350
|
private async remove(module: ModuleSetting) {
|
|
241
351
|
try {
|
|
@@ -249,37 +359,19 @@ export class ModuleManager {
|
|
|
249
359
|
}
|
|
250
360
|
}
|
|
251
361
|
|
|
252
|
-
// Remove module files.
|
|
253
|
-
private async removeModuleFiles(moduleName: string) {
|
|
254
|
-
const module = await this.project.module(moduleName);
|
|
255
|
-
if (!module) {
|
|
256
|
-
throw new Error(`Module '${moduleName}' not found`);
|
|
257
|
-
}
|
|
258
|
-
await deleteDir(module.path);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Updates module's 'location' not to have 'protocol:' in the beginning (only for "file:" needed).
|
|
262
|
-
private removeProtocolFromLocation(module: ModuleSetting) {
|
|
263
|
-
const protocol = this.protocol(module);
|
|
264
|
-
module.location = module.location.substring(
|
|
265
|
-
protocol.length + 1,
|
|
266
|
-
module.location.length,
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
362
|
// Checks for duplicate ModuleSetting entries and throws an error if modules
|
|
271
363
|
// with the same name have different branches or locations.
|
|
272
|
-
// Treats undefined branch, empty string branch, and
|
|
364
|
+
// Treats undefined branch, empty string branch, and default branch as equivalent.
|
|
273
365
|
// Returns an array with duplicate entries removed
|
|
274
|
-
private removeDuplicates(
|
|
366
|
+
private async removeDuplicates(
|
|
367
|
+
modules: ModuleSetting[],
|
|
368
|
+
): Promise<ModuleSetting[]> {
|
|
275
369
|
const moduleMap = new Map<string, ModuleSetting>();
|
|
276
370
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
return branch;
|
|
371
|
+
// Normalize branch names by checking against the default branch for each module
|
|
372
|
+
|
|
373
|
+
const normalizeBranch = async (module: ModuleSetting) => {
|
|
374
|
+
return module.branch ? module.branch! : await this.defaultBranch(module);
|
|
283
375
|
};
|
|
284
376
|
|
|
285
377
|
for (const module of modules) {
|
|
@@ -291,8 +383,8 @@ export class ModuleManager {
|
|
|
291
383
|
) {
|
|
292
384
|
throw new Error(
|
|
293
385
|
`Module conflict: '${module.name}' has different access:\n` +
|
|
294
|
-
` - ${existingModule.private
|
|
295
|
-
` - ${module.private
|
|
386
|
+
` - ${Boolean(existingModule.private)}\n` +
|
|
387
|
+
` - ${Boolean(module.private)}`,
|
|
296
388
|
);
|
|
297
389
|
}
|
|
298
390
|
if (existingModule.location !== module.location) {
|
|
@@ -302,8 +394,8 @@ export class ModuleManager {
|
|
|
302
394
|
` - ${module.location}`,
|
|
303
395
|
);
|
|
304
396
|
}
|
|
305
|
-
const existingBranch = normalizeBranch(existingModule
|
|
306
|
-
const newBranch = normalizeBranch(module
|
|
397
|
+
const existingBranch = await normalizeBranch(existingModule);
|
|
398
|
+
const newBranch = await normalizeBranch(module);
|
|
307
399
|
|
|
308
400
|
if (existingBranch !== newBranch) {
|
|
309
401
|
throw new Error(
|
|
@@ -319,6 +411,15 @@ export class ModuleManager {
|
|
|
319
411
|
return Array.from(moduleMap.values());
|
|
320
412
|
}
|
|
321
413
|
|
|
414
|
+
// Remove module files.
|
|
415
|
+
private async removeModuleFiles(moduleName: string) {
|
|
416
|
+
const module = await this.project.module(moduleName);
|
|
417
|
+
if (!module) {
|
|
418
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
419
|
+
}
|
|
420
|
+
await deleteDir(module.path);
|
|
421
|
+
}
|
|
422
|
+
|
|
322
423
|
// Gets repository name from gitUrl
|
|
323
424
|
private repositoryName(gitUrl: string): string {
|
|
324
425
|
const last = gitUrl.lastIndexOf('/');
|
|
@@ -326,19 +427,47 @@ export class ModuleManager {
|
|
|
326
427
|
return repoName;
|
|
327
428
|
}
|
|
328
429
|
|
|
329
|
-
// Sets cloning options.
|
|
330
|
-
private setCloneOptions(module: ModuleSetting) {
|
|
430
|
+
// Sets cloning options with support for default branch.
|
|
431
|
+
private async setCloneOptions(module: ModuleSetting): Promise<string[]> {
|
|
331
432
|
const cloneOptions = ['--depth', '1'];
|
|
433
|
+
const defaultBranch = await this.defaultBranch(module);
|
|
434
|
+
// Only specify branch if it's different from the default branch
|
|
332
435
|
if (
|
|
333
436
|
module.branch &&
|
|
334
437
|
module.branch !== '' &&
|
|
335
|
-
module.branch !==
|
|
438
|
+
module.branch !== defaultBranch
|
|
336
439
|
) {
|
|
337
440
|
cloneOptions.push('--branch', module.branch);
|
|
338
441
|
}
|
|
339
442
|
return cloneOptions;
|
|
340
443
|
}
|
|
341
444
|
|
|
445
|
+
// Updates module's 'location' not to have 'protocol:' in the beginning (only for "file:" needed).
|
|
446
|
+
private stripProtocolFromLocation(module: ModuleSetting) {
|
|
447
|
+
const protocol = this.isFileModule(module) ? 'file' : 'git';
|
|
448
|
+
module.location = module.location.substring(
|
|
449
|
+
protocol.length + 1,
|
|
450
|
+
module.location.length,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Fetches all dependencies for a module.
|
|
455
|
+
private async transientDependencies(
|
|
456
|
+
moduleName: string,
|
|
457
|
+
): Promise<Set<string>> {
|
|
458
|
+
const dependencies = await this.dependencies(moduleName);
|
|
459
|
+
let transientDependencies: Set<string> = new Set(dependencies);
|
|
460
|
+
for (const dependency of dependencies) {
|
|
461
|
+
const depTransients = await this.transientDependencies(dependency);
|
|
462
|
+
transientDependencies = new Set([
|
|
463
|
+
...transientDependencies,
|
|
464
|
+
...depTransients,
|
|
465
|
+
]);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return transientDependencies;
|
|
469
|
+
}
|
|
470
|
+
|
|
342
471
|
// Updates modules in the project.
|
|
343
472
|
private async update(module?: ModuleSetting, credentials?: Credentials) {
|
|
344
473
|
// Prints dots every half second so that user knows that something is ongoing
|
|
@@ -350,7 +479,9 @@ export class ModuleManager {
|
|
|
350
479
|
// Stops the above, and shows results
|
|
351
480
|
function finished(interval: NodeJS.Timeout, modules: string[]) {
|
|
352
481
|
clearInterval(interval);
|
|
353
|
-
|
|
482
|
+
if (modules.length > 0) {
|
|
483
|
+
console.log(`\n... Found modules: ${modules.join(', ')}`);
|
|
484
|
+
}
|
|
354
485
|
}
|
|
355
486
|
|
|
356
487
|
await this.prepare();
|
|
@@ -366,8 +497,7 @@ export class ModuleManager {
|
|
|
366
497
|
let uniqueModules: ModuleSetting[] = [];
|
|
367
498
|
try {
|
|
368
499
|
await this.collectModulePrefixes(modules, credentials);
|
|
369
|
-
|
|
370
|
-
uniqueModules = this.removeDuplicates(this.modules);
|
|
500
|
+
uniqueModules = await this.removeDuplicates(this.modules);
|
|
371
501
|
} finally {
|
|
372
502
|
finished(
|
|
373
503
|
dotInterval,
|
|
@@ -380,7 +510,6 @@ export class ModuleManager {
|
|
|
380
510
|
promises.push(this.handleModule(module)),
|
|
381
511
|
);
|
|
382
512
|
await Promise.all(promises);
|
|
383
|
-
|
|
384
513
|
await deleteDir(this.tempModulesDir);
|
|
385
514
|
await this.project.collectModuleResources();
|
|
386
515
|
}
|
|
@@ -467,6 +596,39 @@ export class ModuleManager {
|
|
|
467
596
|
return modulePrefix;
|
|
468
597
|
}
|
|
469
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Removed module from project.
|
|
601
|
+
* If module is not used by any other modules, then will remove the module from disk as well.
|
|
602
|
+
* Otherwise, only updates project configuration.
|
|
603
|
+
* @param moduleName Name of the module to remove
|
|
604
|
+
*/
|
|
605
|
+
public async removeModule(moduleName: string) {
|
|
606
|
+
const projectModules = this.project.configuration.modules;
|
|
607
|
+
const dependencies = await this.buildDependencyGraph(projectModules);
|
|
608
|
+
const module = await this.project.module(moduleName);
|
|
609
|
+
|
|
610
|
+
if (!module) {
|
|
611
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Project module can always be removed from project configuration,
|
|
615
|
+
// but modules under .cards/modules must be checked not to be used by
|
|
616
|
+
// other modules.
|
|
617
|
+
if (this.canBeRemoved(dependencies, moduleName)) {
|
|
618
|
+
const orphans = await this.orphanedModules(dependencies, moduleName);
|
|
619
|
+
await deleteDir(module.path);
|
|
620
|
+
for (const moduleToDelete of orphans) {
|
|
621
|
+
const modulePath = join(
|
|
622
|
+
this.project.paths.modulesFolder,
|
|
623
|
+
moduleToDelete,
|
|
624
|
+
);
|
|
625
|
+
await deleteDir(modulePath);
|
|
626
|
+
}
|
|
627
|
+
await this.project.collectModuleResources();
|
|
628
|
+
}
|
|
629
|
+
await this.project.configuration.removeModule(moduleName);
|
|
630
|
+
}
|
|
631
|
+
|
|
470
632
|
/**
|
|
471
633
|
* Imports module from a local file path or a git URL.
|
|
472
634
|
* @param module Module to update. If not provided, updates all modules.
|
package/src/project-settings.ts
CHANGED
|
@@ -57,18 +57,9 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
57
57
|
|
|
58
58
|
// Sets configuration values from file.
|
|
59
59
|
private readSettings() {
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
settings = readJsonFileSync(this.settingPath) as ProjectConfiguration;
|
|
63
|
-
} catch {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Invalid path '${this.settingPath}' to configuration file`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
60
|
+
const settings = readJsonFileSync(this.settingPath) as ProjectConfiguration;
|
|
68
61
|
if (!settings) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
`Invalid path '${this.settingPath}' to configuration file`,
|
|
71
|
-
);
|
|
62
|
+
throw new Error(`File at '${this.settingPath}' is not a valid JSON file`);
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
const valid =
|