@wot-ui/cli 0.0.1-beta.2
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 +93 -0
- package/README.zh-CN.md +74 -0
- package/data/v2.json +19827 -0
- package/data/versions.json +10 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +416 -0
- package/dist/scanner-BFHnD5iE.mjs +199 -0
- package/dist/server-HjZltXBO.mjs +248 -0
- package/package.json +49 -0
- package/skills/wot-ui/SKILL.md +18 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as resolveVersion, i as listComponents, n as lintProject, o as loadMetadataFile, r as findComponent, t as analyzeUsage } from "./scanner-BFHnD5iE.mjs";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/shared.ts
|
|
9
|
+
function addQueryOptions(command) {
|
|
10
|
+
return command.option("--format <format>", "output format: text, json, markdown", "text").option("--lang <lang>", "output language: zh, en", "zh").option("--version <version>", "target wot-ui version");
|
|
11
|
+
}
|
|
12
|
+
function normalizeQueryOptions(options) {
|
|
13
|
+
return {
|
|
14
|
+
format: options.format === "json" || options.format === "markdown" ? options.format : "text",
|
|
15
|
+
lang: options.lang === "en" ? "en" : "zh",
|
|
16
|
+
version: typeof options.version === "string" ? options.version : void 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function printError(message, format) {
|
|
20
|
+
if (format === "json") console.log(JSON.stringify({
|
|
21
|
+
error: true,
|
|
22
|
+
message
|
|
23
|
+
}, null, 2));
|
|
24
|
+
else console.error(message);
|
|
25
|
+
}
|
|
26
|
+
function getComponentLabel(component, lang) {
|
|
27
|
+
return lang === "zh" ? `${component.name} ${component.nameZh}` : component.name;
|
|
28
|
+
}
|
|
29
|
+
function getComponentDescription(component, lang) {
|
|
30
|
+
return lang === "zh" ? component.descriptionZh : component.description;
|
|
31
|
+
}
|
|
32
|
+
function formatCssVars(cssVars) {
|
|
33
|
+
return cssVars.map((cssVar) => ({
|
|
34
|
+
name: cssVar.name,
|
|
35
|
+
defaultValue: cssVar.defaultValue ?? cssVar.token ?? "-",
|
|
36
|
+
description: cssVar.description
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/commands/changelog.ts
|
|
42
|
+
function registerChangelogCommand(program) {
|
|
43
|
+
addQueryOptions(program.command("changelog").argument("[versionOrComponent]", "Version or component name").argument("[component]", "Optional component name").description("Show changelog entries")).action((versionOrComponent, componentArg, options) => {
|
|
44
|
+
const query = normalizeQueryOptions(options);
|
|
45
|
+
try {
|
|
46
|
+
const metadata = loadMetadataFile(resolveVersion(query.version));
|
|
47
|
+
let versionFilter;
|
|
48
|
+
let componentFilter;
|
|
49
|
+
if (versionOrComponent && /^v?\d/.test(versionOrComponent)) versionFilter = versionOrComponent;
|
|
50
|
+
else if (versionOrComponent) componentFilter = versionOrComponent;
|
|
51
|
+
if (componentArg) componentFilter = componentArg;
|
|
52
|
+
const entries = (metadata.changelog ?? []).filter((entry) => {
|
|
53
|
+
const versionMatches = versionFilter ? entry.version === versionFilter || `v${entry.version}` === versionFilter : true;
|
|
54
|
+
const componentMatches = componentFilter ? (entry.components ?? []).some((component) => component.toLowerCase() === componentFilter.toLowerCase()) : true;
|
|
55
|
+
return versionMatches && componentMatches;
|
|
56
|
+
});
|
|
57
|
+
if (query.format === "json") {
|
|
58
|
+
console.log(JSON.stringify({ entries }, null, 2));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const lines = entries.flatMap((entry) => [
|
|
62
|
+
`${entry.version}${entry.date ? ` (${entry.date})` : ""}`,
|
|
63
|
+
entry.summary,
|
|
64
|
+
...entry.highlights.map((item) => `- ${item}`),
|
|
65
|
+
""
|
|
66
|
+
]);
|
|
67
|
+
console.log(lines.join("\n").trim());
|
|
68
|
+
} catch (error) {
|
|
69
|
+
printError(error instanceof Error ? error.message : "Failed to load changelog", query.format);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/commands/demo.ts
|
|
77
|
+
function registerDemoCommand(program) {
|
|
78
|
+
addQueryOptions(program.command("demo").argument("<component>", "Component name or wd-* tag").argument("[name]", "Demo name").description("Show component demo source code")).action((componentName, demoName, options) => {
|
|
79
|
+
const query = normalizeQueryOptions(options);
|
|
80
|
+
try {
|
|
81
|
+
const component = findComponent(componentName, query.version);
|
|
82
|
+
if (!component) {
|
|
83
|
+
printError(`Component not found: ${componentName}`, query.format);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const demos = component.demos ?? [];
|
|
88
|
+
if (!demoName) {
|
|
89
|
+
if (query.format === "json") {
|
|
90
|
+
console.log(JSON.stringify({
|
|
91
|
+
component: component.name,
|
|
92
|
+
demos
|
|
93
|
+
}, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log(demos.map((demo$1) => `- ${demo$1.name}: ${demo$1.title}`).join("\n"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const demo = demos.find((item) => item.name.toLowerCase() === demoName.toLowerCase());
|
|
100
|
+
if (!demo) {
|
|
101
|
+
printError(`Demo not found: ${demoName}`, query.format);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (query.format === "json") {
|
|
106
|
+
console.log(JSON.stringify({
|
|
107
|
+
component: component.name,
|
|
108
|
+
demo
|
|
109
|
+
}, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(demo.code);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
printError(error instanceof Error ? error.message : "Failed to load demo", query.format);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/commands/doc.ts
|
|
122
|
+
function registerDocCommand(program) {
|
|
123
|
+
addQueryOptions(program.command("doc").argument("<component>", "Component name or wd-* tag").description("Print component markdown documentation")).action((componentName, options) => {
|
|
124
|
+
const query = normalizeQueryOptions(options);
|
|
125
|
+
try {
|
|
126
|
+
const component = findComponent(componentName, query.version);
|
|
127
|
+
if (!component?.doc) {
|
|
128
|
+
printError(`Documentation not found: ${componentName}`, query.format);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (query.format === "json") {
|
|
133
|
+
console.log(JSON.stringify({
|
|
134
|
+
name: component.name,
|
|
135
|
+
doc: component.doc
|
|
136
|
+
}, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.log(component.doc);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
printError(error instanceof Error ? error.message : "Failed to load documentation", query.format);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/utils/project.ts
|
|
149
|
+
function readPackageJson(dir) {
|
|
150
|
+
const packageJsonPath = join(dir, "package.json");
|
|
151
|
+
if (!existsSync(packageJsonPath)) return void 0;
|
|
152
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
153
|
+
}
|
|
154
|
+
function getDependencyVersion(pkg, name) {
|
|
155
|
+
return pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name];
|
|
156
|
+
}
|
|
157
|
+
function detectWotDependency(pkg) {
|
|
158
|
+
for (const name of ["wot-design-uni", "wot-ui"]) {
|
|
159
|
+
const version = getDependencyVersion(pkg, name);
|
|
160
|
+
if (version) return `${name}@${version}`;
|
|
161
|
+
}
|
|
162
|
+
if (pkg?.dependencies) {
|
|
163
|
+
const scoped = Object.keys(pkg.dependencies).find((name) => name.includes("wot"));
|
|
164
|
+
if (scoped) return `${scoped}@${pkg.dependencies[scoped]}`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function createCheck(name, status, message, suggestion) {
|
|
168
|
+
if (status === "pass") return {
|
|
169
|
+
name,
|
|
170
|
+
status,
|
|
171
|
+
message
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
name,
|
|
175
|
+
status,
|
|
176
|
+
severity: status === "fail" ? "error" : "warning",
|
|
177
|
+
message,
|
|
178
|
+
suggestion
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function diagnoseProject(targetDir) {
|
|
182
|
+
const dir = resolve(targetDir);
|
|
183
|
+
const pkg = readPackageJson(dir);
|
|
184
|
+
const checks = [];
|
|
185
|
+
if (!pkg) {
|
|
186
|
+
checks.push(createCheck("package-json", "fail", "No package.json found in target directory.", "Run the command in a project root or pass a directory containing package.json."));
|
|
187
|
+
return {
|
|
188
|
+
dir,
|
|
189
|
+
checks
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
checks.push(createCheck("package-json", "pass", "package.json found."));
|
|
193
|
+
const wotVersion = detectWotDependency(pkg);
|
|
194
|
+
if (wotVersion) checks.push(createCheck("wot-ui-installed", "pass", `Detected ${wotVersion}.`));
|
|
195
|
+
else checks.push(createCheck("wot-ui-installed", "fail", "No wot-ui dependency detected.", "Add wot-ui or wot-design-uni to dependencies before using doctor, usage, and lint."));
|
|
196
|
+
const vueVersion = getDependencyVersion(pkg, "vue");
|
|
197
|
+
if (vueVersion) checks.push(createCheck("vue-version", vueVersion.includes("3") ? "pass" : "warn", `Detected vue ${vueVersion}.`, vueVersion.includes("3") ? void 0 : "wot-ui v2 is designed for Vue 3 based projects."));
|
|
198
|
+
else checks.push(createCheck("vue-version", "warn", "Vue dependency not found.", "Add vue@3 to align with wot-ui v2 expectations."));
|
|
199
|
+
const uniAppVersion = getDependencyVersion(pkg, "@dcloudio/uni-app") ?? getDependencyVersion(pkg, "uni-app");
|
|
200
|
+
if (uniAppVersion) checks.push(createCheck("uni-app-version", "pass", `Detected uni-app ${uniAppVersion}.`));
|
|
201
|
+
else checks.push(createCheck("uni-app-version", "warn", "uni-app dependency not found.", "If this is a uni-app project, add @dcloudio/uni-app."));
|
|
202
|
+
const typescriptVersion = getDependencyVersion(pkg, "typescript");
|
|
203
|
+
if (typescriptVersion) checks.push(createCheck("typescript", "pass", `Detected typescript ${typescriptVersion}.`));
|
|
204
|
+
else checks.push(createCheck("typescript", "warn", "TypeScript dependency not found.", "Add TypeScript if you want type-safe wot-ui development and CLI analysis."));
|
|
205
|
+
const hasNodeModules = existsSync(join(dir, "node_modules"));
|
|
206
|
+
checks.push(createCheck("node-modules", hasNodeModules ? "pass" : "warn", hasNodeModules ? "node_modules directory found." : "node_modules directory not found.", hasNodeModules ? void 0 : "Run your package manager install command before deeper diagnostics."));
|
|
207
|
+
return {
|
|
208
|
+
dir,
|
|
209
|
+
checks
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/commands/doctor.ts
|
|
215
|
+
function registerDoctorCommand(program) {
|
|
216
|
+
addQueryOptions(program.command("doctor").argument("[dir]", "Project directory", ".").description("Diagnose a wot-ui project")).action((dir, options) => {
|
|
217
|
+
const query = normalizeQueryOptions(options);
|
|
218
|
+
try {
|
|
219
|
+
const report = diagnoseProject(resolve(dir));
|
|
220
|
+
if (query.format === "json") {
|
|
221
|
+
console.log(JSON.stringify(report, null, 2));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const lines = [`Directory: ${report.dir}`, ...report.checks.map((check) => `${check.status.toUpperCase()} ${check.name}: ${check.message}${check.suggestion ? ` | ${check.suggestion}` : ""}`)];
|
|
225
|
+
console.log(lines.join("\n"));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
printError(error instanceof Error ? error.message : "Failed to diagnose project", query.format);
|
|
228
|
+
process.exitCode = 1;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/commands/info.ts
|
|
235
|
+
function registerInfoCommand(program) {
|
|
236
|
+
addQueryOptions(program.command("info").argument("<component>", "Component name or wd-* tag").description("Show component props, events, slots and CSS variables")).action((componentName, options) => {
|
|
237
|
+
const query = normalizeQueryOptions(options);
|
|
238
|
+
try {
|
|
239
|
+
const component = findComponent(componentName, query.version);
|
|
240
|
+
if (!component) {
|
|
241
|
+
printError(`Component not found: ${componentName}`, query.format);
|
|
242
|
+
process.exitCode = 1;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (query.format === "json") {
|
|
246
|
+
console.log(JSON.stringify(component, null, 2));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const lines = [
|
|
250
|
+
`${getComponentLabel(component, query.lang)} (${component.tag})`,
|
|
251
|
+
getComponentDescription(component, query.lang),
|
|
252
|
+
"",
|
|
253
|
+
"Props:",
|
|
254
|
+
...component.props.map((prop) => `- ${prop.name}: ${prop.type}${prop.default ? ` = ${prop.default}` : ""} | ${prop.description}`),
|
|
255
|
+
"",
|
|
256
|
+
"Events:",
|
|
257
|
+
...component.events.map((event) => `- ${event.name}${event.payload ? ` (${event.payload})` : ""}: ${event.description}`),
|
|
258
|
+
"",
|
|
259
|
+
"Slots:",
|
|
260
|
+
...component.slots.map((slot) => `- ${slot.name}: ${slot.description}`),
|
|
261
|
+
"",
|
|
262
|
+
"CSS Variables:",
|
|
263
|
+
...component.cssVars.map((cssVar) => {
|
|
264
|
+
const defaultValue = cssVar.defaultValue ?? cssVar.token;
|
|
265
|
+
return `- ${cssVar.name}${defaultValue ? ` = ${defaultValue}` : ""}: ${cssVar.description}`;
|
|
266
|
+
})
|
|
267
|
+
];
|
|
268
|
+
console.log(lines.join("\n"));
|
|
269
|
+
} catch (error) {
|
|
270
|
+
printError(error instanceof Error ? error.message : "Failed to load component info", query.format);
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/commands/lint.ts
|
|
278
|
+
function registerLintCommand(program) {
|
|
279
|
+
addQueryOptions(program.command("lint").argument("[dir]", "Project directory", ".").description("Lint wot-ui usage")).action((dir, options) => {
|
|
280
|
+
const query = normalizeQueryOptions(options);
|
|
281
|
+
try {
|
|
282
|
+
const report = lintProject(resolve(dir), query.version);
|
|
283
|
+
if (query.format === "json") {
|
|
284
|
+
console.log(JSON.stringify(report, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!report.issues.length) {
|
|
288
|
+
console.log(`No lint issues found. Scanned files: ${report.scannedFiles}`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const lines = [`Scanned files: ${report.scannedFiles}`, ...report.issues.map((issue) => `${issue.severity.toUpperCase()} ${issue.file}:${issue.line} [${issue.rule}] ${issue.message}`)];
|
|
292
|
+
console.log(lines.join("\n"));
|
|
293
|
+
} catch (error) {
|
|
294
|
+
printError(error instanceof Error ? error.message : "Failed to lint project", query.format);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/commands/list.ts
|
|
302
|
+
function registerListCommand(program) {
|
|
303
|
+
addQueryOptions(program.command("list").description("List available wot-ui components")).action((options) => {
|
|
304
|
+
const query = normalizeQueryOptions(options);
|
|
305
|
+
try {
|
|
306
|
+
const components = listComponents(query.version);
|
|
307
|
+
if (query.format === "json") {
|
|
308
|
+
console.log(JSON.stringify({ components }, null, 2));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const lines = components.map((component) => `- ${getComponentLabel(component, query.lang)} (${component.tag}): ${getComponentDescription(component, query.lang)}`);
|
|
312
|
+
console.log(lines.join("\n"));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
printError(error instanceof Error ? error.message : "Failed to list components", query.format);
|
|
315
|
+
process.exitCode = 1;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/commands/mcp.ts
|
|
322
|
+
function registerMcpCommand(program) {
|
|
323
|
+
program.command("mcp").description("Start the wot-ui MCP server").action(async () => {
|
|
324
|
+
const { startMcpServer } = await import("./server-HjZltXBO.mjs");
|
|
325
|
+
await startMcpServer();
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/commands/token.ts
|
|
331
|
+
function registerTokenCommand(program) {
|
|
332
|
+
addQueryOptions(program.command("token").argument("[component]", "Component name or wd-* tag").description("Query component CSS variables")).action((componentName, options) => {
|
|
333
|
+
const query = normalizeQueryOptions(options);
|
|
334
|
+
try {
|
|
335
|
+
if (!componentName) {
|
|
336
|
+
const tokens = listComponents(query.version).flatMap((component$1) => component$1.cssVars.map((cssVar) => ({
|
|
337
|
+
component: component$1.name,
|
|
338
|
+
...cssVar
|
|
339
|
+
})));
|
|
340
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const component = findComponent(componentName, query.version);
|
|
344
|
+
if (!component) {
|
|
345
|
+
printError(`Component not found: ${componentName}`, query.format);
|
|
346
|
+
process.exitCode = 1;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (query.format === "json") {
|
|
350
|
+
console.log(JSON.stringify({
|
|
351
|
+
name: component.name,
|
|
352
|
+
cssVars: formatCssVars(component.cssVars)
|
|
353
|
+
}, null, 2));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const lines = [component.name, ...component.cssVars.map((cssVar) => {
|
|
357
|
+
const defaultValue = cssVar.defaultValue ?? cssVar.token;
|
|
358
|
+
return `- ${cssVar.name}${defaultValue ? ` = ${defaultValue}` : ""}: ${cssVar.description}`;
|
|
359
|
+
})];
|
|
360
|
+
console.log(lines.join("\n"));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
printError(error instanceof Error ? error.message : "Failed to query CSS variables", query.format);
|
|
363
|
+
process.exitCode = 1;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/commands/usage.ts
|
|
370
|
+
function registerUsageCommand(program) {
|
|
371
|
+
addQueryOptions(program.command("usage").argument("[dir]", "Project directory", ".").description("Analyze project usage of wot-ui")).action((dir, options) => {
|
|
372
|
+
const query = normalizeQueryOptions(options);
|
|
373
|
+
try {
|
|
374
|
+
const report = analyzeUsage(resolve(dir), query.version);
|
|
375
|
+
if (query.format === "json") {
|
|
376
|
+
console.log(JSON.stringify(report, null, 2));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const lines = [
|
|
380
|
+
`Scanned files: ${report.scannedFiles}`,
|
|
381
|
+
"Components:",
|
|
382
|
+
...report.components.map((component) => `- ${component.name} (${component.tag}): ${component.count} in ${component.files.join(", ")}`)
|
|
383
|
+
];
|
|
384
|
+
if (report.imports.length) lines.push("", "Imports:", ...report.imports.map((item) => `- ${item}`));
|
|
385
|
+
console.log(lines.join("\n"));
|
|
386
|
+
} catch (error) {
|
|
387
|
+
printError(error instanceof Error ? error.message : "Failed to analyze project usage", query.format);
|
|
388
|
+
process.exitCode = 1;
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/app.ts
|
|
395
|
+
function createCliProgram() {
|
|
396
|
+
const program = new Command();
|
|
397
|
+
program.name("wot").description("wot-ui AI toolkit CLI").version("0.0.0");
|
|
398
|
+
registerListCommand(program);
|
|
399
|
+
registerInfoCommand(program);
|
|
400
|
+
registerDocCommand(program);
|
|
401
|
+
registerDemoCommand(program);
|
|
402
|
+
registerTokenCommand(program);
|
|
403
|
+
registerChangelogCommand(program);
|
|
404
|
+
registerDoctorCommand(program);
|
|
405
|
+
registerUsageCommand(program);
|
|
406
|
+
registerLintCommand(program);
|
|
407
|
+
registerMcpCommand(program);
|
|
408
|
+
return program;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/index.ts
|
|
413
|
+
await createCliProgram().parseAsync(process.argv);
|
|
414
|
+
|
|
415
|
+
//#endregion
|
|
416
|
+
export { };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { gunzipSync } from "node:zlib";
|
|
5
|
+
import { parse } from "@vue/compiler-sfc";
|
|
6
|
+
|
|
7
|
+
//#region src/data/loader.ts
|
|
8
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
function resolveDataDir() {
|
|
10
|
+
const candidates = [
|
|
11
|
+
join(currentDir, "..", "data"),
|
|
12
|
+
join(currentDir, "..", "..", "data"),
|
|
13
|
+
join(currentDir, "data")
|
|
14
|
+
];
|
|
15
|
+
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
|
|
16
|
+
throw new Error("Unable to locate bundled data directory");
|
|
17
|
+
}
|
|
18
|
+
const dataDir = resolveDataDir();
|
|
19
|
+
function readJsonFile(baseName) {
|
|
20
|
+
const jsonPath = join(dataDir, `${baseName}.json`);
|
|
21
|
+
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
22
|
+
const gzipPath = join(dataDir, `${baseName}.json.gz`);
|
|
23
|
+
if (existsSync(gzipPath)) {
|
|
24
|
+
const compressed = readFileSync(gzipPath);
|
|
25
|
+
return JSON.parse(gunzipSync(compressed).toString("utf8"));
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Data file not found for ${baseName}`);
|
|
28
|
+
}
|
|
29
|
+
function loadVersionsFile() {
|
|
30
|
+
return readJsonFile("versions");
|
|
31
|
+
}
|
|
32
|
+
function loadMetadataFile(versionKey) {
|
|
33
|
+
return readJsonFile(versionKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/data/version.ts
|
|
38
|
+
const VERSION_KEY = "v2";
|
|
39
|
+
function resolveVersion(requested) {
|
|
40
|
+
const versions = loadVersionsFile();
|
|
41
|
+
if (!requested) return VERSION_KEY;
|
|
42
|
+
const normalized = requested.trim();
|
|
43
|
+
if (versions.aliases[normalized]) return VERSION_KEY;
|
|
44
|
+
if (versions.supported.includes(normalized)) return VERSION_KEY;
|
|
45
|
+
if (normalized === VERSION_KEY) return VERSION_KEY;
|
|
46
|
+
throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/data/metadata.ts
|
|
51
|
+
function loadResolvedMetadata(version) {
|
|
52
|
+
return loadMetadataFile(resolveVersion(version));
|
|
53
|
+
}
|
|
54
|
+
function listComponents(version) {
|
|
55
|
+
return loadResolvedMetadata(version).components;
|
|
56
|
+
}
|
|
57
|
+
function findComponent(name, version) {
|
|
58
|
+
const normalized = name.trim().toLowerCase();
|
|
59
|
+
return listComponents(version).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/utils/files.ts
|
|
64
|
+
const DEFAULT_IGNORES = new Set([
|
|
65
|
+
".git",
|
|
66
|
+
".idea",
|
|
67
|
+
".output",
|
|
68
|
+
".turbo",
|
|
69
|
+
".vscode",
|
|
70
|
+
"dist",
|
|
71
|
+
"build",
|
|
72
|
+
"coverage",
|
|
73
|
+
"node_modules"
|
|
74
|
+
]);
|
|
75
|
+
function walkFiles(rootDir, extensions) {
|
|
76
|
+
const results = [];
|
|
77
|
+
function visit(dir) {
|
|
78
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
79
|
+
if (DEFAULT_IGNORES.has(entry.name)) continue;
|
|
80
|
+
const fullPath = join(dir, entry.name);
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
visit(fullPath);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
visit(rootDir);
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
function safeRelative(rootDir, filePath) {
|
|
92
|
+
return relative(rootDir, filePath) || ".";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/utils/scanner.ts
|
|
97
|
+
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
|
|
98
|
+
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
|
|
99
|
+
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
|
|
100
|
+
function getLineNumber(source, index) {
|
|
101
|
+
return source.slice(0, index).split("\n").length;
|
|
102
|
+
}
|
|
103
|
+
function collectTemplateTags(content) {
|
|
104
|
+
const counts = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const match of content.matchAll(TAG_RE)) {
|
|
106
|
+
const tag = match[1]?.toLowerCase();
|
|
107
|
+
if (!tag) continue;
|
|
108
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
109
|
+
}
|
|
110
|
+
return counts;
|
|
111
|
+
}
|
|
112
|
+
function collectImports(scriptContent) {
|
|
113
|
+
const imports = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
|
|
115
|
+
return [...imports];
|
|
116
|
+
}
|
|
117
|
+
function analyzeUsage(targetDir, version) {
|
|
118
|
+
const dir = resolve(targetDir);
|
|
119
|
+
const files = walkFiles(dir, [".vue"]);
|
|
120
|
+
const knownByTag = new Map(listComponents(version).map((component) => [component.tag.toLowerCase(), component]));
|
|
121
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
122
|
+
const imports = /* @__PURE__ */ new Set();
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
|
|
125
|
+
const template = parsed.descriptor.template?.content ?? "";
|
|
126
|
+
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
|
|
127
|
+
for (const item of collectImports(script)) imports.add(item);
|
|
128
|
+
for (const [tag, count] of collectTemplateTags(template)) {
|
|
129
|
+
const known = knownByTag.get(tag);
|
|
130
|
+
const key = known?.name ?? tag;
|
|
131
|
+
const existing = usageMap.get(key);
|
|
132
|
+
if (existing) {
|
|
133
|
+
existing.count += count;
|
|
134
|
+
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
usageMap.set(key, {
|
|
138
|
+
name: known?.name ?? tag,
|
|
139
|
+
tag,
|
|
140
|
+
count,
|
|
141
|
+
files: [safeRelative(dir, file)]
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
scannedFiles: files.length,
|
|
147
|
+
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
|
|
148
|
+
imports: [...imports].sort()
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function lintProject(targetDir, version) {
|
|
152
|
+
const dir = resolve(targetDir);
|
|
153
|
+
const files = walkFiles(dir, [".vue"]);
|
|
154
|
+
const issues = [];
|
|
155
|
+
for (const file of files) {
|
|
156
|
+
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
|
|
157
|
+
for (const match of template.matchAll(TAG_RE)) {
|
|
158
|
+
const tag = match[1]?.toLowerCase();
|
|
159
|
+
if (!tag) continue;
|
|
160
|
+
if (!findComponent(tag, version)) issues.push({
|
|
161
|
+
file: safeRelative(dir, file),
|
|
162
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
163
|
+
rule: "unknown-component",
|
|
164
|
+
severity: "warning",
|
|
165
|
+
message: `Unknown wot-ui component tag: ${tag}`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
for (const match of template.matchAll(BUTTON_RE)) {
|
|
169
|
+
const attrs = (match[1] ?? match[3] ?? "").trim();
|
|
170
|
+
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
171
|
+
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
|
|
172
|
+
file: safeRelative(dir, file),
|
|
173
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
174
|
+
rule: "button-content",
|
|
175
|
+
severity: "warning",
|
|
176
|
+
message: "wd-button should include visible text content or an icon attribute."
|
|
177
|
+
});
|
|
178
|
+
const component = findComponent("wd-button", version);
|
|
179
|
+
for (const prop of component?.props ?? []) {
|
|
180
|
+
if (!prop.deprecated) continue;
|
|
181
|
+
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
|
|
182
|
+
issues.push({
|
|
183
|
+
file: safeRelative(dir, file),
|
|
184
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
185
|
+
rule: "deprecated-prop",
|
|
186
|
+
severity: "warning",
|
|
187
|
+
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
scannedFiles: files.length,
|
|
194
|
+
issues
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
export { resolveVersion as a, listComponents as i, lintProject as n, loadMetadataFile as o, findComponent as r, analyzeUsage as t };
|