offbyt 1.0.0 → 1.0.2

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.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * CLI Formatting Utilities
3
+ * Provides attractive visual output for Offbyt CLI
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+
8
+ export function printBanner() {
9
+ console.log('\n');
10
+ console.log(chalk.bold.cyan('╔════════════════════════════════════════════════════════════════╗'));
11
+ console.log(chalk.bold.cyan('║ ║'));
12
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ██████╗ ███████╗███████╗██████╗ ██╗ ██╗████████╗ ') + chalk.bold.cyan('║'));
13
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ██╔═══██╗██╔════╝██╔════╝██╔══██╗╚██╗ ██╔╝╚══██╔══╝ ') + chalk.bold.cyan('║'));
14
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ██║ ██║█████╗ █████╗ ██████╔╝ ╚████╔╝ ██║ ') + chalk.bold.cyan('║'));
15
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ██║ ██║██╔══╝ ██╔══╝ ██╔══██╗ ╚██╔╝ ██║ ') + chalk.bold.cyan('║'));
16
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ╚██████╔╝██║ ██║ ██████╔╝ ██║ ██║ ') + chalk.bold.cyan('║'));
17
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.blue(' ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ') + chalk.bold.cyan('║'));
18
+ console.log(chalk.bold.cyan('║ ║'));
19
+ console.log(chalk.bold.cyan('║ ') + chalk.bold.white('Backend Generator - Offline + AI Powered') + chalk.bold.cyan(' ║'));
20
+ console.log(chalk.bold.cyan('║ ║'));
21
+ console.log(chalk.bold.cyan('╚════════════════════════════════════════════════════════════════╝'));
22
+ console.log('\n');
23
+ }
24
+
25
+ export function printSection(title) {
26
+ console.log(chalk.bold.cyan('\n╔' + '═'.repeat(title.length + 4) + '╗'));
27
+ console.log(chalk.bold.cyan('║ ' + title + ' ║'));
28
+ console.log(chalk.bold.cyan('╚' + '═'.repeat(title.length + 4) + '╝\n'));
29
+ }
30
+
31
+ export function printStep(number, total, title) {
32
+ console.log(chalk.bold.magenta(`\n>> STEP ${number}/${total}`) + chalk.bold.white(` :: ${title}`));
33
+ console.log(chalk.gray('═'.repeat(60)));
34
+ }
35
+
36
+ export function printSuccess(message) {
37
+ console.log(chalk.green('[OK] ') + chalk.white(message));
38
+ }
39
+
40
+ export function printWarning(message) {
41
+ console.log(chalk.yellow('[WARN] ') + chalk.white(message));
42
+ }
43
+
44
+ export function printError(message) {
45
+ console.log(chalk.red('[ERR] ') + chalk.white(message));
46
+ }
47
+
48
+ export function printInfo(message) {
49
+ console.log(chalk.cyan('[INFO] ') + chalk.white(message));
50
+ }
51
+
52
+ export function printBox(title, items = []) {
53
+ console.log(chalk.bold.cyan('┌─ ' + title));
54
+ for (const item of items) {
55
+ console.log(chalk.cyan('│ ') + chalk.white(item));
56
+ }
57
+ console.log(chalk.cyan('└─\n'));
58
+ }
59
+
60
+ export function printSummary(title, items = []) {
61
+ const safeItems = Array.isArray(items) ? items.map((i) => String(i)) : [];
62
+ const titleText = ` ${String(title || '').toUpperCase()}`;
63
+ const contentWidth = Math.max(64, titleText.length, ...safeItems.map((i) => i.length + 4));
64
+
65
+ console.log(chalk.bold.cyan(`\n╔${'═'.repeat(contentWidth)}╗`));
66
+ console.log(chalk.bold.cyan('║') + chalk.bold.green(chalk.bold.white(titleText.padEnd(contentWidth))) + chalk.bold.cyan('║'));
67
+ console.log(chalk.bold.cyan(`╠${'═'.repeat(contentWidth)}╣`));
68
+
69
+ for (const item of safeItems) {
70
+ const line = ` - ${item}`;
71
+ console.log(chalk.bold.cyan('║') + chalk.white(line.padEnd(contentWidth)) + chalk.bold.cyan('║'));
72
+ }
73
+
74
+ console.log(chalk.bold.cyan(`╚${'═'.repeat(contentWidth)}╝\n`));
75
+ }
76
+
77
+ export function printFooter(stepsInput = []) {
78
+ const nextSteps = Array.isArray(stepsInput) ? stepsInput.map((s) => String(s)) : [String(stepsInput)];
79
+ const contentWidth = Math.max(64, ' NEXT STEPS'.length, ...nextSteps.map((s, i) => `${i + 1}. ${s}`.length + 2));
80
+
81
+ console.log(chalk.bold.cyan(`\n╔${'═'.repeat(contentWidth)}╗`));
82
+ console.log(chalk.bold.cyan('║') + chalk.bold.yellow(' NEXT STEPS'.padEnd(contentWidth)) + chalk.bold.cyan('║'));
83
+ console.log(chalk.bold.cyan(`╠${'═'.repeat(contentWidth)}╣`));
84
+
85
+ nextSteps.forEach((step, idx) => {
86
+ const line = ` ${idx + 1}. ${step}`;
87
+ console.log(chalk.bold.cyan('║') + chalk.white(line.padEnd(contentWidth)) + chalk.bold.cyan('║'));
88
+ });
89
+
90
+ console.log(chalk.bold.cyan(`╚${'═'.repeat(contentWidth)}╝\n`));
91
+ }
92
+
93
+ export function printTable(headers, rows) {
94
+ const colWidths = headers.map((h, i) => {
95
+ return Math.max(h.length, Math.max(...rows.map(r => String(r[i] || '').length)));
96
+ });
97
+
98
+ // Header
99
+ console.log(chalk.bold.cyan('┌' + colWidths.map(w => '─'.repeat(w + 2)).join('┬') + '┐'));
100
+ console.log(
101
+ chalk.bold.cyan('│'),
102
+ colWidths.map((w, i) => chalk.bold.white(headers[i].padEnd(w))).join(chalk.bold.cyan(' │ ')),
103
+ chalk.bold.cyan('│')
104
+ );
105
+ console.log(chalk.bold.cyan('├' + colWidths.map(w => '─'.repeat(w + 2)).join('┼') + '┤'));
106
+
107
+ // Rows
108
+ rows.forEach((row) => {
109
+ console.log(
110
+ chalk.cyan('│'),
111
+ colWidths.map((w, i) => String(row[i] || '').padEnd(w)).join(chalk.cyan(' │ ')),
112
+ chalk.cyan('│')
113
+ );
114
+ });
115
+
116
+ console.log(chalk.bold.cyan('└' + colWidths.map(w => '─'.repeat(w + 2)).join('┴') + '┘\n'));
117
+ }
118
+
119
+ export default {
120
+ printBanner,
121
+ printSection,
122
+ printStep,
123
+ printSuccess,
124
+ printWarning,
125
+ printError,
126
+ printInfo,
127
+ printBox,
128
+ printSummary,
129
+ printFooter,
130
+ printTable
131
+ };
@@ -10,9 +10,10 @@ import { globSync } from 'glob';
10
10
  /**
11
11
  * Inject API calls into frontend files
12
12
  */
