@maxisoft/instantcms-mcp 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/__tests__/artifact-tool.test.js +196 -0
  4. package/dist/__tests__/db-tool.test.js +152 -0
  5. package/dist/__tests__/define-tool.test.js +106 -0
  6. package/dist/__tests__/find-tool.test.js +185 -0
  7. package/dist/__tests__/get-component-api.test.js +144 -0
  8. package/dist/__tests__/hardening.test.js +73 -0
  9. package/dist/__tests__/hooks-tool.test.js +173 -0
  10. package/dist/__tests__/mcp-integration-extra.test.js +166 -0
  11. package/dist/__tests__/mcp-integration.test.js +53 -0
  12. package/dist/__tests__/pagination.test.js +114 -0
  13. package/dist/__tests__/performance-baseline.test.js +56 -0
  14. package/dist/__tests__/project-patch.test.js +144 -0
  15. package/dist/__tests__/project-source.test.js +44 -0
  16. package/dist/__tests__/project-workflows-extra.test.js +165 -0
  17. package/dist/__tests__/project-workflows.test.js +51 -0
  18. package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
  19. package/dist/__tests__/serialization.test.js +170 -0
  20. package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
  21. package/dist/__tests__/template-development.test.js +78 -0
  22. package/dist/__tests__/template-productivity.test.js +87 -0
  23. package/dist/__tests__/tools.test.js +2143 -0
  24. package/dist/data/components.js +2818 -0
  25. package/dist/data/controllers-map.js +7571 -0
  26. package/dist/data/core-api.js +9356 -0
  27. package/dist/data/database-schema.js +4555 -0
  28. package/dist/data/events-map.js +1339 -0
  29. package/dist/data/fields-map.js +3692 -0
  30. package/dist/data/hooks.js +2257 -0
  31. package/dist/data/js-api.js +123 -0
  32. package/dist/data/libs-api.js +264 -0
  33. package/dist/data/routes-map.js +203 -0
  34. package/dist/data/schemas-validation.js +255 -0
  35. package/dist/data/schemas.js +1404 -0
  36. package/dist/data/traits-map.js +1004 -0
  37. package/dist/data/version-profiles.js +41 -0
  38. package/dist/data/widgets-map.js +131 -0
  39. package/dist/data/wysiwyg-map.js +486 -0
  40. package/dist/generated/components-source.js +14748 -0
  41. package/dist/generated/hooks-source.js +3399 -0
  42. package/dist/generated/knowledge-meta.js +101 -0
  43. package/dist/index.js +13 -0
  44. package/dist/registry/database-tools.js +112 -0
  45. package/dist/registry/extension-tools.js +473 -0
  46. package/dist/registry/generator-tools.js +506 -0
  47. package/dist/registry/knowledge-tools.js +441 -0
  48. package/dist/registry/language-tools.js +99 -0
  49. package/dist/registry/meta-tools.js +152 -0
  50. package/dist/registry/project-tools.js +48 -0
  51. package/dist/registry/resources.js +98 -0
  52. package/dist/registry/source-tools.js +211 -0
  53. package/dist/registry/template-development-tools.js +79 -0
  54. package/dist/server.js +32 -0
  55. package/dist/tools/addon-tool.js +1437 -0
  56. package/dist/tools/admin-partial-tool.js +498 -0
  57. package/dist/tools/api-tool.js +294 -0
  58. package/dist/tools/artifact-tool.js +95 -0
  59. package/dist/tools/cache-tool.js +360 -0
  60. package/dist/tools/component-tool.js +415 -0
  61. package/dist/tools/controllers-tool.js +125 -0
  62. package/dist/tools/cron-tool.js +197 -0
  63. package/dist/tools/crud-tool.js +659 -0
  64. package/dist/tools/db-tool.js +198 -0
  65. package/dist/tools/email-tool.js +222 -0
  66. package/dist/tools/external-api-tool.js +596 -0
  67. package/dist/tools/filter-tool.js +341 -0
  68. package/dist/tools/form-tool.js +275 -0
  69. package/dist/tools/grid-tool.js +189 -0
  70. package/dist/tools/hooks-tool.js +104 -0
  71. package/dist/tools/import-export-tool.js +548 -0
  72. package/dist/tools/lang-tool.js +251 -0
  73. package/dist/tools/layout-override-tool.js +125 -0
  74. package/dist/tools/layout-tool.js +548 -0
  75. package/dist/tools/maria-tool.js +127 -0
  76. package/dist/tools/mariadb.js +201 -0
  77. package/dist/tools/migration-tool.js +329 -0
  78. package/dist/tools/oauth-tool.js +520 -0
  79. package/dist/tools/parser/components-parser.js +76 -0
  80. package/dist/tools/parser/controllers-parser.js +313 -0
  81. package/dist/tools/parser/core-parser.js +294 -0
  82. package/dist/tools/parser/coverage-generator.js +424 -0
  83. package/dist/tools/parser/events-parser.js +127 -0
  84. package/dist/tools/parser/fields-parser.js +382 -0
  85. package/dist/tools/parser/hooks-parser.js +106 -0
  86. package/dist/tools/parser/sql-parser.js +197 -0
  87. package/dist/tools/parser/traits-parser.js +161 -0
  88. package/dist/tools/parser/widgets-parser.js +150 -0
  89. package/dist/tools/permission-tool.js +348 -0
  90. package/dist/tools/project-patch-tool.js +73 -0
  91. package/dist/tools/project-source-tool.js +188 -0
  92. package/dist/tools/project-workflow-tool.js +186 -0
  93. package/dist/tools/requirement-tool.js +212 -0
  94. package/dist/tools/scaffold-tool.js +815 -0
  95. package/dist/tools/seo-tool.js +412 -0
  96. package/dist/tools/source-tool.js +155 -0
  97. package/dist/tools/template-development-tool.js +271 -0
  98. package/dist/tools/template-overrides-tool.js +294 -0
  99. package/dist/tools/template-productivity-tool.js +319 -0
  100. package/dist/tools/template-tool.js +665 -0
  101. package/dist/tools/test-tool.js +183 -0
  102. package/dist/tools/webhook-tool.js +427 -0
  103. package/dist/tools/widget-tool.js +331 -0
  104. package/dist/tools/wysiwyg-tool.js +137 -0
  105. package/dist/types/scaffold.js +68 -0
  106. package/dist/utils/define-tool.js +37 -0
  107. package/dist/utils/find-tool.js +97 -0
  108. package/dist/utils/mcp-result.js +18 -0
  109. package/dist/utils/pagination.js +27 -0
  110. package/dist/utils/serialization.js +27 -0
  111. package/package.json +94 -0
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateControllersMap = generateControllersMap;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function parsePhpFile(content) {
40
+ const classMatch = content.match(/class\s+(\w+)\s+extends\s+(\w+)/);
41
+ if (!classMatch)
42
+ return null;
43
+ const className = classMatch[1];
44
+ const extendsClass = classMatch[2];
45
+ const traitMatches = content.matchAll(/use\s+([a-zA-Z0-9_\\]+)\s*;/g);
46
+ const traits = [];
47
+ for (const match of traitMatches) {
48
+ const fullTrait = match[1];
49
+ if (fullTrait.startsWith('icms') || fullTrait.startsWith('cms')) {
50
+ const trait = fullTrait.replace(/^(icms|cms)[\\]?/, '');
51
+ if (trait && trait.includes('\\') && !traits.includes(trait)) {
52
+ traits.push(trait);
53
+ }
54
+ }
55
+ }
56
+ const actions = [];
57
+ const actionRegex = /(public|private|protected)\s+function\s+(action\w+)\s*\(([^)]*)\)/g;
58
+ let actionMatch;
59
+ while ((actionMatch = actionRegex.exec(content)) !== null) {
60
+ const paramsStr = actionMatch[3].trim();
61
+ const params = paramsStr ? paramsStr.split(',').map(p => p.trim()) : [];
62
+ actions.push({
63
+ name: actionMatch[2],
64
+ visibility: actionMatch[1],
65
+ params,
66
+ });
67
+ }
68
+ return { className, extends: extendsClass, traits, actions };
69
+ }
70
+ function normalizeControllerName(name, type) {
71
+ if (type === 'backend') {
72
+ if (name.startsWith('backend')) {
73
+ return name.replace(/^backend/i, '').toLowerCase();
74
+ }
75
+ }
76
+ return name.toLowerCase();
77
+ }
78
+ function detectControllerType(className, extendsClass, filePath) {
79
+ if (className.toLowerCase().startsWith('backend'))
80
+ return 'backend';
81
+ if (extendsClass.toLowerCase().includes('backend'))
82
+ return 'backend';
83
+ if (filePath.includes('/backend/'))
84
+ return 'backend';
85
+ return 'frontend';
86
+ }
87
+ function scanControllerDir(dirPath, controllers) {
88
+ if (!fs.existsSync(dirPath))
89
+ return;
90
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ if (!entry.isDirectory())
93
+ continue;
94
+ if (entry.name.startsWith('.'))
95
+ continue;
96
+ const controllerPath = path.join(dirPath, entry.name);
97
+ const frontendPath = path.join(controllerPath, 'frontend.php');
98
+ const backendPath = path.join(controllerPath, 'backend.php');
99
+ const actionsDir = path.join(controllerPath, 'actions');
100
+ const modelPath = path.join(controllerPath, 'model.php');
101
+ const backendModelPath = path.join(controllerPath, 'backend', 'model.php');
102
+ const controllerInfo = {
103
+ name: entry.name,
104
+ className: '',
105
+ type: 'frontend',
106
+ extends: '',
107
+ filePath: '',
108
+ actions: [],
109
+ hasBackendFolder: fs.existsSync(path.join(controllerPath, 'backend')),
110
+ hasModel: fs.existsSync(modelPath),
111
+ modelFile: fs.existsSync(modelPath) ? 'model.php' : undefined,
112
+ };
113
+ if (fs.existsSync(backendModelPath)) {
114
+ controllerInfo.hasBackendFolder = true;
115
+ controllerInfo.modelFile = 'backend/model.php';
116
+ }
117
+ if (fs.existsSync(frontendPath)) {
118
+ const parsed = parsePhpFile(fs.readFileSync(frontendPath, 'utf-8'));
119
+ if (parsed) {
120
+ controllerInfo.className = parsed.className;
121
+ controllerInfo.extends = parsed.extends;
122
+ controllerInfo.filePath = frontendPath.replace(process.cwd(), '');
123
+ controllerInfo.type = detectControllerType(parsed.className, parsed.extends, frontendPath);
124
+ controllerInfo.name = normalizeControllerName(entry.name, controllerInfo.type);
125
+ for (const action of parsed.actions) {
126
+ controllerInfo.actions.push({
127
+ name: action.name,
128
+ className: parsed.className,
129
+ filePath: frontendPath.replace(process.cwd(), ''),
130
+ visibility: action.visibility,
131
+ hasParams: action.params.length > 0,
132
+ params: action.params,
133
+ traits: parsed.traits,
134
+ });
135
+ }
136
+ if (fs.existsSync(backendPath)) {
137
+ controllerInfo.hasBackendFolder = true;
138
+ }
139
+ controllers.push(controllerInfo);
140
+ }
141
+ }
142
+ if (fs.existsSync(backendPath)) {
143
+ const parsed = parsePhpFile(fs.readFileSync(backendPath, 'utf-8'));
144
+ if (parsed) {
145
+ const backendInfo = {
146
+ name: normalizeControllerName(entry.name, 'backend'),
147
+ className: parsed.className,
148
+ type: 'backend',
149
+ extends: parsed.extends,
150
+ filePath: backendPath.replace(process.cwd(), ''),
151
+ actions: [],
152
+ hasBackendFolder: true,
153
+ hasModel: fs.existsSync(modelPath) || fs.existsSync(backendModelPath),
154
+ modelFile: fs.existsSync(backendModelPath)
155
+ ? 'backend/model.php'
156
+ : fs.existsSync(modelPath)
157
+ ? 'model.php'
158
+ : undefined,
159
+ };
160
+ for (const action of parsed.actions) {
161
+ backendInfo.actions.push({
162
+ name: action.name,
163
+ className: parsed.className,
164
+ filePath: backendPath.replace(process.cwd(), ''),
165
+ visibility: action.visibility,
166
+ hasParams: action.params.length > 0,
167
+ params: action.params,
168
+ traits: parsed.traits,
169
+ });
170
+ }
171
+ controllers.push(backendInfo);
172
+ }
173
+ }
174
+ if (fs.existsSync(actionsDir) && fs.statSync(actionsDir).isDirectory()) {
175
+ const actionFiles = fs.readdirSync(actionsDir).filter(f => f.endsWith('.php'));
176
+ for (const actionFile of actionFiles) {
177
+ const actionPath = path.join(actionsDir, actionFile);
178
+ const content = fs.readFileSync(actionPath, 'utf-8');
179
+ const parsed = parsePhpFile(content);
180
+ if (parsed && parsed.className) {
181
+ let controllerName = entry.name;
182
+ let controllerType = 'frontend';
183
+ let actionNameFromClass = '';
184
+ const knownControllers = [
185
+ 'Content',
186
+ 'Users',
187
+ 'Messages',
188
+ 'Activity',
189
+ 'Admin',
190
+ 'Auth',
191
+ 'Comments',
192
+ ];
193
+ let matchedCtrl = '';
194
+ for (const ctrl of knownControllers) {
195
+ if (parsed.className.startsWith('action' + ctrl)) {
196
+ matchedCtrl = ctrl;
197
+ break;
198
+ }
199
+ }
200
+ if (matchedCtrl) {
201
+ controllerName = matchedCtrl.toLowerCase();
202
+ actionNameFromClass = parsed.className.replace('action' + matchedCtrl, 'action');
203
+ if (parsed.className.toLowerCase().startsWith('actionadmin') ||
204
+ parsed.className.toLowerCase().startsWith('backend')) {
205
+ controllerType = 'backend';
206
+ }
207
+ }
208
+ const existingController = controllers.find(c => c.name === controllerName && c.type === controllerType);
209
+ const fileActionName = actionFile
210
+ .replace('.php', '')
211
+ .replace(/_([a-z])/g, (_, c) => c.toUpperCase());
212
+ if (existingController) {
213
+ existingController.actions.push({
214
+ name: actionNameFromClass ||
215
+ 'action' + fileActionName.charAt(0).toUpperCase() + fileActionName.slice(1),
216
+ className: parsed.className,
217
+ filePath: actionPath.replace(process.cwd(), ''),
218
+ visibility: (parsed.actions[0]?.visibility ||
219
+ 'public'),
220
+ hasParams: (parsed.actions[0]?.params?.length || 0) > 0,
221
+ params: parsed.actions[0]?.params || [],
222
+ traits: parsed.traits,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ function generateControllersMap(sourceDir, outputPath) {
231
+ const controllersDir = path.join(sourceDir, 'system', 'controllers');
232
+ const controllers = [];
233
+ scanControllerDir(controllersDir, controllers);
234
+ controllers.sort((a, b) => a.name.localeCompare(b.name));
235
+ const byName = {};
236
+ for (const ctrl of controllers) {
237
+ const key = `${ctrl.name}_${ctrl.type}`;
238
+ if (!byName[key]) {
239
+ byName[key] = ctrl;
240
+ }
241
+ }
242
+ const data = {
243
+ controllers,
244
+ byName,
245
+ controllerCount: controllers.length,
246
+ generatedAt: process.env.KNOWLEDGE_GENERATED_AT || new Date().toISOString(),
247
+ };
248
+ const typescriptContent = `// AUTO-GENERATED from source/system/controllers
249
+ // Do not edit manually - run 'npm run parse:controllers' to regenerate
250
+
251
+ export interface ControllerAction {
252
+ name: string;
253
+ className: string;
254
+ filePath: string;
255
+ visibility: 'public' | 'private' | 'protected';
256
+ hasParams: boolean;
257
+ params: string[];
258
+ traits: string[];
259
+ description?: string;
260
+ }
261
+
262
+ export interface ControllerInfo {
263
+ name: string;
264
+ className: string;
265
+ type: 'frontend' | 'backend';
266
+ extends: string;
267
+ filePath: string;
268
+ actions: ControllerAction[];
269
+ hasBackendFolder: boolean;
270
+ hasModel: boolean;
271
+ modelFile?: string;
272
+ }
273
+
274
+ export interface ControllersMap {
275
+ controllers: ControllerInfo[];
276
+ byName: Record<string, ControllerInfo>;
277
+ controllerCount: number;
278
+ generatedAt: string;
279
+ }
280
+
281
+ export const controllersMap: ControllersMap = ${JSON.stringify(data, null, 2)};
282
+
283
+ export function getController(name: string, type?: 'frontend' | 'backend'): ControllerInfo | undefined {
284
+ if (type) {
285
+ return controllersMap.controllers.find(c => c.name === name && c.type === type);
286
+ }
287
+ return controllersMap.controllers.find(c => c.name === name);
288
+ }
289
+
290
+ export function getControllerActions(name: string, type?: 'frontend' | 'backend'): ControllerAction[] {
291
+ const ctrl = getController(name, type);
292
+ return ctrl?.actions || [];
293
+ }
294
+
295
+ export function getControllersWithBackend(): ControllerInfo[] {
296
+ return controllersMap.controllers.filter(c => c.type === 'frontend' && c.hasBackendFolder);
297
+ }
298
+
299
+ export function getBackendControllers(): ControllerInfo[] {
300
+ return controllersMap.controllers.filter(c => c.type === 'backend');
301
+ }
302
+ `;
303
+ fs.writeFileSync(outputPath, typescriptContent);
304
+ console.log(`Generated ${data.controllerCount} controller entries to ${outputPath}`);
305
+ const uniqueControllers = new Set(controllers.map(c => `${c.name}_${c.type}`));
306
+ console.log(`Unique controllers: ${uniqueControllers.size}`);
307
+ console.log(`Total actions: ${controllers.reduce((sum, c) => sum + c.actions.length, 0)}`);
308
+ }
309
+ if (require.main === module) {
310
+ const sourcePath = path.resolve(process.env.INSTANTCMS_SOURCE || 'source');
311
+ const outputPath = path.resolve('src/data/controllers-map.ts');
312
+ generateControllersMap(sourcePath, outputPath);
313
+ }
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseCoreFile = parseCoreFile;
37
+ exports.parseAllCoreFiles = parseAllCoreFiles;
38
+ exports.generateCoreAPIData = generateCoreAPIData;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const glob = __importStar(require("glob"));
42
+ function getVisibility(modifiers) {
43
+ if (modifiers.includes('private'))
44
+ return 'private';
45
+ if (modifiers.includes('protected'))
46
+ return 'protected';
47
+ if (modifiers.includes('static'))
48
+ return 'static';
49
+ return 'public';
50
+ }
51
+ function extractReturnType(docComment) {
52
+ const returnMatch = docComment.match(/@return\s+([^\s*]+)/);
53
+ if (returnMatch)
54
+ return returnMatch[1];
55
+ return 'void';
56
+ }
57
+ function cleanDescription(desc) {
58
+ let result = desc
59
+ .replace(/^\/\*\*[\s\*]*/gm, '')
60
+ .replace(/\*\/$/gm, '')
61
+ .replace(/^\s*\*\s*/gm, ' ')
62
+ .replace(/^\s*$/gm, '')
63
+ .trim();
64
+ result = result.replace(/\s+/g, ' ').trim();
65
+ if (result.includes('class ') ||
66
+ result.includes('function ') ||
67
+ result.includes('protected $') ||
68
+ result.includes('public $')) {
69
+ return '';
70
+ }
71
+ if (result.length > 10 && (result.includes('*/') || result.includes('/**'))) {
72
+ return '';
73
+ }
74
+ return result.slice(0, 200);
75
+ }
76
+ function isDeprecated(docComment) {
77
+ return docComment.includes('@deprecated');
78
+ }
79
+ function extractMethods(content) {
80
+ const methods = [];
81
+ const methodPattern = /(?:\/\*\*([\s\S]*?)\*\/\s*)?((?:(?:public|private|protected|static|final|abstract)\s+)*)function\s+&?\s*(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\s{]+))?/g;
82
+ for (const match of content.matchAll(methodPattern)) {
83
+ const docComment = match[1] ? `/**${match[1]}*/` : '';
84
+ const modifiers = match[2].trim().split(/\s+/).filter(Boolean);
85
+ const funcName = match[3];
86
+ const paramsStr = match[4];
87
+ const returnType = match[5] || (docComment ? extractReturnType(docComment) : 'mixed');
88
+ const params = paramsStr
89
+ .split(',')
90
+ .map(part => part.trim())
91
+ .filter(Boolean)
92
+ .flatMap(part => {
93
+ const parameter = part.match(/^(?:(.*?)\s+)?(?:&\s*)?(\.\.\.)?\$([a-zA-Z_]\w*)\s*(?:=\s*(.+))?$/);
94
+ if (!parameter)
95
+ return [];
96
+ return [
97
+ {
98
+ name: parameter[3],
99
+ type: `${parameter[2] || ''}${parameter[1] || 'mixed'}`,
100
+ description: '',
101
+ required: parameter[4] === undefined,
102
+ default: parameter[4]?.trim(),
103
+ },
104
+ ];
105
+ });
106
+ methods.push({
107
+ name: funcName,
108
+ visibility: getVisibility(modifiers),
109
+ signature: `function ${funcName}(${paramsStr}): ${returnType}`,
110
+ description: cleanDescription(docComment),
111
+ returnType,
112
+ params,
113
+ deprecated: isDeprecated(docComment),
114
+ });
115
+ }
116
+ return methods;
117
+ }
118
+ function extractProperties(content) {
119
+ const properties = [];
120
+ const lines = content.split('\n');
121
+ let i = 0;
122
+ while (i < lines.length) {
123
+ const line = lines[i];
124
+ if (line.trim().startsWith('/**')) {
125
+ const docLines = [line];
126
+ i++;
127
+ while (i < lines.length && !lines[i].trim().startsWith('*/')) {
128
+ docLines.push(lines[i]);
129
+ i++;
130
+ }
131
+ if (i < lines.length) {
132
+ docLines.push(lines[i]);
133
+ i++;
134
+ }
135
+ const docComment = docLines.join('\n');
136
+ while (i < lines.length && !lines[i].match(/\$(public|private|protected)\s+\$/)) {
137
+ if (lines[i].match(/\$(public|private|protected)\s+\w/)) {
138
+ const propMatch = lines[i].match(/\$(public|private|protected)\s+\$(\w+)/);
139
+ if (propMatch) {
140
+ properties.push({
141
+ name: propMatch[2],
142
+ visibility: getVisibility([propMatch[1]]),
143
+ type: 'mixed',
144
+ description: cleanDescription(docComment),
145
+ });
146
+ }
147
+ i++;
148
+ break;
149
+ }
150
+ i++;
151
+ }
152
+ }
153
+ else {
154
+ i++;
155
+ }
156
+ }
157
+ return properties;
158
+ }
159
+ function extractConstants(content) {
160
+ const constants = [];
161
+ const constRegex = /^\s*const\s+(\w+)\s*=\s*([^;]+);/gm;
162
+ let match;
163
+ while ((match = constRegex.exec(content)) !== null) {
164
+ constants.push({
165
+ name: match[1],
166
+ value: match[2].trim().slice(0, 100),
167
+ description: '',
168
+ });
169
+ }
170
+ return constants;
171
+ }
172
+ function parseCoreFile(filePath) {
173
+ const content = fs.readFileSync(filePath, 'utf-8');
174
+ const classMatch = content.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?/);
175
+ if (!classMatch)
176
+ return null;
177
+ const className = classMatch[1];
178
+ const extendsClass = classMatch[2];
179
+ const fileName = path.basename(filePath, '.php');
180
+ const classDocMatch = content.match(/\/\*\*[\s\S]*?\*\//);
181
+ const description = classDocMatch ? cleanDescription(classDocMatch[0]) : '';
182
+ const methods = extractMethods(content);
183
+ const properties = extractProperties(content);
184
+ const constants = extractConstants(content);
185
+ return {
186
+ name: className,
187
+ extends: extendsClass,
188
+ description,
189
+ file: fileName,
190
+ methods,
191
+ properties,
192
+ constants,
193
+ };
194
+ }
195
+ function parseAllCoreFiles(sourceDir) {
196
+ const pattern = path.join(sourceDir, 'system', 'core', '*.php');
197
+ const files = glob.sync(pattern);
198
+ const classes = [];
199
+ for (const file of files) {
200
+ const parsed = parseCoreFile(file);
201
+ if (parsed) {
202
+ classes.push(parsed);
203
+ }
204
+ }
205
+ return classes.sort((a, b) => a.name.localeCompare(b.name));
206
+ }
207
+ function generateCoreAPIData(sourceDir) {
208
+ const classes = parseAllCoreFiles(sourceDir);
209
+ const lines = [
210
+ `// Auto-generated from system/core/*.php`,
211
+ `// Generated at: ${process.env.KNOWLEDGE_GENERATED_AT || new Date().toISOString()}`,
212
+ ``,
213
+ `export interface CoreClass {`,
214
+ ` name: string;`,
215
+ ` extends?: string;`,
216
+ ` description: string;`,
217
+ ` file: string;`,
218
+ ` methods: number;`,
219
+ ` properties: number;`,
220
+ ` constants: number;`,
221
+ `}`,
222
+ ``,
223
+ `export const coreClasses: CoreClass[] = [`,
224
+ ];
225
+ for (const cls of classes) {
226
+ lines.push(` {`);
227
+ lines.push(` name: ${JSON.stringify(cls.name)},`);
228
+ if (cls.extends)
229
+ lines.push(` extends: ${JSON.stringify(cls.extends)},`);
230
+ lines.push(` description: ${JSON.stringify(cls.description)},`);
231
+ lines.push(` file: ${JSON.stringify(`${cls.file}.php`)},`);
232
+ lines.push(` methods: ${cls.methods.length},`);
233
+ lines.push(` properties: ${cls.properties.length},`);
234
+ lines.push(` constants: ${cls.constants.length},`);
235
+ lines.push(` },`);
236
+ }
237
+ lines.push(`];`);
238
+ lines.push(``);
239
+ lines.push(`export const coreAPIMap = {`);
240
+ for (const cls of classes) {
241
+ lines.push(` ${cls.name}: {`);
242
+ lines.push(` name: ${JSON.stringify(cls.name)},`);
243
+ lines.push(` extends: ${cls.extends ? JSON.stringify(cls.extends) : 'undefined'},`);
244
+ lines.push(` file: ${JSON.stringify(`${cls.file}.php`)},`);
245
+ lines.push(` description: ${JSON.stringify(cls.description)},`);
246
+ lines.push(` methods: [`);
247
+ for (const method of cls.methods) {
248
+ lines.push(` {`);
249
+ lines.push(` name: ${JSON.stringify(method.name)},`);
250
+ lines.push(` visibility: ${JSON.stringify(method.visibility)},`);
251
+ lines.push(` signature: ${JSON.stringify(method.signature)},`);
252
+ lines.push(` returnType: ${JSON.stringify(method.returnType)},`);
253
+ lines.push(` params: ${JSON.stringify(method.params)},`);
254
+ lines.push(` description: ${JSON.stringify(method.description)},`);
255
+ if (method.deprecated)
256
+ lines.push(` deprecated: true,`);
257
+ lines.push(` },`);
258
+ }
259
+ lines.push(` ],`);
260
+ lines.push(` properties: [`);
261
+ for (const prop of cls.properties.slice(0, 20)) {
262
+ lines.push(` ${JSON.stringify(prop)},`);
263
+ }
264
+ if (cls.properties.length > 20) {
265
+ lines.push(` // ... ${cls.properties.length - 20} more properties`);
266
+ }
267
+ lines.push(` ],`);
268
+ lines.push(` constants: [`);
269
+ for (const c of cls.constants) {
270
+ lines.push(` ${JSON.stringify(c)},`);
271
+ }
272
+ lines.push(` ],`);
273
+ lines.push(` },`);
274
+ }
275
+ lines.push(`};`);
276
+ return lines.join('\n');
277
+ }
278
+ if (require.main === module) {
279
+ const sourceDir = path.resolve(process.env.INSTANTCMS_SOURCE || path.resolve(__dirname, '../../../source'));
280
+ const outputFile = path.resolve(__dirname, '../../../src/data/core-api.ts');
281
+ console.log(`Parsing system/core/ files from: ${sourceDir}`);
282
+ const classes = parseAllCoreFiles(sourceDir);
283
+ console.log(`\nFound ${classes.length} core classes:\n`);
284
+ for (const cls of classes) {
285
+ const publicMethods = cls.methods.filter(m => m.visibility === 'public');
286
+ console.log(` ${cls.name}${cls.extends ? ` extends ${cls.extends}` : ''} (${cls.file}.php)`);
287
+ console.log(` Methods: ${cls.methods.length} (${publicMethods.length} public)`);
288
+ console.log(` Properties: ${cls.properties.length}`);
289
+ console.log(` Constants: ${cls.constants.length}`);
290
+ }
291
+ const output = generateCoreAPIData(sourceDir);
292
+ fs.writeFileSync(outputFile, output);
293
+ console.log(`\nGenerated: ${outputFile}`);
294
+ }