@inforge/migrations-tools-cli 1.0.0 → 1.1.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/README.md +21 -8
- package/dist/index.js +335 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,13 @@ Inforge's interactive CLI tool that enables side-effect-free Salesforce data ope
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
- **Feature-grouped menu** - Organized by Automations, Backups, and Logs
|
|
8
|
+
- **Object search/filter** - Instantly find objects by typing instead of scrolling
|
|
9
|
+
- **Individual automation control** - Toggle specific automations with granular preview
|
|
7
10
|
- **Deactivate automation** (validation rules, flows, triggers) for clean data operations
|
|
8
11
|
- **Smart restore** with automatic state detection
|
|
9
12
|
- **Atomic operations** with automatic rollback on failure
|
|
10
|
-
- **Local backups** organized by org/object/type
|
|
13
|
+
- **Local backups** organized by org/object/type with operation type labels
|
|
11
14
|
- **Managed package awareness** - skip managed components gracefully
|
|
12
15
|
- **Full audit logging** for compliance
|
|
13
16
|
- **Beautiful interactive UI** powered by @clack/prompts
|
|
@@ -44,27 +47,37 @@ npx @inforge/migrations-tools-cli
|
|
|
44
47
|
|
|
45
48
|
### Deactivate Automation
|
|
46
49
|
|
|
47
|
-
1. Select "Deactivate
|
|
48
|
-
2. Choose org, object, and automation type
|
|
50
|
+
1. Select "Automations" > "Deactivate all"
|
|
51
|
+
2. Choose org, object (with search/filter), and automation type
|
|
49
52
|
3. Preview what will be affected
|
|
50
53
|
4. Confirm and execute
|
|
51
54
|
5. Backup saved automatically
|
|
52
55
|
|
|
56
|
+
### Manage Automations Individually
|
|
57
|
+
|
|
58
|
+
1. Select "Automations" > "Manage individually"
|
|
59
|
+
2. Choose org and object (with search/filter)
|
|
60
|
+
3. Choose automation type
|
|
61
|
+
4. Select specific items to toggle with checkboxes
|
|
62
|
+
5. Preview changes (activations and deactivations)
|
|
63
|
+
6. Confirm and execute
|
|
64
|
+
7. Backup saved automatically as "individual" operation
|
|
65
|
+
|
|
53
66
|
### Restore from Backup
|
|
54
67
|
|
|
55
|
-
1. Select "
|
|
56
|
-
2. Choose org, object, and automation type
|
|
57
|
-
3. Select backup from list (
|
|
68
|
+
1. Select "Automations" > "Restore"
|
|
69
|
+
2. Choose org, object (with search/filter), and automation type
|
|
70
|
+
3. Select backup from list (labels show "Bulk deactivate" or "Individual changes")
|
|
58
71
|
4. Preview smart detection (only restore what's needed)
|
|
59
72
|
5. Confirm and execute
|
|
60
73
|
|
|
61
74
|
### Manage Backups
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
Select "Backups" to view all backups organized by org/object/type.
|
|
64
77
|
|
|
65
78
|
### View Logs
|
|
66
79
|
|
|
67
|
-
|
|
80
|
+
Select "Logs" to see recent operations with status, timestamps, and details.
|
|
68
81
|
|
|
69
82
|
## Architecture
|
|
70
83
|
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,49 @@ var Prompts = class {
|
|
|
34
34
|
}
|
|
35
35
|
return selected;
|
|
36
36
|
}
|
|
37
|
+
async selectObjectWithSearch(objects) {
|
|
38
|
+
const filter = await clack.text({
|
|
39
|
+
message: "Filter objects (leave empty for all):",
|
|
40
|
+
placeholder: "Type to filter...",
|
|
41
|
+
defaultValue: ""
|
|
42
|
+
});
|
|
43
|
+
if (clack.isCancel(filter)) {
|
|
44
|
+
this.cancel();
|
|
45
|
+
}
|
|
46
|
+
const filterStr = filter.toLowerCase().trim();
|
|
47
|
+
const filteredObjects = filterStr ? objects.filter((obj) => obj.toLowerCase().includes(filterStr)) : objects;
|
|
48
|
+
const selected = await clack.select({
|
|
49
|
+
message: `Select an object (${filteredObjects.length} matches):`,
|
|
50
|
+
options: filteredObjects.map((obj) => ({ value: obj, label: obj }))
|
|
51
|
+
});
|
|
52
|
+
if (clack.isCancel(selected)) {
|
|
53
|
+
this.cancel();
|
|
54
|
+
}
|
|
55
|
+
return selected;
|
|
56
|
+
}
|
|
57
|
+
async selectWithCheckboxes(message, items, managedItems) {
|
|
58
|
+
const options = [
|
|
59
|
+
...items.map((item) => ({
|
|
60
|
+
value: item.fullName,
|
|
61
|
+
label: `${item.fullName} (${item.active ? "Active" : "Inactive"})`
|
|
62
|
+
})),
|
|
63
|
+
...managedItems.map((item) => ({
|
|
64
|
+
value: item.fullName,
|
|
65
|
+
label: `${item.fullName} (Active)`,
|
|
66
|
+
hint: `Managed by ${item.namespace} - cannot modify`
|
|
67
|
+
}))
|
|
68
|
+
];
|
|
69
|
+
const selected = await clack.multiselect({
|
|
70
|
+
message,
|
|
71
|
+
options,
|
|
72
|
+
required: false
|
|
73
|
+
});
|
|
74
|
+
if (clack.isCancel(selected)) {
|
|
75
|
+
this.cancel();
|
|
76
|
+
}
|
|
77
|
+
const managedFullNames = new Set(managedItems.map((m) => m.fullName));
|
|
78
|
+
return selected.filter((s) => !managedFullNames.has(s));
|
|
79
|
+
}
|
|
37
80
|
async selectAutomationType() {
|
|
38
81
|
const selected = await clack.select({
|
|
39
82
|
message: "Select automation type:",
|
|
@@ -52,10 +95,9 @@ var Prompts = class {
|
|
|
52
95
|
const selected = await clack.select({
|
|
53
96
|
message: "What would you like to do?",
|
|
54
97
|
options: [
|
|
55
|
-
{ value: "
|
|
56
|
-
{ value: "
|
|
57
|
-
{ value: "
|
|
58
|
-
{ value: "logs", label: "View operation logs" },
|
|
98
|
+
{ value: "automations", label: "Automations" },
|
|
99
|
+
{ value: "backups", label: "Backups" },
|
|
100
|
+
{ value: "logs", label: "Logs" },
|
|
59
101
|
{ value: "exit", label: "Exit" }
|
|
60
102
|
]
|
|
61
103
|
});
|
|
@@ -144,7 +186,7 @@ var BackupManager = class {
|
|
|
144
186
|
constructor(backupDir = ".backups") {
|
|
145
187
|
this.backupDir = backupDir;
|
|
146
188
|
}
|
|
147
|
-
async save(org, object, type, items, managedItems) {
|
|
189
|
+
async save(org, object, type, items, managedItems, operationType = "bulk") {
|
|
148
190
|
const timestamp = this.generateTimestamp();
|
|
149
191
|
const backupPath = this.getBackupPath(org.alias, object, type, timestamp);
|
|
150
192
|
const backup = {
|
|
@@ -153,6 +195,7 @@ var BackupManager = class {
|
|
|
153
195
|
org,
|
|
154
196
|
object,
|
|
155
197
|
type,
|
|
198
|
+
operationType,
|
|
156
199
|
items,
|
|
157
200
|
managedItems,
|
|
158
201
|
restoredAt: null,
|
|
@@ -422,12 +465,20 @@ var TriggersHandler = class {
|
|
|
422
465
|
await this.updateStatus(connection, triggerNames, "Active");
|
|
423
466
|
}
|
|
424
467
|
async updateStatus(connection, triggerNames, status) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
468
|
+
const query = `
|
|
469
|
+
SELECT Id, Name
|
|
470
|
+
FROM ApexTrigger
|
|
471
|
+
WHERE Name IN (${triggerNames.map((n) => `'${n}'`).join(", ")})
|
|
472
|
+
`;
|
|
473
|
+
const result = await connection.tooling.query(query);
|
|
474
|
+
if (result.records.length === 0) {
|
|
475
|
+
throw new Error("No triggers found with the specified names");
|
|
476
|
+
}
|
|
477
|
+
const updates = result.records.map((trigger) => ({
|
|
478
|
+
Id: trigger.Id,
|
|
479
|
+
Status: status
|
|
429
480
|
}));
|
|
430
|
-
const results = await connection.
|
|
481
|
+
const results = await connection.tooling.update("ApexTrigger", updates);
|
|
431
482
|
if (Array.isArray(results)) {
|
|
432
483
|
const failures = results.filter((r) => !r.success);
|
|
433
484
|
if (failures.length > 0) {
|
|
@@ -459,7 +510,7 @@ var DeactivateCommand = class {
|
|
|
459
510
|
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
460
511
|
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
461
512
|
const objects = await this.sfClient.queryObjects(connection);
|
|
462
|
-
const selectedObject = await this.prompts.
|
|
513
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
463
514
|
const automationType = await this.prompts.selectAutomationType();
|
|
464
515
|
if (automationType === "validation-rules") {
|
|
465
516
|
await this.deactivateValidationRules(org, selectedObject, connection);
|
|
@@ -744,7 +795,7 @@ var RestoreCommand = class {
|
|
|
744
795
|
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
745
796
|
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
746
797
|
const objects = await this.sfClient.queryObjects(connection);
|
|
747
|
-
const selectedObject = await this.prompts.
|
|
798
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
748
799
|
const automationType = await this.prompts.selectAutomationType();
|
|
749
800
|
const backups = await this.backupManager.list(selectedOrg, selectedObject, automationType);
|
|
750
801
|
if (backups.length === 0) {
|
|
@@ -756,10 +807,12 @@ var RestoreCommand = class {
|
|
|
756
807
|
const backup2 = await this.backupManager.load(backupPath);
|
|
757
808
|
const filename = path3.basename(backupPath, ".json");
|
|
758
809
|
const status = backup2.restoredAt ? `restored on ${backup2.restoredAt.split("T")[0]}` : "not restored";
|
|
810
|
+
const opType = backup2.operationType === "individual" ? "Individual changes" : "Bulk deactivate";
|
|
811
|
+
const itemCount = `${backup2.items.length} ${backup2.type === "validation-rules" ? "rules" : backup2.type}`;
|
|
759
812
|
return {
|
|
760
813
|
value: backupPath,
|
|
761
|
-
label: filename
|
|
762
|
-
hint: `${
|
|
814
|
+
label: `${filename} - ${opType}`,
|
|
815
|
+
hint: `${itemCount}, ${status}`
|
|
763
816
|
};
|
|
764
817
|
})
|
|
765
818
|
);
|
|
@@ -1007,10 +1060,243 @@ var RestoreCommand = class {
|
|
|
1007
1060
|
}
|
|
1008
1061
|
};
|
|
1009
1062
|
|
|
1010
|
-
// src/commands/
|
|
1063
|
+
// src/commands/individual.ts
|
|
1064
|
+
var IndividualCommand = class {
|
|
1065
|
+
sfClient;
|
|
1066
|
+
backupManager;
|
|
1067
|
+
logger;
|
|
1068
|
+
prompts;
|
|
1069
|
+
constructor() {
|
|
1070
|
+
this.sfClient = new SfClient();
|
|
1071
|
+
this.backupManager = new BackupManager();
|
|
1072
|
+
this.logger = new Logger();
|
|
1073
|
+
this.prompts = new Prompts();
|
|
1074
|
+
}
|
|
1075
|
+
async execute() {
|
|
1076
|
+
const orgs = await this.sfClient.listOrgs();
|
|
1077
|
+
if (orgs.length === 0) {
|
|
1078
|
+
this.prompts.error("No authenticated orgs found.");
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const selectedOrg = await this.prompts.selectOrg(orgs);
|
|
1082
|
+
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
1083
|
+
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
1084
|
+
const objects = await this.sfClient.queryObjects(connection);
|
|
1085
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
1086
|
+
const automationType = await this.prompts.selectAutomationType();
|
|
1087
|
+
const spinner2 = this.prompts.spinner();
|
|
1088
|
+
spinner2.start("Fetching automations...");
|
|
1089
|
+
let currentItems;
|
|
1090
|
+
let managedItems;
|
|
1091
|
+
let handler;
|
|
1092
|
+
if (automationType === "validation-rules") {
|
|
1093
|
+
handler = new ValidationRulesHandler();
|
|
1094
|
+
} else if (automationType === "flows") {
|
|
1095
|
+
handler = new FlowsHandler();
|
|
1096
|
+
} else {
|
|
1097
|
+
handler = new TriggersHandler();
|
|
1098
|
+
}
|
|
1099
|
+
const separated = await handler.fetchSeparated(connection, selectedObject);
|
|
1100
|
+
currentItems = separated.custom;
|
|
1101
|
+
managedItems = separated.managed;
|
|
1102
|
+
spinner2.stop("Automations fetched");
|
|
1103
|
+
if (currentItems.length === 0 && managedItems.length === 0) {
|
|
1104
|
+
this.prompts.warning(`No ${automationType} found for ${selectedObject}.`);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const selectedFullNames = await this.prompts.selectWithCheckboxes(
|
|
1108
|
+
`${selectedObject} > ${automationType}
|
|
1109
|
+
|
|
1110
|
+
Select items to toggle (Space to select, Enter to continue):`,
|
|
1111
|
+
currentItems,
|
|
1112
|
+
managedItems
|
|
1113
|
+
);
|
|
1114
|
+
if (selectedFullNames.length === 0) {
|
|
1115
|
+
this.prompts.warning("No changes selected.");
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const currentItemsMap = new Map(currentItems.map((item) => [item.fullName, item]));
|
|
1119
|
+
const toActivate = [];
|
|
1120
|
+
const toDeactivate = [];
|
|
1121
|
+
for (const fullName of selectedFullNames) {
|
|
1122
|
+
const item = currentItemsMap.get(fullName);
|
|
1123
|
+
if (item) {
|
|
1124
|
+
if (item.active) {
|
|
1125
|
+
toDeactivate.push(fullName);
|
|
1126
|
+
} else {
|
|
1127
|
+
toActivate.push(fullName);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
let previewMessage = `Changes to apply:
|
|
1132
|
+
`;
|
|
1133
|
+
if (toDeactivate.length > 0) {
|
|
1134
|
+
previewMessage += `
|
|
1135
|
+
Deactivate (${toDeactivate.length}):
|
|
1136
|
+
`;
|
|
1137
|
+
toDeactivate.forEach((name) => {
|
|
1138
|
+
previewMessage += ` - ${name}
|
|
1139
|
+
`;
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
if (toActivate.length > 0) {
|
|
1143
|
+
previewMessage += `
|
|
1144
|
+
Activate (${toActivate.length}):
|
|
1145
|
+
`;
|
|
1146
|
+
toActivate.forEach((name) => {
|
|
1147
|
+
previewMessage += ` - ${name}
|
|
1148
|
+
`;
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
previewMessage += "\nBackup will be created automatically.";
|
|
1152
|
+
this.prompts.note(previewMessage, "Preview");
|
|
1153
|
+
const confirmed = await this.prompts.confirm("Proceed with these changes?");
|
|
1154
|
+
if (!confirmed) {
|
|
1155
|
+
this.prompts.cancel("Operation cancelled");
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const operationId = this.logger.generateOperationId();
|
|
1159
|
+
const backupSpinner = this.prompts.spinner();
|
|
1160
|
+
backupSpinner.start("Creating backup...");
|
|
1161
|
+
const backupItems = selectedFullNames.map((fn) => currentItemsMap.get(fn));
|
|
1162
|
+
const backupPath = await this.backupManager.save(
|
|
1163
|
+
org,
|
|
1164
|
+
selectedObject,
|
|
1165
|
+
automationType,
|
|
1166
|
+
backupItems,
|
|
1167
|
+
[],
|
|
1168
|
+
"individual"
|
|
1169
|
+
);
|
|
1170
|
+
backupSpinner.stop("Backup created");
|
|
1171
|
+
const applySpinner = this.prompts.spinner();
|
|
1172
|
+
applySpinner.start("Applying changes...");
|
|
1173
|
+
try {
|
|
1174
|
+
await this.applyChanges(
|
|
1175
|
+
handler,
|
|
1176
|
+
connection,
|
|
1177
|
+
selectedObject,
|
|
1178
|
+
automationType,
|
|
1179
|
+
toDeactivate,
|
|
1180
|
+
toActivate,
|
|
1181
|
+
currentItemsMap
|
|
1182
|
+
);
|
|
1183
|
+
await this.logger.log({
|
|
1184
|
+
id: operationId,
|
|
1185
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1186
|
+
operation: "deactivate",
|
|
1187
|
+
org: org.alias,
|
|
1188
|
+
object: selectedObject,
|
|
1189
|
+
type: automationType,
|
|
1190
|
+
status: "success",
|
|
1191
|
+
itemsAffected: selectedFullNames.length,
|
|
1192
|
+
itemsSkipped: 0,
|
|
1193
|
+
backupPath,
|
|
1194
|
+
error: null
|
|
1195
|
+
});
|
|
1196
|
+
applySpinner.stop("Changes applied");
|
|
1197
|
+
this.prompts.success(
|
|
1198
|
+
`Successfully modified ${selectedFullNames.length} ${automationType}.`
|
|
1199
|
+
);
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
applySpinner.stop("Changes failed");
|
|
1202
|
+
await this.logger.log({
|
|
1203
|
+
id: operationId,
|
|
1204
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1205
|
+
operation: "deactivate",
|
|
1206
|
+
org: org.alias,
|
|
1207
|
+
object: selectedObject,
|
|
1208
|
+
type: automationType,
|
|
1209
|
+
status: "failure",
|
|
1210
|
+
itemsAffected: 0,
|
|
1211
|
+
itemsSkipped: 0,
|
|
1212
|
+
backupPath,
|
|
1213
|
+
error: error.message
|
|
1214
|
+
});
|
|
1215
|
+
this.prompts.error(`Failed to apply changes: ${error.message}`);
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async applyChanges(handler, connection, objectName, automationType, toDeactivate, toActivate, itemsMap) {
|
|
1220
|
+
if (automationType === "validation-rules") {
|
|
1221
|
+
const vrHandler = handler;
|
|
1222
|
+
if (toDeactivate.length > 0) {
|
|
1223
|
+
const ruleNames = toDeactivate.map((fn) => fn.split(".")[1]);
|
|
1224
|
+
await vrHandler.deactivate(connection, objectName, ruleNames);
|
|
1225
|
+
}
|
|
1226
|
+
if (toActivate.length > 0) {
|
|
1227
|
+
const ruleNames = toActivate.map((fn) => fn.split(".")[1]);
|
|
1228
|
+
await vrHandler.activate(connection, objectName, ruleNames);
|
|
1229
|
+
}
|
|
1230
|
+
} else if (automationType === "flows") {
|
|
1231
|
+
const flowHandler = handler;
|
|
1232
|
+
if (toDeactivate.length > 0) {
|
|
1233
|
+
const flowIds = toDeactivate.map((fn) => {
|
|
1234
|
+
const item = itemsMap.get(fn);
|
|
1235
|
+
return item.metadata.Id;
|
|
1236
|
+
});
|
|
1237
|
+
await flowHandler.deactivate(connection, flowIds);
|
|
1238
|
+
}
|
|
1239
|
+
if (toActivate.length > 0) {
|
|
1240
|
+
const flowsWithVersions = toActivate.map((fn) => {
|
|
1241
|
+
const item = itemsMap.get(fn);
|
|
1242
|
+
return {
|
|
1243
|
+
id: item.metadata.Id,
|
|
1244
|
+
version: item.metadata.LatestVersion.VersionNumber
|
|
1245
|
+
};
|
|
1246
|
+
});
|
|
1247
|
+
await flowHandler.activate(connection, flowsWithVersions);
|
|
1248
|
+
}
|
|
1249
|
+
} else {
|
|
1250
|
+
const triggerHandler = handler;
|
|
1251
|
+
if (toDeactivate.length > 0) {
|
|
1252
|
+
await triggerHandler.deactivate(connection, toDeactivate);
|
|
1253
|
+
}
|
|
1254
|
+
if (toActivate.length > 0) {
|
|
1255
|
+
await triggerHandler.activate(connection, toActivate);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
// src/commands/automations.ts
|
|
1262
|
+
var AutomationsCommand = class {
|
|
1263
|
+
prompts;
|
|
1264
|
+
constructor() {
|
|
1265
|
+
this.prompts = new Prompts();
|
|
1266
|
+
}
|
|
1267
|
+
async execute() {
|
|
1268
|
+
const action = await this.prompts.selectFromOptions(
|
|
1269
|
+
"Automations",
|
|
1270
|
+
[
|
|
1271
|
+
{ value: "deactivate", label: "Deactivate all" },
|
|
1272
|
+
{ value: "restore", label: "Restore" },
|
|
1273
|
+
{ value: "individual", label: "Manage individually" },
|
|
1274
|
+
{ value: "back", label: "Back to main menu" }
|
|
1275
|
+
]
|
|
1276
|
+
);
|
|
1277
|
+
switch (action) {
|
|
1278
|
+
case "deactivate":
|
|
1279
|
+
const deactivateCmd = new DeactivateCommand();
|
|
1280
|
+
await deactivateCmd.execute();
|
|
1281
|
+
break;
|
|
1282
|
+
case "restore":
|
|
1283
|
+
const restoreCmd = new RestoreCommand();
|
|
1284
|
+
await restoreCmd.execute();
|
|
1285
|
+
break;
|
|
1286
|
+
case "individual":
|
|
1287
|
+
const individualCmd = new IndividualCommand();
|
|
1288
|
+
await individualCmd.execute();
|
|
1289
|
+
break;
|
|
1290
|
+
case "back":
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
// src/commands/backups.ts
|
|
1011
1297
|
import * as fs3 from "fs/promises";
|
|
1012
1298
|
import * as path4 from "path";
|
|
1013
|
-
var
|
|
1299
|
+
var BackupsCommand = class {
|
|
1014
1300
|
backupManager;
|
|
1015
1301
|
prompts;
|
|
1016
1302
|
constructor() {
|
|
@@ -1019,7 +1305,7 @@ var ManageCommand = class {
|
|
|
1019
1305
|
}
|
|
1020
1306
|
async execute() {
|
|
1021
1307
|
const action = await this.prompts.selectFromOptions(
|
|
1022
|
-
"
|
|
1308
|
+
"Backups",
|
|
1023
1309
|
[
|
|
1024
1310
|
{ value: "view", label: "View all backups" },
|
|
1025
1311
|
{ value: "cleanup", label: "Clean up old backups" },
|
|
@@ -1116,6 +1402,31 @@ var LogsCommand = class {
|
|
|
1116
1402
|
}
|
|
1117
1403
|
};
|
|
1118
1404
|
|
|
1405
|
+
// src/commands/logs-menu.ts
|
|
1406
|
+
var LogsMenuCommand = class {
|
|
1407
|
+
prompts;
|
|
1408
|
+
constructor() {
|
|
1409
|
+
this.prompts = new Prompts();
|
|
1410
|
+
}
|
|
1411
|
+
async execute() {
|
|
1412
|
+
const action = await this.prompts.selectFromOptions(
|
|
1413
|
+
"Logs",
|
|
1414
|
+
[
|
|
1415
|
+
{ value: "view", label: "View recent operations" },
|
|
1416
|
+
{ value: "back", label: "Back to main menu" }
|
|
1417
|
+
]
|
|
1418
|
+
);
|
|
1419
|
+
switch (action) {
|
|
1420
|
+
case "view":
|
|
1421
|
+
const logsCmd = new LogsCommand();
|
|
1422
|
+
await logsCmd.execute();
|
|
1423
|
+
break;
|
|
1424
|
+
case "back":
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1119
1430
|
// src/index.ts
|
|
1120
1431
|
async function main() {
|
|
1121
1432
|
const prompts = new Prompts();
|
|
@@ -1124,20 +1435,16 @@ async function main() {
|
|
|
1124
1435
|
while (true) {
|
|
1125
1436
|
const action = await prompts.selectMainAction();
|
|
1126
1437
|
switch (action) {
|
|
1127
|
-
case "
|
|
1128
|
-
const
|
|
1129
|
-
await
|
|
1130
|
-
break;
|
|
1131
|
-
case "restore":
|
|
1132
|
-
const restoreCmd = new RestoreCommand();
|
|
1133
|
-
await restoreCmd.execute();
|
|
1438
|
+
case "automations":
|
|
1439
|
+
const automationsCmd = new AutomationsCommand();
|
|
1440
|
+
await automationsCmd.execute();
|
|
1134
1441
|
break;
|
|
1135
|
-
case "
|
|
1136
|
-
const
|
|
1137
|
-
await
|
|
1442
|
+
case "backups":
|
|
1443
|
+
const backupsCmd = new BackupsCommand();
|
|
1444
|
+
await backupsCmd.execute();
|
|
1138
1445
|
break;
|
|
1139
1446
|
case "logs":
|
|
1140
|
-
const logsCmd = new
|
|
1447
|
+
const logsCmd = new LogsMenuCommand();
|
|
1141
1448
|
await logsCmd.execute();
|
|
1142
1449
|
break;
|
|
1143
1450
|
case "exit":
|
package/package.json
CHANGED