13
- export function injectApiCalls(projectPath, resources) {
13
+ export function injectApiCalls(projectPath, resources, options = {}) {
14
14
  const injectedFiles = [];
15
15
  const files = getFrontendFiles(projectPath);
16
+ const idField = options.idField || '_id';
16
17
 
17
18
  for (const file of files) {
18
19
  try {
@@ -23,7 +24,7 @@ export function injectApiCalls(projectPath, resources) {
23
24
  for (const resource of resources) {
24
25
  // Check if this file uses this resource
25
26
  if (usesResource(content, resource)) {
26
- const result = injectForResource(content, resource);
27
+ const result = injectForResource(content, resource, { idField });
27
28
  if (result.modified) {
28
29
  content = result.content;
29
30
  modified = true;
@@ -70,13 +71,19 @@ function usesResource(content, resource) {
70
71
  const statePattern = new RegExp(`\\[\\s*${varName}\\s*,\\s*${setterName}\\s*\\]\\s*=\\s*useState\\(`, 'g');
71
72
  if (statePattern.test(content)) return true;
72
73
 
74
+ const staticArrayPattern = new RegExp(`const\\s+${varName}\\s*=\\s*\\[[\\s\\S]*?\\]\\s*;?`, 'g');
75
+ if (staticArrayPattern.test(content)) return true;
76
+
77
+ const mapPattern = new RegExp(`${varName}\\.map\\s*\\(`, 'g');
78
+ if (mapPattern.test(content)) return true;
79
+
73
80
  return false;
74
81
  }
75
82
 
76
83
  /**
77
84
  * Inject API calls for a specific resource
78
85
  */
79
- function injectForResource(content, resource) {
86
+ function injectForResource(content, resource, options = {}) {
80
87
  let modified = false;
81
88
  let nextContent = content;
82
89
 
@@ -85,10 +92,20 @@ function injectForResource(content, resource) {
85
92
  const capitalSingular = capitalize(singular);
86
93
  const capitalResource = capitalize(resourceName);
87
94
 
88
- const importResult = ensureUseEffectImport(nextContent);
95
+ const hasStaticArray = new RegExp(`const\\s+${resourceName}\\s*=\\s*\\[[\\s\\S]*?\\]\\s*;?`).test(nextContent);
96
+ const hasApiImplementation = hasResourceApiCalls(nextContent, resourceName);
97
+ if (hasApiImplementation && !hasStaticArray && options.force !== true) {
98
+ return { content: nextContent, modified: false };
99
+ }
100
+
101
+ const importResult = ensureReactHooksImport(nextContent, ['useEffect', 'useState']);
89
102
  nextContent = importResult.content;
90
103
  modified = modified || importResult.changed;
91
104
 
105
+ const staticToStateResult = convertStaticArrayToState(nextContent, resourceName, capitalResource);
106
+ nextContent = staticToStateResult.content;
107
+ modified = modified || staticToStateResult.changed;
108
+
92
109
  const apiUrlResult = ensureApiUrlConstant(nextContent);
93
110
  nextContent = apiUrlResult.content;
94
111
  modified = modified || apiUrlResult.changed;
@@ -125,15 +142,20 @@ function injectForResource(content, resource) {
125
142
  nextContent = deleteResult.content;
126
143
  modified = modified || deleteResult.changed;
127
144
 
128
- const jsxResult = ensureMongoIdsInJsx(nextContent, capitalSingular);
145
+ const jsxResult = ensureIdsInJsx(nextContent, capitalSingular, options.idField || '_id');
129
146
  nextContent = jsxResult.content;
130
147
  modified = modified || jsxResult.changed;
131
148
 
132
149
  return { content: nextContent, modified };
133
150
  }
134
151
 
135
- function ensureUseEffectImport(content) {
136
- if (/\buseEffect\b/.test(content)) {
152
+ function ensureReactHooksImport(content, requiredHooks = []) {
153
+ if (!requiredHooks.length) {
154
+ return { content, changed: false };
155
+ }
156
+
157
+ const missing = requiredHooks.filter((hook) => !new RegExp(`\\b${hook}\\b`).test(content));
158
+ if (missing.length === 0) {
137
159
  return { content, changed: false };
138
160
  }
139
161
 
@@ -144,19 +166,64 @@ function ensureUseEffectImport(content) {
144
166
  .split(',')
145
167
  .map(name => name.trim())
146
168
  .filter(Boolean);
147
- if (!cleaned.includes('useEffect')) {
148
- cleaned.push('useEffect');
169
+
170
+ for (const hook of requiredHooks) {
171
+ if (!cleaned.includes(hook)) {
172
+ cleaned.push(hook);
173
+ }
149
174
  }
175
+
150
176
  return `import { ${cleaned.join(', ')} } from 'react';`;
151
177
  });
152
178
  return { content: next, changed: next !== content };
153
179
  }
154
180
 
181
+ const reactDefaultImport = /import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]react['"];?/;
182
+ if (reactDefaultImport.test(content)) {
183
+ const next = content.replace(reactDefaultImport, (_, defaultName) => {
184
+ return `import ${defaultName}, { ${requiredHooks.join(', ')} } from 'react';`;
185
+ });
186
+ return { content: next, changed: next !== content };
187
+ }
188
+
155
189
  return { content, changed: false };
156
190
  }
157
191
 
192
+ function convertStaticArrayToState(content, resourceName, capitalResource) {
193
+ const stateRegex = new RegExp(`const\\s+\\[\\s*${resourceName}\\s*,\\s*set${capitalResource}\\s*\\]\\s*=\\s*useState\\(`);
194
+ if (stateRegex.test(content)) {
195
+ return { content, changed: false };
196
+ }
197
+
198
+ const staticRegex = new RegExp(`const\\s+${resourceName}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s*;?`);
199
+ const staticMatch = content.match(staticRegex);
200
+ if (!staticMatch) {
201
+ return { content, changed: false };
202
+ }
203
+
204
+ const replacement = `const [${resourceName}, set${capitalResource}] = useState([]);`;
205
+ const matchIndex = staticMatch.index ?? -1;
206
+
207
+ const componentStart = findPrimaryComponentStart(content);
208
+ if (componentStart !== -1 && matchIndex !== -1 && matchIndex < componentStart) {
209
+ // Remove top-level static data and declare hook state inside component scope.
210
+ const withoutStatic = content.replace(staticRegex, '');
211
+ const componentStartAfterRemoval = findPrimaryComponentStart(withoutStatic);
212
+ const insertPos = findFirstLineBreakAfterBrace(withoutStatic, componentStartAfterRemoval);
213
+ if (insertPos !== -1) {
214
+ const indentedReplacement = `\n ${replacement}`;
215
+ const next = `${withoutStatic.slice(0, insertPos)}${indentedReplacement}${withoutStatic.slice(insertPos)}`;
216
+ return { content: next, changed: next !== content };
217
+ }
218
+ }
219
+
220
+ // If static array is already inside a component/function scope, replace in-place.
221
+ const next = content.replace(staticRegex, replacement);
222
+ return { content: next, changed: next !== content };
223
+ }
224
+
158
225
  function ensureApiUrlConstant(content) {
159
- if (/\b(API_URL|BACKEND_URL)\b/.test(content)) {
226
+ if (/\b(API_URL|API_BASE_URL|BACKEND_URL)\b/.test(content)) {
160
227
  return { content, changed: false };
161
228
  }
162
229
 
@@ -167,7 +234,7 @@ function ensureApiUrlConstant(content) {
167
234
 
168
235
  const lastImport = importMatches[importMatches.length - 1];
169
236
  const insertPos = lastImport.index + lastImport[0].length;
170
- const next = `${content.slice(0, insertPos)}\n\nconst API_URL = 'http://localhost:5000/api';${content.slice(insertPos)}`;
237
+ const next = `${content.slice(0, insertPos)}\n\nconst API_BASE_URL =\n+ (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_API_URL) ||\n+ (typeof process !== 'undefined' && process.env && process.env.REACT_APP_API_URL) ||\n+ 'http://localhost:5000';\n+const API_URL = \`${'${API_BASE_URL}'}/api\`;${content.slice(insertPos)}`;
171
238
  return { content: next, changed: true };
172
239
  }
173
240
 
@@ -195,7 +262,7 @@ function ensureFetchFunction(content, resourceName, capitalResource, fetchFuncti
195
262
  }
196
263
 
197
264
  const insertPos = content.indexOf(stateMatch[0]) + stateMatch[0].length;
198
- const snippet = `\n\n const ${fetchFunctionName} = async () => {\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}\`);\n const payload = await res.json();\n set${capitalResource}(payload.data || []);\n } catch (error) {\n console.error('Error fetching ${resourceName}:', error);\n }\n };`;
265
+ const snippet = `\n\n const ${fetchFunctionName} = async () => {\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}\`);\n if (!res.ok) {\n throw new Error(\`Failed to fetch ${resourceName}: \${res.status}\`);\n }\n const payload = await res.json();\n const list = Array.isArray(payload)\n ? payload\n : payload.data || payload.items || payload.results || [];\n set${capitalResource}(Array.isArray(list) ? list : []);\n } catch (error) {\n console.error('Error fetching ${resourceName}:', error);\n }\n };`;
199
266
 
200
267
  const next = `${content.slice(0, insertPos)}${snippet}${content.slice(insertPos)}`;
201
268
  return { content: next, changed: true };
@@ -247,7 +314,7 @@ function replaceCreateHandler(content, ctx) {
247
314
  const initialStateLiteral = extractInitialStateLiteral(content, newStateName, setNewStateName) || '{}';
248
315
  const nonEmptyGuard = buildNonEmptyGuard(newStateName, initialStateLiteral);
249
316
 
250
- const replacement = `const ${functionName} = async (e) => {\n e.preventDefault();\n ${nonEmptyGuard}\n\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(${newStateName}),\n });\n\n if (res.ok) {\n await ${fetchFunctionName}();\n ${setNewStateName}(${initialStateLiteral});\n }\n } catch (error) {\n console.error('Error adding ${singular}:', error);\n }\n };\n\n`;
317
+ const replacement = `const ${functionName} = async (e) => {\n e.preventDefault();\n ${nonEmptyGuard}\n\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(${newStateName}),\n });\n\n if (res.ok) {\n await ${fetchFunctionName}();\n ${setNewStateName}(${initialStateLiteral});\n } else {\n console.error('Failed to add ${singular}:', res.status);\n }\n } catch (error) {\n console.error('Error adding ${singular}:', error);\n }\n };\n\n`;
251
318
 
252
319
  const next = `${content.slice(0, block.start)}${replacement}${content.slice(block.end)}`;
253
320
  return { content: next, changed: true };
@@ -266,34 +333,49 @@ function replaceDeleteHandler(content, ctx) {
266
333
  return { content, changed: false };
267
334
  }
268
335
 
269
- const replacement = `const ${functionName} = async (id) => {\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}/\${id}\`, {\n method: 'DELETE',\n });\n\n if (res.ok) {\n await ${fetchFunctionName}();\n }\n } catch (error) {\n console.error('Error deleting ${singular}:', error);\n }\n };\n\n`;
336
+ const replacement = `const ${functionName} = async (id) => {\n try {\n const res = await fetch(\`\${API_URL}/${resourceName}/\${id}\`, {\n method: 'DELETE',\n });\n\n if (res.ok) {\n await ${fetchFunctionName}();\n } else {\n console.error('Failed to delete ${singular}:', res.status);\n }\n } catch (error) {\n console.error('Error deleting ${singular}:', error);\n }\n };\n\n`;
270
337
 
271
338
  const next = `${content.slice(0, block.start)}${replacement}${content.slice(block.end)}`;
272
339
  return { content: next, changed: true };
273
340
  }
274
341
 
275
- function ensureMongoIdsInJsx(content, capitalSingular) {
342
+ function ensureIdsInJsx(content, capitalSingular, idField = '_id') {
276
343
  let next = content;
277
344
  let changed = false;
278
345
 
279
- const keyReplaced = next.replace(/key=\{(\w+)\.id\}/g, 'key={$1._id}');
280
- if (keyReplaced !== next) {
281
- next = keyReplaced;
282
- changed = true;
283
- }
346
+ if (idField === '_id') {
347
+ const keyReplaced = next.replace(/key=\{(\w+)\.id\}/g, 'key={$1._id}');
348
+ if (keyReplaced !== next) {
349
+ next = keyReplaced;
350
+ changed = true;
351
+ }
352
+
353
+ const deleteCallPattern = new RegExp(`onClick=\\{\\(\\)\\s*=>\\s*delete${capitalSingular}\\((\\w+)\\.id\\)\\}`, 'g');
354
+ const clickReplaced = next.replace(deleteCallPattern, `onClick={() => delete${capitalSingular}($1._id)}`);
355
+ if (clickReplaced !== next) {
356
+ next = clickReplaced;
357
+ changed = true;
358
+ }
359
+ } else {
360
+ const keyReplaced = next.replace(/key=\{(\w+)\._id\}/g, 'key={$1.id}');
361
+ if (keyReplaced !== next) {
362
+ next = keyReplaced;
363
+ changed = true;
364
+ }
284
365
 
285
- const deleteCallPattern = new RegExp(`onClick=\\{\\(\\)\\s*=>\\s*delete${capitalSingular}\\((\\w+)\\.id\\)\\}`, 'g');
286
- const clickReplaced = next.replace(deleteCallPattern, `onClick={() => delete${capitalSingular}($1._id)}`);
287
- if (clickReplaced !== next) {
288
- next = clickReplaced;
289
- changed = true;
366
+ const deleteCallPattern = new RegExp(`onClick=\\{\\(\\)\\s*=>\\s*delete${capitalSingular}\\((\\w+)\\._id\\)\\}`, 'g');
367
+ const clickReplaced = next.replace(deleteCallPattern, `onClick={() => delete${capitalSingular}($1.id)}`);
368
+ if (clickReplaced !== next) {
369
+ next = clickReplaced;
370
+ changed = true;
371
+ }
290
372
  }
291
373
 
292
374
  return { content: next, changed };
293
375
  }
294
376
 
295
377
  function extractInitialStateLiteral(content, stateName, setterName) {
296
- const regex = new RegExp(`const\\s+\\[\\s*${stateName}\\s*,\\s*${setterName}\\s*\\]\\s*=\\s*useState\\((\\{[\\s\\S]*?\\})\\)`);
378
+ const regex = new RegExp(`const\\s+\\[\\s*${stateName}\\s*,\\s*${setterName}\\s*\\]\\s*=\\s*(?:React\\.)?useState\\((\\{[\\s\\S]*?\\})\\)`);
297
379
  const match = content.match(regex);
298
380
  return match ? match[1] : null;
299
381
  }
@@ -339,6 +421,42 @@ function findConstArrowFunction(content, functionName) {
339
421
  return { start: startMatch.index, end };
340
422
  }
341
423
 
424
+ function findPrimaryComponentStart(content) {
425
+ const functionComponentMatch = /function\s+[A-Z][A-Za-z0-9_$]*\s*\([^)]*\)\s*\{/.exec(content);
426
+ if (functionComponentMatch) {
427
+ return functionComponentMatch.index;
428
+ }
429
+
430
+ const arrowComponentMatch = /const\s+[A-Z][A-Za-z0-9_$]*\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{/.exec(content);
431
+ if (arrowComponentMatch) {
432
+ return arrowComponentMatch.index;
433
+ }
434
+
435
+ return -1;
436
+ }
437
+
438
+ function findFirstLineBreakAfterBrace(content, startIndex) {
439
+ const braceIndex = content.indexOf('{', startIndex);
440
+ if (braceIndex === -1) return -1;
441
+
442
+ const lineBreakIndex = content.indexOf('\n', braceIndex);
443
+ if (lineBreakIndex === -1) return -1;
444
+
445
+ return lineBreakIndex + 1;
446
+ }
447
+
448
+ function hasResourceApiCalls(content, resourceName) {
449
+ const escaped = escapeRegExp(resourceName);
450
+ const quotedOrTemplatePath = '(["\'])[^\\n]*?\\/api\\/' + escaped + '[^\\n]*?\\1|`[^`]*?\\/api\\/' + escaped + '[^`]*?`';
451
+ const fetchPattern = new RegExp('fetch\\s*\\(\\s*(?:' + quotedOrTemplatePath + ')', 'i');
452
+ const axiosPattern = new RegExp('axios\\.(get|post|put|patch|delete)\\s*\\(\\s*(?:' + quotedOrTemplatePath + ')', 'i');
453
+ return fetchPattern.test(content) || axiosPattern.test(content);
454
+ }
455
+
456
+ function escapeRegExp(value) {
457
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
458
+ }
459
+
342
460
  /**
343
461
  * Capitalize string
344
462
  */