@magentrix-corp/magentrix-cli 1.3.9 → 1.3.11
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 +42 -4
- package/actions/iris/buildStage.js +93 -5
- package/actions/iris/delete.js +46 -1
- package/actions/iris/dev.js +129 -13
- package/actions/iris/recover.js +47 -7
- package/package.json +1 -1
- package/utils/iris/backup.js +262 -52
- package/utils/iris/builder.js +334 -112
- package/utils/iris/config-reader.js +210 -35
- package/utils/iris/deleteHelper.js +55 -7
- package/utils/iris/errors.js +537 -0
- package/utils/iris/linker.js +118 -13
- package/utils/iris/lock.js +360 -0
- package/utils/iris/validation.js +360 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
|
+
import { validateSlug, validateUrl, validateAppName, detectExpression, MAX_LENGTHS } from './validation.js';
|
|
4
|
+
import { formatError, detectErrorType, ErrorTypes } from './errors.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Common locations where config.ts might be found in a Vue project.
|
|
@@ -17,6 +19,11 @@ const CONFIG_LOCATIONS = [
|
|
|
17
19
|
*/
|
|
18
20
|
const ENV_FILE = '.env.development';
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Maximum size of assets array before warning.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_ASSETS_WARNING_THRESHOLD = 500;
|
|
26
|
+
|
|
20
27
|
/**
|
|
21
28
|
* Parse a .env file and return key-value pairs.
|
|
22
29
|
*
|
|
@@ -68,7 +75,9 @@ function parseEnvFile(envPath) {
|
|
|
68
75
|
* siteUrl: string | null,
|
|
69
76
|
* assets: string[],
|
|
70
77
|
* refreshToken: string | null,
|
|
71
|
-
* envFileUsed: string | null
|
|
78
|
+
* envFileUsed: string | null,
|
|
79
|
+
* warnings: string[],
|
|
80
|
+
* errors: string[]
|
|
72
81
|
* }}
|
|
73
82
|
*/
|
|
74
83
|
export function readEnvConfig(projectPath) {
|
|
@@ -76,7 +85,9 @@ export function readEnvConfig(projectPath) {
|
|
|
76
85
|
siteUrl: null,
|
|
77
86
|
assets: [],
|
|
78
87
|
refreshToken: null,
|
|
79
|
-
envFileUsed: null
|
|
88
|
+
envFileUsed: null,
|
|
89
|
+
warnings: [],
|
|
90
|
+
errors: []
|
|
80
91
|
};
|
|
81
92
|
|
|
82
93
|
const envPath = join(projectPath, ENV_FILE);
|
|
@@ -85,23 +96,59 @@ export function readEnvConfig(projectPath) {
|
|
|
85
96
|
return result;
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
|
|
99
|
+
let envVars;
|
|
100
|
+
try {
|
|
101
|
+
envVars = parseEnvFile(envPath);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
result.errors.push(`Failed to read ${ENV_FILE}: ${err.message}`);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
89
107
|
result.envFileUsed = ENV_FILE;
|
|
90
108
|
|
|
91
|
-
// Read VITE_SITE_URL
|
|
109
|
+
// Read and validate VITE_SITE_URL
|
|
92
110
|
if (envVars.VITE_SITE_URL) {
|
|
93
|
-
|
|
111
|
+
const urlValue = envVars.VITE_SITE_URL;
|
|
112
|
+
const urlValidation = validateUrl(urlValue);
|
|
113
|
+
|
|
114
|
+
if (!urlValidation.valid) {
|
|
115
|
+
result.errors.push(`VITE_SITE_URL is invalid: ${urlValidation.error}`);
|
|
116
|
+
if (urlValidation.suggestion) {
|
|
117
|
+
result.warnings.push(` Suggestion: ${urlValidation.suggestion}`);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
result.siteUrl = urlValidation.normalized || urlValue;
|
|
121
|
+
}
|
|
94
122
|
}
|
|
95
123
|
|
|
96
|
-
// Read VITE_ASSETS
|
|
124
|
+
// Read VITE_ASSETS with better error handling
|
|
97
125
|
if (envVars.VITE_ASSETS) {
|
|
98
126
|
try {
|
|
99
127
|
const parsed = JSON.parse(envVars.VITE_ASSETS);
|
|
100
128
|
if (Array.isArray(parsed)) {
|
|
101
129
|
result.assets = parsed;
|
|
130
|
+
|
|
131
|
+
// Warn if assets array is very large
|
|
132
|
+
if (parsed.length > MAX_ASSETS_WARNING_THRESHOLD) {
|
|
133
|
+
result.warnings.push(
|
|
134
|
+
`VITE_ASSETS contains ${parsed.length} items. ` +
|
|
135
|
+
`Large arrays may cause performance issues.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
result.warnings.push('VITE_ASSETS is not an array, ignoring');
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
// Provide helpful error for JSON parsing issues
|
|
143
|
+
if (envVars.VITE_ASSETS.includes("'") && !envVars.VITE_ASSETS.includes('"')) {
|
|
144
|
+
result.errors.push(
|
|
145
|
+
'VITE_ASSETS contains invalid JSON. ' +
|
|
146
|
+
'JSON requires double quotes for strings, not single quotes. ' +
|
|
147
|
+
'Example: VITE_ASSETS = \'["url1", "url2"]\''
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
result.errors.push(`VITE_ASSETS contains invalid JSON: ${err.message}`);
|
|
102
151
|
}
|
|
103
|
-
} catch {
|
|
104
|
-
// If not valid JSON, skip
|
|
105
152
|
}
|
|
106
153
|
}
|
|
107
154
|
|
|
@@ -129,6 +176,24 @@ export function findConfigFile(projectPath) {
|
|
|
129
176
|
return null;
|
|
130
177
|
}
|
|
131
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Strip comments from config file content to prevent parsing issues.
|
|
181
|
+
* Removes both single-line (//) and multi-line comments.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} content - Raw config file content
|
|
184
|
+
* @returns {string} - Content with comments removed
|
|
185
|
+
*/
|
|
186
|
+
function stripComments(content) {
|
|
187
|
+
// Remove single-line comments (but not URLs with //)
|
|
188
|
+
// Match // that's not preceded by : (to avoid breaking URLs)
|
|
189
|
+
let result = content.replace(/(?<!:)\/\/.*$/gm, '');
|
|
190
|
+
|
|
191
|
+
// Remove multi-line comments
|
|
192
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
132
197
|
/**
|
|
133
198
|
* Parse a config.ts file and extract Iris configuration.
|
|
134
199
|
* Uses regex parsing to avoid TypeScript compilation.
|
|
@@ -141,7 +206,8 @@ export function findConfigFile(projectPath) {
|
|
|
141
206
|
* appIconId: string | null,
|
|
142
207
|
* siteUrl: string | null,
|
|
143
208
|
* assets: string[],
|
|
144
|
-
* raw: string
|
|
209
|
+
* raw: string,
|
|
210
|
+
* warnings: string[]
|
|
145
211
|
* }}
|
|
146
212
|
*/
|
|
147
213
|
export function parseConfigFile(configPath) {
|
|
@@ -152,24 +218,46 @@ export function parseConfigFile(configPath) {
|
|
|
152
218
|
appIconId: null,
|
|
153
219
|
siteUrl: null,
|
|
154
220
|
assets: [],
|
|
155
|
-
raw: ''
|
|
221
|
+
raw: '',
|
|
222
|
+
warnings: []
|
|
156
223
|
};
|
|
157
224
|
|
|
158
225
|
if (!existsSync(configPath)) {
|
|
159
226
|
return result;
|
|
160
227
|
}
|
|
161
228
|
|
|
162
|
-
|
|
229
|
+
let content;
|
|
230
|
+
try {
|
|
231
|
+
content = readFileSync(configPath, 'utf-8');
|
|
232
|
+
} catch (err) {
|
|
233
|
+
result.warnings.push(`Failed to read config file: ${err.message}`);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
163
237
|
result.raw = content;
|
|
164
238
|
|
|
239
|
+
// Strip comments before parsing to avoid regex issues
|
|
240
|
+
const cleanContent = stripComments(content);
|
|
241
|
+
|
|
165
242
|
// Extract slug (various patterns)
|
|
166
|
-
// slug: "dashboard",
|
|
243
|
+
// slug: "dashboard", appSlug: "dashboard", app_slug: "dashboard"
|
|
167
244
|
// Also handles: slug: env.slug || "fallback"
|
|
168
|
-
const slugMatch =
|
|
245
|
+
const slugMatch = cleanContent.match(/(?:slug|appSlug|app_slug)\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
169
246
|
if (slugMatch) {
|
|
170
247
|
result.slug = slugMatch[1];
|
|
248
|
+
|
|
249
|
+
// Check for template literal expressions
|
|
250
|
+
const exprCheck = detectExpression(result.slug);
|
|
251
|
+
if (exprCheck.isExpression) {
|
|
252
|
+
result.warnings.push(
|
|
253
|
+
`slug appears to contain a ${exprCheck.type} (${result.slug}). ` +
|
|
254
|
+
`The CLI cannot evaluate dynamic values. ` +
|
|
255
|
+
`Use a static string value instead.`
|
|
256
|
+
);
|
|
257
|
+
result.slug = null;
|
|
258
|
+
}
|
|
171
259
|
} else {
|
|
172
|
-
const slugFallbackMatch =
|
|
260
|
+
const slugFallbackMatch = cleanContent.match(/(?:slug|appSlug|app_slug)\s*:\s*[^,\n]+\|\|\s*["'`]([^"'`]+)["'`]/);
|
|
173
261
|
if (slugFallbackMatch) {
|
|
174
262
|
result.slug = slugFallbackMatch[1];
|
|
175
263
|
}
|
|
@@ -178,11 +266,22 @@ export function parseConfigFile(configPath) {
|
|
|
178
266
|
// Extract appName (various patterns)
|
|
179
267
|
// appName: "Dashboard App", app_name: "Dashboard", name: "Dashboard"
|
|
180
268
|
// Also handles: appName: env.appName || "fallback"
|
|
181
|
-
const appNameMatch =
|
|
269
|
+
const appNameMatch = cleanContent.match(/(?:appName|app_name)\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
182
270
|
if (appNameMatch) {
|
|
183
271
|
result.appName = appNameMatch[1];
|
|
272
|
+
|
|
273
|
+
// Check for template literal expressions
|
|
274
|
+
const exprCheck = detectExpression(result.appName);
|
|
275
|
+
if (exprCheck.isExpression) {
|
|
276
|
+
result.warnings.push(
|
|
277
|
+
`appName appears to contain a ${exprCheck.type} (${result.appName}). ` +
|
|
278
|
+
`The CLI cannot evaluate dynamic values. ` +
|
|
279
|
+
`Use a static string value instead.`
|
|
280
|
+
);
|
|
281
|
+
result.appName = null;
|
|
282
|
+
}
|
|
184
283
|
} else {
|
|
185
|
-
const appNameFallbackMatch =
|
|
284
|
+
const appNameFallbackMatch = cleanContent.match(/(?:appName|app_name)\s*:\s*[^,\n]+\|\|\s*["'`]([^"'`]+)["'`]/);
|
|
186
285
|
if (appNameFallbackMatch) {
|
|
187
286
|
result.appName = appNameFallbackMatch[1];
|
|
188
287
|
}
|
|
@@ -190,14 +289,14 @@ export function parseConfigFile(configPath) {
|
|
|
190
289
|
|
|
191
290
|
// Extract appDescription (optional)
|
|
192
291
|
// appDescription: "Description text"
|
|
193
|
-
const appDescriptionMatch =
|
|
292
|
+
const appDescriptionMatch = cleanContent.match(/(?:appDescription|app_description)\s*:\s*["'`]([^"'`]*)["'`]/);
|
|
194
293
|
if (appDescriptionMatch) {
|
|
195
294
|
result.appDescription = appDescriptionMatch[1];
|
|
196
295
|
}
|
|
197
296
|
|
|
198
297
|
// Extract appIconId (optional)
|
|
199
298
|
// appIconId: "icon-id-here"
|
|
200
|
-
const appIconIdMatch =
|
|
299
|
+
const appIconIdMatch = cleanContent.match(/(?:appIconId|app_icon_id)\s*:\s*["'`]([^"'`]*)["'`]/);
|
|
201
300
|
if (appIconIdMatch) {
|
|
202
301
|
result.appIconId = appIconIdMatch[1];
|
|
203
302
|
}
|
|
@@ -205,12 +304,12 @@ export function parseConfigFile(configPath) {
|
|
|
205
304
|
// Extract siteUrl (various patterns)
|
|
206
305
|
// siteUrl: "https://...", site_url: "https://...", baseUrl: "https://..."
|
|
207
306
|
// Also handles: siteUrl: env.siteUrl || "https://..."
|
|
208
|
-
const siteUrlMatch =
|
|
307
|
+
const siteUrlMatch = cleanContent.match(/(?:siteUrl|site_url|baseUrl|base_url)\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
209
308
|
if (siteUrlMatch) {
|
|
210
309
|
result.siteUrl = siteUrlMatch[1];
|
|
211
310
|
} else {
|
|
212
311
|
// Try to match fallback pattern: siteUrl: env.x || "fallback"
|
|
213
|
-
const siteUrlFallbackMatch =
|
|
312
|
+
const siteUrlFallbackMatch = cleanContent.match(/(?:siteUrl|site_url|baseUrl|base_url)\s*:\s*[^,\n]+\|\|\s*["'`]([^"'`]+)["'`]/);
|
|
214
313
|
if (siteUrlFallbackMatch) {
|
|
215
314
|
result.siteUrl = siteUrlFallbackMatch[1];
|
|
216
315
|
}
|
|
@@ -218,7 +317,7 @@ export function parseConfigFile(configPath) {
|
|
|
218
317
|
|
|
219
318
|
// Extract assets array
|
|
220
319
|
// assets: ["url1", "url2"]
|
|
221
|
-
const assetsMatch =
|
|
320
|
+
const assetsMatch = cleanContent.match(/assets\s*:\s*\[([\s\S]*?)\]/);
|
|
222
321
|
if (assetsMatch) {
|
|
223
322
|
const assetsContent = assetsMatch[1];
|
|
224
323
|
// Extract all quoted strings from the array
|
|
@@ -282,11 +381,37 @@ export function readVueConfig(projectPath) {
|
|
|
282
381
|
|
|
283
382
|
// Parse config.ts for slug, appName, appDescription, appIconId (always from config.ts)
|
|
284
383
|
const parsed = parseConfigFile(configPath);
|
|
384
|
+
|
|
385
|
+
// Merge warnings from parsing
|
|
386
|
+
if (parsed.warnings?.length > 0) {
|
|
387
|
+
result.warnings.push(...parsed.warnings);
|
|
388
|
+
}
|
|
389
|
+
|
|
285
390
|
result.slug = parsed.slug;
|
|
286
391
|
result.appName = parsed.appName;
|
|
287
392
|
result.appDescription = parsed.appDescription;
|
|
288
393
|
result.appIconId = parsed.appIconId;
|
|
289
394
|
|
|
395
|
+
// Validate slug format if present
|
|
396
|
+
if (result.slug) {
|
|
397
|
+
const slugValidation = validateSlug(result.slug);
|
|
398
|
+
if (!slugValidation.valid) {
|
|
399
|
+
result.errors.push(`Invalid slug format: ${slugValidation.error}`);
|
|
400
|
+
if (slugValidation.suggestion) {
|
|
401
|
+
result.warnings.push(` Suggestion: Use "${slugValidation.suggestion}" instead`);
|
|
402
|
+
}
|
|
403
|
+
// Keep the slug for reference but mark as invalid
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Validate appName if present
|
|
408
|
+
if (result.appName) {
|
|
409
|
+
const appNameValidation = validateAppName(result.appName);
|
|
410
|
+
if (!appNameValidation.valid) {
|
|
411
|
+
result.errors.push(`Invalid appName: ${appNameValidation.error}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
290
415
|
// Read .env.development for siteUrl, assets, and refreshToken (no fallback to config.ts)
|
|
291
416
|
const envConfig = readEnvConfig(projectPath);
|
|
292
417
|
result.envFileUsed = envConfig.envFileUsed;
|
|
@@ -294,9 +419,17 @@ export function readVueConfig(projectPath) {
|
|
|
294
419
|
result.assets = envConfig.assets;
|
|
295
420
|
result.refreshToken = envConfig.refreshToken;
|
|
296
421
|
|
|
422
|
+
// Merge env config errors and warnings
|
|
423
|
+
if (envConfig.errors?.length > 0) {
|
|
424
|
+
result.errors.push(...envConfig.errors);
|
|
425
|
+
}
|
|
426
|
+
if (envConfig.warnings?.length > 0) {
|
|
427
|
+
result.warnings.push(...envConfig.warnings);
|
|
428
|
+
}
|
|
429
|
+
|
|
297
430
|
// Validate required fields in config.ts
|
|
298
431
|
if (!result.slug) {
|
|
299
|
-
result.errors.push('Missing required field in config.ts: slug (
|
|
432
|
+
result.errors.push('Missing required field in config.ts: slug (appSlug)');
|
|
300
433
|
}
|
|
301
434
|
if (!result.appName) {
|
|
302
435
|
result.errors.push('Missing required field in config.ts: appName');
|
|
@@ -306,7 +439,7 @@ export function readVueConfig(projectPath) {
|
|
|
306
439
|
if (!envConfig.envFileUsed) {
|
|
307
440
|
result.warnings.push('No .env.development file found');
|
|
308
441
|
} else {
|
|
309
|
-
if (!result.siteUrl) {
|
|
442
|
+
if (!result.siteUrl && !result.errors.some(e => e.includes('VITE_SITE_URL'))) {
|
|
310
443
|
result.warnings.push('VITE_SITE_URL not set in .env.development');
|
|
311
444
|
}
|
|
312
445
|
}
|
|
@@ -364,17 +497,22 @@ export function getInjectionTarget(projectPath) {
|
|
|
364
497
|
*
|
|
365
498
|
* @param {string} projectPath - Path to the Vue project
|
|
366
499
|
* @param {string[]} assets - Array of asset URLs to inject
|
|
367
|
-
* @returns {{success: boolean, targetFile: string | null, targetName: string | null}}
|
|
500
|
+
* @returns {{success: boolean, targetFile: string | null, targetName: string | null, error: string | null}}
|
|
368
501
|
*/
|
|
369
502
|
export function injectAssets(projectPath, assets) {
|
|
370
503
|
const { targetFile, targetName } = getInjectionTarget(projectPath);
|
|
371
504
|
|
|
372
505
|
if (!targetFile) {
|
|
373
|
-
return { success: false, targetFile: null, targetName: null };
|
|
506
|
+
return { success: false, targetFile: null, targetName: null, error: 'No .env.development file found' };
|
|
374
507
|
}
|
|
375
508
|
|
|
376
|
-
const
|
|
377
|
-
return {
|
|
509
|
+
const result = injectAssetsIntoEnv(targetFile, assets);
|
|
510
|
+
return {
|
|
511
|
+
success: result.success,
|
|
512
|
+
targetFile,
|
|
513
|
+
targetName,
|
|
514
|
+
error: result.error
|
|
515
|
+
};
|
|
378
516
|
}
|
|
379
517
|
|
|
380
518
|
/**
|
|
@@ -382,16 +520,34 @@ export function injectAssets(projectPath, assets) {
|
|
|
382
520
|
*
|
|
383
521
|
* @param {string} envPath - Path to the .env file
|
|
384
522
|
* @param {string[]} assets - Array of asset URLs to inject
|
|
385
|
-
* @returns {boolean
|
|
523
|
+
* @returns {{success: boolean, error: string | null}}
|
|
386
524
|
*/
|
|
387
525
|
export function injectAssetsIntoEnv(envPath, assets) {
|
|
388
526
|
if (!existsSync(envPath)) {
|
|
389
|
-
return false;
|
|
527
|
+
return { success: false, error: 'File does not exist' };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let content;
|
|
531
|
+
try {
|
|
532
|
+
content = readFileSync(envPath, 'utf-8');
|
|
533
|
+
} catch (err) {
|
|
534
|
+
const errorType = detectErrorType(err);
|
|
535
|
+
if (errorType === ErrorTypes.PERMISSION) {
|
|
536
|
+
return {
|
|
537
|
+
success: false,
|
|
538
|
+
error: `Permission denied reading ${envPath}. Check file permissions.`
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
return { success: false, error: `Failed to read file: ${err.message}` };
|
|
390
542
|
}
|
|
391
543
|
|
|
392
|
-
|
|
544
|
+
// JSON stringify handles escaping properly
|
|
393
545
|
const assetsJson = JSON.stringify(assets);
|
|
394
|
-
|
|
546
|
+
|
|
547
|
+
// Escape any single quotes in the JSON to prevent .env parsing issues
|
|
548
|
+
// This is extra safety - JSON.stringify shouldn't produce single quotes
|
|
549
|
+
const safeJson = assetsJson.replace(/'/g, "\\'");
|
|
550
|
+
const newLine = `VITE_ASSETS = '${safeJson}'`;
|
|
395
551
|
|
|
396
552
|
// Check if VITE_ASSETS already exists
|
|
397
553
|
const assetsPattern = /^VITE_ASSETS\s*=.*$/m;
|
|
@@ -403,8 +559,27 @@ export function injectAssetsIntoEnv(envPath, assets) {
|
|
|
403
559
|
content = content.trimEnd() + '\n' + newLine + '\n';
|
|
404
560
|
}
|
|
405
561
|
|
|
406
|
-
|
|
407
|
-
|
|
562
|
+
try {
|
|
563
|
+
writeFileSync(envPath, content, 'utf-8');
|
|
564
|
+
} catch (err) {
|
|
565
|
+
const errorType = detectErrorType(err);
|
|
566
|
+
if (errorType === ErrorTypes.PERMISSION) {
|
|
567
|
+
return {
|
|
568
|
+
success: false,
|
|
569
|
+
error: `Permission denied writing to ${envPath}. ` +
|
|
570
|
+
`Check file permissions or close any programs using this file.`
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (errorType === ErrorTypes.DISK_FULL) {
|
|
574
|
+
return {
|
|
575
|
+
success: false,
|
|
576
|
+
error: `Disk full - cannot write to ${envPath}. Free up disk space and try again.`
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
return { success: false, error: `Failed to write file: ${err.message}` };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { success: true, error: null };
|
|
408
583
|
}
|
|
409
584
|
|
|
410
585
|
/**
|
|
@@ -422,7 +597,7 @@ Could not find config.ts in the Vue project.
|
|
|
422
597
|
Expected location: ${join(projectPath, 'src/config.ts')}
|
|
423
598
|
|
|
424
599
|
Required fields in config.ts:
|
|
425
|
-
-
|
|
600
|
+
- appSlug (slug): App identifier (used as folder name)
|
|
426
601
|
- appName: Display name for navigation menu
|
|
427
602
|
|
|
428
603
|
Required fields in .env.development:
|
|
@@ -432,7 +607,7 @@ Required fields in .env.development:
|
|
|
432
607
|
|
|
433
608
|
Example config.ts:
|
|
434
609
|
export const config = {
|
|
435
|
-
|
|
610
|
+
appSlug: "my-app",
|
|
436
611
|
appName: "My Application"
|
|
437
612
|
}
|
|
438
613
|
|
|
@@ -2,6 +2,13 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import Config from '../config.js';
|
|
4
4
|
import { deleteApp } from '../magentrix/api/iris.js';
|
|
5
|
+
import {
|
|
6
|
+
detectErrorType,
|
|
7
|
+
ErrorTypes,
|
|
8
|
+
formatPermissionError,
|
|
9
|
+
formatFileLockError,
|
|
10
|
+
formatNetworkError
|
|
11
|
+
} from './errors.js';
|
|
5
12
|
|
|
6
13
|
const config = new Config();
|
|
7
14
|
|
|
@@ -53,14 +60,28 @@ export async function deleteIrisAppFromServer(instanceUrl, token, slug, options
|
|
|
53
60
|
if (isNotFound) {
|
|
54
61
|
// App doesn't exist on server, but clean up local cache anyway
|
|
55
62
|
if (updateCache) {
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
try {
|
|
64
|
+
config.removeKey(`iris-app:${slug}`, { filename: 'base.json' });
|
|
65
|
+
result.cleanedFromCache = true;
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore cache update errors
|
|
68
|
+
}
|
|
58
69
|
}
|
|
59
70
|
result.success = true;
|
|
60
71
|
result.error = 'App not found on server (already deleted)';
|
|
61
72
|
return result;
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
// Check for network errors and provide helpful messages
|
|
76
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
|
|
77
|
+
result.error = formatNetworkError({
|
|
78
|
+
operation: 'delete app',
|
|
79
|
+
url: instanceUrl,
|
|
80
|
+
error
|
|
81
|
+
});
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
64
85
|
result.error = errorMessage;
|
|
65
86
|
return result;
|
|
66
87
|
}
|
|
@@ -70,7 +91,7 @@ export async function deleteIrisAppFromServer(instanceUrl, token, slug, options
|
|
|
70
91
|
* Delete local Iris app files.
|
|
71
92
|
*
|
|
72
93
|
* @param {string} appPath - Absolute path to the app folder
|
|
73
|
-
* @returns {{success: boolean, existed: boolean, error: string | null, isPermissionError: boolean}}
|
|
94
|
+
* @returns {{success: boolean, existed: boolean, error: string | null, isPermissionError: boolean, isFileLocked: boolean}}
|
|
74
95
|
*/
|
|
75
96
|
export function deleteLocalIrisAppFiles(appPath) {
|
|
76
97
|
if (!fs.existsSync(appPath)) {
|
|
@@ -78,7 +99,8 @@ export function deleteLocalIrisAppFiles(appPath) {
|
|
|
78
99
|
success: true,
|
|
79
100
|
existed: false,
|
|
80
101
|
error: null,
|
|
81
|
-
isPermissionError: false
|
|
102
|
+
isPermissionError: false,
|
|
103
|
+
isFileLocked: false
|
|
82
104
|
};
|
|
83
105
|
}
|
|
84
106
|
|
|
@@ -88,15 +110,41 @@ export function deleteLocalIrisAppFiles(appPath) {
|
|
|
88
110
|
success: true,
|
|
89
111
|
existed: true,
|
|
90
112
|
error: null,
|
|
91
|
-
isPermissionError: false
|
|
113
|
+
isPermissionError: false,
|
|
114
|
+
isFileLocked: false
|
|
92
115
|
};
|
|
93
116
|
} catch (error) {
|
|
94
|
-
const
|
|
117
|
+
const errorType = detectErrorType(error);
|
|
118
|
+
|
|
119
|
+
if (errorType === ErrorTypes.PERMISSION) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
existed: true,
|
|
123
|
+
error: formatPermissionError({
|
|
124
|
+
operation: 'delete files',
|
|
125
|
+
path: appPath
|
|
126
|
+
}),
|
|
127
|
+
isPermissionError: true,
|
|
128
|
+
isFileLocked: false
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (errorType === ErrorTypes.FILE_LOCKED) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
existed: true,
|
|
136
|
+
error: formatFileLockError({ path: appPath }),
|
|
137
|
+
isPermissionError: false,
|
|
138
|
+
isFileLocked: true
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
95
142
|
return {
|
|
96
143
|
success: false,
|
|
97
144
|
existed: true,
|
|
98
145
|
error: error.message,
|
|
99
|
-
isPermissionError
|
|
146
|
+
isPermissionError: false,
|
|
147
|
+
isFileLocked: false
|
|
100
148
|
};
|
|
101
149
|
}
|
|
102
150
|
}
|