@cyberismo/data-handler 0.0.17 → 0.0.19
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 +2 -0
- package/dist/command-handler.js +26 -2
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +2 -0
- package/dist/command-manager.js +3 -0
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.d.ts +3 -1
- package/dist/commands/create.js +10 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/migrate.d.ts +33 -0
- package/dist/commands/migrate.js +66 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/containers/project/card-cache.js +13 -1
- package/dist/containers/project/card-cache.js.map +1 -1
- package/dist/containers/project/project-paths.d.ts +1 -0
- package/dist/containers/project/project-paths.js +5 -2
- package/dist/containers/project/project-paths.js.map +1 -1
- package/dist/containers/project.d.ts +10 -0
- package/dist/containers/project.js +39 -2
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.js +2 -0
- package/dist/containers/template.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +6 -1
- package/dist/interfaces/project-interfaces.d.ts +4 -0
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/migrations/index.d.ts +14 -0
- package/dist/migrations/index.js +14 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-executor.d.ts +79 -0
- package/dist/migrations/migration-executor.js +312 -0
- package/dist/migrations/migration-executor.js.map +1 -0
- package/dist/migrations/migration-worker.d.ts +13 -0
- package/dist/migrations/migration-worker.js +156 -0
- package/dist/migrations/migration-worker.js.map +1 -0
- package/dist/migrations/worker-executor.d.ts +24 -0
- package/dist/migrations/worker-executor.js +157 -0
- package/dist/migrations/worker-executor.js.map +1 -0
- package/dist/project-settings.d.ts +2 -0
- package/dist/project-settings.js +7 -0
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +9 -0
- package/dist/resources/calculation-resource.js +13 -2
- package/dist/resources/calculation-resource.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +12 -3
- package/dist/resources/card-type-resource.js +73 -91
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +10 -1
- package/dist/resources/field-type-resource.js +62 -61
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +27 -2
- package/dist/resources/file-resource.js +46 -8
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +5 -0
- package/dist/resources/graph-model-resource.js +6 -0
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +5 -0
- package/dist/resources/graph-view-resource.js +6 -0
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +11 -1
- package/dist/resources/link-type-resource.js +54 -30
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +6 -1
- package/dist/resources/report-resource.js +7 -1
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +22 -7
- package/dist/resources/resource-object.js +44 -15
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +5 -1
- package/dist/resources/template-resource.js +11 -27
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +7 -3
- package/dist/resources/workflow-resource.js +90 -82
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/card-utils.d.ts +1 -1
- package/dist/utils/common-utils.d.ts +8 -0
- package/dist/utils/common-utils.js +14 -0
- package/dist/utils/common-utils.js.map +1 -1
- package/dist/utils/file-utils.d.ts +15 -3
- package/dist/utils/file-utils.js +48 -9
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/json.js +2 -2
- package/dist/utils/json.js.map +1 -1
- package/package.json +5 -3
- package/src/command-handler.ts +38 -1
- package/src/command-manager.ts +3 -0
- package/src/commands/create.ts +11 -0
- package/src/commands/migrate.ts +88 -0
- package/src/containers/project/card-cache.ts +18 -1
- package/src/containers/project/project-paths.ts +6 -2
- package/src/containers/project.ts +66 -1
- package/src/containers/template.ts +5 -0
- package/src/interfaces/command-options.ts +8 -0
- package/src/interfaces/project-interfaces.ts +4 -0
- package/src/migrations/index.ts +20 -0
- package/src/migrations/migration-executor.ts +478 -0
- package/src/migrations/migration-worker.ts +190 -0
- package/src/migrations/worker-executor.ts +185 -0
- package/src/project-settings.ts +7 -0
- package/src/resources/calculation-resource.ts +13 -2
- package/src/resources/card-type-resource.ts +101 -114
- package/src/resources/field-type-resource.ts +78 -71
- package/src/resources/file-resource.ts +68 -9
- package/src/resources/graph-model-resource.ts +6 -0
- package/src/resources/graph-view-resource.ts +6 -0
- package/src/resources/link-type-resource.ts +66 -36
- package/src/resources/report-resource.ts +7 -1
- package/src/resources/resource-object.ts +57 -18
- package/src/resources/template-resource.ts +12 -27
- package/src/resources/workflow-resource.ts +119 -100
- package/src/utils/common-utils.ts +15 -0
- package/src/utils/file-utils.ts +56 -12
- package/src/utils/json.ts +2 -6
|
@@ -39,6 +39,11 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
|
|
|
39
39
|
private cardsFolder = '';
|
|
40
40
|
private cardsSchema = super.contentSchemaContent('cardBaseSchema');
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Creates an instance of TemplateResource
|
|
44
|
+
* @param project Project to use
|
|
45
|
+
* @param name Resource name
|
|
46
|
+
*/
|
|
42
47
|
constructor(project: Project, name: ResourceName) {
|
|
43
48
|
super(project, name, 'templates');
|
|
44
49
|
|
|
@@ -63,6 +68,7 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
|
|
|
63
68
|
await Promise.all([
|
|
64
69
|
super.updateHandleBars(existingName, this.content.name),
|
|
65
70
|
super.updateCalculations(existingName, this.content.name),
|
|
71
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
66
72
|
]);
|
|
67
73
|
await this.write();
|
|
68
74
|
}
|
|
@@ -70,7 +76,6 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
|
|
|
70
76
|
/**
|
|
71
77
|
* Sets new metadata into the template object.
|
|
72
78
|
* @param newContent metadata content for the template.
|
|
73
|
-
* @throws if 'newContent' is not valid.
|
|
74
79
|
*/
|
|
75
80
|
public async create(newContent?: TemplateMetadata) {
|
|
76
81
|
if (!newContent) {
|
|
@@ -140,35 +145,15 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
|
|
|
140
145
|
updateKey: UpdateKey<K>,
|
|
141
146
|
op: Operation<Type>,
|
|
142
147
|
) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const existingName = this.content.name;
|
|
146
|
-
|
|
147
|
-
// Only call super.update for keys that base class supports
|
|
148
|
-
if (key === 'name' || key === 'displayName' || key === 'description') {
|
|
149
|
-
await super.update(updateKey, op);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const content = structuredClone(this.content);
|
|
153
|
-
|
|
154
|
-
if (key === 'name') {
|
|
155
|
-
content.name = super.handleScalar(op) as string;
|
|
156
|
-
} else if (key === 'displayName') {
|
|
157
|
-
content.displayName = super.handleScalar(op) as string;
|
|
158
|
-
} else if (key === 'description') {
|
|
159
|
-
content.description = super.handleScalar(op) as string;
|
|
160
|
-
} else if (key === 'category') {
|
|
148
|
+
if (updateKey.key === 'category') {
|
|
149
|
+
const content = structuredClone(this.content);
|
|
161
150
|
content.category = super.handleScalar(op) as string;
|
|
162
|
-
} else {
|
|
163
|
-
throw new Error(`Unknown property '${key}' for Template`);
|
|
164
|
-
}
|
|
165
151
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Renaming this template causes that references to its name must be updated.
|
|
169
|
-
if (nameChange) {
|
|
170
|
-
await this.onNameChange(existingName);
|
|
152
|
+
await super.postUpdate(content, updateKey, op);
|
|
153
|
+
return;
|
|
171
154
|
}
|
|
155
|
+
|
|
156
|
+
await super.update(updateKey, op);
|
|
172
157
|
}
|
|
173
158
|
|
|
174
159
|
/**
|
|
@@ -36,6 +36,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
|
|
|
36
36
|
* Workflow resource class.
|
|
37
37
|
*/
|
|
38
38
|
export class WorkflowResource extends FileResource<Workflow> {
|
|
39
|
+
/**
|
|
40
|
+
* Creates an instance of WorkflowResource
|
|
41
|
+
* @param project Project to use
|
|
42
|
+
* @param name Resource name
|
|
43
|
+
*/
|
|
39
44
|
constructor(project: Project, name: ResourceName) {
|
|
40
45
|
super(project, name, 'workflows');
|
|
41
46
|
|
|
@@ -49,17 +54,24 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
49
54
|
const promises: Promise<Card[]>[] = [];
|
|
50
55
|
for (const cardType of cardTypes) {
|
|
51
56
|
if (cardType.data?.workflow === resourceNameToString(this.resourceName)) {
|
|
52
|
-
promises.push(
|
|
57
|
+
promises.push(
|
|
58
|
+
this.collectCards(
|
|
59
|
+
cardType.data.name,
|
|
60
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
return (await Promise.all(promises)).flat();
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// When resource name changes.
|
|
59
|
-
|
|
69
|
+
protected async onNameChange(existingName: string) {
|
|
60
70
|
await Promise.all([
|
|
61
71
|
super.updateHandleBars(existingName, this.content.name),
|
|
62
72
|
super.updateCalculations(existingName, this.content.name),
|
|
73
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
74
|
+
this.updateCardTypes(existingName),
|
|
63
75
|
]);
|
|
64
76
|
// Finally, write updated content.
|
|
65
77
|
await this.write();
|
|
@@ -131,6 +143,11 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
131
143
|
}
|
|
132
144
|
}
|
|
133
145
|
|
|
146
|
+
// Check if operation is a string operation.
|
|
147
|
+
private isStringOperation(op: Operation<unknown>): op is Operation<string> {
|
|
148
|
+
return typeof op.target === 'string';
|
|
149
|
+
}
|
|
150
|
+
|
|
134
151
|
// Returns target name irregardless of the type
|
|
135
152
|
private targetName(op: Operation<WorkflowState | WorkflowTransition>) {
|
|
136
153
|
const name = op.target.name ? op.target.name : op.target;
|
|
@@ -167,11 +184,6 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
167
184
|
return op.to;
|
|
168
185
|
}
|
|
169
186
|
|
|
170
|
-
// Check if operation is a string operation.
|
|
171
|
-
private isStringOperation(op: Operation<unknown>): op is Operation<string> {
|
|
172
|
-
return typeof op.target === 'string';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
187
|
// Update card states when state is changed
|
|
176
188
|
private async updateCardStates(oldState: string, newState: string) {
|
|
177
189
|
const cards = await this.collectCardsUsingWorkflow();
|
|
@@ -193,19 +205,21 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
193
205
|
to: this.content.name,
|
|
194
206
|
} as ChangeOperation<string>;
|
|
195
207
|
for (const cardType of cardTypes) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
// Only update card types that use this workflow
|
|
209
|
+
if (cardType.data?.workflow === oldName) {
|
|
210
|
+
await cardType.update(
|
|
211
|
+
{
|
|
212
|
+
key: 'workflow',
|
|
213
|
+
},
|
|
214
|
+
op,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
202
217
|
}
|
|
203
218
|
}
|
|
204
219
|
|
|
205
220
|
/**
|
|
206
221
|
* Sets new metadata into the workflow object.
|
|
207
222
|
* @param newContent metadata content for the workflow.
|
|
208
|
-
* @throws if 'newContent' is not valid.
|
|
209
223
|
*/
|
|
210
224
|
public async create(newContent?: Workflow) {
|
|
211
225
|
if (!newContent) {
|
|
@@ -225,7 +239,7 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
225
239
|
public async rename(newName: ResourceName) {
|
|
226
240
|
const existingName = this.content.name;
|
|
227
241
|
await super.rename(newName);
|
|
228
|
-
return this.
|
|
242
|
+
return this.onNameChange(existingName);
|
|
229
243
|
}
|
|
230
244
|
|
|
231
245
|
/**
|
|
@@ -239,101 +253,106 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
239
253
|
op: Operation<Type>,
|
|
240
254
|
) {
|
|
241
255
|
const { key } = updateKey;
|
|
242
|
-
const nameChange = key === 'name';
|
|
243
|
-
const existingName = this.content.name;
|
|
244
256
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const content = structuredClone(this.content) as Workflow;
|
|
248
|
-
|
|
249
|
-
if (key === 'name') {
|
|
250
|
-
content.name = super.handleScalar(op) as string;
|
|
251
|
-
} else if (key === 'displayName') {
|
|
252
|
-
content.displayName = super.handleScalar(op) as string;
|
|
253
|
-
} else if (key === 'description') {
|
|
254
|
-
content.description = super.handleScalar(op) as string;
|
|
255
|
-
} else if (key === 'states') {
|
|
256
|
-
content.states = super.handleArray(
|
|
257
|
-
op,
|
|
258
|
-
key,
|
|
259
|
-
content.states as Type[],
|
|
260
|
-
) as WorkflowState[];
|
|
261
|
-
} else if (key === 'transitions') {
|
|
262
|
-
content.transitions = super.handleArray(
|
|
263
|
-
op,
|
|
264
|
-
key,
|
|
265
|
-
content.transitions as WorkflowTransition[] as Type[],
|
|
266
|
-
) as WorkflowTransition[];
|
|
257
|
+
if (key === 'name' || key === 'displayName' || key === 'description') {
|
|
258
|
+
await super.update(updateKey, op);
|
|
267
259
|
} else {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
toState: targetTransition.toState,
|
|
286
|
-
fromState: targetTransition.fromState,
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
} else {
|
|
290
|
-
changeOp = op as ChangeOperation<WorkflowTransition>;
|
|
260
|
+
const content = structuredClone(this.content) as Workflow;
|
|
261
|
+
|
|
262
|
+
// Validate state change operations before processing
|
|
263
|
+
if (key === 'states' && op.name === 'change') {
|
|
264
|
+
const changeOp = op as ChangeOperation<WorkflowState>;
|
|
265
|
+
if (
|
|
266
|
+
changeOp.to.name === undefined ||
|
|
267
|
+
changeOp.to.category === undefined
|
|
268
|
+
) {
|
|
269
|
+
const stateName =
|
|
270
|
+
changeOp.target['name' as keyof typeof changeOp.target] ||
|
|
271
|
+
changeOp.target;
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Cannot change state '${stateName}' for workflow '${this.content.name}'.
|
|
274
|
+
Updated state must have 'name' and 'category' properties.`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
291
277
|
}
|
|
292
|
-
const newTransition = await this.transitionObject(changeOp);
|
|
293
|
-
content.transitions = content.transitions.map((item) =>
|
|
294
|
-
item.name == newTransition.name ? newTransition : item,
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
278
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
279
|
+
if (key === 'states') {
|
|
280
|
+
content.states = super.handleArray(
|
|
281
|
+
op,
|
|
282
|
+
key,
|
|
283
|
+
content.states as Type[],
|
|
284
|
+
) as WorkflowState[];
|
|
285
|
+
} else if (key === 'transitions') {
|
|
286
|
+
content.transitions = super.handleArray(
|
|
287
|
+
op,
|
|
288
|
+
key,
|
|
289
|
+
content.transitions as WorkflowTransition[] as Type[],
|
|
290
|
+
) as WorkflowTransition[];
|
|
309
291
|
} else {
|
|
310
|
-
|
|
292
|
+
throw new Error(`Unknown property '${key}' for Workflow`);
|
|
311
293
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
294
|
+
|
|
295
|
+
// If workflow transition is removed, then above call to 'handleArray' is all that is needed.
|
|
296
|
+
|
|
297
|
+
if (key === 'transitions' && op.name === 'change') {
|
|
298
|
+
// If workflow transition is changed, update to full object and change the content.
|
|
299
|
+
let changeOp: ChangeOperation<WorkflowTransition>;
|
|
300
|
+
if (this.isStringOperation(op)) {
|
|
301
|
+
const targetTransition = (this.content as Workflow).transitions.find(
|
|
302
|
+
(transition) => transition.name === op.target,
|
|
303
|
+
)!;
|
|
304
|
+
changeOp = {
|
|
305
|
+
name: 'change',
|
|
306
|
+
target: targetTransition as WorkflowTransition,
|
|
307
|
+
to: {
|
|
308
|
+
name: op.to,
|
|
309
|
+
toState: targetTransition.toState,
|
|
310
|
+
fromState: targetTransition.fromState,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
} else {
|
|
314
|
+
changeOp = op as ChangeOperation<WorkflowTransition>;
|
|
315
|
+
}
|
|
316
|
+
const newTransition = await this.transitionObject(changeOp);
|
|
317
|
+
content.transitions = content.transitions.map((item) =>
|
|
318
|
+
item.name == newTransition.name ? newTransition : item,
|
|
319
319
|
);
|
|
320
|
-
changeOp = {
|
|
321
|
-
name: 'change',
|
|
322
|
-
target: toBeChangedState as WorkflowState,
|
|
323
|
-
to: { name: op.to },
|
|
324
|
-
};
|
|
325
|
-
} else {
|
|
326
|
-
changeOp = op as ChangeOperation<WorkflowState>;
|
|
327
320
|
}
|
|
328
|
-
await this.handleStateChange(changeOp);
|
|
329
|
-
}
|
|
330
321
|
|
|
331
|
-
|
|
322
|
+
if (key === 'states' && op.name === 'remove') {
|
|
323
|
+
// If workflow state is removed, remove all transitions "to" and "from" this state.
|
|
324
|
+
let removeOp: RemoveOperation<WorkflowState>;
|
|
325
|
+
if (this.isStringOperation(op)) {
|
|
326
|
+
const toBeRemovedState = this.content.states.find(
|
|
327
|
+
(state) => state.name === op.target,
|
|
328
|
+
);
|
|
329
|
+
removeOp = {
|
|
330
|
+
name: 'remove',
|
|
331
|
+
target: toBeRemovedState as WorkflowState,
|
|
332
|
+
};
|
|
333
|
+
} else {
|
|
334
|
+
removeOp = op as RemoveOperation<WorkflowState>;
|
|
335
|
+
}
|
|
336
|
+
await this.handleStateRemoval(removeOp);
|
|
337
|
+
} else if (key === 'states' && op.name === 'change') {
|
|
338
|
+
// If workflow state is renamed, replace all transitions "to" and "from" the old state with new state.
|
|
339
|
+
let changeOp: ChangeOperation<WorkflowState>;
|
|
340
|
+
if (this.isStringOperation(op)) {
|
|
341
|
+
const toBeChangedState = this.content.states.find(
|
|
342
|
+
(state) => state.name === op.target,
|
|
343
|
+
);
|
|
344
|
+
changeOp = {
|
|
345
|
+
name: 'change',
|
|
346
|
+
target: toBeChangedState as WorkflowState,
|
|
347
|
+
to: { name: op.to },
|
|
348
|
+
};
|
|
349
|
+
} else {
|
|
350
|
+
changeOp = op as ChangeOperation<WorkflowState>;
|
|
351
|
+
}
|
|
352
|
+
await this.handleStateChange(changeOp);
|
|
353
|
+
}
|
|
332
354
|
|
|
333
|
-
|
|
334
|
-
if (nameChange) {
|
|
335
|
-
await this.handleNameChange(existingName);
|
|
336
|
-
await this.updateCardTypes(existingName);
|
|
355
|
+
await super.postUpdate(content, updateKey, op);
|
|
337
356
|
}
|
|
338
357
|
}
|
|
339
358
|
|
|
@@ -52,3 +52,18 @@ export function isBigInt(value: string): boolean {
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Removes the first occurrence of a value from an array.
|
|
58
|
+
* Modifies the array in place.
|
|
59
|
+
* @param array The array to modify
|
|
60
|
+
* @param value The value to remove
|
|
61
|
+
* @returns The modified array (same reference as input)
|
|
62
|
+
*/
|
|
63
|
+
export function removeValue<T>(array: T[], value: T): T[] {
|
|
64
|
+
const index = array.findIndex((element) => element === value);
|
|
65
|
+
if (index !== -1) {
|
|
66
|
+
array.splice(index, 1);
|
|
67
|
+
}
|
|
68
|
+
return array;
|
|
69
|
+
}
|
package/src/utils/file-utils.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
mkdir,
|
|
18
18
|
readdir,
|
|
19
19
|
rm,
|
|
20
|
+
stat,
|
|
21
|
+
statfs,
|
|
20
22
|
unlink,
|
|
21
23
|
writeFile,
|
|
22
24
|
} from 'node:fs/promises';
|
|
@@ -25,19 +27,17 @@ import { dirname, join, sep } from 'node:path';
|
|
|
25
27
|
import { homedir } from 'node:os';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Get available disk space for a given path.
|
|
31
|
+
* @param path Path to check
|
|
32
|
+
* @returns Available space in bytes
|
|
30
33
|
*/
|
|
31
|
-
export async function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
recursive: true,
|
|
39
|
-
});
|
|
40
|
-
return writeFile(filePath, data, options);
|
|
34
|
+
export async function availableSpace(path: string): Promise<number> {
|
|
35
|
+
try {
|
|
36
|
+
const stats = await statfs(path);
|
|
37
|
+
return stats.bavail * stats.bsize;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Failed to check available disk space: ${error}`);
|
|
40
|
+
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -87,6 +87,34 @@ export async function deleteFile(path: string): Promise<boolean> {
|
|
|
87
87
|
return true;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Calculate the total size of a directory recursively.
|
|
92
|
+
* @param dirPath Path to directory
|
|
93
|
+
* @returns Size in bytes
|
|
94
|
+
*/
|
|
95
|
+
export async function folderSize(dirPath: string): Promise<number> {
|
|
96
|
+
let size = 0;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
100
|
+
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const fullPath = join(dirPath, entry.name);
|
|
103
|
+
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
size += await folderSize(fullPath);
|
|
106
|
+
} else if (entry.isFile()) {
|
|
107
|
+
const stats = await stat(fullPath);
|
|
108
|
+
size += stats.size;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore permission errors or missing directories
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return size;
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
/**
|
|
91
119
|
* Removes extension from filename.
|
|
92
120
|
* @param filename Filename
|
|
@@ -180,3 +208,19 @@ export function resolveTilde(filePath: string): string {
|
|
|
180
208
|
* Path separator RE.
|
|
181
209
|
*/
|
|
182
210
|
export const sepRegex = /[/\\]/;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Works like the writeFile method, but ensures that the directory exists
|
|
214
|
+
* There is only one difference: This method only supports a string as the filePath
|
|
215
|
+
*/
|
|
216
|
+
export async function writeFileSafe(
|
|
217
|
+
filePath: string,
|
|
218
|
+
data: Parameters<typeof writeFile>[1],
|
|
219
|
+
options?: Parameters<typeof writeFile>[2],
|
|
220
|
+
) {
|
|
221
|
+
const dir = dirname(filePath);
|
|
222
|
+
await mkdir(dir, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
});
|
|
225
|
+
return writeFile(filePath, data, options);
|
|
226
|
+
}
|
package/src/utils/json.ts
CHANGED
|
@@ -28,9 +28,7 @@ export function readJsonFileSync(file: string) {
|
|
|
28
28
|
return returnValue;
|
|
29
29
|
} catch (error) {
|
|
30
30
|
if (error instanceof Error) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Error while handling JSON file '${file}' : ${error.message}`,
|
|
33
|
-
);
|
|
31
|
+
throw new Error(`Invalid JSON in file '${file}': ${error.message}`);
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
}
|
|
@@ -47,9 +45,7 @@ export async function readJsonFile(file: string) {
|
|
|
47
45
|
return JSON.parse(raw);
|
|
48
46
|
} catch (error) {
|
|
49
47
|
if (error instanceof Error) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Error while handling JSON file '${file}' : ${error.message}`,
|
|
52
|
-
);
|
|
48
|
+
throw new Error(`Invalid JSON in file '${file}': ${error.message}`);
|
|
53
49
|
}
|
|
54
50
|
}
|
|
55
51
|
}
|