@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17
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/LICENSE +25 -25
- package/README.md +1166 -1166
- package/actions/autopublish.old.js +293 -293
- package/actions/config.js +182 -182
- package/actions/create.js +466 -466
- package/actions/help.js +164 -164
- package/actions/iris/buildStage.js +874 -874
- package/actions/iris/delete.js +256 -256
- package/actions/iris/dev.js +391 -391
- package/actions/iris/index.js +6 -6
- package/actions/iris/link.js +375 -375
- package/actions/iris/recover.js +268 -268
- package/actions/main.js +80 -80
- package/actions/publish.js +1420 -1420
- package/actions/pull.js +684 -684
- package/actions/setup.js +148 -148
- package/actions/status.js +17 -17
- package/actions/update.js +248 -248
- package/bin/magentrix.js +393 -393
- package/package.json +55 -55
- package/utils/assetPaths.js +158 -158
- package/utils/autopublishLock.js +77 -77
- package/utils/cacher.js +206 -206
- package/utils/cli/checkInstanceUrl.js +76 -74
- package/utils/cli/helpers/compare.js +282 -282
- package/utils/cli/helpers/ensureApiKey.js +63 -63
- package/utils/cli/helpers/ensureCredentials.js +68 -68
- package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
- package/utils/cli/writeRecords.js +262 -262
- package/utils/compare.js +135 -135
- package/utils/compress.js +17 -17
- package/utils/config.js +527 -527
- package/utils/debug.js +144 -144
- package/utils/diagnostics/testPublishLogic.js +96 -96
- package/utils/diff.js +49 -49
- package/utils/downloadAssets.js +291 -291
- package/utils/filetag.js +115 -115
- package/utils/hash.js +14 -14
- package/utils/iris/backup.js +411 -411
- package/utils/iris/builder.js +541 -541
- package/utils/iris/config-reader.js +664 -664
- package/utils/iris/deleteHelper.js +150 -150
- package/utils/iris/errors.js +537 -537
- package/utils/iris/linker.js +601 -601
- package/utils/iris/lock.js +360 -360
- package/utils/iris/validation.js +360 -360
- package/utils/iris/validator.js +281 -281
- package/utils/iris/zipper.js +248 -248
- package/utils/logger.js +291 -291
- package/utils/magentrix/api/assets.js +220 -220
- package/utils/magentrix/api/auth.js +107 -107
- package/utils/magentrix/api/createEntity.js +61 -61
- package/utils/magentrix/api/deleteEntity.js +55 -55
- package/utils/magentrix/api/iris.js +251 -251
- package/utils/magentrix/api/meqlQuery.js +36 -36
- package/utils/magentrix/api/retrieveEntity.js +86 -86
- package/utils/magentrix/api/updateEntity.js +66 -66
- package/utils/magentrix/fetch.js +168 -168
- package/utils/merge.js +22 -22
- package/utils/permissionError.js +70 -70
- package/utils/preferences.js +40 -40
- package/utils/progress.js +469 -469
- package/utils/spinner.js +43 -43
- package/utils/template.js +52 -52
- package/utils/updateFileBase.js +121 -121
- package/utils/workspaces.js +108 -108
- package/vars/config.js +11 -11
- package/vars/global.js +50 -50
package/actions/iris/recover.js
CHANGED
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { listBackups, restoreIrisApp, deleteBackup } from '../../utils/iris/backup.js';
|
|
6
|
-
import { linkVueProject } from '../../utils/iris/linker.js';
|
|
7
|
-
import { showPermissionError } from '../../utils/permissionError.js';
|
|
8
|
-
import { EXPORT_ROOT, IRIS_APPS_DIR } from '../../vars/global.js';
|
|
9
|
-
import { acquireLock, releaseLock, LockTypes } from '../../utils/iris/lock.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* iris-app-recover command - Restore a deleted Iris app from backup.
|
|
13
|
-
*
|
|
14
|
-
* Options:
|
|
15
|
-
* --list List all available backups
|
|
16
|
-
*/
|
|
17
|
-
export const irisRecover = async (options = {}) => {
|
|
18
|
-
process.stdout.write('\x1Bc'); // Clear console
|
|
19
|
-
|
|
20
|
-
const { list } = options;
|
|
21
|
-
|
|
22
|
-
console.log(chalk.blue.bold('\n♻ Recover Iris App'));
|
|
23
|
-
console.log(chalk.gray('─'.repeat(48)));
|
|
24
|
-
console.log();
|
|
25
|
-
|
|
26
|
-
// Get available backups
|
|
27
|
-
const backups = listBackups();
|
|
28
|
-
|
|
29
|
-
if (backups.length === 0) {
|
|
30
|
-
console.log(chalk.yellow('No recovery backups found.'));
|
|
31
|
-
console.log();
|
|
32
|
-
console.log(chalk.gray('Backups are created automatically when you delete an Iris app.'));
|
|
33
|
-
console.log(chalk.white(`Use: ${chalk.cyan('magentrix iris-app-delete')}`));
|
|
34
|
-
console.log();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// If --list flag, just show and exit
|
|
39
|
-
if (list) {
|
|
40
|
-
console.log(chalk.white('Available Recovery Backups:'));
|
|
41
|
-
console.log();
|
|
42
|
-
|
|
43
|
-
backups.forEach((backup, i) => {
|
|
44
|
-
const date = new Date(backup.deletedAt);
|
|
45
|
-
console.log(chalk.white(`${i + 1}. ${chalk.cyan(backup.appName)} (${backup.slug})`));
|
|
46
|
-
console.log(chalk.gray(` Deleted: ${date.toLocaleString()}`));
|
|
47
|
-
if (backup.linkedProject) {
|
|
48
|
-
console.log(chalk.gray(` Linked: ${backup.linkedProject.path}`));
|
|
49
|
-
}
|
|
50
|
-
console.log(chalk.gray(` Backup: ${backup.backupPath}`));
|
|
51
|
-
console.log();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
console.log(chalk.white(`To recover, run: ${chalk.cyan('magentrix iris-app-recover')}`));
|
|
55
|
-
console.log();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Build choices for selection
|
|
60
|
-
const choices = backups.map((backup) => {
|
|
61
|
-
const date = new Date(backup.deletedAt);
|
|
62
|
-
const timeAgo = getTimeAgo(date);
|
|
63
|
-
return {
|
|
64
|
-
name: `${backup.appName} (${backup.slug}) - Deleted ${timeAgo}`,
|
|
65
|
-
value: backup
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
choices.push({
|
|
70
|
-
name: 'Cancel',
|
|
71
|
-
value: null
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Select backup to restore
|
|
75
|
-
const selectedBackup = await select({
|
|
76
|
-
message: 'Which backup do you want to restore?',
|
|
77
|
-
choices
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!selectedBackup) {
|
|
81
|
-
console.log(chalk.gray('Cancelled.'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const { slug, appName, linkedProject, backupPath } = selectedBackup;
|
|
86
|
-
|
|
87
|
-
// Show recovery info
|
|
88
|
-
console.log();
|
|
89
|
-
console.log(chalk.white('Recovery Details:'));
|
|
90
|
-
console.log(chalk.gray('─'.repeat(48)));
|
|
91
|
-
console.log(chalk.white(` App: ${chalk.cyan(appName)} (${slug})`));
|
|
92
|
-
console.log(chalk.white(` Backup: ${chalk.gray(backupPath)}`));
|
|
93
|
-
|
|
94
|
-
if (linkedProject) {
|
|
95
|
-
const pathExists = existsSync(linkedProject.path);
|
|
96
|
-
if (pathExists) {
|
|
97
|
-
console.log(chalk.green(` ✓ Linked project: ${linkedProject.path}`));
|
|
98
|
-
} else {
|
|
99
|
-
console.log(chalk.yellow(` ⚠ Linked project path no longer exists:`));
|
|
100
|
-
console.log(chalk.gray(` ${linkedProject.path}`));
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
console.log(chalk.gray(' (No linked Vue project)'));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
console.log();
|
|
107
|
-
|
|
108
|
-
// Check for warnings
|
|
109
|
-
const warnings = [];
|
|
110
|
-
if (linkedProject && !existsSync(linkedProject.path)) {
|
|
111
|
-
warnings.push('The linked Vue project path no longer exists. Only local files will be restored.');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Check if app already exists locally
|
|
115
|
-
const appPath = path.join(EXPORT_ROOT, IRIS_APPS_DIR, slug);
|
|
116
|
-
if (existsSync(appPath)) {
|
|
117
|
-
console.log(chalk.yellow(`⚠ Warning: App folder already exists at ${appPath}`));
|
|
118
|
-
console.log(chalk.yellow(' Recovery will overwrite existing files.'));
|
|
119
|
-
console.log();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (warnings.length > 0) {
|
|
123
|
-
console.log(chalk.yellow('⚠ Warnings:'));
|
|
124
|
-
warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
125
|
-
console.log();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Confirm recovery
|
|
129
|
-
const shouldRecover = await confirm({
|
|
130
|
-
message: 'Do you want to restore this app?',
|
|
131
|
-
default: true
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
if (!shouldRecover) {
|
|
135
|
-
console.log(chalk.gray('Cancelled.'));
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Acquire recover lock to prevent concurrent recoveries
|
|
140
|
-
const lockBasePath = path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.magentrix-locks');
|
|
141
|
-
const lockResult = acquireLock(LockTypes.RECOVER, {
|
|
142
|
-
context: slug,
|
|
143
|
-
operation: `recovering ${slug}`,
|
|
144
|
-
basePath: lockBasePath
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Track if lock was acquired (permission errors are non-fatal)
|
|
148
|
-
let lockAcquired = lockResult.acquired;
|
|
149
|
-
if (!lockResult.acquired) {
|
|
150
|
-
if (lockResult.error?.includes('permission') || lockResult.error?.includes('EACCES')) {
|
|
151
|
-
console.log(chalk.yellow('Warning: Could not create recover lock (permission issue). Proceeding without lock.'));
|
|
152
|
-
lockAcquired = false;
|
|
153
|
-
} else {
|
|
154
|
-
console.log(chalk.red('Cannot recover app:'));
|
|
155
|
-
console.log(chalk.yellow(lockResult.error));
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let restoreResult;
|
|
161
|
-
try {
|
|
162
|
-
// Restore files
|
|
163
|
-
console.log();
|
|
164
|
-
console.log(chalk.blue('Restoring files...'));
|
|
165
|
-
|
|
166
|
-
restoreResult = await restoreIrisApp(backupPath, {
|
|
167
|
-
restoreLocal: true,
|
|
168
|
-
restoreLink: true
|
|
169
|
-
});
|
|
170
|
-
} finally {
|
|
171
|
-
if (lockAcquired) {
|
|
172
|
-
releaseLock(LockTypes.RECOVER, { context: slug, basePath: lockBasePath });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!restoreResult.success) {
|
|
177
|
-
if (restoreResult.isPermissionError) {
|
|
178
|
-
const targetDir = path.join(process.cwd(), EXPORT_ROOT, IRIS_APPS_DIR);
|
|
179
|
-
showPermissionError({
|
|
180
|
-
operation: 'restore',
|
|
181
|
-
targetPath: targetDir,
|
|
182
|
-
backupPath,
|
|
183
|
-
slug
|
|
184
|
-
});
|
|
185
|
-
} else if (restoreResult.isFileLocked) {
|
|
186
|
-
console.log(chalk.red('Cannot restore - files are in use'));
|
|
187
|
-
console.log(chalk.yellow(restoreResult.error));
|
|
188
|
-
console.log();
|
|
189
|
-
console.log(chalk.gray('Close any programs that may be using these files and try again.'));
|
|
190
|
-
} else if (restoreResult.isCorrupted) {
|
|
191
|
-
console.log(chalk.red('Backup appears to be corrupted'));
|
|
192
|
-
console.log(chalk.yellow(restoreResult.error));
|
|
193
|
-
console.log();
|
|
194
|
-
console.log(chalk.gray('The backup file may have been damaged. You may need to delete it.'));
|
|
195
|
-
console.log(chalk.gray(`Backup path: ${backupPath}`));
|
|
196
|
-
} else {
|
|
197
|
-
console.log(chalk.red(`Failed to restore: ${restoreResult.error}`));
|
|
198
|
-
}
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
console.log(chalk.green(`✓ Restored files to ${EXPORT_ROOT}/${IRIS_APPS_DIR}/${slug}/`));
|
|
203
|
-
|
|
204
|
-
// Re-link Vue project if needed
|
|
205
|
-
if (linkedProject && restoreResult.linkedProjectPathExists) {
|
|
206
|
-
console.log(chalk.blue('Re-linking Vue project...'));
|
|
207
|
-
|
|
208
|
-
const linkResult = linkVueProject(linkedProject.path);
|
|
209
|
-
if (linkResult.success) {
|
|
210
|
-
console.log(chalk.green('✓ Vue project re-linked'));
|
|
211
|
-
} else {
|
|
212
|
-
console.log(chalk.yellow(`⚠ Could not re-link Vue project: ${linkResult.error}`));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Show warnings
|
|
217
|
-
if (restoreResult.warnings.length > 0) {
|
|
218
|
-
console.log();
|
|
219
|
-
console.log(chalk.yellow('Warnings:'));
|
|
220
|
-
restoreResult.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Summary
|
|
224
|
-
console.log();
|
|
225
|
-
console.log(chalk.green('─'.repeat(48)));
|
|
226
|
-
console.log(chalk.green.bold('✓ Recovery Complete!'));
|
|
227
|
-
console.log();
|
|
228
|
-
console.log(chalk.cyan('Next steps:'));
|
|
229
|
-
console.log(chalk.white(` • Run ${chalk.yellow('magentrix publish')} to sync the app back to the server`));
|
|
230
|
-
console.log();
|
|
231
|
-
|
|
232
|
-
// Ask if they want to delete the backup
|
|
233
|
-
const deleteBackupConfirm = await confirm({
|
|
234
|
-
message: 'Delete the recovery backup now?',
|
|
235
|
-
default: false
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
if (deleteBackupConfirm) {
|
|
239
|
-
deleteBackup(backupPath);
|
|
240
|
-
console.log(chalk.green('✓ Recovery backup deleted'));
|
|
241
|
-
} else {
|
|
242
|
-
console.log(chalk.gray(`Backup preserved at: ${backupPath}`));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
console.log();
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Get human-readable time ago string.
|
|
250
|
-
* @param {Date} date - Date to compare
|
|
251
|
-
* @returns {string} - Time ago string
|
|
252
|
-
*/
|
|
253
|
-
function getTimeAgo(date) {
|
|
254
|
-
const now = new Date();
|
|
255
|
-
const diffMs = now - date;
|
|
256
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
257
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
258
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
259
|
-
|
|
260
|
-
if (diffMins < 1) return 'just now';
|
|
261
|
-
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
|
262
|
-
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
263
|
-
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
264
|
-
|
|
265
|
-
return date.toLocaleDateString();
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export default irisRecover;
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { listBackups, restoreIrisApp, deleteBackup } from '../../utils/iris/backup.js';
|
|
6
|
+
import { linkVueProject } from '../../utils/iris/linker.js';
|
|
7
|
+
import { showPermissionError } from '../../utils/permissionError.js';
|
|
8
|
+
import { EXPORT_ROOT, IRIS_APPS_DIR } from '../../vars/global.js';
|
|
9
|
+
import { acquireLock, releaseLock, LockTypes } from '../../utils/iris/lock.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* iris-app-recover command - Restore a deleted Iris app from backup.
|
|
13
|
+
*
|
|
14
|
+
* Options:
|
|
15
|
+
* --list List all available backups
|
|
16
|
+
*/
|
|
17
|
+
export const irisRecover = async (options = {}) => {
|
|
18
|
+
process.stdout.write('\x1Bc'); // Clear console
|
|
19
|
+
|
|
20
|
+
const { list } = options;
|
|
21
|
+
|
|
22
|
+
console.log(chalk.blue.bold('\n♻ Recover Iris App'));
|
|
23
|
+
console.log(chalk.gray('─'.repeat(48)));
|
|
24
|
+
console.log();
|
|
25
|
+
|
|
26
|
+
// Get available backups
|
|
27
|
+
const backups = listBackups();
|
|
28
|
+
|
|
29
|
+
if (backups.length === 0) {
|
|
30
|
+
console.log(chalk.yellow('No recovery backups found.'));
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.gray('Backups are created automatically when you delete an Iris app.'));
|
|
33
|
+
console.log(chalk.white(`Use: ${chalk.cyan('magentrix iris-app-delete')}`));
|
|
34
|
+
console.log();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If --list flag, just show and exit
|
|
39
|
+
if (list) {
|
|
40
|
+
console.log(chalk.white('Available Recovery Backups:'));
|
|
41
|
+
console.log();
|
|
42
|
+
|
|
43
|
+
backups.forEach((backup, i) => {
|
|
44
|
+
const date = new Date(backup.deletedAt);
|
|
45
|
+
console.log(chalk.white(`${i + 1}. ${chalk.cyan(backup.appName)} (${backup.slug})`));
|
|
46
|
+
console.log(chalk.gray(` Deleted: ${date.toLocaleString()}`));
|
|
47
|
+
if (backup.linkedProject) {
|
|
48
|
+
console.log(chalk.gray(` Linked: ${backup.linkedProject.path}`));
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.gray(` Backup: ${backup.backupPath}`));
|
|
51
|
+
console.log();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(chalk.white(`To recover, run: ${chalk.cyan('magentrix iris-app-recover')}`));
|
|
55
|
+
console.log();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build choices for selection
|
|
60
|
+
const choices = backups.map((backup) => {
|
|
61
|
+
const date = new Date(backup.deletedAt);
|
|
62
|
+
const timeAgo = getTimeAgo(date);
|
|
63
|
+
return {
|
|
64
|
+
name: `${backup.appName} (${backup.slug}) - Deleted ${timeAgo}`,
|
|
65
|
+
value: backup
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
choices.push({
|
|
70
|
+
name: 'Cancel',
|
|
71
|
+
value: null
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Select backup to restore
|
|
75
|
+
const selectedBackup = await select({
|
|
76
|
+
message: 'Which backup do you want to restore?',
|
|
77
|
+
choices
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!selectedBackup) {
|
|
81
|
+
console.log(chalk.gray('Cancelled.'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { slug, appName, linkedProject, backupPath } = selectedBackup;
|
|
86
|
+
|
|
87
|
+
// Show recovery info
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.white('Recovery Details:'));
|
|
90
|
+
console.log(chalk.gray('─'.repeat(48)));
|
|
91
|
+
console.log(chalk.white(` App: ${chalk.cyan(appName)} (${slug})`));
|
|
92
|
+
console.log(chalk.white(` Backup: ${chalk.gray(backupPath)}`));
|
|
93
|
+
|
|
94
|
+
if (linkedProject) {
|
|
95
|
+
const pathExists = existsSync(linkedProject.path);
|
|
96
|
+
if (pathExists) {
|
|
97
|
+
console.log(chalk.green(` ✓ Linked project: ${linkedProject.path}`));
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.yellow(` ⚠ Linked project path no longer exists:`));
|
|
100
|
+
console.log(chalk.gray(` ${linkedProject.path}`));
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.gray(' (No linked Vue project)'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log();
|
|
107
|
+
|
|
108
|
+
// Check for warnings
|
|
109
|
+
const warnings = [];
|
|
110
|
+
if (linkedProject && !existsSync(linkedProject.path)) {
|
|
111
|
+
warnings.push('The linked Vue project path no longer exists. Only local files will be restored.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if app already exists locally
|
|
115
|
+
const appPath = path.join(EXPORT_ROOT, IRIS_APPS_DIR, slug);
|
|
116
|
+
if (existsSync(appPath)) {
|
|
117
|
+
console.log(chalk.yellow(`⚠ Warning: App folder already exists at ${appPath}`));
|
|
118
|
+
console.log(chalk.yellow(' Recovery will overwrite existing files.'));
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (warnings.length > 0) {
|
|
123
|
+
console.log(chalk.yellow('⚠ Warnings:'));
|
|
124
|
+
warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Confirm recovery
|
|
129
|
+
const shouldRecover = await confirm({
|
|
130
|
+
message: 'Do you want to restore this app?',
|
|
131
|
+
default: true
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!shouldRecover) {
|
|
135
|
+
console.log(chalk.gray('Cancelled.'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Acquire recover lock to prevent concurrent recoveries
|
|
140
|
+
const lockBasePath = path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.magentrix-locks');
|
|
141
|
+
const lockResult = acquireLock(LockTypes.RECOVER, {
|
|
142
|
+
context: slug,
|
|
143
|
+
operation: `recovering ${slug}`,
|
|
144
|
+
basePath: lockBasePath
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Track if lock was acquired (permission errors are non-fatal)
|
|
148
|
+
let lockAcquired = lockResult.acquired;
|
|
149
|
+
if (!lockResult.acquired) {
|
|
150
|
+
if (lockResult.error?.includes('permission') || lockResult.error?.includes('EACCES')) {
|
|
151
|
+
console.log(chalk.yellow('Warning: Could not create recover lock (permission issue). Proceeding without lock.'));
|
|
152
|
+
lockAcquired = false;
|
|
153
|
+
} else {
|
|
154
|
+
console.log(chalk.red('Cannot recover app:'));
|
|
155
|
+
console.log(chalk.yellow(lockResult.error));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let restoreResult;
|
|
161
|
+
try {
|
|
162
|
+
// Restore files
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(chalk.blue('Restoring files...'));
|
|
165
|
+
|
|
166
|
+
restoreResult = await restoreIrisApp(backupPath, {
|
|
167
|
+
restoreLocal: true,
|
|
168
|
+
restoreLink: true
|
|
169
|
+
});
|
|
170
|
+
} finally {
|
|
171
|
+
if (lockAcquired) {
|
|
172
|
+
releaseLock(LockTypes.RECOVER, { context: slug, basePath: lockBasePath });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!restoreResult.success) {
|
|
177
|
+
if (restoreResult.isPermissionError) {
|
|
178
|
+
const targetDir = path.join(process.cwd(), EXPORT_ROOT, IRIS_APPS_DIR);
|
|
179
|
+
showPermissionError({
|
|
180
|
+
operation: 'restore',
|
|
181
|
+
targetPath: targetDir,
|
|
182
|
+
backupPath,
|
|
183
|
+
slug
|
|
184
|
+
});
|
|
185
|
+
} else if (restoreResult.isFileLocked) {
|
|
186
|
+
console.log(chalk.red('Cannot restore - files are in use'));
|
|
187
|
+
console.log(chalk.yellow(restoreResult.error));
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(chalk.gray('Close any programs that may be using these files and try again.'));
|
|
190
|
+
} else if (restoreResult.isCorrupted) {
|
|
191
|
+
console.log(chalk.red('Backup appears to be corrupted'));
|
|
192
|
+
console.log(chalk.yellow(restoreResult.error));
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.gray('The backup file may have been damaged. You may need to delete it.'));
|
|
195
|
+
console.log(chalk.gray(`Backup path: ${backupPath}`));
|
|
196
|
+
} else {
|
|
197
|
+
console.log(chalk.red(`Failed to restore: ${restoreResult.error}`));
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(chalk.green(`✓ Restored files to ${EXPORT_ROOT}/${IRIS_APPS_DIR}/${slug}/`));
|
|
203
|
+
|
|
204
|
+
// Re-link Vue project if needed
|
|
205
|
+
if (linkedProject && restoreResult.linkedProjectPathExists) {
|
|
206
|
+
console.log(chalk.blue('Re-linking Vue project...'));
|
|
207
|
+
|
|
208
|
+
const linkResult = linkVueProject(linkedProject.path);
|
|
209
|
+
if (linkResult.success) {
|
|
210
|
+
console.log(chalk.green('✓ Vue project re-linked'));
|
|
211
|
+
} else {
|
|
212
|
+
console.log(chalk.yellow(`⚠ Could not re-link Vue project: ${linkResult.error}`));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Show warnings
|
|
217
|
+
if (restoreResult.warnings.length > 0) {
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.yellow('Warnings:'));
|
|
220
|
+
restoreResult.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Summary
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(chalk.green('─'.repeat(48)));
|
|
226
|
+
console.log(chalk.green.bold('✓ Recovery Complete!'));
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(chalk.cyan('Next steps:'));
|
|
229
|
+
console.log(chalk.white(` • Run ${chalk.yellow('magentrix publish')} to sync the app back to the server`));
|
|
230
|
+
console.log();
|
|
231
|
+
|
|
232
|
+
// Ask if they want to delete the backup
|
|
233
|
+
const deleteBackupConfirm = await confirm({
|
|
234
|
+
message: 'Delete the recovery backup now?',
|
|
235
|
+
default: false
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (deleteBackupConfirm) {
|
|
239
|
+
deleteBackup(backupPath);
|
|
240
|
+
console.log(chalk.green('✓ Recovery backup deleted'));
|
|
241
|
+
} else {
|
|
242
|
+
console.log(chalk.gray(`Backup preserved at: ${backupPath}`));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get human-readable time ago string.
|
|
250
|
+
* @param {Date} date - Date to compare
|
|
251
|
+
* @returns {string} - Time ago string
|
|
252
|
+
*/
|
|
253
|
+
function getTimeAgo(date) {
|
|
254
|
+
const now = new Date();
|
|
255
|
+
const diffMs = now - date;
|
|
256
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
257
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
258
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
259
|
+
|
|
260
|
+
if (diffMins < 1) return 'just now';
|
|
261
|
+
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
|
262
|
+
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
263
|
+
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
264
|
+
|
|
265
|
+
return date.toLocaleDateString();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default irisRecover;
|