@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,360 +1,360 @@
1
- /**
2
- * Validation utilities for Iris Vue integration.
3
- * Provides input validation, format checking, and user-friendly error messages.
4
- */
5
-
6
- /**
7
- * Valid slug pattern: lowercase alphanumeric, can contain hyphens and underscores
8
- * Must start with alphanumeric, 1-200 characters total
9
- */
10
- const SLUG_PATTERN = /^[a-z0-9][a-z0-9-_]{0,199}$/;
11
-
12
- /**
13
- * Characters not allowed in slugs (for error messages)
14
- */
15
- const INVALID_SLUG_CHARS = /[^a-z0-9-_]/g;
16
-
17
- /**
18
- * Maximum lengths for various fields
19
- */
20
- export const MAX_LENGTHS = {
21
- slug: 200,
22
- appName: 255,
23
- appDescription: 1000,
24
- siteUrl: 500
25
- };
26
-
27
- /**
28
- * Validate a slug format.
29
- *
30
- * @param {string} slug - The slug to validate
31
- * @returns {{
32
- * valid: boolean,
33
- * error: string | null,
34
- * suggestion: string | null
35
- * }}
36
- */
37
- export function validateSlug(slug) {
38
- const result = {
39
- valid: true,
40
- error: null,
41
- suggestion: null
42
- };
43
-
44
- // Check for empty/null/undefined
45
- if (!slug || typeof slug !== 'string') {
46
- result.valid = false;
47
- result.error = 'Slug is required and must be a string';
48
- return result;
49
- }
50
-
51
- // Trim whitespace
52
- const trimmedSlug = slug.trim();
53
-
54
- // Check for empty after trim
55
- if (trimmedSlug.length === 0) {
56
- result.valid = false;
57
- result.error = 'Slug cannot be empty or contain only whitespace';
58
- return result;
59
- }
60
-
61
- // Check for path traversal attempts (security)
62
- if (trimmedSlug.includes('..') || trimmedSlug.includes('/') || trimmedSlug.includes('\\')) {
63
- result.valid = false;
64
- result.error = 'Slug cannot contain path separators (.., /, \\) for security reasons';
65
- return result;
66
- }
67
-
68
- // Check length
69
- if (trimmedSlug.length > MAX_LENGTHS.slug) {
70
- result.valid = false;
71
- result.error = `Slug must be ${MAX_LENGTHS.slug} characters or less (currently ${trimmedSlug.length})`;
72
- result.suggestion = trimmedSlug.substring(0, MAX_LENGTHS.slug);
73
- return result;
74
- }
75
-
76
- // Check for uppercase (common mistake)
77
- if (trimmedSlug !== trimmedSlug.toLowerCase()) {
78
- result.valid = false;
79
- result.error = 'Slug must be lowercase';
80
- result.suggestion = trimmedSlug.toLowerCase();
81
- return result;
82
- }
83
-
84
- // Check first character
85
- if (!/^[a-z0-9]/.test(trimmedSlug)) {
86
- result.valid = false;
87
- result.error = 'Slug must start with a letter or number (not a hyphen or underscore)';
88
- // Suggest removing leading special chars
89
- const cleaned = trimmedSlug.replace(/^[-_]+/, '');
90
- if (cleaned.length > 0) {
91
- result.suggestion = cleaned;
92
- }
93
- return result;
94
- }
95
-
96
- // Check pattern
97
- if (!SLUG_PATTERN.test(trimmedSlug)) {
98
- const invalidChars = trimmedSlug.match(INVALID_SLUG_CHARS);
99
- if (invalidChars) {
100
- const uniqueChars = [...new Set(invalidChars)].join(', ');
101
- result.valid = false;
102
- result.error = `Slug contains invalid characters: ${uniqueChars}`;
103
- result.suggestion = trimmedSlug.replace(INVALID_SLUG_CHARS, '-').replace(/--+/g, '-');
104
- } else {
105
- result.valid = false;
106
- result.error = 'Slug format is invalid. Use only lowercase letters, numbers, hyphens, and underscores.';
107
- }
108
- return result;
109
- }
110
-
111
- return result;
112
- }
113
-
114
- /**
115
- * Validate a URL format.
116
- *
117
- * @param {string} url - The URL to validate
118
- * @param {Object} options - Validation options
119
- * @param {boolean} options.requireHttps - Require HTTPS protocol (default: false)
120
- * @param {boolean} options.allowTrailingSlash - Allow trailing slash (default: true)
121
- * @returns {{
122
- * valid: boolean,
123
- * error: string | null,
124
- * suggestion: string | null,
125
- * normalized: string | null
126
- * }}
127
- */
128
- export function validateUrl(url, options = {}) {
129
- const { requireHttps = false, allowTrailingSlash = true } = options;
130
-
131
- const result = {
132
- valid: true,
133
- error: null,
134
- suggestion: null,
135
- normalized: null
136
- };
137
-
138
- // Check for empty/null/undefined
139
- if (!url || typeof url !== 'string') {
140
- result.valid = false;
141
- result.error = 'URL is required and must be a string';
142
- return result;
143
- }
144
-
145
- const trimmedUrl = url.trim();
146
-
147
- // Check for empty after trim
148
- if (trimmedUrl.length === 0) {
149
- result.valid = false;
150
- result.error = 'URL cannot be empty';
151
- return result;
152
- }
153
-
154
- // Check length
155
- if (trimmedUrl.length > MAX_LENGTHS.siteUrl) {
156
- result.valid = false;
157
- result.error = `URL must be ${MAX_LENGTHS.siteUrl} characters or less`;
158
- return result;
159
- }
160
-
161
- // Check for protocol
162
- if (!trimmedUrl.startsWith('http://') && !trimmedUrl.startsWith('https://')) {
163
- result.valid = false;
164
- result.error = 'URL must start with http:// or https://';
165
- // Suggest adding https://
166
- result.suggestion = `https://${trimmedUrl}`;
167
- return result;
168
- }
169
-
170
- // Check for HTTPS requirement
171
- if (requireHttps && trimmedUrl.startsWith('http://')) {
172
- result.valid = false;
173
- result.error = 'URL must use HTTPS for security';
174
- result.suggestion = trimmedUrl.replace('http://', 'https://');
175
- return result;
176
- }
177
-
178
- // Try to parse as URL
179
- try {
180
- const parsed = new URL(trimmedUrl);
181
-
182
- // Normalize the URL (remove trailing slash if not allowed)
183
- let normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
184
- if (!allowTrailingSlash && normalized.endsWith('/') && normalized.length > parsed.protocol.length + 3) {
185
- normalized = normalized.slice(0, -1);
186
- }
187
-
188
- // Add back query string and hash if present
189
- if (parsed.search) {
190
- normalized += parsed.search;
191
- }
192
- if (parsed.hash) {
193
- normalized += parsed.hash;
194
- }
195
-
196
- result.normalized = normalized;
197
- } catch (err) {
198
- result.valid = false;
199
- result.error = `Invalid URL format: ${err.message}`;
200
- return result;
201
- }
202
-
203
- return result;
204
- }
205
-
206
- /**
207
- * Validate app name format.
208
- *
209
- * @param {string} appName - The app name to validate
210
- * @returns {{
211
- * valid: boolean,
212
- * error: string | null
213
- * }}
214
- */
215
- export function validateAppName(appName) {
216
- const result = {
217
- valid: true,
218
- error: null
219
- };
220
-
221
- // Check for empty/null/undefined
222
- if (!appName || typeof appName !== 'string') {
223
- result.valid = false;
224
- result.error = 'App name is required and must be a string';
225
- return result;
226
- }
227
-
228
- const trimmed = appName.trim();
229
-
230
- // Check for empty after trim
231
- if (trimmed.length === 0) {
232
- result.valid = false;
233
- result.error = 'App name cannot be empty or contain only whitespace';
234
- return result;
235
- }
236
-
237
- // Check length
238
- if (trimmed.length > MAX_LENGTHS.appName) {
239
- result.valid = false;
240
- result.error = `App name must be ${MAX_LENGTHS.appName} characters or less (currently ${trimmed.length})`;
241
- return result;
242
- }
243
-
244
- return result;
245
- }
246
-
247
- /**
248
- * Check if a string looks like it contains a JavaScript expression.
249
- * Used to detect template literals or expressions in config values.
250
- *
251
- * @param {string} value - The value to check
252
- * @returns {{
253
- * isExpression: boolean,
254
- * type: 'template_literal' | 'variable' | 'function_call' | null
255
- * }}
256
- */
257
- export function detectExpression(value) {
258
- const result = {
259
- isExpression: false,
260
- type: null
261
- };
262
-
263
- if (!value || typeof value !== 'string') {
264
- return result;
265
- }
266
-
267
- // Check for template literal syntax
268
- if (value.includes('${') && value.includes('}')) {
269
- result.isExpression = true;
270
- result.type = 'template_literal';
271
- return result;
272
- }
273
-
274
- // Check for variable reference patterns
275
- if (/^(process\.env\.|env\.|import\.meta\.)/i.test(value)) {
276
- result.isExpression = true;
277
- result.type = 'variable';
278
- return result;
279
- }
280
-
281
- // Check for function call patterns
282
- if (/\([^)]*\)$/.test(value)) {
283
- result.isExpression = true;
284
- result.type = 'function_call';
285
- return result;
286
- }
287
-
288
- return result;
289
- }
290
-
291
- /**
292
- * Sanitize a string to be safe for use in file paths.
293
- *
294
- * @param {string} input - The string to sanitize
295
- * @returns {string} - Sanitized string
296
- */
297
- export function sanitizeForPath(input) {
298
- if (!input || typeof input !== 'string') {
299
- return '';
300
- }
301
-
302
- return input
303
- .toLowerCase()
304
- .replace(/[^a-z0-9-_]/g, '-') // Replace invalid chars with hyphen
305
- .replace(/--+/g, '-') // Replace multiple hyphens with single
306
- .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
307
- .substring(0, MAX_LENGTHS.slug);
308
- }
309
-
310
- /**
311
- * Validate that a value is not empty after various processing.
312
- *
313
- * @param {*} value - The value to check
314
- * @param {string} fieldName - Name of the field for error messages
315
- * @returns {{valid: boolean, error: string | null}}
316
- */
317
- export function validateNotEmpty(value, fieldName) {
318
- const result = {
319
- valid: true,
320
- error: null
321
- };
322
-
323
- if (value === null || value === undefined) {
324
- result.valid = false;
325
- result.error = `${fieldName} is required`;
326
- return result;
327
- }
328
-
329
- if (typeof value === 'string' && value.trim().length === 0) {
330
- result.valid = false;
331
- result.error = `${fieldName} cannot be empty`;
332
- return result;
333
- }
334
-
335
- if (Array.isArray(value) && value.length === 0) {
336
- result.valid = false;
337
- result.error = `${fieldName} cannot be empty`;
338
- return result;
339
- }
340
-
341
- return result;
342
- }
343
-
344
- /**
345
- * Format validation errors for display.
346
- *
347
- * @param {string} fieldName - Name of the field
348
- * @param {string} error - Error message
349
- * @param {string | null} suggestion - Suggested fix
350
- * @returns {string} - Formatted error message
351
- */
352
- export function formatValidationError(fieldName, error, suggestion = null) {
353
- let message = `Invalid ${fieldName}: ${error}`;
354
-
355
- if (suggestion) {
356
- message += `\n Suggestion: ${suggestion}`;
357
- }
358
-
359
- return message;
360
- }
1
+ /**
2
+ * Validation utilities for Iris Vue integration.
3
+ * Provides input validation, format checking, and user-friendly error messages.
4
+ */
5
+
6
+ /**
7
+ * Valid slug pattern: lowercase alphanumeric, can contain hyphens and underscores
8
+ * Must start with alphanumeric, 1-200 characters total
9
+ */
10
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9-_]{0,199}$/;
11
+
12
+ /**
13
+ * Characters not allowed in slugs (for error messages)
14
+ */
15
+ const INVALID_SLUG_CHARS = /[^a-z0-9-_]/g;
16
+
17
+ /**
18
+ * Maximum lengths for various fields
19
+ */
20
+ export const MAX_LENGTHS = {
21
+ slug: 200,
22
+ appName: 255,
23
+ appDescription: 1000,
24
+ siteUrl: 500
25
+ };
26
+
27
+ /**
28
+ * Validate a slug format.
29
+ *
30
+ * @param {string} slug - The slug to validate
31
+ * @returns {{
32
+ * valid: boolean,
33
+ * error: string | null,
34
+ * suggestion: string | null
35
+ * }}
36
+ */
37
+ export function validateSlug(slug) {
38
+ const result = {
39
+ valid: true,
40
+ error: null,
41
+ suggestion: null
42
+ };
43
+
44
+ // Check for empty/null/undefined
45
+ if (!slug || typeof slug !== 'string') {
46
+ result.valid = false;
47
+ result.error = 'Slug is required and must be a string';
48
+ return result;
49
+ }
50
+
51
+ // Trim whitespace
52
+ const trimmedSlug = slug.trim();
53
+
54
+ // Check for empty after trim
55
+ if (trimmedSlug.length === 0) {
56
+ result.valid = false;
57
+ result.error = 'Slug cannot be empty or contain only whitespace';
58
+ return result;
59
+ }
60
+
61
+ // Check for path traversal attempts (security)
62
+ if (trimmedSlug.includes('..') || trimmedSlug.includes('/') || trimmedSlug.includes('\\')) {
63
+ result.valid = false;
64
+ result.error = 'Slug cannot contain path separators (.., /, \\) for security reasons';
65
+ return result;
66
+ }
67
+
68
+ // Check length
69
+ if (trimmedSlug.length > MAX_LENGTHS.slug) {
70
+ result.valid = false;
71
+ result.error = `Slug must be ${MAX_LENGTHS.slug} characters or less (currently ${trimmedSlug.length})`;
72
+ result.suggestion = trimmedSlug.substring(0, MAX_LENGTHS.slug);
73
+ return result;
74
+ }
75
+
76
+ // Check for uppercase (common mistake)
77
+ if (trimmedSlug !== trimmedSlug.toLowerCase()) {
78
+ result.valid = false;
79
+ result.error = 'Slug must be lowercase';
80
+ result.suggestion = trimmedSlug.toLowerCase();
81
+ return result;
82
+ }
83
+
84
+ // Check first character
85
+ if (!/^[a-z0-9]/.test(trimmedSlug)) {
86
+ result.valid = false;
87
+ result.error = 'Slug must start with a letter or number (not a hyphen or underscore)';
88
+ // Suggest removing leading special chars
89
+ const cleaned = trimmedSlug.replace(/^[-_]+/, '');
90
+ if (cleaned.length > 0) {
91
+ result.suggestion = cleaned;
92
+ }
93
+ return result;
94
+ }
95
+
96
+ // Check pattern
97
+ if (!SLUG_PATTERN.test(trimmedSlug)) {
98
+ const invalidChars = trimmedSlug.match(INVALID_SLUG_CHARS);
99
+ if (invalidChars) {
100
+ const uniqueChars = [...new Set(invalidChars)].join(', ');
101
+ result.valid = false;
102
+ result.error = `Slug contains invalid characters: ${uniqueChars}`;
103
+ result.suggestion = trimmedSlug.replace(INVALID_SLUG_CHARS, '-').replace(/--+/g, '-');
104
+ } else {
105
+ result.valid = false;
106
+ result.error = 'Slug format is invalid. Use only lowercase letters, numbers, hyphens, and underscores.';
107
+ }
108
+ return result;
109
+ }
110
+
111
+ return result;
112
+ }
113
+
114
+ /**
115
+ * Validate a URL format.
116
+ *
117
+ * @param {string} url - The URL to validate
118
+ * @param {Object} options - Validation options
119
+ * @param {boolean} options.requireHttps - Require HTTPS protocol (default: false)
120
+ * @param {boolean} options.allowTrailingSlash - Allow trailing slash (default: true)
121
+ * @returns {{
122
+ * valid: boolean,
123
+ * error: string | null,
124
+ * suggestion: string | null,
125
+ * normalized: string | null
126
+ * }}
127
+ */
128
+ export function validateUrl(url, options = {}) {
129
+ const { requireHttps = false, allowTrailingSlash = true } = options;
130
+
131
+ const result = {
132
+ valid: true,
133
+ error: null,
134
+ suggestion: null,
135
+ normalized: null
136
+ };
137
+
138
+ // Check for empty/null/undefined
139
+ if (!url || typeof url !== 'string') {
140
+ result.valid = false;
141
+ result.error = 'URL is required and must be a string';
142
+ return result;
143
+ }
144
+
145
+ const trimmedUrl = url.trim();
146
+
147
+ // Check for empty after trim
148
+ if (trimmedUrl.length === 0) {
149
+ result.valid = false;
150
+ result.error = 'URL cannot be empty';
151
+ return result;
152
+ }
153
+
154
+ // Check length
155
+ if (trimmedUrl.length > MAX_LENGTHS.siteUrl) {
156
+ result.valid = false;
157
+ result.error = `URL must be ${MAX_LENGTHS.siteUrl} characters or less`;
158
+ return result;
159
+ }
160
+
161
+ // Check for protocol
162
+ if (!trimmedUrl.startsWith('http://') && !trimmedUrl.startsWith('https://')) {
163
+ result.valid = false;
164
+ result.error = 'URL must start with http:// or https://';
165
+ // Suggest adding https://
166
+ result.suggestion = `https://${trimmedUrl}`;
167
+ return result;
168
+ }
169
+
170
+ // Check for HTTPS requirement
171
+ if (requireHttps && trimmedUrl.startsWith('http://')) {
172
+ result.valid = false;
173
+ result.error = 'URL must use HTTPS for security';
174
+ result.suggestion = trimmedUrl.replace('http://', 'https://');
175
+ return result;
176
+ }
177
+
178
+ // Try to parse as URL
179
+ try {
180
+ const parsed = new URL(trimmedUrl);
181
+
182
+ // Normalize the URL (remove trailing slash if not allowed)
183
+ let normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
184
+ if (!allowTrailingSlash && normalized.endsWith('/') && normalized.length > parsed.protocol.length + 3) {
185
+ normalized = normalized.slice(0, -1);
186
+ }
187
+
188
+ // Add back query string and hash if present
189
+ if (parsed.search) {
190
+ normalized += parsed.search;
191
+ }
192
+ if (parsed.hash) {
193
+ normalized += parsed.hash;
194
+ }
195
+
196
+ result.normalized = normalized;
197
+ } catch (err) {
198
+ result.valid = false;
199
+ result.error = `Invalid URL format: ${err.message}`;
200
+ return result;
201
+ }
202
+
203
+ return result;
204
+ }
205
+
206
+ /**
207
+ * Validate app name format.
208
+ *
209
+ * @param {string} appName - The app name to validate
210
+ * @returns {{
211
+ * valid: boolean,
212
+ * error: string | null
213
+ * }}
214
+ */
215
+ export function validateAppName(appName) {
216
+ const result = {
217
+ valid: true,
218
+ error: null
219
+ };
220
+
221
+ // Check for empty/null/undefined
222
+ if (!appName || typeof appName !== 'string') {
223
+ result.valid = false;
224
+ result.error = 'App name is required and must be a string';
225
+ return result;
226
+ }
227
+
228
+ const trimmed = appName.trim();
229
+
230
+ // Check for empty after trim
231
+ if (trimmed.length === 0) {
232
+ result.valid = false;
233
+ result.error = 'App name cannot be empty or contain only whitespace';
234
+ return result;
235
+ }
236
+
237
+ // Check length
238
+ if (trimmed.length > MAX_LENGTHS.appName) {
239
+ result.valid = false;
240
+ result.error = `App name must be ${MAX_LENGTHS.appName} characters or less (currently ${trimmed.length})`;
241
+ return result;
242
+ }
243
+
244
+ return result;
245
+ }
246
+
247
+ /**
248
+ * Check if a string looks like it contains a JavaScript expression.
249
+ * Used to detect template literals or expressions in config values.
250
+ *
251
+ * @param {string} value - The value to check
252
+ * @returns {{
253
+ * isExpression: boolean,
254
+ * type: 'template_literal' | 'variable' | 'function_call' | null
255
+ * }}
256
+ */
257
+ export function detectExpression(value) {
258
+ const result = {
259
+ isExpression: false,
260
+ type: null
261
+ };
262
+
263
+ if (!value || typeof value !== 'string') {
264
+ return result;
265
+ }
266
+
267
+ // Check for template literal syntax
268
+ if (value.includes('${') && value.includes('}')) {
269
+ result.isExpression = true;
270
+ result.type = 'template_literal';
271
+ return result;
272
+ }
273
+
274
+ // Check for variable reference patterns
275
+ if (/^(process\.env\.|env\.|import\.meta\.)/i.test(value)) {
276
+ result.isExpression = true;
277
+ result.type = 'variable';
278
+ return result;
279
+ }
280
+
281
+ // Check for function call patterns
282
+ if (/\([^)]*\)$/.test(value)) {
283
+ result.isExpression = true;
284
+ result.type = 'function_call';
285
+ return result;
286
+ }
287
+
288
+ return result;
289
+ }
290
+
291
+ /**
292
+ * Sanitize a string to be safe for use in file paths.
293
+ *
294
+ * @param {string} input - The string to sanitize
295
+ * @returns {string} - Sanitized string
296
+ */
297
+ export function sanitizeForPath(input) {
298
+ if (!input || typeof input !== 'string') {
299
+ return '';
300
+ }
301
+
302
+ return input
303
+ .toLowerCase()
304
+ .replace(/[^a-z0-9-_]/g, '-') // Replace invalid chars with hyphen
305
+ .replace(/--+/g, '-') // Replace multiple hyphens with single
306
+ .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
307
+ .substring(0, MAX_LENGTHS.slug);
308
+ }
309
+
310
+ /**
311
+ * Validate that a value is not empty after various processing.
312
+ *
313
+ * @param {*} value - The value to check
314
+ * @param {string} fieldName - Name of the field for error messages
315
+ * @returns {{valid: boolean, error: string | null}}
316
+ */
317
+ export function validateNotEmpty(value, fieldName) {
318
+ const result = {
319
+ valid: true,
320
+ error: null
321
+ };
322
+
323
+ if (value === null || value === undefined) {
324
+ result.valid = false;
325
+ result.error = `${fieldName} is required`;
326
+ return result;
327
+ }
328
+
329
+ if (typeof value === 'string' && value.trim().length === 0) {
330
+ result.valid = false;
331
+ result.error = `${fieldName} cannot be empty`;
332
+ return result;
333
+ }
334
+
335
+ if (Array.isArray(value) && value.length === 0) {
336
+ result.valid = false;
337
+ result.error = `${fieldName} cannot be empty`;
338
+ return result;
339
+ }
340
+
341
+ return result;
342
+ }
343
+
344
+ /**
345
+ * Format validation errors for display.
346
+ *
347
+ * @param {string} fieldName - Name of the field
348
+ * @param {string} error - Error message
349
+ * @param {string | null} suggestion - Suggested fix
350
+ * @returns {string} - Formatted error message
351
+ */
352
+ export function formatValidationError(fieldName, error, suggestion = null) {
353
+ let message = `Invalid ${fieldName}: ${error}`;
354
+
355
+ if (suggestion) {
356
+ message += `\n Suggestion: ${suggestion}`;
357
+ }
358
+
359
+ return message;
360
+ }