magector 1.0.0

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,563 @@
1
+ /**
2
+ * Magento-specific code patterns for high-precision indexing
3
+ */
4
+
5
+ // Magento file type detection based on path
6
+ export const MAGENTO_FILE_TYPES = {
7
+ // PHP patterns
8
+ 'Controller': /\/Controller\/.*\.php$/,
9
+ 'Block': /\/Block\/.*\.php$/,
10
+ 'Model': /\/Model\/(?!ResourceModel).*\.php$/,
11
+ 'ResourceModel': /\/Model\/ResourceModel\/.*\.php$/,
12
+ 'Collection': /\/Collection\.php$/,
13
+ 'Repository': /Repository\.php$/,
14
+ 'RepositoryInterface': /RepositoryInterface\.php$/,
15
+ 'Api': /\/Api\/.*Interface\.php$/,
16
+ 'ApiData': /\/Api\/Data\/.*\.php$/,
17
+ 'Plugin': /\/Plugin\/.*\.php$/,
18
+ 'Observer': /\/Observer\/.*\.php$/,
19
+ 'Helper': /\/Helper\/.*\.php$/,
20
+ 'Setup': /\/Setup\/.*\.php$/,
21
+ 'Console': /\/Console\/Command\/.*\.php$/,
22
+ 'Cron': /\/Cron\/.*\.php$/,
23
+ 'ViewModel': /\/ViewModel\/.*\.php$/,
24
+ 'GraphQlResolver': /\/Model\/Resolver\/.*\.php$/,
25
+ 'DataProvider': /\/DataProvider\/.*\.php$/,
26
+ 'UiComponent': /\/Ui\/.*\.php$/,
27
+
28
+ // XML config patterns
29
+ 'di.xml': /\/etc\/.*di\.xml$/,
30
+ 'events.xml': /\/etc\/.*events\.xml$/,
31
+ 'routes.xml': /\/etc\/.*routes\.xml$/,
32
+ 'webapi.xml': /\/etc\/webapi\.xml$/,
33
+ 'system.xml': /\/etc\/adminhtml\/system\.xml$/,
34
+ 'config.xml': /\/etc\/config\.xml$/,
35
+ 'acl.xml': /\/etc\/acl\.xml$/,
36
+ 'crontab.xml': /\/etc\/crontab\.xml$/,
37
+ 'module.xml': /\/etc\/module\.xml$/,
38
+ 'db_schema.xml': /\/etc\/db_schema\.xml$/,
39
+ 'indexer.xml': /\/etc\/indexer\.xml$/,
40
+ 'mview.xml': /\/etc\/mview\.xml$/,
41
+ 'widget.xml': /\/etc\/widget\.xml$/,
42
+ 'layout.xml': /\/layout\/.*\.xml$/,
43
+ 'ui_component.xml': /\/ui_component\/.*\.xml$/,
44
+
45
+ // Frontend
46
+ 'template': /\.phtml$/,
47
+ 'less': /\.less$/,
48
+ 'requirejs': /requirejs-config\.js$/,
49
+ 'knockout': /\/view\/.*\.html$/,
50
+
51
+ // GraphQL
52
+ 'graphql_schema': /\.graphqls$/
53
+ };
54
+
55
+ // Magento-specific PHP patterns
56
+ export const PHP_PATTERNS = {
57
+ // Plugin methods
58
+ plugin: {
59
+ before: /public\s+function\s+(before\w+)\s*\(/g,
60
+ after: /public\s+function\s+(after\w+)\s*\(/g,
61
+ around: /public\s+function\s+(around\w+)\s*\(/g
62
+ },
63
+
64
+ // Controller actions
65
+ controller: {
66
+ execute: /public\s+function\s+execute\s*\(\s*\)/,
67
+ resultFactory: /\$this->resultFactory->create\s*\(\s*ResultFactory::TYPE_(\w+)/g
68
+ },
69
+
70
+ // Repository patterns
71
+ repository: {
72
+ getById: /public\s+function\s+getById\s*\(/,
73
+ getList: /public\s+function\s+getList\s*\(/,
74
+ save: /public\s+function\s+save\s*\(/,
75
+ delete: /public\s+function\s+delete\s*\(/,
76
+ deleteById: /public\s+function\s+deleteById\s*\(/
77
+ },
78
+
79
+ // Model patterns
80
+ model: {
81
+ beforeSave: /protected\s+function\s+_beforeSave\s*\(/,
82
+ afterSave: /protected\s+function\s+_afterSave\s*\(/,
83
+ beforeLoad: /protected\s+function\s+_beforeLoad\s*\(/,
84
+ afterLoad: /protected\s+function\s+_afterLoad\s*\(/,
85
+ construct: /protected\s+function\s+_construct\s*\(/
86
+ },
87
+
88
+ // Observer
89
+ observer: {
90
+ execute: /public\s+function\s+execute\s*\(\s*Observer\s+\$observer\s*\)/
91
+ },
92
+
93
+ // Block patterns
94
+ block: {
95
+ toHtml: /protected\s+function\s+_toHtml\s*\(/,
96
+ prepareLayout: /protected\s+function\s+_prepareLayout\s*\(/,
97
+ beforeToHtml: /protected\s+function\s+_beforeToHtml\s*\(/
98
+ },
99
+
100
+ // GraphQL Resolver
101
+ resolver: {
102
+ resolve: /public\s+function\s+resolve\s*\(\s*Field\s+\$field/
103
+ },
104
+
105
+ // Setup scripts
106
+ setup: {
107
+ install: /public\s+function\s+install\s*\(/,
108
+ upgrade: /public\s+function\s+upgrade\s*\(/,
109
+ apply: /public\s+function\s+apply\s*\(/
110
+ },
111
+
112
+ // Console command
113
+ console: {
114
+ configure: /protected\s+function\s+configure\s*\(/,
115
+ execute: /protected\s+function\s+execute\s*\(\s*InputInterface/
116
+ },
117
+
118
+ // Data provider
119
+ dataProvider: {
120
+ getData: /public\s+function\s+getData\s*\(/,
121
+ getMeta: /public\s+function\s+getMeta\s*\(/
122
+ }
123
+ };
124
+
125
+ // XML config patterns
126
+ export const XML_PATTERNS = {
127
+ // di.xml
128
+ di: {
129
+ preference: /<preference\s+for="([^"]+)"\s+type="([^"]+)"/g,
130
+ virtualType: /<virtualType\s+name="([^"]+)"\s+type="([^"]+)"/g,
131
+ plugin: /<plugin\s+name="([^"]+)"[^>]*type="([^"]+)"/g,
132
+ type: /<type\s+name="([^"]+)"/g,
133
+ argument: /<argument\s+name="([^"]+)"\s+xsi:type="([^"]+)"/g
134
+ },
135
+
136
+ // events.xml
137
+ events: {
138
+ event: /<event\s+name="([^"]+)"/g,
139
+ observer: /<observer\s+name="([^"]+)"[^>]*instance="([^"]+)"/g
140
+ },
141
+
142
+ // layout.xml
143
+ layout: {
144
+ block: /<block\s+[^>]*class="([^"]+)"/g,
145
+ container: /<container\s+name="([^"]+)"/g,
146
+ referenceBlock: /<referenceBlock\s+name="([^"]+)"/g,
147
+ referenceContainer: /<referenceContainer\s+name="([^"]+)"/g,
148
+ uiComponent: /<uiComponent\s+name="([^"]+)"/g
149
+ },
150
+
151
+ // webapi.xml
152
+ webapi: {
153
+ route: /<route\s+url="([^"]+)"\s+method="([^"]+)"/g,
154
+ service: /<service\s+class="([^"]+)"\s+method="([^"]+)"/g,
155
+ resource: /<resource\s+ref="([^"]+)"/g
156
+ },
157
+
158
+ // system.xml
159
+ system: {
160
+ section: /<section\s+id="([^"]+)"/g,
161
+ group: /<group\s+id="([^"]+)"/g,
162
+ field: /<field\s+id="([^"]+)"/g,
163
+ sourceModel: /<source_model>([^<]+)<\/source_model>/g,
164
+ backendModel: /<backend_model>([^<]+)<\/backend_model>/g
165
+ },
166
+
167
+ // db_schema.xml
168
+ dbSchema: {
169
+ table: /<table\s+name="([^"]+)"/g,
170
+ column: /<column\s+[^>]*name="([^"]+)"/g,
171
+ constraint: /<constraint\s+[^>]*referenceId="([^"]+)"/g,
172
+ index: /<index\s+[^>]*referenceId="([^"]+)"/g
173
+ },
174
+
175
+ // acl.xml
176
+ acl: {
177
+ resource: /<resource\s+id="([^"]+)"/g
178
+ },
179
+
180
+ // crontab.xml
181
+ crontab: {
182
+ job: /<job\s+name="([^"]+)"[^>]*instance="([^"]+)"/g,
183
+ schedule: /<schedule>([^<]+)<\/schedule>/g
184
+ }
185
+ };
186
+
187
+ // GraphQL schema patterns
188
+ export const GRAPHQL_PATTERNS = {
189
+ type: /type\s+(\w+)\s*(?:implements\s+[\w\s,&]+)?\s*\{/g,
190
+ interface: /interface\s+(\w+)\s*\{/g,
191
+ input: /input\s+(\w+)\s*\{/g,
192
+ enum: /enum\s+(\w+)\s*\{/g,
193
+ query: /(\w+)\s*\([^)]*\)\s*:\s*([\w\[\]!]+)/g,
194
+ mutation: /(\w+)\s*\([^)]*\)\s*:\s*([\w\[\]!]+)/g,
195
+ resolver: /@resolver\s*\(\s*class:\s*"([^"]+)"/g
196
+ };
197
+
198
+ // Magento module areas
199
+ export const AREAS = ['frontend', 'adminhtml', 'webapi_rest', 'webapi_soap', 'graphql', 'crontab', 'global'];
200
+
201
+ // Common Magento interfaces
202
+ export const CORE_INTERFACES = {
203
+ 'Magento\\Framework\\App\\ActionInterface': 'Controller',
204
+ 'Magento\\Framework\\View\\Element\\BlockInterface': 'Block',
205
+ 'Magento\\Framework\\Model\\AbstractModel': 'Model',
206
+ 'Magento\\Framework\\Model\\ResourceModel\\AbstractResource': 'ResourceModel',
207
+ 'Magento\\Framework\\Data\\Collection\\AbstractDb': 'Collection',
208
+ 'Magento\\Framework\\Event\\ObserverInterface': 'Observer',
209
+ 'Magento\\Framework\\Interception\\InterceptorInterface': 'Plugin',
210
+ 'Magento\\Framework\\Api\\SearchCriteriaInterface': 'SearchCriteria',
211
+ 'Magento\\Framework\\Api\\ExtensibleDataInterface': 'DataInterface',
212
+ 'Magento\\Framework\\GraphQl\\Query\\ResolverInterface': 'GraphQlResolver',
213
+ 'Magento\\Framework\\Setup\\InstallSchemaInterface': 'InstallSchema',
214
+ 'Magento\\Framework\\Setup\\UpgradeSchemaInterface': 'UpgradeSchema',
215
+ 'Magento\\Framework\\Setup\\Patch\\DataPatchInterface': 'DataPatch',
216
+ 'Magento\\Framework\\Setup\\Patch\\SchemaPatchInterface': 'SchemaPatch',
217
+ 'Magento\\Framework\\Console\\Cli': 'ConsoleCommand',
218
+ 'Magento\\Ui\\DataProvider\\AbstractDataProvider': 'UiDataProvider'
219
+ };
220
+
221
+ /**
222
+ * Detect Magento-specific file type from path
223
+ */
224
+ export function detectMagentoFileType(filePath) {
225
+ for (const [type, pattern] of Object.entries(MAGENTO_FILE_TYPES)) {
226
+ if (pattern.test(filePath)) {
227
+ return type;
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+
233
+ /**
234
+ * Extract Magento-specific metadata from PHP content
235
+ */
236
+ export function extractPhpMagentoMetadata(content, filePath) {
237
+ const metadata = {
238
+ magentoType: detectMagentoFileType(filePath),
239
+ patterns: []
240
+ };
241
+
242
+ // Detect plugin methods
243
+ for (const [method, pattern] of Object.entries(PHP_PATTERNS.plugin)) {
244
+ const matches = [...content.matchAll(pattern)];
245
+ if (matches.length > 0) {
246
+ metadata.isPlugin = true;
247
+ metadata.pluginMethods = matches.map(m => ({ type: method, name: m[1] }));
248
+ metadata.patterns.push('plugin');
249
+ }
250
+ }
251
+
252
+ // Detect controller
253
+ if (PHP_PATTERNS.controller.execute.test(content)) {
254
+ metadata.isController = true;
255
+ metadata.patterns.push('controller');
256
+
257
+ const resultTypes = [...content.matchAll(PHP_PATTERNS.controller.resultFactory)];
258
+ if (resultTypes.length > 0) {
259
+ metadata.resultTypes = resultTypes.map(m => m[1]);
260
+ }
261
+ }
262
+
263
+ // Detect repository pattern
264
+ const repoMethods = [];
265
+ for (const [method, pattern] of Object.entries(PHP_PATTERNS.repository)) {
266
+ if (pattern.test(content)) {
267
+ repoMethods.push(method);
268
+ }
269
+ }
270
+ if (repoMethods.length >= 2) {
271
+ metadata.isRepository = true;
272
+ metadata.repositoryMethods = repoMethods;
273
+ metadata.patterns.push('repository');
274
+ }
275
+
276
+ // Detect model hooks
277
+ const modelHooks = [];
278
+ for (const [hook, pattern] of Object.entries(PHP_PATTERNS.model)) {
279
+ if (pattern.test(content)) {
280
+ modelHooks.push(hook);
281
+ }
282
+ }
283
+ if (modelHooks.length > 0) {
284
+ metadata.isModel = true;
285
+ metadata.modelHooks = modelHooks;
286
+ metadata.patterns.push('model');
287
+ }
288
+
289
+ // Detect observer
290
+ if (PHP_PATTERNS.observer.execute.test(content)) {
291
+ metadata.isObserver = true;
292
+ metadata.patterns.push('observer');
293
+ }
294
+
295
+ // Detect block
296
+ for (const [method, pattern] of Object.entries(PHP_PATTERNS.block)) {
297
+ if (pattern.test(content)) {
298
+ metadata.isBlock = true;
299
+ metadata.patterns.push('block');
300
+ break;
301
+ }
302
+ }
303
+
304
+ // Detect GraphQL resolver
305
+ if (PHP_PATTERNS.resolver.resolve.test(content)) {
306
+ metadata.isResolver = true;
307
+ metadata.patterns.push('graphql_resolver');
308
+ }
309
+
310
+ // Detect setup script
311
+ for (const [type, pattern] of Object.entries(PHP_PATTERNS.setup)) {
312
+ if (pattern.test(content)) {
313
+ metadata.isSetup = true;
314
+ metadata.setupType = type;
315
+ metadata.patterns.push('setup');
316
+ break;
317
+ }
318
+ }
319
+
320
+ // Detect console command
321
+ if (PHP_PATTERNS.console.configure.test(content)) {
322
+ metadata.isConsoleCommand = true;
323
+ metadata.patterns.push('console');
324
+ }
325
+
326
+ // Detect data provider
327
+ if (PHP_PATTERNS.dataProvider.getData.test(content)) {
328
+ metadata.isDataProvider = true;
329
+ metadata.patterns.push('data_provider');
330
+ }
331
+
332
+ // Extract injected dependencies
333
+ const constructorMatch = content.match(/function\s+__construct\s*\(([^)]*)\)/s);
334
+ if (constructorMatch) {
335
+ const deps = [];
336
+ const depRegex = /([\w\\]+(?:Interface)?)\s+\$(\w+)/g;
337
+ let match;
338
+ while ((match = depRegex.exec(constructorMatch[1])) !== null) {
339
+ const fullType = match[1];
340
+ const shortType = fullType.split('\\').pop();
341
+ deps.push({
342
+ type: shortType,
343
+ fullType: fullType.includes('\\') ? fullType : null,
344
+ variable: match[2]
345
+ });
346
+ }
347
+ if (deps.length > 0) {
348
+ metadata.dependencies = deps;
349
+ metadata.dependencyCount = deps.length;
350
+ }
351
+ }
352
+
353
+ return metadata;
354
+ }
355
+
356
+ /**
357
+ * Extract Magento-specific metadata from XML content
358
+ */
359
+ export function extractXmlMagentoMetadata(content, filePath) {
360
+ const metadata = {
361
+ magentoType: detectMagentoFileType(filePath),
362
+ configItems: []
363
+ };
364
+
365
+ // di.xml parsing
366
+ if (filePath.includes('di.xml')) {
367
+ const preferences = [...content.matchAll(XML_PATTERNS.di.preference)];
368
+ if (preferences.length > 0) {
369
+ metadata.preferences = preferences.map(m => ({ for: m[1], type: m[2] }));
370
+ metadata.configItems.push(...metadata.preferences.map(p => `preference:${p.for}`));
371
+ }
372
+
373
+ const virtualTypes = [...content.matchAll(XML_PATTERNS.di.virtualType)];
374
+ if (virtualTypes.length > 0) {
375
+ metadata.virtualTypes = virtualTypes.map(m => ({ name: m[1], type: m[2] }));
376
+ metadata.configItems.push(...metadata.virtualTypes.map(v => `virtualType:${v.name}`));
377
+ }
378
+
379
+ const plugins = [...content.matchAll(XML_PATTERNS.di.plugin)];
380
+ if (plugins.length > 0) {
381
+ metadata.plugins = plugins.map(m => ({ name: m[1], type: m[2] }));
382
+ metadata.configItems.push(...metadata.plugins.map(p => `plugin:${p.name}`));
383
+ }
384
+
385
+ const types = [...content.matchAll(XML_PATTERNS.di.type)];
386
+ if (types.length > 0) {
387
+ metadata.types = types.map(m => m[1]);
388
+ }
389
+ }
390
+
391
+ // events.xml parsing
392
+ if (filePath.includes('events.xml')) {
393
+ const events = [...content.matchAll(XML_PATTERNS.events.event)];
394
+ if (events.length > 0) {
395
+ metadata.events = events.map(m => m[1]);
396
+ metadata.configItems.push(...metadata.events.map(e => `event:${e}`));
397
+ }
398
+
399
+ const observers = [...content.matchAll(XML_PATTERNS.events.observer)];
400
+ if (observers.length > 0) {
401
+ metadata.observers = observers.map(m => ({ name: m[1], instance: m[2] }));
402
+ }
403
+ }
404
+
405
+ // webapi.xml parsing
406
+ if (filePath.includes('webapi.xml')) {
407
+ const routes = [...content.matchAll(XML_PATTERNS.webapi.route)];
408
+ if (routes.length > 0) {
409
+ metadata.apiRoutes = routes.map(m => ({ url: m[1], method: m[2] }));
410
+ metadata.configItems.push(...metadata.apiRoutes.map(r => `api:${r.method}:${r.url}`));
411
+ }
412
+
413
+ const services = [...content.matchAll(XML_PATTERNS.webapi.service)];
414
+ if (services.length > 0) {
415
+ metadata.apiServices = services.map(m => ({ class: m[1], method: m[2] }));
416
+ }
417
+ }
418
+
419
+ // layout.xml parsing
420
+ if (filePath.includes('/layout/')) {
421
+ const blocks = [...content.matchAll(XML_PATTERNS.layout.block)];
422
+ if (blocks.length > 0) {
423
+ metadata.blocks = blocks.map(m => m[1]);
424
+ metadata.configItems.push(...metadata.blocks.map(b => `block:${b.split('\\').pop()}`));
425
+ }
426
+
427
+ const uiComponents = [...content.matchAll(XML_PATTERNS.layout.uiComponent)];
428
+ if (uiComponents.length > 0) {
429
+ metadata.uiComponents = uiComponents.map(m => m[1]);
430
+ }
431
+ }
432
+
433
+ // system.xml parsing
434
+ if (filePath.includes('system.xml')) {
435
+ const sections = [...content.matchAll(XML_PATTERNS.system.section)];
436
+ const groups = [...content.matchAll(XML_PATTERNS.system.group)];
437
+ const fields = [...content.matchAll(XML_PATTERNS.system.field)];
438
+
439
+ if (sections.length > 0) metadata.configSections = sections.map(m => m[1]);
440
+ if (groups.length > 0) metadata.configGroups = groups.map(m => m[1]);
441
+ if (fields.length > 0) {
442
+ metadata.configFields = fields.map(m => m[1]);
443
+ metadata.configItems.push(...metadata.configFields.map(f => `config:${f}`));
444
+ }
445
+ }
446
+
447
+ // db_schema.xml parsing
448
+ if (filePath.includes('db_schema.xml')) {
449
+ const tables = [...content.matchAll(XML_PATTERNS.dbSchema.table)];
450
+ if (tables.length > 0) {
451
+ metadata.tables = tables.map(m => m[1]);
452
+ metadata.configItems.push(...metadata.tables.map(t => `table:${t}`));
453
+ }
454
+
455
+ const columns = [...content.matchAll(XML_PATTERNS.dbSchema.column)];
456
+ if (columns.length > 0) {
457
+ metadata.columns = columns.map(m => m[1]);
458
+ }
459
+ }
460
+
461
+ // crontab.xml parsing
462
+ if (filePath.includes('crontab.xml')) {
463
+ const jobs = [...content.matchAll(XML_PATTERNS.crontab.job)];
464
+ if (jobs.length > 0) {
465
+ metadata.cronJobs = jobs.map(m => ({ name: m[1], instance: m[2] }));
466
+ metadata.configItems.push(...metadata.cronJobs.map(j => `cron:${j.name}`));
467
+ }
468
+ }
469
+
470
+ // acl.xml parsing
471
+ if (filePath.includes('acl.xml')) {
472
+ const resources = [...content.matchAll(XML_PATTERNS.acl.resource)];
473
+ if (resources.length > 0) {
474
+ metadata.aclResources = resources.map(m => m[1]);
475
+ metadata.configItems.push(...metadata.aclResources.map(r => `acl:${r}`));
476
+ }
477
+ }
478
+
479
+ return metadata;
480
+ }
481
+
482
+ /**
483
+ * Extract GraphQL schema metadata
484
+ */
485
+ export function extractGraphqlMetadata(content, filePath) {
486
+ const metadata = {
487
+ magentoType: 'graphql_schema',
488
+ types: [],
489
+ interfaces: [],
490
+ inputs: [],
491
+ enums: [],
492
+ queries: [],
493
+ mutations: [],
494
+ resolvers: []
495
+ };
496
+
497
+ const types = [...content.matchAll(GRAPHQL_PATTERNS.type)];
498
+ metadata.types = types.map(m => m[1]);
499
+
500
+ const interfaces = [...content.matchAll(GRAPHQL_PATTERNS.interface)];
501
+ metadata.interfaces = interfaces.map(m => m[1]);
502
+
503
+ const inputs = [...content.matchAll(GRAPHQL_PATTERNS.input)];
504
+ metadata.inputs = inputs.map(m => m[1]);
505
+
506
+ const enums = [...content.matchAll(GRAPHQL_PATTERNS.enum)];
507
+ metadata.enums = enums.map(m => m[1]);
508
+
509
+ const resolvers = [...content.matchAll(GRAPHQL_PATTERNS.resolver)];
510
+ metadata.resolvers = resolvers.map(m => m[1]);
511
+
512
+ // Extract queries from Query type
513
+ const queryBlock = content.match(/type\s+Query\s*\{([^}]+)\}/s);
514
+ if (queryBlock) {
515
+ const queries = [...queryBlock[1].matchAll(/(\w+)\s*\(/g)];
516
+ metadata.queries = queries.map(m => m[1]);
517
+ }
518
+
519
+ // Extract mutations from Mutation type
520
+ const mutationBlock = content.match(/type\s+Mutation\s*\{([^}]+)\}/s);
521
+ if (mutationBlock) {
522
+ const mutations = [...mutationBlock[1].matchAll(/(\w+)\s*\(/g)];
523
+ metadata.mutations = mutations.map(m => m[1]);
524
+ }
525
+
526
+ return metadata;
527
+ }
528
+
529
+ /**
530
+ * Get Magento area from file path
531
+ */
532
+ export function detectArea(filePath) {
533
+ if (filePath.includes('/adminhtml/')) return 'adminhtml';
534
+ if (filePath.includes('/frontend/')) return 'frontend';
535
+ if (filePath.includes('/webapi_rest/')) return 'webapi_rest';
536
+ if (filePath.includes('/webapi_soap/')) return 'webapi_soap';
537
+ if (filePath.includes('/graphql/')) return 'graphql';
538
+ if (filePath.includes('/crontab/')) return 'crontab';
539
+ return 'global';
540
+ }
541
+
542
+ /**
543
+ * Extract module vendor and name from path
544
+ */
545
+ export function extractModuleInfo(filePath) {
546
+ // app/code/Vendor/Module
547
+ const appMatch = filePath.match(/app\/code\/(\w+)\/(\w+)/);
548
+ if (appMatch) {
549
+ return { vendor: appMatch[1], module: appMatch[2], full: `${appMatch[1]}_${appMatch[2]}` };
550
+ }
551
+
552
+ // vendor/vendor/module-name
553
+ const vendorMatch = filePath.match(/vendor\/([\w-]+)\/(module-[\w-]+)/);
554
+ if (vendorMatch) {
555
+ const vendor = vendorMatch[1].replace(/-/g, '');
556
+ const module = vendorMatch[2].replace('module-', '').split('-').map(
557
+ s => s.charAt(0).toUpperCase() + s.slice(1)
558
+ ).join('');
559
+ return { vendor, module, full: `${vendor}_${module}` };
560
+ }
561
+
562
+ return null;
563
+ }