@taocompany/magic-grid-mcp 0.0.0-stage → 0.14.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.
Files changed (40) hide show
  1. package/bin/magic-grid-mcp.js +7 -0
  2. package/bin/mgc.js +2 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +43 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +5 -0
  9. package/dist/mcpServer.d.ts +7 -0
  10. package/dist/mcpServer.d.ts.map +1 -0
  11. package/dist/mcpServer.js +105 -0
  12. package/dist/packageVersion.d.ts +3 -0
  13. package/dist/packageVersion.d.ts.map +1 -0
  14. package/dist/packageVersion.js +10 -0
  15. package/dist/resolveManifest.d.ts +25 -0
  16. package/dist/resolveManifest.d.ts.map +1 -0
  17. package/dist/resolveManifest.js +169 -0
  18. package/dist/scan/collectSourceFiles.d.ts +2 -0
  19. package/dist/scan/collectSourceFiles.d.ts.map +1 -0
  20. package/dist/scan/collectSourceFiles.js +30 -0
  21. package/dist/scan/parseMagicGrid.d.ts +18 -0
  22. package/dist/scan/parseMagicGrid.d.ts.map +1 -0
  23. package/dist/scan/parseMagicGrid.js +85 -0
  24. package/dist/tools/findRecipes.d.ts +19 -0
  25. package/dist/tools/findRecipes.d.ts.map +1 -0
  26. package/dist/tools/findRecipes.js +104 -0
  27. package/dist/tools/getComponentApi.d.ts +33 -0
  28. package/dist/tools/getComponentApi.d.ts.map +1 -0
  29. package/dist/tools/getComponentApi.js +138 -0
  30. package/dist/types.d.ts +110 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +1 -0
  33. package/dist/validate/propTypeLiteral.d.ts +4 -0
  34. package/dist/validate/propTypeLiteral.d.ts.map +1 -0
  35. package/dist/validate/propTypeLiteral.js +48 -0
  36. package/dist/validate/validateUsage.d.ts +9 -0
  37. package/dist/validate/validateUsage.d.ts.map +1 -0
  38. package/dist/validate/validateUsage.js +342 -0
  39. package/package.json +52 -4
  40. package/README.md +0 -3
