@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.
Files changed (68) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +1166 -1166
  3. package/actions/autopublish.old.js +293 -293
  4. package/actions/config.js +182 -182
  5. package/actions/create.js +466 -466
  6. package/actions/help.js +164 -164
  7. package/actions/iris/buildStage.js +874 -874
  8. package/actions/iris/delete.js +256 -256
  9. package/actions/iris/dev.js +391 -391
  10. package/actions/iris/index.js +6 -6
  11. package/actions/iris/link.js +375 -375
  12. package/actions/iris/recover.js +268 -268
  13. package/actions/main.js +80 -80
  14. package/actions/publish.js +1420 -1420
  15. package/actions/pull.js +684 -684
  16. package/actions/setup.js +148 -148
  17. package/actions/status.js +17 -17
  18. package/actions/update.js +248 -248
  19. package/bin/magentrix.js +393 -393
  20. package/package.json +55 -55
  21. package/utils/assetPaths.js +158 -158
  22. package/utils/autopublishLock.js +77 -77
  23. package/utils/cacher.js +206 -206
  24. package/utils/cli/checkInstanceUrl.js +76 -74
  25. package/utils/cli/helpers/compare.js +282 -282
  26. package/utils/cli/helpers/ensureApiKey.js +63 -63
  27. package/utils/cli/helpers/ensureCredentials.js +68 -68
  28. package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
  29. package/utils/cli/writeRecords.js +262 -262
  30. package/utils/compare.js +135 -135
  31. package/utils/compress.js +17 -17
  32. package/utils/config.js +527 -527
  33. package/utils/debug.js +144 -144
  34. package/utils/diagnostics/testPublishLogic.js +96 -96
  35. package/utils/diff.js +49 -49
  36. package/utils/downloadAssets.js +291 -291
  37. package/utils/filetag.js +115 -115
  38. package/utils/hash.js +14 -14
  39. package/utils/iris/backup.js +411 -411
  40. package/utils/iris/builder.js +541 -541
  41. package/utils/iris/config-reader.js +664 -664
  42. package/utils/iris/deleteHelper.js +150 -150
  43. package/utils/iris/errors.js +537 -537
  44. package/utils/iris/linker.js +601 -601
  45. package/utils/iris/lock.js +360 -360
  46. package/utils/iris/validation.js +360 -360
  47. package/utils/iris/validator.js +281 -281
  48. package/utils/iris/zipper.js +248 -248
  49. package/utils/logger.js +291 -291
  50. package/utils/magentrix/api/assets.js +220 -220
  51. package/utils/magentrix/api/auth.js +107 -107
  52. package/utils/magentrix/api/createEntity.js +61 -61
  53. package/utils/magentrix/api/deleteEntity.js +55 -55
  54. package/utils/magentrix/api/iris.js +251 -251
  55. package/utils/magentrix/api/meqlQuery.js +36 -36
  56. package/utils/magentrix/api/retrieveEntity.js +86 -86
  57. package/utils/magentrix/api/updateEntity.js +66 -66
  58. package/utils/magentrix/fetch.js +168 -168
  59. package/utils/merge.js +22 -22
  60. package/utils/permissionError.js +70 -70
  61. package/utils/preferences.js +40 -40
  62. package/utils/progress.js +469 -469
  63. package/utils/spinner.js +43 -43
  64. package/utils/template.js +52 -52
  65. package/utils/updateFileBase.js +121 -121
  66. package/utils/workspaces.js +108 -108
  67. package/vars/config.js +11 -11
  68. package/vars/global.js +50 -50
@@ -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;