@inforge/migrations-tools-cli 1.0.0 → 1.1.0
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 +322 -23
- 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,
|
|
@@ -459,7 +502,7 @@ var DeactivateCommand = class {
|
|
|
459
502
|
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
460
503
|
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
461
504
|
const objects = await this.sfClient.queryObjects(connection);
|
|
462
|
-
const selectedObject = await this.prompts.
|
|
505
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
463
506
|
const automationType = await this.prompts.selectAutomationType();
|
|
464
507
|
if (automationType === "validation-rules") {
|
|
465
508
|
await this.deactivateValidationRules(org, selectedObject, connection);
|
|
@@ -744,7 +787,7 @@ var RestoreCommand = class {
|
|
|
744
787
|
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
745
788
|
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
746
789
|
const objects = await this.sfClient.queryObjects(connection);
|
|
747
|
-
const selectedObject = await this.prompts.
|
|
790
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
748
791
|
const automationType = await this.prompts.selectAutomationType();
|
|
749
792
|
const backups = await this.backupManager.list(selectedOrg, selectedObject, automationType);
|
|
750
793
|
if (backups.length === 0) {
|
|
@@ -756,10 +799,12 @@ var RestoreCommand = class {
|
|
|
756
799
|
const backup2 = await this.backupManager.load(backupPath);
|
|
757
800
|
const filename = path3.basename(backupPath, ".json");
|
|
758
801
|
const status = backup2.restoredAt ? `restored on ${backup2.restoredAt.split("T")[0]}` : "not restored";
|
|
802
|
+
const opType = backup2.operationType === "individual" ? "Individual changes" : "Bulk deactivate";
|
|
803
|
+
const itemCount = `${backup2.items.length} ${backup2.type === "validation-rules" ? "rules" : backup2.type}`;
|
|
759
804
|
return {
|
|
760
805
|
value: backupPath,
|
|
761
|
-
label: filename
|
|
762
|
-
hint: `${
|
|
806
|
+
label: `${filename} - ${opType}`,
|
|
807
|
+
hint: `${itemCount}, ${status}`
|
|
763
808
|
};
|
|
764
809
|
})
|
|
765
810
|
);
|
|
@@ -1007,10 +1052,243 @@ var RestoreCommand = class {
|
|
|
1007
1052
|
}
|
|
1008
1053
|
};
|
|
1009
1054
|
|
|
1010
|
-
// src/commands/
|
|
1055
|
+
// src/commands/individual.ts
|
|
1056
|
+
var IndividualCommand = class {
|
|
1057
|
+
sfClient;
|
|
1058
|
+
backupManager;
|
|
1059
|
+
logger;
|
|
1060
|
+
prompts;
|
|
1061
|
+
constructor() {
|
|
1062
|
+
this.sfClient = new SfClient();
|
|
1063
|
+
this.backupManager = new BackupManager();
|
|
1064
|
+
this.logger = new Logger();
|
|
1065
|
+
this.prompts = new Prompts();
|
|
1066
|
+
}
|
|
1067
|
+
async execute() {
|
|
1068
|
+
const orgs = await this.sfClient.listOrgs();
|
|
1069
|
+
if (orgs.length === 0) {
|
|
1070
|
+
this.prompts.error("No authenticated orgs found.");
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const selectedOrg = await this.prompts.selectOrg(orgs);
|
|
1074
|
+
const org = orgs.find((o) => o.alias === selectedOrg);
|
|
1075
|
+
const connection = await this.sfClient.getConnection(selectedOrg);
|
|
1076
|
+
const objects = await this.sfClient.queryObjects(connection);
|
|
1077
|
+
const selectedObject = await this.prompts.selectObjectWithSearch(objects);
|
|
1078
|
+
const automationType = await this.prompts.selectAutomationType();
|
|
1079
|
+
const spinner2 = this.prompts.spinner();
|
|
1080
|
+
spinner2.start("Fetching automations...");
|
|
1081
|
+
let currentItems;
|
|
1082
|
+
let managedItems;
|
|
1083
|
+
let handler;
|
|
1084
|
+
if (automationType === "validation-rules") {
|
|
1085
|
+
handler = new ValidationRulesHandler();
|
|
1086
|
+
} else if (automationType === "flows") {
|
|
1087
|
+
handler = new FlowsHandler();
|
|
1088
|
+
} else {
|
|
1089
|
+
handler = new TriggersHandler();
|
|
1090
|
+
}
|
|
1091
|
+
const separated = await handler.fetchSeparated(connection, selectedObject);
|
|
1092
|
+
currentItems = separated.custom;
|
|
1093
|
+
managedItems = separated.managed;
|
|
1094
|
+
spinner2.stop("Automations fetched");
|
|
1095
|
+
if (currentItems.length === 0 && managedItems.length === 0) {
|
|
1096
|
+
this.prompts.warning(`No ${automationType} found for ${selectedObject}.`);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const selectedFullNames = await this.prompts.selectWithCheckboxes(
|
|
1100
|
+
`${selectedObject} > ${automationType}
|
|
1101
|
+
|
|
1102
|
+
Select items to toggle (Space to select, Enter to continue):`,
|
|
1103
|
+
currentItems,
|
|
1104
|
+
managedItems
|
|
1105
|
+
);
|
|
1106
|
+
if (selectedFullNames.length === 0) {
|
|
1107
|
+
this.prompts.warning("No changes selected.");
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const currentItemsMap = new Map(currentItems.map((item) => [item.fullName, item]));
|
|
1111
|
+
const toActivate = [];
|
|
1112
|
+
const toDeactivate = [];
|
|
1113
|
+
for (const fullName of selectedFullNames) {
|
|
1114
|
+
const item = currentItemsMap.get(fullName);
|
|
1115
|
+
if (item) {
|
|
1116
|
+
if (item.active) {
|
|
1117
|
+
toDeactivate.push(fullName);
|
|
1118
|
+
} else {
|
|
1119
|
+
toActivate.push(fullName);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
let previewMessage = `Changes to apply:
|
|
1124
|
+
`;
|
|
1125
|
+
if (toDeactivate.length > 0) {
|
|
1126
|
+
previewMessage += `
|
|
1127
|
+
Deactivate (${toDeactivate.length}):
|
|
1128
|
+
`;
|
|
1129
|
+
toDeactivate.forEach((name) => {
|
|
1130
|
+
previewMessage += ` - ${name}
|
|
1131
|
+
`;
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
if (toActivate.length > 0) {
|
|
1135
|
+
previewMessage += `
|
|
1136
|
+
Activate (${toActivate.length}):
|
|
1137
|
+
`;
|
|
1138
|
+
toActivate.forEach((name) => {
|
|
1139
|
+
previewMessage += ` - ${name}
|
|
1140
|
+
`;
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
previewMessage += "\nBackup will be created automatically.";
|
|
1144
|
+
this.prompts.note(previewMessage, "Preview");
|
|
1145
|
+
const confirmed = await this.prompts.confirm("Proceed with these changes?");
|
|
1146
|
+
if (!confirmed) {
|
|
1147
|
+
this.prompts.cancel("Operation cancelled");
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const operationId = this.logger.generateOperationId();
|
|
1151
|
+
const backupSpinner = this.prompts.spinner();
|
|
1152
|
+
backupSpinner.start("Creating backup...");
|
|
1153
|
+
const backupItems = selectedFullNames.map((fn) => currentItemsMap.get(fn));
|
|
1154
|
+
const backupPath = await this.backupManager.save(
|
|
1155
|
+
org,
|
|
1156
|
+
selectedObject,
|
|
1157
|
+
automationType,
|
|
1158
|
+
backupItems,
|
|
1159
|
+
[],
|
|
1160
|
+
"individual"
|
|
1161
|
+
);
|
|
1162
|
+
backupSpinner.stop("Backup created");
|
|
1163
|
+
const applySpinner = this.prompts.spinner();
|
|
1164
|
+
applySpinner.start("Applying changes...");
|
|
1165
|
+
try {
|
|
1166
|
+
await this.applyChanges(
|
|
1167
|
+
handler,
|
|
1168
|
+
connection,
|
|
1169
|
+
selectedObject,
|
|
1170
|
+
automationType,
|
|
1171
|
+
toDeactivate,
|
|
1172
|
+
toActivate,
|
|
1173
|
+
currentItemsMap
|
|
1174
|
+
);
|
|
1175
|
+
await this.logger.log({
|
|
1176
|
+
id: operationId,
|
|
1177
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1178
|
+
operation: "deactivate",
|
|
1179
|
+
org: org.alias,
|
|
1180
|
+
object: selectedObject,
|
|
1181
|
+
type: automationType,
|
|
1182
|
+
status: "success",
|
|
1183
|
+
itemsAffected: selectedFullNames.length,
|
|
1184
|
+
itemsSkipped: 0,
|
|
1185
|
+
backupPath,
|
|
1186
|
+
error: null
|
|
1187
|
+
});
|
|
1188
|
+
applySpinner.stop("Changes applied");
|
|
1189
|
+
this.prompts.success(
|
|
1190
|
+
`Successfully modified ${selectedFullNames.length} ${automationType}.`
|
|
1191
|
+
);
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
applySpinner.stop("Changes failed");
|
|
1194
|
+
await this.logger.log({
|
|
1195
|
+
id: operationId,
|
|
1196
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1197
|
+
operation: "deactivate",
|
|
1198
|
+
org: org.alias,
|
|
1199
|
+
object: selectedObject,
|
|
1200
|
+
type: automationType,
|
|
1201
|
+
status: "failure",
|
|
1202
|
+
itemsAffected: 0,
|
|
1203
|
+
itemsSkipped: 0,
|
|
1204
|
+
backupPath,
|
|
1205
|
+
error: error.message
|
|
1206
|
+
});
|
|
1207
|
+
this.prompts.error(`Failed to apply changes: ${error.message}`);
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async applyChanges(handler, connection, objectName, automationType, toDeactivate, toActivate, itemsMap) {
|
|
1212
|
+
if (automationType === "validation-rules") {
|
|
1213
|
+
const vrHandler = handler;
|
|
1214
|
+
if (toDeactivate.length > 0) {
|
|
1215
|
+
const ruleNames = toDeactivate.map((fn) => fn.split(".")[1]);
|
|
1216
|
+
await vrHandler.deactivate(connection, objectName, ruleNames);
|
|
1217
|
+
}
|
|
1218
|
+
if (toActivate.length > 0) {
|
|
1219
|
+
const ruleNames = toActivate.map((fn) => fn.split(".")[1]);
|
|
1220
|
+
await vrHandler.activate(connection, objectName, ruleNames);
|
|
1221
|
+
}
|
|
1222
|
+
} else if (automationType === "flows") {
|
|
1223
|
+
const flowHandler = handler;
|
|
1224
|
+
if (toDeactivate.length > 0) {
|
|
1225
|
+
const flowIds = toDeactivate.map((fn) => {
|
|
1226
|
+
const item = itemsMap.get(fn);
|
|
1227
|
+
return item.metadata.Id;
|
|
1228
|
+
});
|
|
1229
|
+
await flowHandler.deactivate(connection, flowIds);
|
|
1230
|
+
}
|
|
1231
|
+
if (toActivate.length > 0) {
|
|
1232
|
+
const flowsWithVersions = toActivate.map((fn) => {
|
|
1233
|
+
const item = itemsMap.get(fn);
|
|
1234
|
+
return {
|
|
1235
|
+
id: item.metadata.Id,
|
|
1236
|
+
version: item.metadata.LatestVersion.VersionNumber
|
|
1237
|
+
};
|
|
1238
|
+
});
|
|
1239
|
+
await flowHandler.activate(connection, flowsWithVersions);
|
|
1240
|
+
}
|
|
1241
|
+
} else {
|
|
1242
|
+
const triggerHandler = handler;
|
|
1243
|
+
if (toDeactivate.length > 0) {
|
|
1244
|
+
await triggerHandler.deactivate(connection, toDeactivate);
|
|
1245
|
+
}
|
|
1246
|
+
if (toActivate.length > 0) {
|
|
1247
|
+
await triggerHandler.activate(connection, toActivate);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// src/commands/automations.ts
|
|
1254
|
+
var AutomationsCommand = class {
|
|
1255
|
+
prompts;
|
|
1256
|
+
constructor() {
|
|
1257
|
+
this.prompts = new Prompts();
|
|
1258
|
+
}
|
|
1259
|
+
async execute() {
|
|
1260
|
+
const action = await this.prompts.selectFromOptions(
|
|
1261
|
+
"Automations",
|
|
1262
|
+
[
|
|
1263
|
+
{ value: "deactivate", label: "Deactivate all" },
|
|
1264
|
+
{ value: "restore", label: "Restore" },
|
|
1265
|
+
{ value: "individual", label: "Manage individually" },
|
|
1266
|
+
{ value: "back", label: "Back to main menu" }
|
|
1267
|
+
]
|
|
1268
|
+
);
|
|
1269
|
+
switch (action) {
|
|
1270
|
+
case "deactivate":
|
|
1271
|
+
const deactivateCmd = new DeactivateCommand();
|
|
1272
|
+
await deactivateCmd.execute();
|
|
1273
|
+
break;
|
|
1274
|
+
case "restore":
|
|
1275
|
+
const restoreCmd = new RestoreCommand();
|
|
1276
|
+
await restoreCmd.execute();
|
|
1277
|
+
break;
|
|
1278
|
+
case "individual":
|
|
1279
|
+
const individualCmd = new IndividualCommand();
|
|
1280
|
+
await individualCmd.execute();
|
|
1281
|
+
break;
|
|
1282
|
+
case "back":
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// src/commands/backups.ts
|
|
1011
1289
|
import * as fs3 from "fs/promises";
|
|
1012
1290
|
import * as path4 from "path";
|
|
1013
|
-
var
|
|
1291
|
+
var BackupsCommand = class {
|
|
1014
1292
|
backupManager;
|
|
1015
1293
|
prompts;
|
|
1016
1294
|
constructor() {
|
|
@@ -1019,7 +1297,7 @@ var ManageCommand = class {
|
|
|
1019
1297
|
}
|
|
1020
1298
|
async execute() {
|
|
1021
1299
|
const action = await this.prompts.selectFromOptions(
|
|
1022
|
-
"
|
|
1300
|
+
"Backups",
|
|
1023
1301
|
[
|
|
1024
1302
|
{ value: "view", label: "View all backups" },
|
|
1025
1303
|
{ value: "cleanup", label: "Clean up old backups" },
|
|
@@ -1116,6 +1394,31 @@ var LogsCommand = class {
|
|
|
1116
1394
|
}
|
|
1117
1395
|
};
|
|
1118
1396
|
|
|
1397
|
+
// src/commands/logs-menu.ts
|
|
1398
|
+
var LogsMenuCommand = class {
|
|
1399
|
+
prompts;
|
|
1400
|
+
constructor() {
|
|
1401
|
+
this.prompts = new Prompts();
|
|
1402
|
+
}
|
|
1403
|
+
async execute() {
|
|
1404
|
+
const action = await this.prompts.selectFromOptions(
|
|
1405
|
+
"Logs",
|
|
1406
|
+
[
|
|
1407
|
+
{ value: "view", label: "View recent operations" },
|
|
1408
|
+
{ value: "back", label: "Back to main menu" }
|
|
1409
|
+
]
|
|
1410
|
+
);
|
|
1411
|
+
switch (action) {
|
|
1412
|
+
case "view":
|
|
1413
|
+
const logsCmd = new LogsCommand();
|
|
1414
|
+
await logsCmd.execute();
|
|
1415
|
+
break;
|
|
1416
|
+
case "back":
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1119
1422
|
// src/index.ts
|
|
1120
1423
|
async function main() {
|
|
1121
1424
|
const prompts = new Prompts();
|
|
@@ -1124,20 +1427,16 @@ async function main() {
|
|
|
1124
1427
|
while (true) {
|
|
1125
1428
|
const action = await prompts.selectMainAction();
|
|
1126
1429
|
switch (action) {
|
|
1127
|
-
case "
|
|
1128
|
-
const
|
|
1129
|
-
await
|
|
1130
|
-
break;
|
|
1131
|
-
case "restore":
|
|
1132
|
-
const restoreCmd = new RestoreCommand();
|
|
1133
|
-
await restoreCmd.execute();
|
|
1430
|
+
case "automations":
|
|
1431
|
+
const automationsCmd = new AutomationsCommand();
|
|
1432
|
+
await automationsCmd.execute();
|
|
1134
1433
|
break;
|
|
1135
|
-
case "
|
|
1136
|
-
const
|
|
1137
|
-
await
|
|
1434
|
+
case "backups":
|
|
1435
|
+
const backupsCmd = new BackupsCommand();
|
|
1436
|
+
await backupsCmd.execute();
|
|
1138
1437
|
break;
|
|
1139
1438
|
case "logs":
|
|
1140
|
-
const logsCmd = new
|
|
1439
|
+
const logsCmd = new LogsMenuCommand();
|
|
1141
1440
|
await logsCmd.execute();
|
|
1142
1441
|
break;
|
|
1143
1442
|
case "exit":
|
package/package.json
CHANGED