@@ -0,0 +1,342 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { collectSourceFiles } from '../scan/collectSourceFiles.js';
4
+ import { findMagicGridBlocks, findStyleImports, } from '../scan/parseMagicGrid.js';
5
+ import { checkPropTypeLiteral } from './propTypeLiteral.js';
6
+ const RESERVED_COL_KEYS = new Set([
7
+ '__tree__',
8
+ '__index__',
9
+ '__selection__',
10
+ '__expand__',
11
+ '__group__',
12
+ ]);
13
+ export function validateMagicGridUsage(resolved, options) {
14
+ const { manifest, source, manifestPath } = resolved;
15
+ const propNames = new Set(manifest.props.map((p) => p.name));
16
+ const emitNames = new Set((manifest.emits ?? []).map((e) => e.name));
17
+ const columnDefNames = new Set(manifest.columnDef.map((c) => c.name));
18
+ const propMistakes = new Map(manifest.commonMistakes.filter((m) => m.kind === 'prop').map((m) => [m.wrong, m.right]));
19
+ const errors = [];
20
+ const unchecked = [];
21
+ let checkable = 0;
22
+ let checked = 0;
23
+ const files = options.paths.flatMap((p) => collectSourceFiles(path.resolve(options.cwd ?? process.cwd(), p)));
24
+ for (const file of files) {
25
+ const rel = path.relative(options.cwd ?? process.cwd(), file);
26
+ const sourceText = fs.readFileSync(file, 'utf8');
27
+ checkCssImports(rel, sourceText, errors);
28
+ checkWrongSubpath(rel, sourceText, errors);
29
+ const blocks = findMagicGridBlocks(file, sourceText);
30
+ for (const block of blocks) {
31
+ const literalProps = new Map();
32
+ for (const attr of block.attrs) {
33
+ if (attr.name.startsWith('@')) {
34
+ checkable++;
35
+ const emitName = attr.name.slice(1);
36
+ if (!emitNames.has(emitName)) {
37
+ errors.push({
38
+ file: rel,
39
+ line: attr.line,
40
+ rule: 'unknown-emit',
41
+ severity: 'error',
42
+ message: `未知 emit「@${emitName}」,不在 manifest 的 ${emitNames.size} 个事件内`,
43
+ });
44
+ }
45
+ checked++;
46
+ continue;
47
+ }
48
+ if (propMistakes.has(attr.name)) {
49
+ const right = propMistakes.get(attr.name);
50
+ errors.push({
51
+ file: rel,
52
+ line: attr.line,
53
+ rule: 'wrong-field-name',
54
+ severity: 'error',
55
+ message: `${attr.name} 不是本库的 prop,应为 ${right}`,
56
+ fix: { kind: 'rename-attr', from: attr.name, to: right },
57
+ });
58
+ checked++;
59
+ continue;
60
+ }
61
+ if (attr.isBinding) {
62
+ unchecked.push({
63
+ reason: 'binding-not-literal',
64
+ at: `${rel}:${attr.line}`,
65
+ detail: `${attr.raw} 无法静态校验`,
66
+ });
67
+ continue;
68
+ }
69
+ if (checkPropTypeLiteral(rel, attr, errors)) {
70
+ checked++;
71
+ if (typeof attr.literalValue === 'string' || typeof attr.literalValue === 'boolean') {
72
+ literalProps.set(resolvePropName(attr.name), attr);
73
+ }
74
+ continue;
75
+ }
76
+ if (attr.name === 'data' && attr.raw.includes('reactive(')) {
77
+ errors.push({
78
+ file: rel,
79
+ line: attr.line,
80
+ rule: 'reactive-data',
81
+ severity: 'warning',
82
+ message: 'data 不应传入 reactive() 深代理,请使用普通数组/对象',
83
+ });
84
+ checked++;
85
+ continue;
86
+ }
87
+ if (attr.name === 'columns' && typeof attr.literalValue === 'string') {
88
+ checkColumnsLiteral(rel, attr, columnDefNames, errors, unchecked);
89
+ checked++;
90
+ literalProps.set(resolvePropName(attr.name), attr);
91
+ continue;
92
+ }
93
+ checkable++;
94
+ const propKey = resolvePropName(attr.name);
95
+ if (!propNames.has(propKey) && !isVueSpecialAttr(attr.name)) {
96
+ errors.push({
97
+ file: rel,
98
+ line: attr.line,
99
+ rule: 'unknown-prop',
100
+ severity: 'error',
101
+ message: `未知 prop「${attr.name}」,不在 manifest 的 ${propNames.size} 个运行时 props 内`,
102
+ });
103
+ }
104
+ checked++;
105
+ if (typeof attr.literalValue === 'string' || typeof attr.literalValue === 'boolean') {
106
+ literalProps.set(resolvePropName(attr.name), attr);
107
+ }
108
+ }
109
+ checkTreeRules(rel, block.line, literalProps, block.attrs, errors);
110
+ }
111
+ checkScriptHeuristics(rel, sourceText, errors, unchecked);
112
+ }
113
+ const coverage = checkable === 0 ? 1 : Math.min(1, checked / checkable);
114
+ const score = scoreResult(errors);
115
+ return {
116
+ libraryVersion: manifest.libraryVersion,
117
+ source,
118
+ manifestPath,
119
+ errors,
120
+ unchecked,
121
+ coverage,
122
+ score,
123
+ };
124
+ }
125
+ const VUE_SPECIAL_ATTRS = new Set([
126
+ 'ref',
127
+ 'key',
128
+ 'class',
129
+ 'style',
130
+ 'is',
131
+ ]);
132
+ function isVueSpecialAttr(name) {
133
+ return (name.startsWith('v-') ||
134
+ name.startsWith('@') ||
135
+ name.startsWith('#') ||
136
+ VUE_SPECIAL_ATTRS.has(name));
137
+ }
138
+ function resolvePropName(name) {
139
+ return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
140
+ }
141
+ function checkCssImports(file, source, errors) {
142
+ const imports = findStyleImports(source);
143
+ const tokens = imports.find((i) => i.path.endsWith('tokens.css'));
144
+ const style = imports.find((i) => i.path.endsWith('style.css'));
145
+ if (style && !tokens) {
146
+ errors.push({
147
+ file,
148
+ line: style.line,
149
+ rule: 'missing-tokens-css',
150
+ severity: 'error',
151
+ message: '缺少 @taocompany/magic-grid/tokens.css,请先于 style.css 引入',
152
+ fix: { kind: 'add-import', to: '@taocompany/magic-grid/tokens.css' },
153
+ });
154
+ }
155
+ if (tokens && style && tokens.line > style.line) {
156
+ errors.push({
157
+ file,
158
+ line: style.line,
159
+ rule: 'css-order',
160
+ severity: 'error',
161
+ message: '应先引入 tokens.css,再引入 style.css',
162
+ fix: { kind: 'reorder-css' },
163
+ });
164
+ }
165
+ }
166
+ function checkWrongSubpath(file, source, errors) {
167
+ const wrong = /@taocompany\/magic-grid\/types\/(?!core|validation)/g;
168
+ let match;
169
+ const lines = source.split('\n');
170
+ while ((match = wrong.exec(source)) !== null) {
171
+ const line = source.slice(0, match.index).split('\n').length;
172
+ errors.push({
173
+ file,
174
+ line,
175
+ rule: 'wrong-subpath',
176
+ severity: 'error',
177
+ message: `子路径拼写错误:${match[0]},类型请用 @taocompany/magic-grid/types/core`,
178
+ fix: { kind: 'fix-subpath', to: '@taocompany/magic-grid/types/core' },
179
+ });
180
+ void lines;
181
+ }
182
+ }
183
+ function checkColumnsLiteral(file, attr, columnDefNames, errors, unchecked) {
184
+ const value = attr.literalValue;
185
+ if (typeof value !== 'string' || !value.trim().startsWith('[')) {
186
+ unchecked.push({
187
+ reason: 'binding-not-literal',
188
+ at: `${file}:${attr.line}`,
189
+ detail: 'columns 非字面量数组,无法静态校验 ColumnDef',
190
+ });
191
+ return;
192
+ }
193
+ try {
194
+ const columns = new Function(`return (${value})`)();
195
+ if (!Array.isArray(columns)) {
196
+ return;
197
+ }
198
+ const keys = new Map();
199
+ const colMistakes = new Map([
200
+ ['field', 'prop'],
201
+ ['headerName', 'label'],
202
+ ['colId', 'key'],
203
+ ['valueFormatter', 'formatter'],
204
+ ]);
205
+ for (const col of columns) {
206
+ if (!col || typeof col !== 'object') {
207
+ continue;
208
+ }
209
+ for (const [wrong, right] of colMistakes) {
210
+ if (wrong in col) {
211
+ errors.push({
212
+ file,
213
+ line: attr.line,
214
+ rule: 'wrong-field-name',
215
+ severity: 'error',
216
+ message: `ColumnDef 字段 ${wrong} 应改为 ${right}`,
217
+ fix: { kind: 'rename-field', from: wrong, to: right },
218
+ });
219
+ }
220
+ }
221
+ for (const key of Object.keys(col)) {
222
+ if (columnDefNames.size > 0 && !columnDefNames.has(key) && !colMistakes.has(key)) {
223
+ errors.push({
224
+ file,
225
+ line: attr.line,
226
+ rule: 'columndef-unknown-field',
227
+ severity: 'error',
228
+ message: `ColumnDef 未知字段「${key}」`,
229
+ });
230
+ }
231
+ }
232
+ const id = String(col.key ?? col.prop ?? '');
233
+ if (!col.prop && !col.key) {
234
+ errors.push({
235
+ file,
236
+ line: attr.line,
237
+ rule: 'columndef-missing-key',
238
+ severity: 'error',
239
+ message: 'ColumnDef 需要 prop 或 key',
240
+ });
241
+ }
242
+ else if (id) {
243
+ keys.set(id, (keys.get(id) ?? 0) + 1);
244
+ if (RESERVED_COL_KEYS.has(id)) {
245
+ errors.push({
246
+ file,
247
+ line: attr.line,
248
+ rule: 'columndef-reserved-colid',
249
+ severity: 'error',
250
+ message: `列 key/prop 不能使用保留值 ${id}`,
251
+ });
252
+ }
253
+ }
254
+ }
255
+ for (const [key, count] of keys) {
256
+ if (count > 1) {
257
+ errors.push({
258
+ file,
259
+ line: attr.line,
260
+ rule: 'columndef-duplicate-key',
261
+ severity: 'error',
262
+ message: `重复的列 key/prop:${key}`,
263
+ });
264
+ }
265
+ }
266
+ }
267
+ catch {
268
+ unchecked.push({
269
+ reason: 'parse-error',
270
+ at: `${file}:${attr.line}`,
271
+ detail: 'columns 字面量无法解析为数组',
272
+ });
273
+ }
274
+ }
275
+ function checkTreeRules(file, blockLine, props, attrs, errors) {
276
+ const tree = props.get('tree');
277
+ const rowGrouping = props.get('rowGrouping');
278
+ const treeLazy = props.get('treeLazyLoad');
279
+ const getDataPath = props.get('getDataPath') ??
280
+ attrs.find((a) => resolvePropName(a.name) === 'getDataPath');
281
+ const loadTree = props.get('loadTreeChildren') ??
282
+ attrs.find((a) => resolvePropName(a.name) === 'loadTreeChildren');
283
+ const truthy = (attr) => attr?.literalValue === true || attr?.literalValue === 'true';
284
+ if (truthy(tree) && !getDataPath) {
285
+ errors.push({
286
+ file,
287
+ line: blockLine,
288
+ rule: 'tree-requires-getdatapath',
289
+ severity: 'error',
290
+ message: 'tree: true 时必须提供 getDataPath',
291
+ });
292
+ }
293
+ if (truthy(tree) && truthy(rowGrouping)) {
294
+ errors.push({
295
+ file,
296
+ line: blockLine,
297
+ rule: 'tree-vs-rowgrouping',
298
+ severity: 'error',
299
+ message: 'tree 与 rowGrouping 不能同时为 true',
300
+ });
301
+ }
302
+ if (truthy(treeLazy) && !loadTree) {
303
+ errors.push({
304
+ file,
305
+ line: blockLine,
306
+ rule: 'tree-lazy-requires-loader',
307
+ severity: 'error',
308
+ message: 'treeLazyLoad: true 时必须提供 loadTreeChildren',
309
+ });
310
+ }
311
+ }
312
+ function checkScriptHeuristics(file, source, errors, unchecked) {
313
+ const hasMove = /\b(moveRow|moveRows|moveUp|moveDown)\s*\(/.test(source);
314
+ const hasSortFilter = /\bsetSortModel\b/.test(source) ||
315
+ /\bsetFilterModel\b/.test(source) ||
316
+ /:defaultSort\b/.test(source) ||
317
+ /:sortable\s*=\s*["']?true/.test(source) ||
318
+ /:filterable\s*=\s*["']?true/.test(source);
319
+ if (hasMove && hasSortFilter) {
320
+ errors.push({
321
+ file,
322
+ line: 1,
323
+ rule: 'move-under-sort-filter',
324
+ severity: 'warning',
325
+ message: '检测到 move* 与排序/筛选启用信号;排序或筛选激活时 move* 可能静默失败(启发式)',
326
+ });
327
+ unchecked.push({
328
+ reason: 'behavior-out-of-scope',
329
+ at: file,
330
+ detail: 'move-under-sort-filter 为 script 启发式,未命中场景见 unchecked',
331
+ });
332
+ }
333
+ }
334
+ function scoreResult(errors) {
335
+ if (errors.some((e) => e.severity === 'error')) {
336
+ return 'fail';
337
+ }
338
+ if (errors.some((e) => e.severity === 'warning')) {
339
+ return 'pass-with-warnings';
340
+ }
341
+ return 'pass';
342
+ }
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@taocompany/magic-grid-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.14.1",
4
+ "description": "CLI and MCP tools for validating @taocompany/magic-grid usage",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "engines": {
12
+ "node": ">=20.19"
13
+ },
14
+ "bin": {
15
+ "magic-grid-mcp": "./bin/magic-grid-mcp.js",
16
+ "mgc": "./bin/mgc.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "bin",
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test": "vitest run",
33
+ "test:run": "vitest run"
34
+ },
35
+ "peerDependencies": {
36
+ "@taocompany/magic-grid": "0.14.1"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@taocompany/magic-grid": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "1.12.0",
45
+ "@vue/compiler-sfc": "^3.5.40",
46
+ "zod": "3.24.0"
47
+ },
48
+ "devDependencies": {
49
+ "@taocompany/magic-grid": "workspace:*",
50
+ "@types/node": "^22.15.0",
51
+ "typescript": "^5.9.3",
52
+ "vitest": "^4.1.10"
53
+ }
54
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.