magector 2.3.0 → 2.3.1
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 +31 -6
- package/package.json +5 -5
- package/src/mcp-server.js +6 -6
- package/src/validation/test-data-generator.js +0 -672
- package/src/validation/test-queries.js +0 -326
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Technology-aware MCP server for Magento 2 and Adobe Commerce with intelligent indexing and search.**
|
|
4
4
|
|
|
5
|
-
Magector is a Model Context Protocol (MCP) server that deeply understands Magento 2 and Adobe Commerce. It builds a semantic vector index of your entire codebase — 18,000+ files across hundreds of modules — and exposes
|
|
5
|
+
Magector is a Model Context Protocol (MCP) server that deeply understands Magento 2 and Adobe Commerce. It builds a semantic vector index of your entire codebase — 18,000+ files across hundreds of modules — and exposes 34 tools that let AI assistants search, navigate, and understand the code with domain-specific intelligence. Instead of grepping for keywords, your AI asks *"how are checkout totals calculated?"* and gets ranked, relevant results in under 50ms, enriched with Magento pattern detection (plugins, observers, controllers, DI preferences, layout XML, and 20+ more).
|
|
6
6
|
|
|
7
7
|
[](https://www.rust-lang.org)
|
|
8
8
|
[](https://nodejs.org)
|
|
@@ -58,7 +58,7 @@ The result: your AI assistant calls one MCP tool and gets ranked, pattern-enrich
|
|
|
58
58
|
- **Complexity analysis** -- cyclomatic complexity, function count, and hotspot detection across modules
|
|
59
59
|
- **Fast** -- 10-45ms queries via persistent serve process, batched ONNX embedding with adaptive thread scaling
|
|
60
60
|
- **LLM description enrichment** -- generate natural-language descriptions of di.xml files using Claude, stored in SQLite, and prepend them to embedding text so descriptions influence vector search ranking (not just post-retrieval display)
|
|
61
|
-
- **MCP server** --
|
|
61
|
+
- **MCP server** -- 34 tools integrating with Claude Code, Cursor, and any MCP-compatible AI tool
|
|
62
62
|
- **Clean architecture** -- Rust core handles all indexing/search, Node.js MCP server delegates to it
|
|
63
63
|
|
|
64
64
|
---
|
|
@@ -70,7 +70,7 @@ flowchart LR
|
|
|
70
70
|
subgraph node ["Node.js Layer"]
|
|
71
71
|
direction TB
|
|
72
72
|
G["CLI<br/>init · index · search · describe"]
|
|
73
|
-
E["MCP Server<br/>
|
|
73
|
+
E["MCP Server<br/>34 tools · LRU cache"]
|
|
74
74
|
F["Persistent Serve Process"]
|
|
75
75
|
G --> F
|
|
76
76
|
E --> F
|
|
@@ -369,7 +369,7 @@ npx magector index --force
|
|
|
369
369
|
|
|
370
370
|
## MCP Server Tools
|
|
371
371
|
|
|
372
|
-
The MCP server exposes
|
|
372
|
+
The MCP server exposes 34 tools for AI-assisted Magento 2 and Adobe Commerce development. All search tools return **structured JSON** with file paths, class names, methods, role badges, and content snippets -- enabling AI clients to parse results programmatically and minimize file-read round-trips.
|
|
373
373
|
|
|
374
374
|
### Output Format
|
|
375
375
|
|
|
@@ -432,7 +432,10 @@ All search tools return structured JSON:
|
|
|
432
432
|
| `magento_trace_flow` | Trace execution flow from an entry point (route, API, GraphQL, event, cron) -- maps controller → plugins → observers → templates in one call |
|
|
433
433
|
| `magento_trace_dependency` | Trace DI graph for a class/interface -- preferences, plugins, virtualTypes, argument overrides (parses all di.xml, no index needed) |
|
|
434
434
|
| `magento_find_event_flow` | Trace complete event chain: dispatchers → observers → handler PHP classes (parses events.xml + vector search) |
|
|
435
|
+
| `magento_find_event_dispatchers` | Find all PHP locations where a specific event is dispatched -- exact grep matching with method context and surrounding code **(v2.3)** |
|
|
435
436
|
| `magento_find_layout` | Find layout XML files by handle or content -- lists blocks, containers, and referenceBlock declarations |
|
|
437
|
+
| `magento_trace_data_flow` | Trace how a data attribute flows: find all setters (magic setter, setData, addData) and getters (magic getter, getData) across PHP and XML **(v2.3)** |
|
|
438
|
+
| `magento_trace_call_chain` | Trace internal method call chain: follows `$this->method()`, `$this->dep->method()`, and `dispatch()` calls to build an execution tree **(v2.2)** |
|
|
436
439
|
|
|
437
440
|
Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event, `camelCase` → GraphQL, `path/segments` → route), or override with `entryType`. Use `depth: "shallow"` (entry + config + plugins) or `depth: "deep"` (adds observers, layout, templates, DI preferences).
|
|
438
441
|
|
|
@@ -442,6 +445,9 @@ Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event,
|
|
|
442
445
|
|------|-------------|
|
|
443
446
|
| `magento_impact_analysis` | Analyze impact of changing a class -- finds use statements, DI references, direct instantiations, and type hints across the codebase |
|
|
444
447
|
| `magento_find_test` | Find PHPUnit tests for a given class/method -- searches Test/ directories for coverage, mocks, and assertions |
|
|
448
|
+
| `magento_find_implementors` | Find all classes implementing a given PHP interface -- scans `implements` keywords and di.xml `<preference>` declarations **(v2.2)** |
|
|
449
|
+
| `magento_find_callers` | Find all call sites of a method across PHP and XML files -- `->method()` and `::method()` calls **(v2.2)** |
|
|
450
|
+
| `magento_find_di_wiring` | Complete DI picture for a class: preferences, plugins, constructor args, virtual types, and argument overrides from di.xml **(v2.2)** |
|
|
445
451
|
|
|
446
452
|
### Diagnostics
|
|
447
453
|
|
|
@@ -470,9 +476,22 @@ Auto-detects entry type from pattern (`/V1/...` → API, `snake_case` → event,
|
|
|
470
476
|
|
|
471
477
|
- **Hybrid BM25+vector search** -- combines text frequency scoring with semantic vector similarity for better exact class name matches
|
|
472
478
|
- **Query expansion** -- automatically expands queries with Magento domain synonyms (plugin → interceptor, checkout → cart/quote/totals, etc.)
|
|
473
|
-
- **Module filtering** -- `moduleFilter` parameter on `magento_search` to limit results by vendor/module pattern
|
|
479
|
+
- **Module filtering** -- `moduleFilter` parameter on `magento_search` to limit results by vendor/module pattern. Accepts a single string or array of strings. Supports wildcards, e.g., `"Vendor_*"` or `["Acme_PaymentGateway", "Acme_FreeShipping"]`
|
|
474
480
|
- **Non-blocking reindex** -- old index stays usable during background rebuild; new index is built to a temp path and swapped in atomically on completion
|
|
475
481
|
|
|
482
|
+
### Deep Code Analysis (v2.2)
|
|
483
|
+
|
|
484
|
+
- **`magento_find_implementors`** -- find all classes implementing a PHP interface (PHP `implements` + di.xml `<preference>`)
|
|
485
|
+
- **`magento_find_callers`** -- find all call sites of a method across PHP and XML files
|
|
486
|
+
- **`magento_find_di_wiring`** -- complete DI picture: preferences, plugins, constructor args, virtual types, argument overrides
|
|
487
|
+
- **`magento_trace_call_chain`** -- trace internal method execution chain: `$this->method()`, `$this->dep->method()`, and `dispatch()` calls with event→observer resolution
|
|
488
|
+
|
|
489
|
+
### Data Flow & Event Tracing (v2.3)
|
|
490
|
+
|
|
491
|
+
- **`magento_trace_data_flow`** -- trace all setters and getters for a data attribute (magic methods, setData/getData, addData, constants, XML references). Answers "who writes/reads `custom_discounted_price_incl_tax` on `Quote\Address`?"
|
|
492
|
+
- **`magento_find_event_dispatchers`** -- grep-based exact search for all PHP locations dispatching a specific event, with method context and surrounding code. Complements `magento_find_event_flow` with higher precision.
|
|
493
|
+
- **`magento_find_plugin` area context** -- enriched output shows DI area (frontend/adminhtml/global/graphql) and explicit di.xml plugin registrations when `targetClass` is provided
|
|
494
|
+
|
|
476
495
|
### Tool Cross-References
|
|
477
496
|
|
|
478
497
|
Each tool description includes "See also" hints to help AI clients chain tools effectively:
|
|
@@ -549,6 +568,12 @@ magento_trace_flow({ entryPoint: "checkout/cart/add", depth: "deep" })
|
|
|
549
568
|
magento_trace_flow({ entryPoint: "/V1/products" })
|
|
550
569
|
magento_trace_flow({ entryPoint: "placeOrder", entryType: "graphql" })
|
|
551
570
|
magento_trace_flow({ entryPoint: "sales_order_place_after" })
|
|
571
|
+
magento_trace_data_flow({ attributeKey: "custom_discounted_price_incl_tax", modelClass: "Quote\\Address" })
|
|
572
|
+
magento_find_event_dispatchers({ eventName: "custom_discount_rule_validation_before" })
|
|
573
|
+
magento_find_implementors({ interfaceName: "ProductRepositoryInterface" })
|
|
574
|
+
magento_find_callers({ methodName: "collectTotals", className: "TotalsCollector" })
|
|
575
|
+
magento_find_di_wiring({ className: "CartManagementInterface" })
|
|
576
|
+
magento_trace_call_chain({ className: "Magento\\Quote\\Model\\QuoteManagement", methodName: "submit" })
|
|
552
577
|
```
|
|
553
578
|
|
|
554
579
|
---
|
|
@@ -629,7 +654,7 @@ cd rust-core && cargo run --release -- validate -m ./magento2 --skip-index
|
|
|
629
654
|
magector/
|
|
630
655
|
├── src/ # Node.js source
|
|
631
656
|
│ ├── cli.js # CLI entry point (npx magector <command>)
|
|
632
|
-
│ ├── mcp-server.js # MCP server (
|
|
657
|
+
│ ├── mcp-server.js # MCP server (34 tools, structured JSON output)
|
|
633
658
|
│ ├── binary.js # Platform binary resolver
|
|
634
659
|
│ ├── model.js # ONNX model resolver/downloader
|
|
635
660
|
│ ├── init.js # Full init command (index + IDE config)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Semantic code search for Magento 2 — index, search, MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp-server.js",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"ruvector": "^0.1.96"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@magector/cli-darwin-arm64": "2.3.
|
|
37
|
-
"@magector/cli-linux-x64": "2.3.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.3.
|
|
39
|
-
"@magector/cli-win32-x64": "2.3.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.3.1",
|
|
37
|
+
"@magector/cli-linux-x64": "2.3.1",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.3.1",
|
|
39
|
+
"@magector/cli-win32-x64": "2.3.1"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -2848,7 +2848,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2848
2848
|
{ type: 'string' },
|
|
2849
2849
|
{ type: 'array', items: { type: 'string' } }
|
|
2850
2850
|
],
|
|
2851
|
-
description: 'Filter results by vendor/module pattern(s). Accepts a single string or array of strings. Supports wildcards and vendor prefix matching. Uses "/" or "_" interchangeably as separator. Examples: "Vendor_*", ["
|
|
2851
|
+
description: 'Filter results by vendor/module pattern(s). Accepts a single string or array of strings. Supports wildcards and vendor prefix matching. Uses "/" or "_" interchangeably as separator. Examples: "Vendor_*", ["Acme_PaymentGateway", "Acme_FreeShipping"], "Magento_Catalog".'
|
|
2852
2852
|
},
|
|
2853
2853
|
expand: {
|
|
2854
2854
|
type: 'boolean',
|
|
@@ -3330,7 +3330,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3330
3330
|
properties: {
|
|
3331
3331
|
methodName: {
|
|
3332
3332
|
type: 'string',
|
|
3333
|
-
description: 'Method name to find callers for. Examples: "execute", "save", "collectTotals", "
|
|
3333
|
+
description: 'Method name to find callers for. Examples: "execute", "save", "collectTotals", "copySalesRuleIdsFromParentToChildQuote"'
|
|
3334
3334
|
},
|
|
3335
3335
|
className: {
|
|
3336
3336
|
type: 'string',
|
|
@@ -3362,7 +3362,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3362
3362
|
properties: {
|
|
3363
3363
|
className: {
|
|
3364
3364
|
type: 'string',
|
|
3365
|
-
description: 'Full PHP class name (FQCN) to start tracing from. Examples: "
|
|
3365
|
+
description: 'Full PHP class name (FQCN) to start tracing from. Examples: "Vendor\\OrderSplit\\Model\\CreateOrder\\CreateChildOrder", "Magento\\Quote\\Model\\QuoteManagement"'
|
|
3366
3366
|
},
|
|
3367
3367
|
methodName: {
|
|
3368
3368
|
type: 'string',
|
|
@@ -3379,13 +3379,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3379
3379
|
},
|
|
3380
3380
|
{
|
|
3381
3381
|
name: 'magento_trace_data_flow',
|
|
3382
|
-
description: 'Trace how a data attribute flows through the Magento codebase: find all PHP files that set (via magic setter, setData, addData) and get (via magic getter, getData) a specific attribute key. Shows which classes write vs read the attribute, in which methods, and whether XML configs reference it. Use this to understand data dependencies — e.g., who sets
|
|
3382
|
+
description: 'Trace how a data attribute flows through the Magento codebase: find all PHP files that set (via magic setter, setData, addData) and get (via magic getter, getData) a specific attribute key. Shows which classes write vs read the attribute, in which methods, and whether XML configs reference it. Use this to understand data dependencies — e.g., who sets custom_discounted_price_incl_tax on Quote\\Address and who reads it.',
|
|
3383
3383
|
inputSchema: {
|
|
3384
3384
|
type: 'object',
|
|
3385
3385
|
properties: {
|
|
3386
3386
|
attributeKey: {
|
|
3387
3387
|
type: 'string',
|
|
3388
|
-
description: 'The snake_case data attribute key to trace. Examples: "
|
|
3388
|
+
description: 'The snake_case data attribute key to trace. Examples: "custom_discounted_price_incl_tax", "base_grand_total", "custom_free_shipping_price", "subtotal_with_discount"'
|
|
3389
3389
|
},
|
|
3390
3390
|
modelClass: {
|
|
3391
3391
|
type: 'string',
|
|
@@ -3403,7 +3403,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3403
3403
|
properties: {
|
|
3404
3404
|
eventName: {
|
|
3405
3405
|
type: 'string',
|
|
3406
|
-
description: 'Magento event name to find dispatchers for. Examples: "sales_order_place_after", "
|
|
3406
|
+
description: 'Magento event name to find dispatchers for. Examples: "sales_order_place_after", "custom_discount_rule_validation_before", "checkout_cart_add_product_complete"'
|
|
3407
3407
|
}
|
|
3408
3408
|
},
|
|
3409
3409
|
required: ['eventName']
|
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates synthetic Magento code for validation testing
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const MOCK_MODULES = [
|
|
6
|
-
'Acme_Catalog',
|
|
7
|
-
'Acme_Checkout',
|
|
8
|
-
'Acme_Customer',
|
|
9
|
-
'Acme_Sales',
|
|
10
|
-
'Acme_Inventory'
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
export function generateMockController(moduleName, controllerName, action = 'execute') {
|
|
14
|
-
const [vendor, module] = moduleName.split('_');
|
|
15
|
-
return {
|
|
16
|
-
path: `app/code/${vendor}/${module}/Controller/${controllerName}.php`,
|
|
17
|
-
content: `<?php
|
|
18
|
-
declare(strict_types=1);
|
|
19
|
-
|
|
20
|
-
namespace ${vendor}\\${module}\\Controller;
|
|
21
|
-
|
|
22
|
-
use Magento\\Framework\\App\\Action\\HttpGetActionInterface;
|
|
23
|
-
use Magento\\Framework\\Controller\\ResultFactory;
|
|
24
|
-
use Magento\\Framework\\View\\Result\\Page;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* ${controllerName} controller action
|
|
28
|
-
*/
|
|
29
|
-
class ${controllerName} implements HttpGetActionInterface
|
|
30
|
-
{
|
|
31
|
-
private ResultFactory \$resultFactory;
|
|
32
|
-
|
|
33
|
-
public function __construct(
|
|
34
|
-
ResultFactory \$resultFactory
|
|
35
|
-
) {
|
|
36
|
-
\$this->resultFactory = \$resultFactory;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Execute action
|
|
41
|
-
* @return Page
|
|
42
|
-
*/
|
|
43
|
-
public function ${action}(): Page
|
|
44
|
-
{
|
|
45
|
-
/** @var Page \$page */
|
|
46
|
-
\$page = \$this->resultFactory->create(ResultFactory::TYPE_PAGE);
|
|
47
|
-
return \$page;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
`,
|
|
51
|
-
metadata: {
|
|
52
|
-
type: 'php',
|
|
53
|
-
magentoType: 'Controller',
|
|
54
|
-
module: moduleName,
|
|
55
|
-
className: controllerName,
|
|
56
|
-
methodName: action,
|
|
57
|
-
isController: true
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function generateMockModel(moduleName, modelName, tableName) {
|
|
63
|
-
const [vendor, module] = moduleName.split('_');
|
|
64
|
-
return {
|
|
65
|
-
path: `app/code/${vendor}/${module}/Model/${modelName}.php`,
|
|
66
|
-
content: `<?php
|
|
67
|
-
declare(strict_types=1);
|
|
68
|
-
|
|
69
|
-
namespace ${vendor}\\${module}\\Model;
|
|
70
|
-
|
|
71
|
-
use Magento\\Framework\\Model\\AbstractModel;
|
|
72
|
-
use ${vendor}\\${module}\\Model\\ResourceModel\\${modelName} as ResourceModel;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* ${modelName} model
|
|
76
|
-
*/
|
|
77
|
-
class ${modelName} extends AbstractModel
|
|
78
|
-
{
|
|
79
|
-
protected \$_eventPrefix = '${tableName}';
|
|
80
|
-
|
|
81
|
-
protected function _construct(): void
|
|
82
|
-
{
|
|
83
|
-
\$this->_init(ResourceModel::class);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
protected function _beforeSave(): AbstractModel
|
|
87
|
-
{
|
|
88
|
-
// Custom before save logic
|
|
89
|
-
return parent::_beforeSave();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
protected function _afterLoad(): AbstractModel
|
|
93
|
-
{
|
|
94
|
-
// Custom after load logic
|
|
95
|
-
return parent::_afterLoad();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public function getName(): ?string
|
|
99
|
-
{
|
|
100
|
-
return \$this->getData('name');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public function setName(string \$name): self
|
|
104
|
-
{
|
|
105
|
-
return \$this->setData('name', \$name);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
`,
|
|
109
|
-
metadata: {
|
|
110
|
-
type: 'php',
|
|
111
|
-
magentoType: 'Model',
|
|
112
|
-
module: moduleName,
|
|
113
|
-
className: modelName,
|
|
114
|
-
isModel: true,
|
|
115
|
-
tableName
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function generateMockRepository(moduleName, entityName) {
|
|
121
|
-
const [vendor, module] = moduleName.split('_');
|
|
122
|
-
return {
|
|
123
|
-
path: `app/code/${vendor}/${module}/Model/${entityName}Repository.php`,
|
|
124
|
-
content: `<?php
|
|
125
|
-
declare(strict_types=1);
|
|
126
|
-
|
|
127
|
-
namespace ${vendor}\\${module}\\Model;
|
|
128
|
-
|
|
129
|
-
use ${vendor}\\${module}\\Api\\${entityName}RepositoryInterface;
|
|
130
|
-
use ${vendor}\\${module}\\Api\\Data\\${entityName}Interface;
|
|
131
|
-
use ${vendor}\\${module}\\Model\\ResourceModel\\${entityName} as ResourceModel;
|
|
132
|
-
use Magento\\Framework\\Api\\SearchCriteriaInterface;
|
|
133
|
-
use Magento\\Framework\\Api\\SearchResultsInterface;
|
|
134
|
-
use Magento\\Framework\\Exception\\CouldNotSaveException;
|
|
135
|
-
use Magento\\Framework\\Exception\\NoSuchEntityException;
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* ${entityName} repository implementation
|
|
139
|
-
*/
|
|
140
|
-
class ${entityName}Repository implements ${entityName}RepositoryInterface
|
|
141
|
-
{
|
|
142
|
-
private ResourceModel \$resourceModel;
|
|
143
|
-
private ${entityName}Factory \$${entityName.toLowerCase()}Factory;
|
|
144
|
-
|
|
145
|
-
public function __construct(
|
|
146
|
-
ResourceModel \$resourceModel,
|
|
147
|
-
${entityName}Factory \$${entityName.toLowerCase()}Factory
|
|
148
|
-
) {
|
|
149
|
-
\$this->resourceModel = \$resourceModel;
|
|
150
|
-
\$this->${entityName.toLowerCase()}Factory = \$${entityName.toLowerCase()}Factory;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
public function getById(int \$id): ${entityName}Interface
|
|
154
|
-
{
|
|
155
|
-
\$entity = \$this->${entityName.toLowerCase()}Factory->create();
|
|
156
|
-
\$this->resourceModel->load(\$entity, \$id);
|
|
157
|
-
if (!\$entity->getId()) {
|
|
158
|
-
throw new NoSuchEntityException(__('Entity with id "%1" does not exist.', \$id));
|
|
159
|
-
}
|
|
160
|
-
return \$entity;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
public function save(${entityName}Interface \$entity): ${entityName}Interface
|
|
164
|
-
{
|
|
165
|
-
try {
|
|
166
|
-
\$this->resourceModel->save(\$entity);
|
|
167
|
-
} catch (\\Exception \$e) {
|
|
168
|
-
throw new CouldNotSaveException(__(\$e->getMessage()));
|
|
169
|
-
}
|
|
170
|
-
return \$entity;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
public function delete(${entityName}Interface \$entity): bool
|
|
174
|
-
{
|
|
175
|
-
\$this->resourceModel->delete(\$entity);
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
public function deleteById(int \$id): bool
|
|
180
|
-
{
|
|
181
|
-
return \$this->delete(\$this->getById(\$id));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public function getList(SearchCriteriaInterface \$searchCriteria): SearchResultsInterface
|
|
185
|
-
{
|
|
186
|
-
// Implementation
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
`,
|
|
190
|
-
metadata: {
|
|
191
|
-
type: 'php',
|
|
192
|
-
magentoType: 'Repository',
|
|
193
|
-
module: moduleName,
|
|
194
|
-
className: `${entityName}Repository`,
|
|
195
|
-
isRepository: true,
|
|
196
|
-
repositoryMethods: ['getById', 'save', 'delete', 'deleteById', 'getList']
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function generateMockPlugin(moduleName, targetClass, targetMethod) {
|
|
202
|
-
const [vendor, module] = moduleName.split('_');
|
|
203
|
-
const pluginName = `${targetClass}${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}Plugin`;
|
|
204
|
-
return {
|
|
205
|
-
path: `app/code/${vendor}/${module}/Plugin/${pluginName}.php`,
|
|
206
|
-
content: `<?php
|
|
207
|
-
declare(strict_types=1);
|
|
208
|
-
|
|
209
|
-
namespace ${vendor}\\${module}\\Plugin;
|
|
210
|
-
|
|
211
|
-
use ${targetClass.includes('\\') ? targetClass : `Magento\\Framework\\${targetClass}`};
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Plugin for ${targetClass}::${targetMethod}
|
|
215
|
-
*/
|
|
216
|
-
class ${pluginName}
|
|
217
|
-
{
|
|
218
|
-
/**
|
|
219
|
-
* Before ${targetMethod}
|
|
220
|
-
*/
|
|
221
|
-
public function before${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
|
|
222
|
-
${targetClass.split('\\').pop()} \$subject,
|
|
223
|
-
...\$args
|
|
224
|
-
): array {
|
|
225
|
-
// Modify arguments before method execution
|
|
226
|
-
return \$args;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* After ${targetMethod}
|
|
231
|
-
*/
|
|
232
|
-
public function after${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
|
|
233
|
-
${targetClass.split('\\').pop()} \$subject,
|
|
234
|
-
\$result
|
|
235
|
-
) {
|
|
236
|
-
// Modify result after method execution
|
|
237
|
-
return \$result;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Around ${targetMethod}
|
|
242
|
-
*/
|
|
243
|
-
public function around${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}(
|
|
244
|
-
${targetClass.split('\\').pop()} \$subject,
|
|
245
|
-
callable \$proceed,
|
|
246
|
-
...\$args
|
|
247
|
-
) {
|
|
248
|
-
// Execute before
|
|
249
|
-
\$result = \$proceed(...\$args);
|
|
250
|
-
// Execute after
|
|
251
|
-
return \$result;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
`,
|
|
255
|
-
metadata: {
|
|
256
|
-
type: 'php',
|
|
257
|
-
magentoType: 'Plugin',
|
|
258
|
-
module: moduleName,
|
|
259
|
-
className: pluginName,
|
|
260
|
-
isPlugin: true,
|
|
261
|
-
targetClass,
|
|
262
|
-
targetMethod,
|
|
263
|
-
pluginMethods: [
|
|
264
|
-
{ type: 'before', name: `before${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` },
|
|
265
|
-
{ type: 'after', name: `after${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` },
|
|
266
|
-
{ type: 'around', name: `around${targetMethod.charAt(0).toUpperCase() + targetMethod.slice(1)}` }
|
|
267
|
-
]
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function generateMockObserver(moduleName, eventName, observerName) {
|
|
273
|
-
const [vendor, module] = moduleName.split('_');
|
|
274
|
-
return {
|
|
275
|
-
path: `app/code/${vendor}/${module}/Observer/${observerName}.php`,
|
|
276
|
-
content: `<?php
|
|
277
|
-
declare(strict_types=1);
|
|
278
|
-
|
|
279
|
-
namespace ${vendor}\\${module}\\Observer;
|
|
280
|
-
|
|
281
|
-
use Magento\\Framework\\Event\\Observer;
|
|
282
|
-
use Magento\\Framework\\Event\\ObserverInterface;
|
|
283
|
-
use Psr\\Log\\LoggerInterface;
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Observer for ${eventName} event
|
|
287
|
-
*/
|
|
288
|
-
class ${observerName} implements ObserverInterface
|
|
289
|
-
{
|
|
290
|
-
private LoggerInterface \$logger;
|
|
291
|
-
|
|
292
|
-
public function __construct(LoggerInterface \$logger)
|
|
293
|
-
{
|
|
294
|
-
\$this->logger = \$logger;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Execute observer
|
|
299
|
-
* @param Observer \$observer
|
|
300
|
-
* @return void
|
|
301
|
-
*/
|
|
302
|
-
public function execute(Observer \$observer): void
|
|
303
|
-
{
|
|
304
|
-
\$event = \$observer->getEvent();
|
|
305
|
-
\$this->logger->info('Event ${eventName} triggered');
|
|
306
|
-
// Observer logic here
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
`,
|
|
310
|
-
metadata: {
|
|
311
|
-
type: 'php',
|
|
312
|
-
magentoType: 'Observer',
|
|
313
|
-
module: moduleName,
|
|
314
|
-
className: observerName,
|
|
315
|
-
isObserver: true,
|
|
316
|
-
eventName
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function generateMockDiXml(moduleName, configs) {
|
|
322
|
-
const [vendor, module] = moduleName.split('_');
|
|
323
|
-
let content = `<?xml version="1.0"?>
|
|
324
|
-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
325
|
-
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
|
326
|
-
`;
|
|
327
|
-
|
|
328
|
-
for (const config of configs) {
|
|
329
|
-
if (config.type === 'preference') {
|
|
330
|
-
content += ` <preference for="${config.for}" type="${config.implementation}" />\n`;
|
|
331
|
-
} else if (config.type === 'plugin') {
|
|
332
|
-
content += ` <type name="${config.target}">
|
|
333
|
-
<plugin name="${config.name}" type="${config.class}" sortOrder="${config.sortOrder || 10}" />
|
|
334
|
-
</type>\n`;
|
|
335
|
-
} else if (config.type === 'virtualType') {
|
|
336
|
-
content += ` <virtualType name="${config.name}" type="${config.extends}">
|
|
337
|
-
<arguments>
|
|
338
|
-
<argument name="${config.argName}" xsi:type="string">${config.argValue}</argument>
|
|
339
|
-
</arguments>
|
|
340
|
-
</virtualType>\n`;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
content += `</config>`;
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
path: `app/code/${vendor}/${module}/etc/di.xml`,
|
|
348
|
-
content,
|
|
349
|
-
metadata: {
|
|
350
|
-
type: 'xml',
|
|
351
|
-
magentoType: 'di.xml',
|
|
352
|
-
module: moduleName,
|
|
353
|
-
preferences: configs.filter(c => c.type === 'preference'),
|
|
354
|
-
plugins: configs.filter(c => c.type === 'plugin'),
|
|
355
|
-
virtualTypes: configs.filter(c => c.type === 'virtualType')
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
export function generateMockEventsXml(moduleName, events) {
|
|
361
|
-
const [vendor, module] = moduleName.split('_');
|
|
362
|
-
let content = `<?xml version="1.0"?>
|
|
363
|
-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
364
|
-
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
|
|
365
|
-
`;
|
|
366
|
-
|
|
367
|
-
for (const event of events) {
|
|
368
|
-
content += ` <event name="${event.name}">
|
|
369
|
-
<observer name="${event.observerName}" instance="${vendor}\\${module}\\Observer\\${event.observerClass}" />
|
|
370
|
-
</event>\n`;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
content += `</config>`;
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
path: `app/code/${vendor}/${module}/etc/events.xml`,
|
|
377
|
-
content,
|
|
378
|
-
metadata: {
|
|
379
|
-
type: 'xml',
|
|
380
|
-
magentoType: 'events.xml',
|
|
381
|
-
module: moduleName,
|
|
382
|
-
events: events.map(e => e.name),
|
|
383
|
-
observers: events.map(e => ({ name: e.observerName, instance: `${vendor}\\${module}\\Observer\\${e.observerClass}` }))
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export function generateMockWebApiXml(moduleName, routes) {
|
|
389
|
-
const [vendor, module] = moduleName.split('_');
|
|
390
|
-
let content = `<?xml version="1.0"?>
|
|
391
|
-
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
392
|
-
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
|
|
393
|
-
`;
|
|
394
|
-
|
|
395
|
-
for (const route of routes) {
|
|
396
|
-
content += ` <route url="${route.url}" method="${route.method}">
|
|
397
|
-
<service class="${vendor}\\${module}\\Api\\${route.serviceClass}" method="${route.serviceMethod}" />
|
|
398
|
-
<resources>
|
|
399
|
-
<resource ref="${route.resource || 'anonymous'}" />
|
|
400
|
-
</resources>
|
|
401
|
-
</route>\n`;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
content += `</routes>`;
|
|
405
|
-
|
|
406
|
-
return {
|
|
407
|
-
path: `app/code/${vendor}/${module}/etc/webapi.xml`,
|
|
408
|
-
content,
|
|
409
|
-
metadata: {
|
|
410
|
-
type: 'xml',
|
|
411
|
-
magentoType: 'webapi.xml',
|
|
412
|
-
module: moduleName,
|
|
413
|
-
apiRoutes: routes.map(r => ({ url: r.url, method: r.method })),
|
|
414
|
-
apiServices: routes.map(r => ({ class: `${vendor}\\${module}\\Api\\${r.serviceClass}`, method: r.serviceMethod }))
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export function generateMockGraphqlSchema(moduleName, types, queries, mutations) {
|
|
420
|
-
const [vendor, module] = moduleName.split('_');
|
|
421
|
-
let content = '';
|
|
422
|
-
|
|
423
|
-
for (const type of types) {
|
|
424
|
-
content += `type ${type.name} ${type.implements ? `implements ${type.implements}` : ''} {
|
|
425
|
-
${type.fields.map(f => ` ${f.name}: ${f.type}`).join('\n')}
|
|
426
|
-
}\n\n`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (queries.length > 0) {
|
|
430
|
-
content += `type Query {\n`;
|
|
431
|
-
for (const q of queries) {
|
|
432
|
-
content += ` ${q.name}(${q.args || ''}): ${q.returnType} @resolver(class: "${vendor}\\\\${module}\\\\Model\\\\Resolver\\\\${q.resolver}")\n`;
|
|
433
|
-
}
|
|
434
|
-
content += `}\n\n`;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (mutations.length > 0) {
|
|
438
|
-
content += `type Mutation {\n`;
|
|
439
|
-
for (const m of mutations) {
|
|
440
|
-
content += ` ${m.name}(${m.args || ''}): ${m.returnType} @resolver(class: "${vendor}\\\\${module}\\\\Model\\\\Resolver\\\\${m.resolver}")\n`;
|
|
441
|
-
}
|
|
442
|
-
content += `}\n`;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
path: `app/code/${vendor}/${module}/etc/schema.graphqls`,
|
|
447
|
-
content,
|
|
448
|
-
metadata: {
|
|
449
|
-
type: 'graphql',
|
|
450
|
-
magentoType: 'graphql_schema',
|
|
451
|
-
module: moduleName,
|
|
452
|
-
types: types.map(t => t.name),
|
|
453
|
-
queries: queries.map(q => q.name),
|
|
454
|
-
mutations: mutations.map(m => m.name),
|
|
455
|
-
resolvers: [...queries, ...mutations].map(x => x.resolver)
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function generateMockResolver(moduleName, resolverName, queryName) {
|
|
461
|
-
const [vendor, module] = moduleName.split('_');
|
|
462
|
-
return {
|
|
463
|
-
path: `app/code/${vendor}/${module}/Model/Resolver/${resolverName}.php`,
|
|
464
|
-
content: `<?php
|
|
465
|
-
declare(strict_types=1);
|
|
466
|
-
|
|
467
|
-
namespace ${vendor}\\${module}\\Model\\Resolver;
|
|
468
|
-
|
|
469
|
-
use Magento\\Framework\\GraphQl\\Config\\Element\\Field;
|
|
470
|
-
use Magento\\Framework\\GraphQl\\Query\\ResolverInterface;
|
|
471
|
-
use Magento\\Framework\\GraphQl\\Schema\\Type\\ResolveInfo;
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* GraphQL resolver for ${queryName}
|
|
475
|
-
*/
|
|
476
|
-
class ${resolverName} implements ResolverInterface
|
|
477
|
-
{
|
|
478
|
-
/**
|
|
479
|
-
* @inheritdoc
|
|
480
|
-
*/
|
|
481
|
-
public function resolve(
|
|
482
|
-
Field \$field,
|
|
483
|
-
\$context,
|
|
484
|
-
ResolveInfo \$info,
|
|
485
|
-
array \$value = null,
|
|
486
|
-
array \$args = null
|
|
487
|
-
) {
|
|
488
|
-
// Resolver implementation
|
|
489
|
-
return [];
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
`,
|
|
493
|
-
metadata: {
|
|
494
|
-
type: 'php',
|
|
495
|
-
magentoType: 'GraphQlResolver',
|
|
496
|
-
module: moduleName,
|
|
497
|
-
className: resolverName,
|
|
498
|
-
isResolver: true,
|
|
499
|
-
queryName
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export function generateMockBlock(moduleName, blockName) {
|
|
505
|
-
const [vendor, module] = moduleName.split('_');
|
|
506
|
-
return {
|
|
507
|
-
path: `app/code/${vendor}/${module}/Block/${blockName}.php`,
|
|
508
|
-
content: `<?php
|
|
509
|
-
declare(strict_types=1);
|
|
510
|
-
|
|
511
|
-
namespace ${vendor}\\${module}\\Block;
|
|
512
|
-
|
|
513
|
-
use Magento\\Framework\\View\\Element\\Template;
|
|
514
|
-
use Magento\\Framework\\View\\Element\\Template\\Context;
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* ${blockName} block
|
|
518
|
-
*/
|
|
519
|
-
class ${blockName} extends Template
|
|
520
|
-
{
|
|
521
|
-
protected \$_template = '${vendor}_${module}::${blockName.toLowerCase()}.phtml';
|
|
522
|
-
|
|
523
|
-
public function __construct(Context \$context, array \$data = [])
|
|
524
|
-
{
|
|
525
|
-
parent::__construct(\$context, \$data);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
protected function _prepareLayout(): self
|
|
529
|
-
{
|
|
530
|
-
parent::_prepareLayout();
|
|
531
|
-
return \$this;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
protected function _toHtml(): string
|
|
535
|
-
{
|
|
536
|
-
return parent::_toHtml();
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
public function getItems(): array
|
|
540
|
-
{
|
|
541
|
-
return [];
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
`,
|
|
545
|
-
metadata: {
|
|
546
|
-
type: 'php',
|
|
547
|
-
magentoType: 'Block',
|
|
548
|
-
module: moduleName,
|
|
549
|
-
className: blockName,
|
|
550
|
-
isBlock: true
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
export function generateMockCronJob(moduleName, cronName, schedule = '0 * * * *') {
|
|
556
|
-
const [vendor, module] = moduleName.split('_');
|
|
557
|
-
return {
|
|
558
|
-
php: {
|
|
559
|
-
path: `app/code/${vendor}/${module}/Cron/${cronName}.php`,
|
|
560
|
-
content: `<?php
|
|
561
|
-
declare(strict_types=1);
|
|
562
|
-
|
|
563
|
-
namespace ${vendor}\\${module}\\Cron;
|
|
564
|
-
|
|
565
|
-
use Psr\\Log\\LoggerInterface;
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* ${cronName} cron job
|
|
569
|
-
*/
|
|
570
|
-
class ${cronName}
|
|
571
|
-
{
|
|
572
|
-
private LoggerInterface \$logger;
|
|
573
|
-
|
|
574
|
-
public function __construct(LoggerInterface \$logger)
|
|
575
|
-
{
|
|
576
|
-
\$this->logger = \$logger;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
public function execute(): void
|
|
580
|
-
{
|
|
581
|
-
\$this->logger->info('Cron job ${cronName} executed');
|
|
582
|
-
// Cron logic here
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
`,
|
|
586
|
-
metadata: {
|
|
587
|
-
type: 'php',
|
|
588
|
-
magentoType: 'Cron',
|
|
589
|
-
module: moduleName,
|
|
590
|
-
className: cronName,
|
|
591
|
-
isCron: true
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
xml: {
|
|
595
|
-
path: `app/code/${vendor}/${module}/etc/crontab.xml`,
|
|
596
|
-
content: `<?xml version="1.0"?>
|
|
597
|
-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
598
|
-
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
|
|
599
|
-
<group id="default">
|
|
600
|
-
<job name="${vendor.toLowerCase()}_${module.toLowerCase()}_${cronName.toLowerCase()}"
|
|
601
|
-
instance="${vendor}\\${module}\\Cron\\${cronName}"
|
|
602
|
-
method="execute">
|
|
603
|
-
<schedule>${schedule}</schedule>
|
|
604
|
-
</job>
|
|
605
|
-
</group>
|
|
606
|
-
</config>`,
|
|
607
|
-
metadata: {
|
|
608
|
-
type: 'xml',
|
|
609
|
-
magentoType: 'crontab.xml',
|
|
610
|
-
module: moduleName,
|
|
611
|
-
cronJobs: [{ name: `${vendor.toLowerCase()}_${module.toLowerCase()}_${cronName.toLowerCase()}`, instance: `${vendor}\\${module}\\Cron\\${cronName}` }]
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Generate a complete mock module with all components
|
|
619
|
-
*/
|
|
620
|
-
export function generateCompleteMockModule(moduleName) {
|
|
621
|
-
const [vendor, module] = moduleName.split('_');
|
|
622
|
-
const files = [];
|
|
623
|
-
|
|
624
|
-
// Controllers
|
|
625
|
-
files.push(generateMockController(moduleName, 'Index', 'execute'));
|
|
626
|
-
files.push(generateMockController(moduleName, 'View', 'execute'));
|
|
627
|
-
|
|
628
|
-
// Models
|
|
629
|
-
files.push(generateMockModel(moduleName, 'Item', `${vendor.toLowerCase()}_${module.toLowerCase()}_item`));
|
|
630
|
-
files.push(generateMockRepository(moduleName, 'Item'));
|
|
631
|
-
|
|
632
|
-
// Plugin
|
|
633
|
-
files.push(generateMockPlugin(moduleName, 'Magento\\Catalog\\Model\\Product', 'getPrice'));
|
|
634
|
-
|
|
635
|
-
// Observer
|
|
636
|
-
files.push(generateMockObserver(moduleName, 'catalog_product_save_after', 'ProductSaveObserver'));
|
|
637
|
-
|
|
638
|
-
// Block
|
|
639
|
-
files.push(generateMockBlock(moduleName, 'ItemList'));
|
|
640
|
-
|
|
641
|
-
// DI config
|
|
642
|
-
files.push(generateMockDiXml(moduleName, [
|
|
643
|
-
{ type: 'preference', for: `${vendor}\\${module}\\Api\\ItemRepositoryInterface`, implementation: `${vendor}\\${module}\\Model\\ItemRepository` },
|
|
644
|
-
{ type: 'plugin', target: 'Magento\\Catalog\\Model\\Product', name: `${vendor.toLowerCase()}_${module.toLowerCase()}_product_price`, class: `${vendor}\\${module}\\Plugin\\ProductGetPricePlugin` }
|
|
645
|
-
]));
|
|
646
|
-
|
|
647
|
-
// Events
|
|
648
|
-
files.push(generateMockEventsXml(moduleName, [
|
|
649
|
-
{ name: 'catalog_product_save_after', observerName: `${vendor.toLowerCase()}_product_save`, observerClass: 'ProductSaveObserver' }
|
|
650
|
-
]));
|
|
651
|
-
|
|
652
|
-
// Web API
|
|
653
|
-
files.push(generateMockWebApiXml(moduleName, [
|
|
654
|
-
{ url: `/V1/${module.toLowerCase()}/items/:id`, method: 'GET', serviceClass: 'ItemRepositoryInterface', serviceMethod: 'getById' },
|
|
655
|
-
{ url: `/V1/${module.toLowerCase()}/items`, method: 'POST', serviceClass: 'ItemRepositoryInterface', serviceMethod: 'save' }
|
|
656
|
-
]));
|
|
657
|
-
|
|
658
|
-
// GraphQL
|
|
659
|
-
files.push(generateMockGraphqlSchema(moduleName,
|
|
660
|
-
[{ name: `${module}Item`, fields: [{ name: 'id', type: 'Int!' }, { name: 'name', type: 'String' }] }],
|
|
661
|
-
[{ name: `${module.toLowerCase()}Item`, args: 'id: Int!', returnType: `${module}Item`, resolver: 'ItemResolver' }],
|
|
662
|
-
[{ name: `create${module}Item`, args: 'input: CreateItemInput!', returnType: `${module}Item`, resolver: 'CreateItemResolver' }]
|
|
663
|
-
));
|
|
664
|
-
files.push(generateMockResolver(moduleName, 'ItemResolver', `${module.toLowerCase()}Item`));
|
|
665
|
-
|
|
666
|
-
// Cron
|
|
667
|
-
const cronJob = generateMockCronJob(moduleName, 'CleanupJob');
|
|
668
|
-
files.push(cronJob.php);
|
|
669
|
-
files.push(cronJob.xml);
|
|
670
|
-
|
|
671
|
-
return files;
|
|
672
|
-
}
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test queries with expected results for accuracy validation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const TEST_QUERIES = [
|
|
6
|
-
// Controller queries
|
|
7
|
-
{
|
|
8
|
-
id: 'ctrl-1',
|
|
9
|
-
query: 'controller execute action',
|
|
10
|
-
type: 'semantic',
|
|
11
|
-
expectedTypes: ['Controller'],
|
|
12
|
-
expectedPatterns: ['controller'],
|
|
13
|
-
minResults: 1,
|
|
14
|
-
category: 'controller'
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: 'ctrl-2',
|
|
18
|
-
query: 'Index controller',
|
|
19
|
-
type: 'exact',
|
|
20
|
-
expectedClasses: ['Index'],
|
|
21
|
-
expectedTypes: ['Controller'],
|
|
22
|
-
category: 'controller'
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
// Model queries
|
|
26
|
-
{
|
|
27
|
-
id: 'model-1',
|
|
28
|
-
query: 'model beforeSave afterLoad',
|
|
29
|
-
type: 'semantic',
|
|
30
|
-
expectedTypes: ['Model'],
|
|
31
|
-
expectedPatterns: ['model'],
|
|
32
|
-
category: 'model'
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 'model-2',
|
|
36
|
-
query: 'AbstractModel extends',
|
|
37
|
-
type: 'semantic',
|
|
38
|
-
expectedInContent: ['extends AbstractModel'],
|
|
39
|
-
category: 'model'
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
// Repository queries
|
|
43
|
-
{
|
|
44
|
-
id: 'repo-1',
|
|
45
|
-
query: 'repository getById save delete',
|
|
46
|
-
type: 'semantic',
|
|
47
|
-
expectedTypes: ['Repository'],
|
|
48
|
-
expectedPatterns: ['repository'],
|
|
49
|
-
category: 'repository'
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: 'repo-2',
|
|
53
|
-
query: 'ItemRepository',
|
|
54
|
-
type: 'exact',
|
|
55
|
-
expectedClasses: ['ItemRepository'],
|
|
56
|
-
category: 'repository'
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: 'repo-3',
|
|
60
|
-
query: 'getList SearchCriteria',
|
|
61
|
-
type: 'semantic',
|
|
62
|
-
expectedInContent: ['getList', 'SearchCriteria'],
|
|
63
|
-
category: 'repository'
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
// Plugin queries
|
|
67
|
-
{
|
|
68
|
-
id: 'plugin-1',
|
|
69
|
-
query: 'plugin interceptor before after around',
|
|
70
|
-
type: 'semantic',
|
|
71
|
-
expectedTypes: ['Plugin'],
|
|
72
|
-
expectedPatterns: ['plugin'],
|
|
73
|
-
category: 'plugin'
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'plugin-2',
|
|
77
|
-
query: 'beforeGetPrice plugin',
|
|
78
|
-
type: 'semantic',
|
|
79
|
-
expectedInContent: ['beforeGetPrice', 'before'],
|
|
80
|
-
category: 'plugin'
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 'plugin-3',
|
|
84
|
-
query: 'around method interceptor',
|
|
85
|
-
type: 'semantic',
|
|
86
|
-
expectedInContent: ['around', 'proceed'],
|
|
87
|
-
category: 'plugin'
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
// Observer queries
|
|
91
|
-
{
|
|
92
|
-
id: 'obs-1',
|
|
93
|
-
query: 'observer execute event',
|
|
94
|
-
type: 'semantic',
|
|
95
|
-
expectedTypes: ['Observer'],
|
|
96
|
-
expectedPatterns: ['observer'],
|
|
97
|
-
category: 'observer'
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
id: 'obs-2',
|
|
101
|
-
query: 'catalog_product_save_after observer',
|
|
102
|
-
type: 'semantic',
|
|
103
|
-
expectedInContent: ['catalog_product_save_after'],
|
|
104
|
-
category: 'observer'
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'obs-3',
|
|
108
|
-
query: 'ObserverInterface implement',
|
|
109
|
-
type: 'semantic',
|
|
110
|
-
expectedInContent: ['ObserverInterface'],
|
|
111
|
-
category: 'observer'
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
// Block queries
|
|
115
|
-
{
|
|
116
|
-
id: 'block-1',
|
|
117
|
-
query: 'block template phtml',
|
|
118
|
-
type: 'semantic',
|
|
119
|
-
expectedTypes: ['Block'],
|
|
120
|
-
expectedPatterns: ['block'],
|
|
121
|
-
category: 'block'
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
id: 'block-2',
|
|
125
|
-
query: '_toHtml _prepareLayout',
|
|
126
|
-
type: 'semantic',
|
|
127
|
-
expectedInContent: ['_toHtml', '_prepareLayout'],
|
|
128
|
-
category: 'block'
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
// DI.xml queries
|
|
132
|
-
{
|
|
133
|
-
id: 'di-1',
|
|
134
|
-
query: 'preference interface implementation',
|
|
135
|
-
type: 'semantic',
|
|
136
|
-
expectedFileTypes: ['xml'],
|
|
137
|
-
expectedInContent: ['preference'],
|
|
138
|
-
category: 'di'
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
id: 'di-2',
|
|
142
|
-
query: 'plugin configuration di.xml',
|
|
143
|
-
type: 'semantic',
|
|
144
|
-
expectedFileTypes: ['xml'],
|
|
145
|
-
expectedInContent: ['plugin'],
|
|
146
|
-
category: 'di'
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
id: 'di-3',
|
|
150
|
-
query: 'virtualType argument',
|
|
151
|
-
type: 'semantic',
|
|
152
|
-
expectedInContent: ['virtualType'],
|
|
153
|
-
category: 'di'
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
// Events.xml queries
|
|
157
|
-
{
|
|
158
|
-
id: 'event-1',
|
|
159
|
-
query: 'event observer configuration',
|
|
160
|
-
type: 'semantic',
|
|
161
|
-
expectedFileTypes: ['xml'],
|
|
162
|
-
expectedInContent: ['event', 'observer'],
|
|
163
|
-
category: 'events'
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
id: 'event-2',
|
|
167
|
-
query: 'catalog_product_save_after event config',
|
|
168
|
-
type: 'exact',
|
|
169
|
-
expectedInContent: ['catalog_product_save_after'],
|
|
170
|
-
category: 'events'
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
// Web API queries
|
|
174
|
-
{
|
|
175
|
-
id: 'api-1',
|
|
176
|
-
query: 'REST API endpoint route',
|
|
177
|
-
type: 'semantic',
|
|
178
|
-
expectedFileTypes: ['xml'],
|
|
179
|
-
expectedInContent: ['route', 'service'],
|
|
180
|
-
category: 'webapi'
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
id: 'api-2',
|
|
184
|
-
query: 'GET /V1/items',
|
|
185
|
-
type: 'semantic',
|
|
186
|
-
expectedInContent: ['GET', '/V1/'],
|
|
187
|
-
category: 'webapi'
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
// GraphQL queries
|
|
191
|
-
{
|
|
192
|
-
id: 'gql-1',
|
|
193
|
-
query: 'GraphQL resolver',
|
|
194
|
-
type: 'semantic',
|
|
195
|
-
expectedTypes: ['GraphQlResolver'],
|
|
196
|
-
expectedPatterns: ['graphql_resolver'],
|
|
197
|
-
category: 'graphql'
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: 'gql-2',
|
|
201
|
-
query: 'type Query mutation',
|
|
202
|
-
type: 'semantic',
|
|
203
|
-
expectedFileTypes: ['graphql'],
|
|
204
|
-
expectedInContent: ['type', 'Query'],
|
|
205
|
-
category: 'graphql'
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
id: 'gql-3',
|
|
209
|
-
query: 'ResolverInterface resolve Field',
|
|
210
|
-
type: 'semantic',
|
|
211
|
-
expectedInContent: ['ResolverInterface', 'resolve'],
|
|
212
|
-
category: 'graphql'
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
// Cron queries
|
|
216
|
-
{
|
|
217
|
-
id: 'cron-1',
|
|
218
|
-
query: 'cron job schedule execute',
|
|
219
|
-
type: 'semantic',
|
|
220
|
-
expectedTypes: ['Cron'],
|
|
221
|
-
category: 'cron'
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
id: 'cron-2',
|
|
225
|
-
query: 'crontab.xml job instance',
|
|
226
|
-
type: 'semantic',
|
|
227
|
-
expectedFileTypes: ['xml'],
|
|
228
|
-
expectedInContent: ['job', 'instance'],
|
|
229
|
-
category: 'cron'
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
// Cross-cutting queries
|
|
233
|
-
{
|
|
234
|
-
id: 'cross-1',
|
|
235
|
-
query: 'dependency injection constructor',
|
|
236
|
-
type: 'semantic',
|
|
237
|
-
expectedInContent: ['__construct'],
|
|
238
|
-
minResults: 3,
|
|
239
|
-
category: 'di_pattern'
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
id: 'cross-2',
|
|
243
|
-
query: 'LoggerInterface logging',
|
|
244
|
-
type: 'semantic',
|
|
245
|
-
expectedInContent: ['LoggerInterface', 'logger'],
|
|
246
|
-
category: 'logging'
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
id: 'cross-3',
|
|
250
|
-
query: 'exception handling try catch',
|
|
251
|
-
type: 'semantic',
|
|
252
|
-
expectedInContent: ['Exception', 'throw'],
|
|
253
|
-
category: 'error_handling'
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
// Module-specific queries
|
|
257
|
-
{
|
|
258
|
-
id: 'mod-1',
|
|
259
|
-
query: 'Acme_Catalog module',
|
|
260
|
-
type: 'module',
|
|
261
|
-
expectedModule: 'Acme_Catalog',
|
|
262
|
-
minResults: 3,
|
|
263
|
-
category: 'module'
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
// Method-specific queries
|
|
267
|
-
{
|
|
268
|
-
id: 'method-1',
|
|
269
|
-
query: 'function getById',
|
|
270
|
-
type: 'method',
|
|
271
|
-
expectedMethods: ['getById'],
|
|
272
|
-
category: 'method'
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
id: 'method-2',
|
|
276
|
-
query: 'save method repository',
|
|
277
|
-
type: 'method',
|
|
278
|
-
expectedMethods: ['save'],
|
|
279
|
-
category: 'method'
|
|
280
|
-
}
|
|
281
|
-
];
|
|
282
|
-
|
|
283
|
-
export const QUERY_CATEGORIES = {
|
|
284
|
-
controller: { weight: 1.0, description: 'Controller action detection' },
|
|
285
|
-
model: { weight: 1.0, description: 'Model and lifecycle hooks' },
|
|
286
|
-
repository: { weight: 1.2, description: 'Repository pattern detection' },
|
|
287
|
-
plugin: { weight: 1.3, description: 'Plugin/interceptor detection' },
|
|
288
|
-
observer: { weight: 1.2, description: 'Observer pattern detection' },
|
|
289
|
-
block: { weight: 1.0, description: 'Block class detection' },
|
|
290
|
-
di: { weight: 1.2, description: 'DI configuration detection' },
|
|
291
|
-
events: { weight: 1.1, description: 'Event configuration detection' },
|
|
292
|
-
webapi: { weight: 1.2, description: 'Web API route detection' },
|
|
293
|
-
graphql: { weight: 1.3, description: 'GraphQL schema/resolver detection' },
|
|
294
|
-
cron: { weight: 1.0, description: 'Cron job detection' },
|
|
295
|
-
di_pattern: { weight: 0.8, description: 'DI pattern recognition' },
|
|
296
|
-
logging: { weight: 0.7, description: 'Logging pattern recognition' },
|
|
297
|
-
error_handling: { weight: 0.7, description: 'Error handling patterns' },
|
|
298
|
-
module: { weight: 1.0, description: 'Module filtering' },
|
|
299
|
-
method: { weight: 1.0, description: 'Method search' }
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Generate edge case queries for stress testing
|
|
304
|
-
*/
|
|
305
|
-
export function generateEdgeCaseQueries() {
|
|
306
|
-
return [
|
|
307
|
-
// Very short queries
|
|
308
|
-
{ id: 'edge-1', query: 'save', type: 'short', category: 'edge_short', minResults: 1, expectedMethods: ['save'] },
|
|
309
|
-
{ id: 'edge-2', query: 'get', type: 'short', category: 'edge_short', minResults: 1 },
|
|
310
|
-
|
|
311
|
-
// Very long queries
|
|
312
|
-
{ id: 'edge-3', query: 'public function execute action controller', type: 'long', category: 'edge_long', minResults: 1, expectedPatterns: ['controller'] },
|
|
313
|
-
|
|
314
|
-
// Technical jargon
|
|
315
|
-
{ id: 'edge-4', query: 'CRUD operations repository interface', type: 'jargon', category: 'edge_jargon', minResults: 1, expectedTypes: ['Repository'] },
|
|
316
|
-
|
|
317
|
-
// Magento-specific terms - lower expectations
|
|
318
|
-
{ id: 'edge-5', query: 'service contract API', type: 'magento_specific', category: 'edge_magento', minResults: 1 },
|
|
319
|
-
|
|
320
|
-
// Negative queries (should return few/no results)
|
|
321
|
-
{ id: 'edge-6', query: 'wordpress drupal laravel', type: 'negative', category: 'edge_negative', maxResults: 2 },
|
|
322
|
-
|
|
323
|
-
// Mixed case
|
|
324
|
-
{ id: 'edge-7', query: 'REPOSITORY getbyid SAVE', type: 'case', category: 'edge_case', minResults: 1, expectedTypes: ['Repository'] }
|
|
325
|
-
];
|
|
326
|
-
}
|