@wot-ui/cli 1.0.2 → 1.0.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.
- package/README.md +14 -7
- package/data/v2.2.0.json.gz +0 -0
- package/data/v2.json.gz +0 -0
- package/data/versions.json +2 -1
- package/data/versions.json.gz +0 -0
- package/dist/index.mjs +16 -11
- package/dist/{server-CjiMp3bA.mjs → server-CbSmyhFy.mjs} +33 -2
- package/dist/update-check-cp6uTpce.mjs +547 -0
- package/package.json +1 -1
- package/dist/scanner-CO2cUuK4.mjs +0 -281
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ npm install -g @wot-ui/cli
|
|
|
24
24
|
|
|
25
25
|
安装完成后可直接使用 `wot` 命令。
|
|
26
26
|
|
|
27
|
+
`wot` 在交互式终端启动时会自动检查 `@wot-ui/cli` 是否有新版本。检查结果最多缓存 24 小时,提示只写入 stderr,不会污染 `--format json` 的 stdout;CI 和非交互式环境会自动跳过。`wot mcp` 不会在启动时输出更新提示,MCP 客户端可通过 `wot_status` tool 查询 CLI 更新状态。若需要关闭检查,可设置 `WOT_DISABLE_UPDATE_CHECK=1` 或 `NO_UPDATE_NOTIFIER=1`。
|
|
28
|
+
|
|
27
29
|
如果你在仓库内本地调试,推荐直接运行源码入口,而不是依赖全局命令:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
@@ -98,13 +100,18 @@ wot list --format json --version latest
|
|
|
98
100
|
|
|
99
101
|
当前 MCP Server 提供以下 tools:
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- `
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
| Tool | 功能 | 主要参数 |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `wot_status` | 查看 MCP Server 与 `@wot-ui/cli` 状态,包括当前版本、是否有 CLI 更新及更新命令。 | 无 |
|
|
106
|
+
| `wot_list` | 列出当前离线知识库中的 wot-ui 组件元数据,适合在生成页面前发现可用组件。 | `version` |
|
|
107
|
+
| `wot_info` | 查询单个组件的 props、events、slots、CSS 变量等结构化信息。 | `component`, `version` |
|
|
108
|
+
| `wot_doc` | 获取单个组件的完整 markdown 文档,适合需要阅读用法细节或限制说明时调用。 | `component`, `version` |
|
|
109
|
+
| `wot_demo` | 获取组件 demo 列表,或按 demo 名称获取指定示例源码。 | `component`, `demo`, `version` |
|
|
110
|
+
| `wot_token` | 查询组件 CSS 变量;不传组件名时返回所有组件的 CSS 变量摘要。 | `component`, `version` |
|
|
111
|
+
| `wot_changelog` | 查询 wot-ui v2 离线数据中的更新记录,可按版本或组件过滤。 | `version`, `component` |
|
|
112
|
+
| `wot_lint` | 扫描本地项目中的 wot-ui 使用问题,例如未知组件、空按钮等规则。 | `dir`, `version` |
|
|
113
|
+
|
|
114
|
+
其中 `version` 支持与 CLI 一致的写法,例如 `2.0`、`2.0.4`、`latest`;不传时会按项目依赖或离线数据自动解析。
|
|
108
115
|
|
|
109
116
|
## 数据来源
|
|
110
117
|
|
|
Binary file
|
package/data/v2.json.gz
CHANGED
|
Binary file
|
package/data/versions.json
CHANGED
package/data/versions.json.gz
CHANGED
|
Binary file
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as findComponent, c as formatStatusLabel, d as resolveVersion, f as loadMetadataFile, i as lintProject, l as writeStderrLine, m as version, o as listComponents, p as name, r as analyzeUsage, s as formatLogMessage, t as checkForCliUpdate, u as detectVersion } from "./update-check-cp6uTpce.mjs";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -24,7 +24,7 @@ function addQueryOptions(command) {
|
|
|
24
24
|
function normalizeQueryOptions(options) {
|
|
25
25
|
const format = options.format === "json" || options.format === "markdown" ? options.format : "text";
|
|
26
26
|
const info = detectVersion(typeof options.version === "string" ? options.version : void 0);
|
|
27
|
-
if (info.source === "fallback")
|
|
27
|
+
if (info.source === "fallback") writeStderrLine(formatLogMessage("warn", `Version not detected in project, falling back to ${info.version}`));
|
|
28
28
|
return {
|
|
29
29
|
format,
|
|
30
30
|
version: info.version
|
|
@@ -35,7 +35,7 @@ function printError(message, format) {
|
|
|
35
35
|
error: true,
|
|
36
36
|
message
|
|
37
37
|
});
|
|
38
|
-
else console.error(message);
|
|
38
|
+
else console.error(formatLogMessage("error", message));
|
|
39
39
|
}
|
|
40
40
|
function getComponentLabel(component) {
|
|
41
41
|
return `${component.name} ${component.nameZh}`;
|
|
@@ -164,21 +164,21 @@ function readPackageJson(dir) {
|
|
|
164
164
|
if (!existsSync(packageJsonPath)) return void 0;
|
|
165
165
|
return JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
166
166
|
}
|
|
167
|
-
function getDependencyVersion(pkg, name) {
|
|
168
|
-
return pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name];
|
|
167
|
+
function getDependencyVersion(pkg, name$1) {
|
|
168
|
+
return pkg?.dependencies?.[name$1] ?? pkg?.devDependencies?.[name$1];
|
|
169
169
|
}
|
|
170
170
|
function detectWotDependency(pkg) {
|
|
171
171
|
const version$1 = getDependencyVersion(pkg, "@wot-ui/ui");
|
|
172
172
|
return version$1 ? `@wot-ui/ui@${version$1}` : void 0;
|
|
173
173
|
}
|
|
174
|
-
function createCheck(name, status, message, suggestion) {
|
|
174
|
+
function createCheck(name$1, status, message, suggestion) {
|
|
175
175
|
if (status === "pass") return {
|
|
176
|
-
name,
|
|
176
|
+
name: name$1,
|
|
177
177
|
status,
|
|
178
178
|
message
|
|
179
179
|
};
|
|
180
180
|
return {
|
|
181
|
-
name,
|
|
181
|
+
name: name$1,
|
|
182
182
|
status,
|
|
183
183
|
severity: status === "fail" ? "error" : "warning",
|
|
184
184
|
message,
|
|
@@ -228,7 +228,7 @@ function registerDoctorCommand(program) {
|
|
|
228
228
|
writeJson(report);
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
|
-
writeLine([`Directory: ${report.dir}`, ...report.checks.map((check) => `${check.status
|
|
231
|
+
writeLine([`Directory: ${report.dir}`, ...report.checks.map((check) => `${formatStatusLabel(check.status)} ${check.name}: ${check.message}${check.suggestion ? ` | ${check.suggestion}` : ""}`)].join("\n"));
|
|
232
232
|
} catch (error) {
|
|
233
233
|
printError(error instanceof Error ? error.message : "Failed to diagnose project", query.format);
|
|
234
234
|
process.exitCode = 1;
|
|
@@ -293,7 +293,7 @@ function registerLintCommand(program) {
|
|
|
293
293
|
writeLine(`No lint issues found. Scanned files: ${report.scannedFiles}`);
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
296
|
-
writeLine([`Scanned files: ${report.scannedFiles}`, ...report.issues.map((issue) => `${issue.severity
|
|
296
|
+
writeLine([`Scanned files: ${report.scannedFiles}`, ...report.issues.map((issue) => `${formatStatusLabel(issue.severity)} ${issue.file}:${issue.line} [${issue.rule}] ${issue.message}`)].join("\n"));
|
|
297
297
|
} catch (error) {
|
|
298
298
|
printError(error instanceof Error ? error.message : "Failed to lint project", query.format);
|
|
299
299
|
process.exitCode = 1;
|
|
@@ -324,7 +324,7 @@ function registerListCommand(program) {
|
|
|
324
324
|
//#region src/commands/mcp.ts
|
|
325
325
|
function registerMcpCommand(program) {
|
|
326
326
|
program.command("mcp").description("Start the wot-ui MCP server").action(async () => {
|
|
327
|
-
const { startMcpServer } = await import("./server-
|
|
327
|
+
const { startMcpServer } = await import("./server-CbSmyhFy.mjs");
|
|
328
328
|
await startMcpServer();
|
|
329
329
|
});
|
|
330
330
|
}
|
|
@@ -411,6 +411,11 @@ function createCliProgram() {
|
|
|
411
411
|
|
|
412
412
|
//#endregion
|
|
413
413
|
//#region src/index.ts
|
|
414
|
+
checkForCliUpdate({
|
|
415
|
+
args: process.argv,
|
|
416
|
+
currentVersion: version,
|
|
417
|
+
packageName: name
|
|
418
|
+
});
|
|
414
419
|
await createCliProgram().parseAsync(process.argv);
|
|
415
420
|
|
|
416
421
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as findComponent, d as resolveVersion, f as loadMetadataFile, i as lintProject, m as version, n as getCliUpdateStatus, o as listComponents, p as name } from "./update-check-cp6uTpce.mjs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
|
|
4
4
|
import * as z from "zod/v4";
|
|
@@ -6,6 +6,7 @@ import * as z from "zod/v4";
|
|
|
6
6
|
//#region src/mcp/prompts.ts
|
|
7
7
|
const WOT_EXPERT_PROMPT = [
|
|
8
8
|
"You are a wot-ui expert assistant.",
|
|
9
|
+
"Use wot_status when the user asks about tool health, updates, or unexpected missing metadata.",
|
|
9
10
|
"Always query component metadata before generating code.",
|
|
10
11
|
"Prefer using wot_list, wot_info, wot_doc, and wot_token before writing UI code.",
|
|
11
12
|
"Assume only wot-ui v2 is supported by this server."
|
|
@@ -21,7 +22,33 @@ const WOT_PAGE_GENERATOR_PROMPT = [
|
|
|
21
22
|
function jsonText(value) {
|
|
22
23
|
return JSON.stringify(value, null, 2);
|
|
23
24
|
}
|
|
24
|
-
function registerMcpTools(server) {
|
|
25
|
+
function registerMcpTools(server, options = {}) {
|
|
26
|
+
server.registerTool("wot_status", {
|
|
27
|
+
description: "Get wot-ui MCP server and CLI update status.",
|
|
28
|
+
inputSchema: z.object({}),
|
|
29
|
+
annotations: {
|
|
30
|
+
readOnlyHint: true,
|
|
31
|
+
destructiveHint: false,
|
|
32
|
+
idempotentHint: true,
|
|
33
|
+
openWorldHint: true
|
|
34
|
+
}
|
|
35
|
+
}, async () => {
|
|
36
|
+
const update = await getCliUpdateStatus({
|
|
37
|
+
currentVersion: version,
|
|
38
|
+
packageName: name,
|
|
39
|
+
...options.updateCheckOptions
|
|
40
|
+
});
|
|
41
|
+
return { content: [{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: jsonText({
|
|
44
|
+
server: {
|
|
45
|
+
name: "wot-ui",
|
|
46
|
+
version
|
|
47
|
+
},
|
|
48
|
+
cli: update
|
|
49
|
+
})
|
|
50
|
+
}] };
|
|
51
|
+
});
|
|
25
52
|
server.registerTool("wot_list", {
|
|
26
53
|
description: "List available wot-ui components.",
|
|
27
54
|
inputSchema: z.object({ version: z.string().optional() }),
|
|
@@ -217,6 +244,10 @@ async function startMcpServer() {
|
|
|
217
244
|
capabilities: { logging: {} }
|
|
218
245
|
});
|
|
219
246
|
registerMcpTools(server);
|
|
247
|
+
getCliUpdateStatus({
|
|
248
|
+
currentVersion: version,
|
|
249
|
+
packageName: name
|
|
250
|
+
}).catch(() => {});
|
|
220
251
|
server.registerPrompt("wot-expert", { description: "General wot-ui expert workflow." }, async () => ({ messages: [{
|
|
221
252
|
role: "assistant",
|
|
222
253
|
content: {
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { gunzipSync } from "node:zlib";
|
|
6
|
+
import { parse } from "@vue/compiler-sfc";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
|
|
9
|
+
//#region package.json
|
|
10
|
+
var name = "@wot-ui/cli";
|
|
11
|
+
var version = "1.0.3";
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/data/loader.ts
|
|
15
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
function resolveDataDir() {
|
|
17
|
+
const candidates = [
|
|
18
|
+
join(currentDir, "..", "data"),
|
|
19
|
+
join(currentDir, "..", "..", "data"),
|
|
20
|
+
join(currentDir, "data")
|
|
21
|
+
];
|
|
22
|
+
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
|
|
23
|
+
throw new Error("Unable to locate bundled data directory");
|
|
24
|
+
}
|
|
25
|
+
const dataDir = resolveDataDir();
|
|
26
|
+
function readJsonFile(baseName) {
|
|
27
|
+
const jsonPath = join(dataDir, `${baseName}.json`);
|
|
28
|
+
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
29
|
+
const gzipPath = join(dataDir, `${baseName}.json.gz`);
|
|
30
|
+
if (existsSync(gzipPath)) {
|
|
31
|
+
const compressed = readFileSync(gzipPath);
|
|
32
|
+
return JSON.parse(gunzipSync(compressed).toString("utf8"));
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Data file not found for ${baseName}`);
|
|
35
|
+
}
|
|
36
|
+
function loadVersionsFile() {
|
|
37
|
+
return readJsonFile("versions");
|
|
38
|
+
}
|
|
39
|
+
function loadMetadataFile(versionKey) {
|
|
40
|
+
return readJsonFile(versionKey);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/data/version.ts
|
|
45
|
+
/** Strip semver range operators (^, ~, >=, >, <=, <, =, whitespace). */
|
|
46
|
+
function stripRange(ver) {
|
|
47
|
+
return ver.replace(/[\^~>=<\s]/g, "");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns all stable version strings for major key 'v2',
|
|
51
|
+
* sorted ascending by semver.
|
|
52
|
+
*/
|
|
53
|
+
function stableV2Versions() {
|
|
54
|
+
const map = loadVersionsFile().v2 ?? {};
|
|
55
|
+
return Object.values(map).filter((v) => !v.includes("-")).sort((a, b) => {
|
|
56
|
+
const pa = a.split(".").map(Number);
|
|
57
|
+
const pb = b.split(".").map(Number);
|
|
58
|
+
for (let i = 0; i < 3; i++) {
|
|
59
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
60
|
+
if (diff !== 0) return diff;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Auto-detect the wot-ui version to use.
|
|
67
|
+
*
|
|
68
|
+
* Priority:
|
|
69
|
+
* 1. --version flag (flagVersion arg)
|
|
70
|
+
* 2. node_modules/@wot-ui/ui/package.json in cwd
|
|
71
|
+
* 3. package.json dependencies[@wot-ui/ui] in cwd
|
|
72
|
+
* 4. Fallback to latest stable version from versions.json
|
|
73
|
+
*/
|
|
74
|
+
function detectVersion(flagVersion, cwd) {
|
|
75
|
+
const dir = cwd ?? process.cwd();
|
|
76
|
+
if (flagVersion) return {
|
|
77
|
+
version: flagVersion,
|
|
78
|
+
source: "flag"
|
|
79
|
+
};
|
|
80
|
+
const nmPath = join(dir, "node_modules", "@wot-ui", "ui", "package.json");
|
|
81
|
+
if (existsSync(nmPath)) try {
|
|
82
|
+
const pkg = JSON.parse(readFileSync(nmPath, "utf8"));
|
|
83
|
+
if (pkg.version) return {
|
|
84
|
+
version: pkg.version,
|
|
85
|
+
source: "node_modules"
|
|
86
|
+
};
|
|
87
|
+
} catch {}
|
|
88
|
+
const pkgPath = join(dir, "package.json");
|
|
89
|
+
if (existsSync(pkgPath)) try {
|
|
90
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
91
|
+
const depVersion = pkg.dependencies?.["@wot-ui/ui"] ?? pkg.devDependencies?.["@wot-ui/ui"] ?? pkg.peerDependencies?.["@wot-ui/ui"];
|
|
92
|
+
if (depVersion) return {
|
|
93
|
+
version: stripRange(depVersion),
|
|
94
|
+
source: "package.json"
|
|
95
|
+
};
|
|
96
|
+
} catch {}
|
|
97
|
+
return {
|
|
98
|
+
version: stableV2Versions().at(-1) ?? "2.0.0",
|
|
99
|
+
source: "fallback"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a version string (from detectVersion or CLI flag) to a data file key.
|
|
104
|
+
*
|
|
105
|
+
* Examples:
|
|
106
|
+
* undefined / 'v2' → 'v2' (major alias, data/v2.json)
|
|
107
|
+
* 'latest' → 'v2.0.4' (latest stable snapshot)
|
|
108
|
+
* '2.0' → 'v2.0.4' (minor → lookup in versions.json)
|
|
109
|
+
* '2.0.4' → 'v2.0.4' (exact patch)
|
|
110
|
+
* '2.0.0-alpha.5' → 'v2.0.0-alpha.5' (pre-release exact)
|
|
111
|
+
*/
|
|
112
|
+
function resolveVersion(requested) {
|
|
113
|
+
if (!requested || requested === "v2") return "v2";
|
|
114
|
+
const normalized = requested.trim();
|
|
115
|
+
if (normalized === "latest") {
|
|
116
|
+
const latest = stableV2Versions().at(-1);
|
|
117
|
+
if (!latest) return "v2";
|
|
118
|
+
return `v${latest}`;
|
|
119
|
+
}
|
|
120
|
+
const map = loadVersionsFile().v2 ?? {};
|
|
121
|
+
if (/^\d+\.\d+$/.test(normalized)) {
|
|
122
|
+
const patch = map[normalized];
|
|
123
|
+
if (!patch) throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
124
|
+
return `v${patch}`;
|
|
125
|
+
}
|
|
126
|
+
if (/^\d+\.\d+\.\d+/.test(normalized)) {
|
|
127
|
+
if (normalized.split(".")[0] !== "2") throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
128
|
+
return `v${normalized}`;
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/utils/terminal.ts
|
|
135
|
+
const ANSI = {
|
|
136
|
+
cyan: ["\x1B[36m", "\x1B[39m"],
|
|
137
|
+
dim: ["\x1B[2m", "\x1B[22m"],
|
|
138
|
+
green: ["\x1B[32m", "\x1B[39m"],
|
|
139
|
+
red: ["\x1B[31m", "\x1B[39m"],
|
|
140
|
+
yellow: ["\x1B[33m", "\x1B[39m"]
|
|
141
|
+
};
|
|
142
|
+
function supportsColor(options = {}) {
|
|
143
|
+
const env = options.env ?? process.env;
|
|
144
|
+
if (!(options.isTty ?? process.stderr.isTTY)) return false;
|
|
145
|
+
if ("NO_COLOR" in env || env.FORCE_COLOR === "0" || env.TERM === "dumb") return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function writeStderrLine(message) {
|
|
149
|
+
process.stderr.write(`${message}\n`);
|
|
150
|
+
}
|
|
151
|
+
function formatLogMessage(level, message, options = {}) {
|
|
152
|
+
const color = createColorizer(options);
|
|
153
|
+
return `${color.dim("[wot]")} ${styleLevel(level, message, color)}`;
|
|
154
|
+
}
|
|
155
|
+
function formatStatusLabel(status, options = {}) {
|
|
156
|
+
const normalized = status.toUpperCase();
|
|
157
|
+
const color = createColorizer(options);
|
|
158
|
+
if (status === "ok" || status === "pass") return color.green(normalized);
|
|
159
|
+
if (status === "warn" || status === "warning") return color.yellow(normalized);
|
|
160
|
+
return color.red(normalized);
|
|
161
|
+
}
|
|
162
|
+
function formatCommand(command, options = {}) {
|
|
163
|
+
return createColorizer(options).cyan(command);
|
|
164
|
+
}
|
|
165
|
+
function formatUpdateNotice(status, options = {}) {
|
|
166
|
+
const color = createColorizer(options);
|
|
167
|
+
const currentVersion = color.dim(status.currentVersion);
|
|
168
|
+
const latestVersion = color.green(status.latestVersion ?? "unknown");
|
|
169
|
+
return [
|
|
170
|
+
formatLogMessage("update", "Update available", options),
|
|
171
|
+
`${color.dim("[wot]")} ${status.packageName} ${currentVersion} -> ${latestVersion}`,
|
|
172
|
+
`${color.dim("[wot]")} Run: ${formatCommand(status.command, options)}`
|
|
173
|
+
].join("\n");
|
|
174
|
+
}
|
|
175
|
+
function createColorizer(options) {
|
|
176
|
+
const enabled = supportsColor(options);
|
|
177
|
+
return {
|
|
178
|
+
cyan: (value) => applyAnsi(value, ANSI.cyan, enabled),
|
|
179
|
+
dim: (value) => applyAnsi(value, ANSI.dim, enabled),
|
|
180
|
+
green: (value) => applyAnsi(value, ANSI.green, enabled),
|
|
181
|
+
red: (value) => applyAnsi(value, ANSI.red, enabled),
|
|
182
|
+
yellow: (value) => applyAnsi(value, ANSI.yellow, enabled)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function styleLevel(level, message, color) {
|
|
186
|
+
if (level === "error") return color.red(message);
|
|
187
|
+
if (level === "success") return color.green(message);
|
|
188
|
+
if (level === "warn" || level === "update") return color.yellow(message);
|
|
189
|
+
if (level === "hint") return color.cyan(message);
|
|
190
|
+
return message;
|
|
191
|
+
}
|
|
192
|
+
function applyAnsi(value, code, enabled) {
|
|
193
|
+
return enabled ? `${code[0]}${value}${code[1]}` : value;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/data/metadata.ts
|
|
198
|
+
function loadResolvedMetadata(version$1) {
|
|
199
|
+
return loadMetadataFile(resolveVersion(version$1));
|
|
200
|
+
}
|
|
201
|
+
function listComponents(version$1) {
|
|
202
|
+
return loadResolvedMetadata(version$1).components;
|
|
203
|
+
}
|
|
204
|
+
function findComponent(name$1, version$1) {
|
|
205
|
+
const normalized = name$1.trim().toLowerCase();
|
|
206
|
+
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/utils/files.ts
|
|
211
|
+
const DEFAULT_IGNORES = new Set([
|
|
212
|
+
".git",
|
|
213
|
+
".idea",
|
|
214
|
+
".output",
|
|
215
|
+
".turbo",
|
|
216
|
+
".vscode",
|
|
217
|
+
"dist",
|
|
218
|
+
"build",
|
|
219
|
+
"coverage",
|
|
220
|
+
"node_modules"
|
|
221
|
+
]);
|
|
222
|
+
function walkFiles(rootDir, extensions) {
|
|
223
|
+
const results = [];
|
|
224
|
+
function visit(dir) {
|
|
225
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
226
|
+
if (DEFAULT_IGNORES.has(entry.name)) continue;
|
|
227
|
+
const fullPath = join(dir, entry.name);
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
visit(fullPath);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
visit(rootDir);
|
|
236
|
+
return results;
|
|
237
|
+
}
|
|
238
|
+
function safeRelative(rootDir, filePath) {
|
|
239
|
+
return relative(rootDir, filePath) || ".";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/utils/scanner.ts
|
|
244
|
+
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
|
|
245
|
+
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
|
|
246
|
+
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
|
|
247
|
+
function getLineNumber(source, index) {
|
|
248
|
+
return source.slice(0, index).split("\n").length;
|
|
249
|
+
}
|
|
250
|
+
function collectTemplateTags(content) {
|
|
251
|
+
const counts = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const match of content.matchAll(TAG_RE)) {
|
|
253
|
+
const tag = match[1]?.toLowerCase();
|
|
254
|
+
if (!tag) continue;
|
|
255
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
256
|
+
}
|
|
257
|
+
return counts;
|
|
258
|
+
}
|
|
259
|
+
function collectImports(scriptContent) {
|
|
260
|
+
const imports = /* @__PURE__ */ new Set();
|
|
261
|
+
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
|
|
262
|
+
return [...imports];
|
|
263
|
+
}
|
|
264
|
+
function analyzeUsage(targetDir, version$1) {
|
|
265
|
+
const dir = resolve(targetDir);
|
|
266
|
+
const files = walkFiles(dir, [".vue"]);
|
|
267
|
+
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
|
|
268
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
269
|
+
const imports = /* @__PURE__ */ new Set();
|
|
270
|
+
for (const file of files) {
|
|
271
|
+
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
|
|
272
|
+
const template = parsed.descriptor.template?.content ?? "";
|
|
273
|
+
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
|
|
274
|
+
for (const item of collectImports(script)) imports.add(item);
|
|
275
|
+
for (const [tag, count] of collectTemplateTags(template)) {
|
|
276
|
+
const known = knownByTag.get(tag);
|
|
277
|
+
const key = known?.name ?? tag;
|
|
278
|
+
const existing = usageMap.get(key);
|
|
279
|
+
if (existing) {
|
|
280
|
+
existing.count += count;
|
|
281
|
+
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
usageMap.set(key, {
|
|
285
|
+
name: known?.name ?? tag,
|
|
286
|
+
tag,
|
|
287
|
+
count,
|
|
288
|
+
files: [safeRelative(dir, file)]
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
scannedFiles: files.length,
|
|
294
|
+
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
|
|
295
|
+
imports: [...imports].sort()
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function lintProject(targetDir, version$1) {
|
|
299
|
+
const dir = resolve(targetDir);
|
|
300
|
+
const files = walkFiles(dir, [".vue"]);
|
|
301
|
+
const issues = [];
|
|
302
|
+
for (const file of files) {
|
|
303
|
+
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
|
|
304
|
+
for (const match of template.matchAll(TAG_RE)) {
|
|
305
|
+
const tag = match[1]?.toLowerCase();
|
|
306
|
+
if (!tag) continue;
|
|
307
|
+
if (!findComponent(tag, version$1)) issues.push({
|
|
308
|
+
file: safeRelative(dir, file),
|
|
309
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
310
|
+
rule: "unknown-component",
|
|
311
|
+
severity: "warning",
|
|
312
|
+
message: `Unknown wot-ui component tag: ${tag}`
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
for (const match of template.matchAll(BUTTON_RE)) {
|
|
316
|
+
const attrs = (match[1] ?? match[3] ?? "").trim();
|
|
317
|
+
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
318
|
+
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
|
|
319
|
+
file: safeRelative(dir, file),
|
|
320
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
321
|
+
rule: "button-content",
|
|
322
|
+
severity: "warning",
|
|
323
|
+
message: "wd-button should include visible text content or an icon attribute."
|
|
324
|
+
});
|
|
325
|
+
const component = findComponent("wd-button", version$1);
|
|
326
|
+
for (const prop of component?.props ?? []) {
|
|
327
|
+
if (!prop.deprecated) continue;
|
|
328
|
+
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
|
|
329
|
+
issues.push({
|
|
330
|
+
file: safeRelative(dir, file),
|
|
331
|
+
line: getLineNumber(template, match.index ?? 0),
|
|
332
|
+
rule: "deprecated-prop",
|
|
333
|
+
severity: "warning",
|
|
334
|
+
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
scannedFiles: files.length,
|
|
341
|
+
issues
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/utils/update-check.ts
|
|
347
|
+
const DEFAULT_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
348
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
349
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
350
|
+
function compareSemver(a, b) {
|
|
351
|
+
const parsedA = parseSemver(a);
|
|
352
|
+
const parsedB = parseSemver(b);
|
|
353
|
+
if (!parsedA || !parsedB) return 0;
|
|
354
|
+
for (const index of [
|
|
355
|
+
0,
|
|
356
|
+
1,
|
|
357
|
+
2
|
|
358
|
+
]) {
|
|
359
|
+
const diff = parsedA[index] - parsedB[index];
|
|
360
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
361
|
+
}
|
|
362
|
+
return comparePrerelease(parsedA[3], parsedB[3]);
|
|
363
|
+
}
|
|
364
|
+
function shouldCheckForCliUpdate(args = process.argv, env = process.env, isTty = process.stderr.isTTY) {
|
|
365
|
+
if (!isTty) return false;
|
|
366
|
+
if (isUpdateCheckDisabled(env) || isTruthyEnv(env.CI) || env.NODE_ENV === "test") return false;
|
|
367
|
+
const userArgs = args.slice(2);
|
|
368
|
+
if (userArgs.some((arg) => arg === "-V" || arg === "-h" || arg === "--help")) return false;
|
|
369
|
+
const command = userArgs.find((arg) => !arg.startsWith("-"));
|
|
370
|
+
return command !== "mcp" && command !== "help";
|
|
371
|
+
}
|
|
372
|
+
function checkForCliUpdate(options) {
|
|
373
|
+
const env = options.env ?? process.env;
|
|
374
|
+
const args = options.args ?? process.argv;
|
|
375
|
+
const stderr = options.stderr ?? process.stderr;
|
|
376
|
+
const isTty = options.isTty ?? process.stderr.isTTY;
|
|
377
|
+
if (!shouldCheckForCliUpdate(args, env, isTty)) return;
|
|
378
|
+
try {
|
|
379
|
+
const status = getCachedCliUpdateStatus(options);
|
|
380
|
+
if (status.updateAvailable && status.latestVersion) stderr.write(`${formatUpdateNotice(status, {
|
|
381
|
+
env,
|
|
382
|
+
isTty
|
|
383
|
+
})}\n`);
|
|
384
|
+
} catch {}
|
|
385
|
+
}
|
|
386
|
+
function getCachedCliUpdateStatus(options) {
|
|
387
|
+
const env = options.env ?? process.env;
|
|
388
|
+
const baseStatus = createBaseStatus(options, env);
|
|
389
|
+
if (baseStatus.disabled) return {
|
|
390
|
+
...baseStatus,
|
|
391
|
+
cached: false,
|
|
392
|
+
updateAvailable: false
|
|
393
|
+
};
|
|
394
|
+
const now = options.now ?? Date.now();
|
|
395
|
+
const cached = readCache(options.cacheFile ?? getDefaultCacheFile(env));
|
|
396
|
+
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
397
|
+
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
|
|
398
|
+
const latestVersion = cacheIsFresh ? cached.latestVersion : void 0;
|
|
399
|
+
return {
|
|
400
|
+
...baseStatus,
|
|
401
|
+
cached: cacheIsFresh,
|
|
402
|
+
checkedAt: cacheIsFresh ? cached.checkedAt : void 0,
|
|
403
|
+
latestVersion,
|
|
404
|
+
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async function getCliUpdateStatus(options) {
|
|
408
|
+
const env = options.env ?? process.env;
|
|
409
|
+
const baseStatus = createBaseStatus(options, env);
|
|
410
|
+
if (baseStatus.disabled) return {
|
|
411
|
+
...baseStatus,
|
|
412
|
+
cached: false,
|
|
413
|
+
updateAvailable: false
|
|
414
|
+
};
|
|
415
|
+
const now = options.now ?? Date.now();
|
|
416
|
+
const cacheFile = options.cacheFile ?? getDefaultCacheFile(env);
|
|
417
|
+
const cached = readCache(cacheFile);
|
|
418
|
+
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
419
|
+
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
|
|
420
|
+
const result = cacheIsFresh ? cached : await fetchAndCacheLatestVersion(options, cacheFile, now);
|
|
421
|
+
const latestVersion = result.latestVersion;
|
|
422
|
+
return {
|
|
423
|
+
...baseStatus,
|
|
424
|
+
cached: cacheIsFresh,
|
|
425
|
+
checkedAt: result.checkedAt,
|
|
426
|
+
latestVersion,
|
|
427
|
+
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function createBaseStatus(options, env) {
|
|
431
|
+
return {
|
|
432
|
+
command: `npm install -g ${options.packageName}`,
|
|
433
|
+
currentVersion: options.currentVersion,
|
|
434
|
+
disabled: isUpdateCheckDisabled(env),
|
|
435
|
+
packageName: options.packageName
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function parseSemver(version$1) {
|
|
439
|
+
const match = version$1.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/);
|
|
440
|
+
if (!match) return void 0;
|
|
441
|
+
return [
|
|
442
|
+
Number(match[1]),
|
|
443
|
+
Number(match[2]),
|
|
444
|
+
Number(match[3]),
|
|
445
|
+
match[4]
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
function comparePrerelease(a, b) {
|
|
449
|
+
if (!a && !b) return 0;
|
|
450
|
+
if (!a) return 1;
|
|
451
|
+
if (!b) return -1;
|
|
452
|
+
const identifiersA = a.split(".");
|
|
453
|
+
const identifiersB = b.split(".");
|
|
454
|
+
const length = Math.max(identifiersA.length, identifiersB.length);
|
|
455
|
+
for (let index = 0; index < length; index++) {
|
|
456
|
+
const identifierA = identifiersA[index];
|
|
457
|
+
const identifierB = identifiersB[index];
|
|
458
|
+
if (identifierA === void 0) return -1;
|
|
459
|
+
if (identifierB === void 0) return 1;
|
|
460
|
+
if (identifierA === identifierB) continue;
|
|
461
|
+
const numberA = parseNumericIdentifier(identifierA);
|
|
462
|
+
const numberB = parseNumericIdentifier(identifierB);
|
|
463
|
+
if (numberA !== void 0 && numberB !== void 0) return numberA > numberB ? 1 : -1;
|
|
464
|
+
if (numberA !== void 0) return -1;
|
|
465
|
+
if (numberB !== void 0) return 1;
|
|
466
|
+
return identifierA > identifierB ? 1 : -1;
|
|
467
|
+
}
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
470
|
+
function parseNumericIdentifier(identifier) {
|
|
471
|
+
if (!/^(?:0|[1-9]\d*)$/.test(identifier)) return void 0;
|
|
472
|
+
return Number(identifier);
|
|
473
|
+
}
|
|
474
|
+
function isTruthyEnv(value) {
|
|
475
|
+
return !!value && value !== "0" && value !== "false";
|
|
476
|
+
}
|
|
477
|
+
function isUpdateCheckDisabled(env) {
|
|
478
|
+
return isTruthyEnv(env.WOT_DISABLE_UPDATE_CHECK) || isTruthyEnv(env.NO_UPDATE_NOTIFIER);
|
|
479
|
+
}
|
|
480
|
+
function getDefaultCacheFile(env) {
|
|
481
|
+
return join(env.XDG_CACHE_HOME ? join(env.XDG_CACHE_HOME, "open-wot") : join(homedir(), ".cache", "open-wot"), "update-check.json");
|
|
482
|
+
}
|
|
483
|
+
function readCache(cacheFile) {
|
|
484
|
+
if (!existsSync(cacheFile)) return void 0;
|
|
485
|
+
let cache;
|
|
486
|
+
try {
|
|
487
|
+
cache = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
488
|
+
} catch {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (!cache || typeof cache !== "object" || typeof cache.checkedAt !== "number") return void 0;
|
|
492
|
+
return {
|
|
493
|
+
checkedAt: cache.checkedAt,
|
|
494
|
+
latestVersion: typeof cache.latestVersion === "string" ? cache.latestVersion : void 0
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async function fetchAndCacheLatestVersion(options, cacheFile, now) {
|
|
498
|
+
let latestVersion;
|
|
499
|
+
try {
|
|
500
|
+
latestVersion = await fetchLatestVersion(options.packageName, options.registry ?? options.env?.npm_config_registry ?? DEFAULT_REGISTRY, options.fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
501
|
+
} catch {
|
|
502
|
+
latestVersion = void 0;
|
|
503
|
+
}
|
|
504
|
+
const cache = {
|
|
505
|
+
checkedAt: now,
|
|
506
|
+
latestVersion
|
|
507
|
+
};
|
|
508
|
+
writeCache(cacheFile, cache);
|
|
509
|
+
return cache;
|
|
510
|
+
}
|
|
511
|
+
async function fetchLatestVersion(packageName, registry, fetchFn, timeoutMs) {
|
|
512
|
+
const request = fetchFn ?? globalThis.fetch;
|
|
513
|
+
if (typeof request !== "function") return void 0;
|
|
514
|
+
const controller = new AbortController();
|
|
515
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
516
|
+
try {
|
|
517
|
+
const response = await request(`${registry.replace(/\/+$/, "")}/${encodePackageName(packageName)}/latest`, {
|
|
518
|
+
headers: {
|
|
519
|
+
"accept": "application/json",
|
|
520
|
+
"user-agent": `${packageName} update-check`
|
|
521
|
+
},
|
|
522
|
+
signal: controller.signal
|
|
523
|
+
});
|
|
524
|
+
if (!response.ok) return void 0;
|
|
525
|
+
const json = await response.json();
|
|
526
|
+
if (isRegistryLatestResponse(json)) return json.version;
|
|
527
|
+
} finally {
|
|
528
|
+
clearTimeout(timeout);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function encodePackageName(packageName) {
|
|
532
|
+
if (!packageName.startsWith("@")) return encodeURIComponent(packageName);
|
|
533
|
+
const [scope, name$1] = packageName.split("/");
|
|
534
|
+
return `${scope}%2f${name$1}`;
|
|
535
|
+
}
|
|
536
|
+
function isRegistryLatestResponse(value) {
|
|
537
|
+
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
|
|
538
|
+
}
|
|
539
|
+
function writeCache(cacheFile, cache) {
|
|
540
|
+
try {
|
|
541
|
+
mkdirSync(dirname(cacheFile), { recursive: true });
|
|
542
|
+
writeFileSync(cacheFile, `${JSON.stringify(cache, null, 2)}\n`);
|
|
543
|
+
} catch {}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
//#endregion
|
|
547
|
+
export { findComponent as a, formatStatusLabel as c, resolveVersion as d, loadMetadataFile as f, lintProject as i, writeStderrLine as l, version as m, getCliUpdateStatus as n, listComponents as o, name as p, analyzeUsage as r, formatLogMessage as s, checkForCliUpdate as t, detectVersion as u };
|
package/package.json
CHANGED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { gunzipSync } from "node:zlib";
|
|
6
|
-
import { parse } from "@vue/compiler-sfc";
|
|
7
|
-
|
|
8
|
-
//#region package.json
|
|
9
|
-
var version = "1.0.2";
|
|
10
|
-
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/data/loader.ts
|
|
13
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
function resolveDataDir() {
|
|
15
|
-
const candidates = [
|
|
16
|
-
join(currentDir, "..", "data"),
|
|
17
|
-
join(currentDir, "..", "..", "data"),
|
|
18
|
-
join(currentDir, "data")
|
|
19
|
-
];
|
|
20
|
-
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
|
|
21
|
-
throw new Error("Unable to locate bundled data directory");
|
|
22
|
-
}
|
|
23
|
-
const dataDir = resolveDataDir();
|
|
24
|
-
function readJsonFile(baseName) {
|
|
25
|
-
const jsonPath = join(dataDir, `${baseName}.json`);
|
|
26
|
-
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
27
|
-
const gzipPath = join(dataDir, `${baseName}.json.gz`);
|
|
28
|
-
if (existsSync(gzipPath)) {
|
|
29
|
-
const compressed = readFileSync(gzipPath);
|
|
30
|
-
return JSON.parse(gunzipSync(compressed).toString("utf8"));
|
|
31
|
-
}
|
|
32
|
-
throw new Error(`Data file not found for ${baseName}`);
|
|
33
|
-
}
|
|
34
|
-
function loadVersionsFile() {
|
|
35
|
-
return readJsonFile("versions");
|
|
36
|
-
}
|
|
37
|
-
function loadMetadataFile(versionKey) {
|
|
38
|
-
return readJsonFile(versionKey);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/data/version.ts
|
|
43
|
-
/** Strip semver range operators (^, ~, >=, >, <=, <, =, whitespace). */
|
|
44
|
-
function stripRange(ver) {
|
|
45
|
-
return ver.replace(/[\^~>=<\s]/g, "");
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Returns all stable version strings for major key 'v2',
|
|
49
|
-
* sorted ascending by semver.
|
|
50
|
-
*/
|
|
51
|
-
function stableV2Versions() {
|
|
52
|
-
const map = loadVersionsFile().v2 ?? {};
|
|
53
|
-
return Object.values(map).filter((v) => !v.includes("-")).sort((a, b) => {
|
|
54
|
-
const pa = a.split(".").map(Number);
|
|
55
|
-
const pb = b.split(".").map(Number);
|
|
56
|
-
for (let i = 0; i < 3; i++) {
|
|
57
|
-
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
58
|
-
if (diff !== 0) return diff;
|
|
59
|
-
}
|
|
60
|
-
return 0;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Auto-detect the wot-ui version to use.
|
|
65
|
-
*
|
|
66
|
-
* Priority:
|
|
67
|
-
* 1. --version flag (flagVersion arg)
|
|
68
|
-
* 2. node_modules/@wot-ui/ui/package.json in cwd
|
|
69
|
-
* 3. package.json dependencies[@wot-ui/ui] in cwd
|
|
70
|
-
* 4. Fallback to latest stable version from versions.json
|
|
71
|
-
*/
|
|
72
|
-
function detectVersion(flagVersion, cwd) {
|
|
73
|
-
const dir = cwd ?? process.cwd();
|
|
74
|
-
if (flagVersion) return {
|
|
75
|
-
version: flagVersion,
|
|
76
|
-
source: "flag"
|
|
77
|
-
};
|
|
78
|
-
const nmPath = join(dir, "node_modules", "@wot-ui", "ui", "package.json");
|
|
79
|
-
if (existsSync(nmPath)) try {
|
|
80
|
-
const pkg = JSON.parse(readFileSync(nmPath, "utf8"));
|
|
81
|
-
if (pkg.version) return {
|
|
82
|
-
version: pkg.version,
|
|
83
|
-
source: "node_modules"
|
|
84
|
-
};
|
|
85
|
-
} catch {}
|
|
86
|
-
const pkgPath = join(dir, "package.json");
|
|
87
|
-
if (existsSync(pkgPath)) try {
|
|
88
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
89
|
-
const depVersion = pkg.dependencies?.["@wot-ui/ui"] ?? pkg.devDependencies?.["@wot-ui/ui"] ?? pkg.peerDependencies?.["@wot-ui/ui"];
|
|
90
|
-
if (depVersion) return {
|
|
91
|
-
version: stripRange(depVersion),
|
|
92
|
-
source: "package.json"
|
|
93
|
-
};
|
|
94
|
-
} catch {}
|
|
95
|
-
return {
|
|
96
|
-
version: stableV2Versions().at(-1) ?? "2.0.0",
|
|
97
|
-
source: "fallback"
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Resolve a version string (from detectVersion or CLI flag) to a data file key.
|
|
102
|
-
*
|
|
103
|
-
* Examples:
|
|
104
|
-
* undefined / 'v2' → 'v2' (major alias, data/v2.json)
|
|
105
|
-
* 'latest' → 'v2.0.4' (latest stable snapshot)
|
|
106
|
-
* '2.0' → 'v2.0.4' (minor → lookup in versions.json)
|
|
107
|
-
* '2.0.4' → 'v2.0.4' (exact patch)
|
|
108
|
-
* '2.0.0-alpha.5' → 'v2.0.0-alpha.5' (pre-release exact)
|
|
109
|
-
*/
|
|
110
|
-
function resolveVersion(requested) {
|
|
111
|
-
if (!requested || requested === "v2") return "v2";
|
|
112
|
-
const normalized = requested.trim();
|
|
113
|
-
if (normalized === "latest") {
|
|
114
|
-
const latest = stableV2Versions().at(-1);
|
|
115
|
-
if (!latest) return "v2";
|
|
116
|
-
return `v${latest}`;
|
|
117
|
-
}
|
|
118
|
-
const map = loadVersionsFile().v2 ?? {};
|
|
119
|
-
if (/^\d+\.\d+$/.test(normalized)) {
|
|
120
|
-
const patch = map[normalized];
|
|
121
|
-
if (!patch) throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
122
|
-
return `v${patch}`;
|
|
123
|
-
}
|
|
124
|
-
if (/^\d+\.\d+\.\d+/.test(normalized)) {
|
|
125
|
-
if (normalized.split(".")[0] !== "2") throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
126
|
-
return `v${normalized}`;
|
|
127
|
-
}
|
|
128
|
-
throw new Error(`Unsupported wot-ui version: ${requested}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
//#endregion
|
|
132
|
-
//#region src/data/metadata.ts
|
|
133
|
-
function loadResolvedMetadata(version$1) {
|
|
134
|
-
return loadMetadataFile(resolveVersion(version$1));
|
|
135
|
-
}
|
|
136
|
-
function listComponents(version$1) {
|
|
137
|
-
return loadResolvedMetadata(version$1).components;
|
|
138
|
-
}
|
|
139
|
-
function findComponent(name, version$1) {
|
|
140
|
-
const normalized = name.trim().toLowerCase();
|
|
141
|
-
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//#endregion
|
|
145
|
-
//#region src/utils/files.ts
|
|
146
|
-
const DEFAULT_IGNORES = new Set([
|
|
147
|
-
".git",
|
|
148
|
-
".idea",
|
|
149
|
-
".output",
|
|
150
|
-
".turbo",
|
|
151
|
-
".vscode",
|
|
152
|
-
"dist",
|
|
153
|
-
"build",
|
|
154
|
-
"coverage",
|
|
155
|
-
"node_modules"
|
|
156
|
-
]);
|
|
157
|
-
function walkFiles(rootDir, extensions) {
|
|
158
|
-
const results = [];
|
|
159
|
-
function visit(dir) {
|
|
160
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
161
|
-
if (DEFAULT_IGNORES.has(entry.name)) continue;
|
|
162
|
-
const fullPath = join(dir, entry.name);
|
|
163
|
-
if (entry.isDirectory()) {
|
|
164
|
-
visit(fullPath);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
visit(rootDir);
|
|
171
|
-
return results;
|
|
172
|
-
}
|
|
173
|
-
function safeRelative(rootDir, filePath) {
|
|
174
|
-
return relative(rootDir, filePath) || ".";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/utils/scanner.ts
|
|
179
|
-
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
|
|
180
|
-
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
|
|
181
|
-
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
|
|
182
|
-
function getLineNumber(source, index) {
|
|
183
|
-
return source.slice(0, index).split("\n").length;
|
|
184
|
-
}
|
|
185
|
-
function collectTemplateTags(content) {
|
|
186
|
-
const counts = /* @__PURE__ */ new Map();
|
|
187
|
-
for (const match of content.matchAll(TAG_RE)) {
|
|
188
|
-
const tag = match[1]?.toLowerCase();
|
|
189
|
-
if (!tag) continue;
|
|
190
|
-
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
191
|
-
}
|
|
192
|
-
return counts;
|
|
193
|
-
}
|
|
194
|
-
function collectImports(scriptContent) {
|
|
195
|
-
const imports = /* @__PURE__ */ new Set();
|
|
196
|
-
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
|
|
197
|
-
return [...imports];
|
|
198
|
-
}
|
|
199
|
-
function analyzeUsage(targetDir, version$1) {
|
|
200
|
-
const dir = resolve(targetDir);
|
|
201
|
-
const files = walkFiles(dir, [".vue"]);
|
|
202
|
-
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
|
|
203
|
-
const usageMap = /* @__PURE__ */ new Map();
|
|
204
|
-
const imports = /* @__PURE__ */ new Set();
|
|
205
|
-
for (const file of files) {
|
|
206
|
-
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
|
|
207
|
-
const template = parsed.descriptor.template?.content ?? "";
|
|
208
|
-
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
|
|
209
|
-
for (const item of collectImports(script)) imports.add(item);
|
|
210
|
-
for (const [tag, count] of collectTemplateTags(template)) {
|
|
211
|
-
const known = knownByTag.get(tag);
|
|
212
|
-
const key = known?.name ?? tag;
|
|
213
|
-
const existing = usageMap.get(key);
|
|
214
|
-
if (existing) {
|
|
215
|
-
existing.count += count;
|
|
216
|
-
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
usageMap.set(key, {
|
|
220
|
-
name: known?.name ?? tag,
|
|
221
|
-
tag,
|
|
222
|
-
count,
|
|
223
|
-
files: [safeRelative(dir, file)]
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
scannedFiles: files.length,
|
|
229
|
-
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
|
|
230
|
-
imports: [...imports].sort()
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
function lintProject(targetDir, version$1) {
|
|
234
|
-
const dir = resolve(targetDir);
|
|
235
|
-
const files = walkFiles(dir, [".vue"]);
|
|
236
|
-
const issues = [];
|
|
237
|
-
for (const file of files) {
|
|
238
|
-
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
|
|
239
|
-
for (const match of template.matchAll(TAG_RE)) {
|
|
240
|
-
const tag = match[1]?.toLowerCase();
|
|
241
|
-
if (!tag) continue;
|
|
242
|
-
if (!findComponent(tag, version$1)) issues.push({
|
|
243
|
-
file: safeRelative(dir, file),
|
|
244
|
-
line: getLineNumber(template, match.index ?? 0),
|
|
245
|
-
rule: "unknown-component",
|
|
246
|
-
severity: "warning",
|
|
247
|
-
message: `Unknown wot-ui component tag: ${tag}`
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
for (const match of template.matchAll(BUTTON_RE)) {
|
|
251
|
-
const attrs = (match[1] ?? match[3] ?? "").trim();
|
|
252
|
-
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
253
|
-
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
|
|
254
|
-
file: safeRelative(dir, file),
|
|
255
|
-
line: getLineNumber(template, match.index ?? 0),
|
|
256
|
-
rule: "button-content",
|
|
257
|
-
severity: "warning",
|
|
258
|
-
message: "wd-button should include visible text content or an icon attribute."
|
|
259
|
-
});
|
|
260
|
-
const component = findComponent("wd-button", version$1);
|
|
261
|
-
for (const prop of component?.props ?? []) {
|
|
262
|
-
if (!prop.deprecated) continue;
|
|
263
|
-
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
|
|
264
|
-
issues.push({
|
|
265
|
-
file: safeRelative(dir, file),
|
|
266
|
-
line: getLineNumber(template, match.index ?? 0),
|
|
267
|
-
rule: "deprecated-prop",
|
|
268
|
-
severity: "warning",
|
|
269
|
-
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
scannedFiles: files.length,
|
|
276
|
-
issues
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
//#endregion
|
|
281
|
-
export { detectVersion as a, version as c, listComponents as i, lintProject as n, resolveVersion as o, findComponent as r, loadMetadataFile as s, analyzeUsage as t };
|