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.
- package/LICENSE +21 -0
- package/README.md +627 -0
- package/config/mcp-config.json +13 -0
- package/package.json +53 -0
- package/src/binary.js +66 -0
- package/src/cli.js +203 -0
- package/src/init.js +293 -0
- package/src/magento-patterns.js +563 -0
- package/src/mcp-server.js +915 -0
- package/src/model.js +127 -0
- package/src/templates/claude-md.js +47 -0
- package/src/templates/cursorrules.js +45 -0
- package/src/validation/accuracy-calculator.js +397 -0
- package/src/validation/benchmark.js +355 -0
- package/src/validation/test-data-generator.js +672 -0
- package/src/validation/test-queries.js +326 -0
- package/src/validation/validator.js +302 -0
|
@@ -0,0 +1,672 @@
|
|
|
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
|
+
}
|