@cyberismo/data-handler 0.0.7 → 0.0.9
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/command-handler.d.ts +11 -2
- package/dist/command-handler.js +61 -18
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.js +8 -8
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/calculate.d.ts +7 -44
- package/dist/commands/calculate.js +8 -389
- package/dist/commands/calculate.js.map +1 -1
- package/dist/commands/create.d.ts +7 -4
- package/dist/commands/create.js +42 -15
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +9 -3
- package/dist/commands/edit.js +33 -9
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.d.ts +13 -11
- package/dist/commands/export.js +80 -28
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.d.ts +7 -0
- package/dist/commands/import.js +21 -2
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/move.d.ts +11 -12
- package/dist/commands/move.js +12 -13
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.d.ts +2 -4
- package/dist/commands/remove.js +8 -16
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.d.ts +1 -3
- package/dist/commands/rename.js +3 -6
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +37 -5
- package/dist/commands/show.js +85 -7
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.d.ts +1 -3
- package/dist/commands/transition.js +3 -5
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.d.ts +5 -1
- package/dist/commands/update.js +7 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +7 -8
- package/dist/commands/validate.js +30 -35
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.d.ts +6 -0
- package/dist/containers/card-container.js +61 -0
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/calculation-engine.d.ts +90 -0
- package/dist/containers/project/calculation-engine.js +402 -0
- package/dist/containers/project/calculation-engine.js.map +1 -0
- package/dist/containers/project/resource-collector.d.ts +2 -1
- package/dist/containers/project/resource-collector.js +41 -33
- package/dist/containers/project/resource-collector.js.map +1 -1
- package/dist/containers/project.d.ts +18 -6
- package/dist/containers/project.js +37 -72
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.d.ts +5 -0
- package/dist/containers/template.js +9 -0
- package/dist/containers/template.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/index.d.ts +5 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/macros.d.ts +7 -2
- package/dist/interfaces/project-interfaces.d.ts +21 -0
- package/dist/interfaces/project-interfaces.js +4 -0
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +1 -1
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- 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/common.d.ts +16 -9
- package/dist/macros/common.js +22 -9
- package/dist/macros/common.js.map +1 -1
- package/dist/macros/createCards/index.d.ts +1 -2
- package/dist/macros/createCards/index.js.map +1 -1
- package/dist/macros/graph/index.d.ts +1 -3
- package/dist/macros/graph/index.js +11 -9
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/image/index.d.ts +39 -0
- package/dist/macros/image/index.js +78 -0
- package/dist/macros/image/index.js.map +1 -0
- package/dist/macros/image/metadata.d.ts +18 -0
- package/dist/macros/image/metadata.js +22 -0
- package/dist/macros/image/metadata.js.map +1 -0
- package/dist/macros/include/index.d.ts +32 -0
- package/dist/macros/include/index.js +97 -0
- package/dist/macros/include/index.js.map +1 -0
- package/dist/macros/include/metadata.d.ts +15 -0
- package/dist/macros/include/metadata.js +19 -0
- package/dist/macros/include/metadata.js.map +1 -0
- package/dist/macros/index.d.ts +39 -31
- package/dist/macros/index.js +167 -73
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/percentage/index.d.ts +29 -0
- package/dist/macros/percentage/index.js +36 -0
- package/dist/macros/percentage/index.js.map +1 -0
- package/dist/macros/percentage/metadata.d.ts +15 -0
- package/dist/macros/percentage/metadata.js +19 -0
- package/dist/macros/percentage/metadata.js.map +1 -0
- package/dist/macros/report/index.d.ts +2 -5
- package/dist/macros/report/index.js +20 -12
- package/dist/macros/report/index.js.map +1 -1
- package/dist/macros/scoreCard/index.d.ts +15 -15
- package/dist/macros/scoreCard/index.js +16 -17
- package/dist/macros/scoreCard/index.js.map +1 -1
- package/dist/macros/vega/index.d.ts +28 -0
- package/dist/macros/vega/index.js +27 -0
- package/dist/macros/vega/index.js.map +1 -0
- package/dist/macros/vega/metadata.d.ts +15 -0
- package/dist/macros/vega/metadata.js +7 -0
- package/dist/macros/vega/metadata.js.map +1 -0
- package/dist/macros/vegalite/index.d.ts +27 -0
- package/dist/macros/vegalite/index.js +27 -0
- package/dist/macros/vegalite/index.js.map +1 -0
- package/dist/macros/vegalite/metadata.d.ts +15 -0
- package/dist/macros/vegalite/metadata.js +7 -0
- package/dist/macros/vegalite/metadata.js.map +1 -0
- package/dist/macros/xref/index.d.ts +26 -0
- package/dist/macros/xref/index.js +53 -0
- package/dist/macros/xref/index.js.map +1 -0
- package/dist/macros/xref/metadata.d.ts +15 -0
- package/dist/macros/xref/metadata.js +19 -0
- package/dist/macros/xref/metadata.js.map +1 -0
- package/dist/module-manager.d.ts +17 -4
- package/dist/module-manager.js +192 -58
- package/dist/module-manager.js.map +1 -1
- package/dist/permissions/action-guard.d.ts +2 -2
- package/dist/permissions/action-guard.js +1 -1
- package/dist/permissions/action-guard.js.map +1 -1
- package/dist/project-settings.js +2 -8
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +2 -0
- package/dist/resources/card-type-resource.js +63 -0
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +2 -0
- package/dist/resources/file-resource.js +12 -4
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +19 -0
- package/dist/resources/folder-resource.js +51 -2
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.js +1 -1
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.js +1 -1
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/report-resource.js +1 -1
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +8 -0
- package/dist/resources/resource-object.js +9 -0
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.js +1 -1
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +2 -0
- package/dist/resources/workflow-resource.js +53 -9
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/svg/index.d.ts +15 -0
- package/dist/svg/index.js +16 -0
- package/dist/svg/index.js.map +1 -0
- package/dist/svg/lib.d.ts +9 -0
- package/dist/svg/lib.js +26 -0
- package/dist/svg/lib.js.map +1 -0
- package/dist/svg/percentage.d.ts +25 -0
- package/dist/svg/percentage.js +90 -0
- package/dist/svg/percentage.js.map +1 -0
- package/dist/svg/scoreCard.d.ts +19 -0
- package/dist/svg/scoreCard.js +55 -0
- package/dist/svg/scoreCard.js.map +1 -0
- package/dist/types/queries.d.ts +8 -7
- package/dist/types/queries.js.map +1 -1
- package/dist/utils/card-utils.d.ts +6 -0
- package/dist/utils/card-utils.js +12 -0
- package/dist/utils/card-utils.js.map +1 -1
- package/dist/utils/clingo-facts.d.ts +2 -1
- package/dist/utils/clingo-facts.js +19 -2
- package/dist/utils/clingo-facts.js.map +1 -1
- package/dist/utils/clingo-parser.d.ts +1 -0
- package/dist/utils/clingo-parser.js +22 -100
- package/dist/utils/clingo-parser.js.map +1 -1
- package/dist/utils/constants.d.ts +7 -0
- package/dist/utils/constants.js +14 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/csv.js.map +1 -1
- package/dist/utils/report.d.ts +17 -3
- package/dist/utils/report.js +38 -2
- package/dist/utils/report.js.map +1 -1
- package/dist/utils/resource-utils.d.ts +1 -0
- package/dist/utils/resource-utils.js +9 -0
- package/dist/utils/resource-utils.js.map +1 -1
- package/dist/utils/user-preferences.d.ts +1 -11
- package/dist/utils/user-preferences.js +30 -13
- package/dist/utils/user-preferences.js.map +1 -1
- package/dist/utils/validate.d.ts +2 -3
- package/dist/utils/validate.js +2 -2
- package/dist/utils/validate.js.map +1 -1
- package/package.json +8 -6
- package/src/command-handler.ts +96 -17
- package/src/command-manager.ts +8 -8
- package/src/commands/calculate.ts +11 -525
- package/src/commands/create.ts +53 -16
- package/src/commands/edit.ts +53 -11
- package/src/commands/export.ts +108 -34
- package/src/commands/import.ts +31 -2
- package/src/commands/move.ts +12 -15
- package/src/commands/remove.ts +10 -19
- package/src/commands/rename.ts +3 -12
- package/src/commands/show.ts +121 -8
- package/src/commands/transition.ts +3 -7
- package/src/commands/update.ts +6 -0
- package/src/commands/validate.ts +39 -47
- package/src/containers/card-container.ts +74 -0
- package/src/containers/project/calculation-engine.ts +535 -0
- package/src/containers/project/resource-collector.ts +45 -26
- package/src/containers/project.ts +66 -84
- package/src/containers/template.ts +16 -0
- package/src/exceptions/index.ts +36 -0
- package/src/index.ts +13 -2
- package/src/interfaces/macros.ts +7 -1
- package/src/interfaces/project-interfaces.ts +27 -0
- package/src/interfaces/resource-interfaces.ts +1 -0
- package/src/macros/base-macro.ts +89 -25
- package/src/macros/common.ts +22 -9
- package/src/macros/createCards/index.ts +1 -2
- package/src/macros/graph/index.ts +17 -12
- package/src/macros/image/index.ts +121 -0
- package/src/macros/image/metadata.ts +25 -0
- package/src/macros/include/index.ts +147 -0
- package/src/macros/include/metadata.ts +22 -0
- package/src/macros/index.ts +179 -100
- package/src/macros/percentage/index.ts +54 -0
- package/src/macros/percentage/metadata.ts +22 -0
- package/src/macros/report/index.ts +22 -17
- package/src/macros/scoreCard/index.ts +23 -23
- package/src/macros/vega/index.ts +55 -0
- package/src/macros/vega/metadata.ts +21 -0
- package/src/macros/vegalite/index.ts +50 -0
- package/src/macros/vegalite/metadata.ts +21 -0
- package/src/macros/xref/index.ts +73 -0
- package/src/macros/xref/metadata.ts +22 -0
- package/src/module-manager.ts +241 -69
- package/src/permissions/action-guard.ts +3 -3
- package/src/project-settings.ts +2 -11
- package/src/resources/card-type-resource.ts +100 -0
- package/src/resources/file-resource.ts +16 -4
- package/src/resources/folder-resource.ts +59 -2
- package/src/resources/graph-model-resource.ts +1 -1
- package/src/resources/graph-view-resource.ts +1 -1
- package/src/resources/report-resource.ts +1 -1
- package/src/resources/resource-object.ts +14 -0
- package/src/resources/template-resource.ts +1 -1
- package/src/resources/workflow-resource.ts +68 -13
- package/src/svg/index.ts +15 -0
- package/src/svg/lib.ts +31 -0
- package/src/svg/percentage.ts +97 -0
- package/src/svg/scoreCard.ts +88 -0
- package/src/types/queries.ts +8 -7
- package/src/types/string-pixel-width.d.ts +23 -0
- package/src/utils/card-utils.ts +13 -0
- package/src/utils/clingo-facts.ts +65 -3
- package/src/utils/clingo-parser.ts +31 -144
- package/src/utils/constants.ts +16 -0
- package/src/utils/csv.ts +1 -1
- package/src/utils/report.ts +45 -4
- package/src/utils/resource-utils.ts +9 -0
- package/src/utils/user-preferences.ts +32 -14
- package/src/utils/validate.ts +3 -3
package/src/module-manager.ts
CHANGED
|
@@ -29,10 +29,12 @@ import { readJsonFile } from './utils/json.js';
|
|
|
29
29
|
import { Validate } from './commands/index.js';
|
|
30
30
|
|
|
31
31
|
const FILE_PROTOCOL = 'file:';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
32
|
+
const HTTPS_PROTOCOL = 'https:';
|
|
33
|
+
|
|
34
|
+
// When dependencies are built to a map, use map that has
|
|
35
|
+
// key: module name,
|
|
36
|
+
// value: list of unique prefixes
|
|
37
|
+
type DependencyGraph = Map<string, Set<string>>;
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* Class that handles module updates and imports.
|
|
@@ -40,6 +42,8 @@ const DEFAULT_TIMEOUT = 10000;
|
|
|
40
42
|
export class ModuleManager {
|
|
41
43
|
private modules: ModuleSetting[] = [];
|
|
42
44
|
private tempModulesDir: string = '';
|
|
45
|
+
private defaultBranchCache: Map<string, string> = new Map();
|
|
46
|
+
|
|
43
47
|
constructor(private project: Project) {
|
|
44
48
|
this.tempModulesDir = join(this.project.paths.tempFolder, 'modules');
|
|
45
49
|
}
|
|
@@ -53,6 +57,35 @@ export class ModuleManager {
|
|
|
53
57
|
await this.project.collectModuleResources();
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
// Creates a map of what dependencies each module depend from.
|
|
61
|
+
private async buildDependencyGraph(
|
|
62
|
+
dependencies: ModuleSetting[],
|
|
63
|
+
): Promise<DependencyGraph> {
|
|
64
|
+
const dependencyNames = dependencies.map((item) => item.name);
|
|
65
|
+
const dependencyGraph = new Map<string, Set<string>>() as DependencyGraph;
|
|
66
|
+
for (const dependency of dependencyNames) {
|
|
67
|
+
dependencyGraph.set(
|
|
68
|
+
dependency,
|
|
69
|
+
await this.transientDependencies(dependency),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return dependencyGraph;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Returns 'true' if 'moduleName' can be removed.
|
|
76
|
+
private canBeRemoved(
|
|
77
|
+
dependencies: DependencyGraph,
|
|
78
|
+
moduleName: string,
|
|
79
|
+
): boolean {
|
|
80
|
+
let unused = true;
|
|
81
|
+
dependencies.forEach((transientDependencies, key) => {
|
|
82
|
+
if (key !== moduleName && transientDependencies.has(moduleName)) {
|
|
83
|
+
unused = false;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return unused;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
// Handles cloning of a repository.
|
|
57
90
|
private async clone(
|
|
58
91
|
module: ModuleSetting,
|
|
@@ -67,10 +100,15 @@ export class ModuleManager {
|
|
|
67
100
|
|
|
68
101
|
let remote = module.location;
|
|
69
102
|
if (module.private) {
|
|
70
|
-
if (
|
|
103
|
+
if (
|
|
104
|
+
credentials &&
|
|
105
|
+
credentials?.username &&
|
|
106
|
+
credentials?.token &&
|
|
107
|
+
module.location.startsWith(HTTPS_PROTOCOL)
|
|
108
|
+
) {
|
|
71
109
|
if (verbose) {
|
|
72
110
|
console.log(
|
|
73
|
-
`... Using credentials '${credentials?.username}' for cloning '${module.name}'`,
|
|
111
|
+
`... Using HTTPS with credentials '${credentials?.username}' for cloning '${module.name}'`,
|
|
74
112
|
);
|
|
75
113
|
}
|
|
76
114
|
try {
|
|
@@ -83,21 +121,27 @@ export class ModuleManager {
|
|
|
83
121
|
} catch {
|
|
84
122
|
throw new Error(`Invalid repository URL: ${module.location}`);
|
|
85
123
|
}
|
|
86
|
-
} else {
|
|
124
|
+
} else if (module.location.startsWith('git@')) {
|
|
87
125
|
if (verbose) {
|
|
88
|
-
console.log(`...
|
|
126
|
+
console.log(`... Using SSH for cloning '${module.name}'`);
|
|
89
127
|
}
|
|
90
128
|
}
|
|
129
|
+
} else {
|
|
130
|
+
if (verbose) {
|
|
131
|
+
console.log(
|
|
132
|
+
`... Using HTTPS without credentials for cloning '${module.name}'`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
try {
|
|
94
138
|
await mkdir(this.tempModulesDir, { recursive: true });
|
|
95
|
-
const cloneOptions = this.setCloneOptions(module);
|
|
139
|
+
const cloneOptions = await this.setCloneOptions(module);
|
|
96
140
|
await rm(destinationPath, { recursive: true, force: true });
|
|
97
141
|
|
|
98
142
|
const git: SimpleGit = simpleGit({
|
|
99
143
|
timeout: {
|
|
100
|
-
block:
|
|
144
|
+
block: this.gitTimeout(),
|
|
101
145
|
},
|
|
102
146
|
});
|
|
103
147
|
|
|
@@ -110,7 +154,6 @@ export class ModuleManager {
|
|
|
110
154
|
console.log(`... Cloned '${module.name}' to a temporary folder`);
|
|
111
155
|
}
|
|
112
156
|
} catch (error) {
|
|
113
|
-
console.error(error);
|
|
114
157
|
if (error instanceof Error)
|
|
115
158
|
throw new Error(
|
|
116
159
|
`Failed to clone module '${module.name}': ${error.message}`,
|
|
@@ -143,6 +186,61 @@ export class ModuleManager {
|
|
|
143
186
|
}
|
|
144
187
|
}
|
|
145
188
|
|
|
189
|
+
// Gets the default branch for a repository from remote or cache
|
|
190
|
+
private async defaultBranch(module: ModuleSetting): Promise<string> {
|
|
191
|
+
if (this.defaultBranchCache.has(module.location)) {
|
|
192
|
+
return this.defaultBranchCache.get(module.location)!;
|
|
193
|
+
}
|
|
194
|
+
// Set the default branch if branch was not specified
|
|
195
|
+
if (!module.branch) {
|
|
196
|
+
const destinationPath = join(this.tempModulesDir, module.name);
|
|
197
|
+
// Only return path after cloning
|
|
198
|
+
if (pathExists(destinationPath)) {
|
|
199
|
+
const git: SimpleGit = simpleGit({
|
|
200
|
+
timeout: {
|
|
201
|
+
block: this.gitTimeout(),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
const options = ['--abbrev-ref', 'HEAD'];
|
|
205
|
+
const defaultBranch = await git.cwd(destinationPath).revparse(options);
|
|
206
|
+
this.defaultBranchCache.set(module.location, defaultBranch);
|
|
207
|
+
return defaultBranch;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// The actual default branch will be updated later (after cloning).
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Fetches direct dependencies of a module.
|
|
215
|
+
private async dependencies(moduleName: string): Promise<Set<string>> {
|
|
216
|
+
const allModules = await this.project.modules();
|
|
217
|
+
if (!allModules) return new Set();
|
|
218
|
+
const module = allModules.find((m) => m.name === moduleName);
|
|
219
|
+
if (!module) {
|
|
220
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
221
|
+
}
|
|
222
|
+
const modulePath = join(module.path, module.name, 'cardsConfig.json');
|
|
223
|
+
const moduleConfiguration = (await readJsonFile(
|
|
224
|
+
modulePath,
|
|
225
|
+
)) as ProjectConfiguration;
|
|
226
|
+
return moduleConfiguration.modules
|
|
227
|
+
? new Set(moduleConfiguration.modules.map((m) => m.name))
|
|
228
|
+
: new Set();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Increase timeout for CI environments and add platform-specific adjustments
|
|
232
|
+
private gitTimeout(): number {
|
|
233
|
+
const baseTimeout = 15000;
|
|
234
|
+
const isCI = process.env.CI;
|
|
235
|
+
const isWindows = process.platform === 'win32';
|
|
236
|
+
|
|
237
|
+
let timeout = baseTimeout;
|
|
238
|
+
if (isCI) timeout *= 2; // Double timeout in CI
|
|
239
|
+
if (isWindows) timeout *= 1.5; // 50% more time on Windows
|
|
240
|
+
|
|
241
|
+
return timeout;
|
|
242
|
+
}
|
|
243
|
+
|
|
146
244
|
// Collects one module's dependency prefixes to 'this.modules'.
|
|
147
245
|
// Note that there can be duplicate entries.
|
|
148
246
|
private async doCollectModulePrefix(
|
|
@@ -167,16 +265,17 @@ export class ModuleManager {
|
|
|
167
265
|
|
|
168
266
|
// Updates one module that is read from local file system.
|
|
169
267
|
private async handleFileModule(module: ModuleSetting) {
|
|
170
|
-
this.
|
|
268
|
+
this.stripProtocolFromLocation(module);
|
|
171
269
|
await this.remove(module);
|
|
172
|
-
await this.importFromFolder(module);
|
|
270
|
+
await this.importFromFolder(module.location, module.name);
|
|
173
271
|
}
|
|
174
272
|
|
|
175
273
|
// Updates one module that is received from Git.
|
|
176
274
|
private async handleGitModule(module: ModuleSetting) {
|
|
177
275
|
await this.clone(module);
|
|
276
|
+
const tempLocation = join(this.tempModulesDir, module.name);
|
|
178
277
|
await this.remove(module);
|
|
179
|
-
await this.
|
|
278
|
+
await this.importFromFolder(tempLocation, module.name);
|
|
180
279
|
}
|
|
181
280
|
|
|
182
281
|
// Updates one module.
|
|
@@ -186,19 +285,11 @@ export class ModuleManager {
|
|
|
186
285
|
: this.handleFileModule(module);
|
|
187
286
|
}
|
|
188
287
|
|
|
189
|
-
//
|
|
190
|
-
private async importFromFolder(
|
|
191
|
-
await this.importFileModule(
|
|
288
|
+
// Imports from a given folder. Is used both for .temp/<module name> and file locations.
|
|
289
|
+
private async importFromFolder(path: string, name: string) {
|
|
290
|
+
await this.importFileModule(path);
|
|
192
291
|
console.log(
|
|
193
|
-
`... Imported module '${
|
|
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));
|
|
200
|
-
console.log(
|
|
201
|
-
`... Imported module '${module.name}' to '${this.project.configuration.name}'`,
|
|
292
|
+
`... Imported module '${name}' to '${this.project.configuration.name}'`,
|
|
202
293
|
);
|
|
203
294
|
}
|
|
204
295
|
|
|
@@ -211,7 +302,41 @@ export class ModuleManager {
|
|
|
211
302
|
// Returns true if module is imported from git.
|
|
212
303
|
private isGitModule(module: ModuleSetting): boolean {
|
|
213
304
|
if (!module.location) return false;
|
|
214
|
-
return
|
|
305
|
+
return (
|
|
306
|
+
module.location.startsWith('https:') || module.location.startsWith('git@')
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Collect modules that could be removed from .cards/modules when
|
|
311
|
+
// 'moduleName' is removed.
|
|
312
|
+
private async orphanedModules(
|
|
313
|
+
dependencies: DependencyGraph,
|
|
314
|
+
moduleName: string,
|
|
315
|
+
): Promise<string[]> {
|
|
316
|
+
const projectModules = this.project.configuration.modules;
|
|
317
|
+
const removableTransientModules: string[] = [];
|
|
318
|
+
if (dependencies.has(moduleName)) {
|
|
319
|
+
const deps = dependencies.get(moduleName);
|
|
320
|
+
for (const dependency of deps!) {
|
|
321
|
+
const projectDependency = projectModules.some(
|
|
322
|
+
(item) => item.name === dependency,
|
|
323
|
+
);
|
|
324
|
+
if (projectDependency) continue;
|
|
325
|
+
|
|
326
|
+
let orphanModule = true;
|
|
327
|
+
dependencies.forEach((transientDependencies, key) => {
|
|
328
|
+
if (key === moduleName) return;
|
|
329
|
+
if (transientDependencies.has(dependency)) {
|
|
330
|
+
orphanModule = false;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (orphanModule) {
|
|
335
|
+
removableTransientModules.push(dependency);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return removableTransientModules;
|
|
215
340
|
}
|
|
216
341
|
|
|
217
342
|
// Prepares '.temp/modules' for cloning
|
|
@@ -231,11 +356,6 @@ export class ModuleManager {
|
|
|
231
356
|
}
|
|
232
357
|
}
|
|
233
358
|
|
|
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
359
|
// Handles removing an imported module.
|
|
240
360
|
private async remove(module: ModuleSetting) {
|
|
241
361
|
try {
|
|
@@ -249,37 +369,19 @@ export class ModuleManager {
|
|
|
249
369
|
}
|
|
250
370
|
}
|
|
251
371
|
|
|
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
372
|
// Checks for duplicate ModuleSetting entries and throws an error if modules
|
|
271
373
|
// with the same name have different branches or locations.
|
|
272
|
-
// Treats undefined branch, empty string branch, and
|
|
374
|
+
// Treats undefined branch, empty string branch, and default branch as equivalent.
|
|
273
375
|
// Returns an array with duplicate entries removed
|
|
274
|
-
private removeDuplicates(
|
|
376
|
+
private async removeDuplicates(
|
|
377
|
+
modules: ModuleSetting[],
|
|
378
|
+
): Promise<ModuleSetting[]> {
|
|
275
379
|
const moduleMap = new Map<string, ModuleSetting>();
|
|
276
380
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
return branch;
|
|
381
|
+
// Normalize branch names by checking against the default branch for each module
|
|
382
|
+
|
|
383
|
+
const normalizeBranch = async (module: ModuleSetting) => {
|
|
384
|
+
return module.branch ? module.branch! : await this.defaultBranch(module);
|
|
283
385
|
};
|
|
284
386
|
|
|
285
387
|
for (const module of modules) {
|
|
@@ -291,8 +393,8 @@ export class ModuleManager {
|
|
|
291
393
|
) {
|
|
292
394
|
throw new Error(
|
|
293
395
|
`Module conflict: '${module.name}' has different access:\n` +
|
|
294
|
-
` - ${existingModule.private
|
|
295
|
-
` - ${module.private
|
|
396
|
+
` - ${Boolean(existingModule.private)}\n` +
|
|
397
|
+
` - ${Boolean(module.private)}`,
|
|
296
398
|
);
|
|
297
399
|
}
|
|
298
400
|
if (existingModule.location !== module.location) {
|
|
@@ -302,8 +404,8 @@ export class ModuleManager {
|
|
|
302
404
|
` - ${module.location}`,
|
|
303
405
|
);
|
|
304
406
|
}
|
|
305
|
-
const existingBranch = normalizeBranch(existingModule
|
|
306
|
-
const newBranch = normalizeBranch(module
|
|
407
|
+
const existingBranch = await normalizeBranch(existingModule);
|
|
408
|
+
const newBranch = await normalizeBranch(module);
|
|
307
409
|
|
|
308
410
|
if (existingBranch !== newBranch) {
|
|
309
411
|
throw new Error(
|
|
@@ -319,6 +421,15 @@ export class ModuleManager {
|
|
|
319
421
|
return Array.from(moduleMap.values());
|
|
320
422
|
}
|
|
321
423
|
|
|
424
|
+
// Remove module files.
|
|
425
|
+
private async removeModuleFiles(moduleName: string) {
|
|
426
|
+
const module = await this.project.module(moduleName);
|
|
427
|
+
if (!module) {
|
|
428
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
429
|
+
}
|
|
430
|
+
await deleteDir(module.path);
|
|
431
|
+
}
|
|
432
|
+
|
|
322
433
|
// Gets repository name from gitUrl
|
|
323
434
|
private repositoryName(gitUrl: string): string {
|
|
324
435
|
const last = gitUrl.lastIndexOf('/');
|
|
@@ -326,19 +437,47 @@ export class ModuleManager {
|
|
|
326
437
|
return repoName;
|
|
327
438
|
}
|
|
328
439
|
|
|
329
|
-
// Sets cloning options.
|
|
330
|
-
private setCloneOptions(module: ModuleSetting) {
|
|
440
|
+
// Sets cloning options with support for default branch.
|
|
441
|
+
private async setCloneOptions(module: ModuleSetting): Promise<string[]> {
|
|
331
442
|
const cloneOptions = ['--depth', '1'];
|
|
443
|
+
const defaultBranch = await this.defaultBranch(module);
|
|
444
|
+
// Only specify branch if it's different from the default branch
|
|
332
445
|
if (
|
|
333
446
|
module.branch &&
|
|
334
447
|
module.branch !== '' &&
|
|
335
|
-
module.branch !==
|
|
448
|
+
module.branch !== defaultBranch
|
|
336
449
|
) {
|
|
337
450
|
cloneOptions.push('--branch', module.branch);
|
|
338
451
|
}
|
|
339
452
|
return cloneOptions;
|
|
340
453
|
}
|
|
341
454
|
|
|
455
|
+
// Updates module's 'location' not to have 'protocol:' in the beginning (only for "file:" needed).
|
|
456
|
+
private stripProtocolFromLocation(module: ModuleSetting) {
|
|
457
|
+
const protocol = this.isFileModule(module) ? 'file' : 'git';
|
|
458
|
+
module.location = module.location.substring(
|
|
459
|
+
protocol.length + 1,
|
|
460
|
+
module.location.length,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Fetches all dependencies for a module.
|
|
465
|
+
private async transientDependencies(
|
|
466
|
+
moduleName: string,
|
|
467
|
+
): Promise<Set<string>> {
|
|
468
|
+
const dependencies = await this.dependencies(moduleName);
|
|
469
|
+
let transientDependencies: Set<string> = new Set(dependencies);
|
|
470
|
+
for (const dependency of dependencies) {
|
|
471
|
+
const depTransients = await this.transientDependencies(dependency);
|
|
472
|
+
transientDependencies = new Set([
|
|
473
|
+
...transientDependencies,
|
|
474
|
+
...depTransients,
|
|
475
|
+
]);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return transientDependencies;
|
|
479
|
+
}
|
|
480
|
+
|
|
342
481
|
// Updates modules in the project.
|
|
343
482
|
private async update(module?: ModuleSetting, credentials?: Credentials) {
|
|
344
483
|
// Prints dots every half second so that user knows that something is ongoing
|
|
@@ -350,7 +489,9 @@ export class ModuleManager {
|
|
|
350
489
|
// Stops the above, and shows results
|
|
351
490
|
function finished(interval: NodeJS.Timeout, modules: string[]) {
|
|
352
491
|
clearInterval(interval);
|
|
353
|
-
|
|
492
|
+
if (modules.length > 0) {
|
|
493
|
+
console.log(`\n... Found modules: ${modules.join(', ')}`);
|
|
494
|
+
}
|
|
354
495
|
}
|
|
355
496
|
|
|
356
497
|
await this.prepare();
|
|
@@ -366,8 +507,7 @@ export class ModuleManager {
|
|
|
366
507
|
let uniqueModules: ModuleSetting[] = [];
|
|
367
508
|
try {
|
|
368
509
|
await this.collectModulePrefixes(modules, credentials);
|
|
369
|
-
|
|
370
|
-
uniqueModules = this.removeDuplicates(this.modules);
|
|
510
|
+
uniqueModules = await this.removeDuplicates(this.modules);
|
|
371
511
|
} finally {
|
|
372
512
|
finished(
|
|
373
513
|
dotInterval,
|
|
@@ -380,7 +520,6 @@ export class ModuleManager {
|
|
|
380
520
|
promises.push(this.handleModule(module)),
|
|
381
521
|
);
|
|
382
522
|
await Promise.all(promises);
|
|
383
|
-
|
|
384
523
|
await deleteDir(this.tempModulesDir);
|
|
385
524
|
await this.project.collectModuleResources();
|
|
386
525
|
}
|
|
@@ -467,6 +606,39 @@ export class ModuleManager {
|
|
|
467
606
|
return modulePrefix;
|
|
468
607
|
}
|
|
469
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Removed module from project.
|
|
611
|
+
* If module is not used by any other modules, then will remove the module from disk as well.
|
|
612
|
+
* Otherwise, only updates project configuration.
|
|
613
|
+
* @param moduleName Name of the module to remove
|
|
614
|
+
*/
|
|
615
|
+
public async removeModule(moduleName: string) {
|
|
616
|
+
const projectModules = this.project.configuration.modules;
|
|
617
|
+
const dependencies = await this.buildDependencyGraph(projectModules);
|
|
618
|
+
const module = await this.project.module(moduleName);
|
|
619
|
+
|
|
620
|
+
if (!module) {
|
|
621
|
+
throw new Error(`Module '${moduleName}' not found`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Project module can always be removed from project configuration,
|
|
625
|
+
// but modules under .cards/modules must be checked not to be used by
|
|
626
|
+
// other modules.
|
|
627
|
+
if (this.canBeRemoved(dependencies, moduleName)) {
|
|
628
|
+
const orphans = await this.orphanedModules(dependencies, moduleName);
|
|
629
|
+
await deleteDir(module.path);
|
|
630
|
+
for (const moduleToDelete of orphans) {
|
|
631
|
+
const modulePath = join(
|
|
632
|
+
this.project.paths.modulesFolder,
|
|
633
|
+
moduleToDelete,
|
|
634
|
+
);
|
|
635
|
+
await deleteDir(modulePath);
|
|
636
|
+
}
|
|
637
|
+
await this.project.collectModuleResources();
|
|
638
|
+
}
|
|
639
|
+
await this.project.configuration.removeModule(moduleName);
|
|
640
|
+
}
|
|
641
|
+
|
|
470
642
|
/**
|
|
471
643
|
* Imports module from a local file path or a git URL.
|
|
472
644
|
* @param module Module to update. If not provided, updates all modules.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type {
|
|
13
|
+
import type { CalculationEngine } from '../containers/project/calculation-engine.js';
|
|
14
14
|
import type { DeniedOperationCollection } from '../types/queries.js';
|
|
15
15
|
|
|
16
16
|
export type Action = keyof DeniedOperationCollection;
|
|
@@ -25,7 +25,7 @@ function checkOperation<T extends { errorMessage: string }>(data: Array<T>) {
|
|
|
25
25
|
* This class is used to guard actions from being used without permissions
|
|
26
26
|
*/
|
|
27
27
|
export class ActionGuard {
|
|
28
|
-
constructor(private calculate:
|
|
28
|
+
constructor(private calculate: CalculationEngine) {}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Checks whether an action can be done
|
|
@@ -39,7 +39,7 @@ export class ActionGuard {
|
|
|
39
39
|
param?: string,
|
|
40
40
|
) {
|
|
41
41
|
await this.calculate.generate();
|
|
42
|
-
const cards = await this.calculate.runQuery('card', {
|
|
42
|
+
const cards = await this.calculate.runQuery('card', 'localApp', {
|
|
43
43
|
cardKey,
|
|
44
44
|
});
|
|
45
45
|
if (cards.length === 0) {
|
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 =
|
|
@@ -153,6 +153,50 @@ export class CardTypeResource extends FileResource {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// Apply state mapping to all cards using this card type.
|
|
157
|
+
// Checks that all states in the current workflow are updated.
|
|
158
|
+
private async handleWorkflowChange<Type>(
|
|
159
|
+
stateMapping: Record<string, string>,
|
|
160
|
+
op: ChangeOperation<Type>,
|
|
161
|
+
) {
|
|
162
|
+
await this.verifyStateMapping(stateMapping, op);
|
|
163
|
+
const cards = await this.collectCards(
|
|
164
|
+
{
|
|
165
|
+
metadata: true,
|
|
166
|
+
content: true,
|
|
167
|
+
},
|
|
168
|
+
this.content.name,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const unmappedStates: string[] = [];
|
|
172
|
+
|
|
173
|
+
// Update each card's workflowState if it has a mapping
|
|
174
|
+
const updatePromises = cards.map(async (card) => {
|
|
175
|
+
if (card.metadata && card.metadata.workflowState) {
|
|
176
|
+
const currentState = card.metadata.workflowState as string;
|
|
177
|
+
const newState = stateMapping[currentState];
|
|
178
|
+
|
|
179
|
+
if (newState && newState !== currentState) {
|
|
180
|
+
this.logger.info(
|
|
181
|
+
`Updating card '${card.key}': ${currentState} -> ${newState}`,
|
|
182
|
+
);
|
|
183
|
+
card.metadata.workflowState = newState;
|
|
184
|
+
await this.project.updateCardMetadata(card, card.metadata);
|
|
185
|
+
} else if (!newState && !unmappedStates.includes(currentState)) {
|
|
186
|
+
unmappedStates.push(currentState);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await Promise.all(updatePromises);
|
|
192
|
+
|
|
193
|
+
if (unmappedStates.length > 0) {
|
|
194
|
+
this.logger.warn(
|
|
195
|
+
`Found unmapped states that were not updated: ${unmappedStates.join(', ')}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
156
200
|
// Checks if field type exists in this card type.
|
|
157
201
|
private async hasFieldType(field: Partial<CustomField>): Promise<boolean> {
|
|
158
202
|
return this.data.customFields.some((item) => item.name === field.name);
|
|
@@ -315,6 +359,57 @@ export class CardTypeResource extends FileResource {
|
|
|
315
359
|
}
|
|
316
360
|
}
|
|
317
361
|
|
|
362
|
+
// Verifies that:
|
|
363
|
+
// - all states in the current workflow are covered in the state mapping
|
|
364
|
+
// - the states are correct
|
|
365
|
+
private async verifyStateMapping<Type>(
|
|
366
|
+
stateMapping: Record<string, string>,
|
|
367
|
+
op: ChangeOperation<Type>,
|
|
368
|
+
) {
|
|
369
|
+
const currentWorkflowName = op.target as string;
|
|
370
|
+
const currentWorkflow =
|
|
371
|
+
await this.project.resource<Workflow>(currentWorkflowName);
|
|
372
|
+
if (!currentWorkflow) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`Workflow '${currentWorkflowName}' does not exist in the project`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const newWorkflow = await this.project.resource<Workflow>(op.to as string);
|
|
379
|
+
if (!newWorkflow) {
|
|
380
|
+
throw new Error(`Workflow '${op.to}' does not exist in the project`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const currentWorkflowStates = currentWorkflow.states.map(
|
|
384
|
+
(state) => state.name,
|
|
385
|
+
);
|
|
386
|
+
const mappedSourceStates = Object.keys(stateMapping);
|
|
387
|
+
const unmappedCurrentStates = currentWorkflowStates.filter(
|
|
388
|
+
(stateName) => !mappedSourceStates.includes(stateName),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (unmappedCurrentStates.length > 0) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`State mapping validation failed: The following states exist in the current workflow '${currentWorkflowName}' ` +
|
|
394
|
+
`but are not mapped from in the state mapping JSON file: ${unmappedCurrentStates.join(', ')}. ` +
|
|
395
|
+
`Please ensure all states in the current workflow are accounted for in the mapping to ensure all cards are properly updated.`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Also verify that all target states exist in the new workflow
|
|
400
|
+
const newWorkflowStates = newWorkflow.states.map((state) => state.name);
|
|
401
|
+
const mappedTargetStates = Object.values(stateMapping);
|
|
402
|
+
const invalidTargetStates = mappedTargetStates.filter(
|
|
403
|
+
(stateName) => !newWorkflowStates.includes(stateName),
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (invalidTargetStates.length > 0) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`State mapping validation failed: The following target states in the mapping do not exist in the new workflow '${op.to}': ${invalidTargetStates.join(', ')}.`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
318
413
|
/**
|
|
319
414
|
* Creates a new card type object. Base class writes the object to disk automatically.
|
|
320
415
|
* @param workflowName Workflow name that this card type uses.
|
|
@@ -404,7 +499,12 @@ export class CardTypeResource extends FileResource {
|
|
|
404
499
|
content.optionallyVisibleFields as Type[],
|
|
405
500
|
) as string[];
|
|
406
501
|
} else if (key === 'workflow') {
|
|
502
|
+
const changeOp = op as ChangeOperation<string>;
|
|
503
|
+
const stateMapping = changeOp.mappingTable?.stateMapping || {};
|
|
407
504
|
content.workflow = super.handleScalar(op) as string;
|
|
505
|
+
if (Object.keys(stateMapping).length > 0) {
|
|
506
|
+
await this.handleWorkflowChange(stateMapping, changeOp);
|
|
507
|
+
}
|
|
408
508
|
} else if (key === 'customFields') {
|
|
409
509
|
await this.validateFieldType(key, op);
|
|
410
510
|
content.customFields = super.handleArray(
|
|
@@ -137,6 +137,16 @@ export class FileResource extends ResourceObject {
|
|
|
137
137
|
return cards;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// Get (resource folder) type name
|
|
141
|
+
protected get getType() {
|
|
142
|
+
return super.getType;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get logger instance
|
|
146
|
+
protected get logger() {
|
|
147
|
+
return super.getLogger(this.getType);
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
// Initialize the resource.
|
|
141
151
|
protected initialize() {
|
|
142
152
|
if (this.resourceName.type === '') {
|
|
@@ -301,9 +311,9 @@ export class FileResource extends ResourceObject {
|
|
|
301
311
|
) {
|
|
302
312
|
function toValue(op: Operation<Type>) {
|
|
303
313
|
if (op.name === 'rank') return op.newIndex;
|
|
304
|
-
if (op.name === 'add') return op.target;
|
|
305
|
-
if (op.name === 'remove') return op.target;
|
|
306
|
-
if (op.name === 'change') return op.to;
|
|
314
|
+
if (op.name === 'add') return JSON.stringify(op.target);
|
|
315
|
+
if (op.name === 'remove') return JSON.stringify(op.target);
|
|
316
|
+
if (op.name === 'change') return JSON.stringify(op.to);
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
// Check that new name is valid.
|
|
@@ -320,7 +330,9 @@ export class FileResource extends ResourceObject {
|
|
|
320
330
|
} catch (error) {
|
|
321
331
|
if (error instanceof Error) {
|
|
322
332
|
const errorValue = typeof op === 'object' ? toValue(op) : op;
|
|
323
|
-
throw new Error(
|
|
333
|
+
throw new Error(
|
|
334
|
+
`Cannot ${op.name} '${key}' --> '${errorValue}: ${error.message}'`,
|
|
335
|
+
);
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
|