@kevlns/v-cli 0.1.1-beta.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.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/cli.mjs +297 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kevlns <936277610@qq.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/kevlns/v-cli/main/logo.png" alt="v-cli logo" width="320" />
|
|
4
|
+
|
|
5
|
+
# v-cli
|
|
6
|
+
|
|
7
|
+
**kevlns 的个人工具箱 CLI:插件化架构,内置命令随版本发布,本地插件放目录即生效。**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@kevlns/v-cli)
|
|
10
|
+
[](https://www.npmjs.com/package/@kevlns/v-cli)
|
|
11
|
+
[](https://github.com/kevlns/v-cli/actions/workflows/test.yml)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
|
|
14
|
+
[Getting started](#getting-started) · [API](#api) · [Examples](#examples) · [Package family](#package-family)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why v-cli?
|
|
21
|
+
|
|
22
|
+
v-cli 让你**用一个命令沉淀所有个人小工具**,而不用为每个工具建仓库、发包、记命令。
|
|
23
|
+
它被设计为**依赖极少、秒级启动、写个文件就能扩展**,与 kevlns 工具家族的其余部分可自由组合。
|
|
24
|
+
|
|
25
|
+
- **插件化架构** - 内置命令走注册表随版本发布;本地插件放进 `~/.v-cli/commands/` 立即生效,无需发版
|
|
26
|
+
- **容错加载** - 单个插件语法错误或形状不符只会被跳过并报告,绝不阻断其他命令
|
|
27
|
+
- **双通道输出** - 结果走 stdout(可管道、可脚本化),诊断走 stderr;全局 `--json` 任意位置生效
|
|
28
|
+
- **TypeScript-first** - 严格类型编写,tsup 将核心逻辑打包为单文件 ESM,仅保留 `commander` 运行依赖
|
|
29
|
+
|
|
30
|
+
## Getting started
|
|
31
|
+
|
|
32
|
+
### Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# beta 阶段请显式安装预发布版本
|
|
36
|
+
npm install -g @kevlns/v-cli@beta
|
|
37
|
+
|
|
38
|
+
# 或直接从 GitHub 仓库安装开发版本
|
|
39
|
+
npm install -g git+https://github.com/kevlns/v-cli.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
发布稳定版并设置 `latest` 标签后,可直接使用 `npm install -g @kevlns/v-cli`。
|
|
43
|
+
|
|
44
|
+
### Quick start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
v-cli doctor # 环境体检:node/版本、主目录、配置、插件状态
|
|
48
|
+
v-cli plugin list # 列出内置命令与本地插件
|
|
49
|
+
v-cli ts 1710000000 # 时间戳互转
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> [!TIP]
|
|
53
|
+
> 所有命令都支持 `--json`(任意位置):`v-cli doctor --json` 输出机器可读结果,方便脚本消费。
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### 写一个本地插件
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
// ~/.v-cli/commands/hello.mjs
|
|
61
|
+
export default {
|
|
62
|
+
name: "hello",
|
|
63
|
+
description: "示例插件",
|
|
64
|
+
register(program, ctx) {
|
|
65
|
+
program.action(() => ctx.log.result("hello world"));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
保存后立即生效:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
v-cli hello # hello world
|
|
74
|
+
v-cli plugin list # [local] hello 已出现
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 在脚本中消费输出
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
v-cli ts 1710000000 --json | jq .seconds
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### 内置命令
|
|
86
|
+
|
|
87
|
+
| 命令 | 说明 |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `v-cli doctor` | 环境体检:node/v-cli 版本、主目录、config 可写性、本地插件与加载错误 |
|
|
90
|
+
| `v-cli plugin list` | 列出全部命令(标注 builtin/local 来源) |
|
|
91
|
+
| `v-cli plugin path` | 打印本地插件目录 |
|
|
92
|
+
| `v-cli ts [value]` | 无参=当前时间;数字(秒/毫秒自动识别)=转可读时间;日期串=转时间戳 |
|
|
93
|
+
|
|
94
|
+
### 插件契约 `CliCommand`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface CliCommand {
|
|
98
|
+
name: string; // 子命令名
|
|
99
|
+
description: string;
|
|
100
|
+
hidden?: boolean;
|
|
101
|
+
register(program: Command, ctx: CliContext): void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface CliContext {
|
|
105
|
+
log: Logger; // result() 走 stdout,info/warn/error 走 stderr
|
|
106
|
+
config: ConfigStore; // <home>/config.json 惰性读写
|
|
107
|
+
json: boolean; // 全局 --json 开关,命令必须尊重
|
|
108
|
+
homeDir: string; // 主目录(默认 ~/.v-cli)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 环境变量
|
|
113
|
+
|
|
114
|
+
| 变量 | 默认值 | 说明 |
|
|
115
|
+
| --- | --- | --- |
|
|
116
|
+
| `V_CLI_HOME` | `~/.v-cli` | 覆盖主目录(配置、本地插件都从这里找) |
|
|
117
|
+
|
|
118
|
+
## Package family
|
|
119
|
+
|
|
120
|
+
kevlns 工具家族共享同一套发布约定(tag 驱动、CI 护栏、MIT)。
|
|
121
|
+
|
|
122
|
+
| Package | Purpose | Status |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| [`v-cli`](https://github.com/kevlns/v-cli) | 个人工具箱 CLI(本仓库) | v0.1.1-beta.1 |
|
|
125
|
+
| [`xlmerge`](https://github.com/kevlns/xlmerge) | Git 中 .xlsx/.xlsm 冲突可视化解决工具 | v1.2.1-beta.1 |
|
|
126
|
+
|
|
127
|
+
## Compatibility
|
|
128
|
+
|
|
129
|
+
| Runtime | Supported versions |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| Node.js | `18` and later |
|
|
132
|
+
| TypeScript | `5.6` and later(仅开发时) |
|
|
133
|
+
|
|
134
|
+
CLI 核心逻辑以单文件 ESM(`dist/cli.mjs`)分发,运行时仅依赖 `commander`。
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
git clone https://github.com/kevlns/v-cli.git
|
|
140
|
+
cd v-cli
|
|
141
|
+
npm install
|
|
142
|
+
npm run build && npm test
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For bugs and feature requests, use
|
|
146
|
+
[GitHub Issues](https://github.com/kevlns/v-cli/issues).
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
Released under the [MIT License](./LICENSE).
|
|
151
|
+
|
|
152
|
+
<div align="center">
|
|
153
|
+
|
|
154
|
+
Part of the **kevlns** tool family.
|
|
155
|
+
|
|
156
|
+
</div>
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command, CommanderError } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/core/context.ts
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path2 from "path";
|
|
9
|
+
|
|
10
|
+
// src/core/config.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
var ConfigStore = class _ConfigStore {
|
|
14
|
+
constructor(file) {
|
|
15
|
+
this.file = file;
|
|
16
|
+
}
|
|
17
|
+
static fromHome(homeDir) {
|
|
18
|
+
return new _ConfigStore(path.join(homeDir, "config.json"));
|
|
19
|
+
}
|
|
20
|
+
get() {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(this.file, "utf-8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
set(updater) {
|
|
28
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
29
|
+
const cfg = updater(this.get());
|
|
30
|
+
fs.writeFileSync(this.file, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
31
|
+
return cfg;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/core/logger.ts
|
|
36
|
+
var Logger = class {
|
|
37
|
+
constructor(jsonMode = false) {
|
|
38
|
+
this.jsonMode = jsonMode;
|
|
39
|
+
}
|
|
40
|
+
/** 命令结果输出:--json 时输出结构化 JSON,否则原样输出 */
|
|
41
|
+
result(data) {
|
|
42
|
+
if (this.jsonMode) {
|
|
43
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
44
|
+
} else if (typeof data === "string") {
|
|
45
|
+
process.stdout.write(data + "\n");
|
|
46
|
+
} else {
|
|
47
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
info(msg) {
|
|
51
|
+
process.stderr.write(`[v-cli] ${msg}
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
warn(msg) {
|
|
55
|
+
process.stderr.write(`[v-cli] \u8B66\u544A: ${msg}
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
error(msg) {
|
|
59
|
+
process.stderr.write(`[v-cli] \u9519\u8BEF: ${msg}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/core/context.ts
|
|
65
|
+
function createContext(homeDir, json) {
|
|
66
|
+
return {
|
|
67
|
+
log: new Logger(json),
|
|
68
|
+
config: ConfigStore.fromHome(homeDir),
|
|
69
|
+
json,
|
|
70
|
+
homeDir
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function defaultHomeDir() {
|
|
74
|
+
return process.env.V_CLI_HOME || path2.join(os.homedir(), ".v-cli");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/core/loader.ts
|
|
78
|
+
import fs3 from "fs";
|
|
79
|
+
import path4 from "path";
|
|
80
|
+
import { pathToFileURL } from "url";
|
|
81
|
+
|
|
82
|
+
// src/core/command.ts
|
|
83
|
+
function isCliCommand(candidate) {
|
|
84
|
+
if (typeof candidate !== "object" || candidate === null) return false;
|
|
85
|
+
const cmd = candidate;
|
|
86
|
+
return typeof cmd.name === "string" && typeof cmd.description === "string" && typeof cmd.register === "function";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/commands/doctor.ts
|
|
90
|
+
import fs2 from "fs";
|
|
91
|
+
|
|
92
|
+
// src/version.ts
|
|
93
|
+
var VERSION = "0.1.1-beta.1";
|
|
94
|
+
|
|
95
|
+
// src/commands/doctor.ts
|
|
96
|
+
var doctor = {
|
|
97
|
+
name: "doctor",
|
|
98
|
+
description: "\u4F53\u68C0\uFF1Anode/v-cli \u7248\u672C\u3001homeDir\u3001config\u3001\u63D2\u4EF6\u72B6\u6001",
|
|
99
|
+
register(program, ctx) {
|
|
100
|
+
program.action(async () => {
|
|
101
|
+
const homeDir = ctx.homeDir;
|
|
102
|
+
const exists = fs2.existsSync(homeDir);
|
|
103
|
+
let configWritable = false;
|
|
104
|
+
try {
|
|
105
|
+
ctx.config.set((cfg) => cfg);
|
|
106
|
+
configWritable = true;
|
|
107
|
+
} catch {
|
|
108
|
+
configWritable = false;
|
|
109
|
+
}
|
|
110
|
+
const plugins = await loadLocalPlugins(ctx);
|
|
111
|
+
const list = plugins.map((p) => ({
|
|
112
|
+
source: p.source,
|
|
113
|
+
file: p.file,
|
|
114
|
+
error: p.error ?? null
|
|
115
|
+
}));
|
|
116
|
+
const report = {
|
|
117
|
+
version: VERSION,
|
|
118
|
+
node: process.version,
|
|
119
|
+
homeDir,
|
|
120
|
+
homeDirExists: exists,
|
|
121
|
+
configWritable,
|
|
122
|
+
localPluginCount: plugins.length,
|
|
123
|
+
localPlugins: list
|
|
124
|
+
};
|
|
125
|
+
if (ctx.json) {
|
|
126
|
+
ctx.log.result(report);
|
|
127
|
+
} else {
|
|
128
|
+
ctx.log.result(`vcli ${VERSION} (node ${process.version})`);
|
|
129
|
+
ctx.log.result(`homeDir: ${homeDir} ${exists ? "(\u5B58\u5728)" : "(\u4E0D\u5B58\u5728)"}`);
|
|
130
|
+
ctx.log.result(`config: ${configWritable ? "\u53EF\u8BFB\u5199" : "\u4E0D\u53EF\u5199"}`);
|
|
131
|
+
ctx.log.result(`\u672C\u5730\u63D2\u4EF6: ${plugins.length} \u4E2A`);
|
|
132
|
+
for (const p of plugins) {
|
|
133
|
+
ctx.log.result(` - ${p.file}${p.error ? ` [\u5931\u8D25: ${p.error}]` : ""}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/commands/plugin.ts
|
|
141
|
+
import path3 from "path";
|
|
142
|
+
var plugin = {
|
|
143
|
+
name: "plugin",
|
|
144
|
+
description: "\u63D2\u4EF6\u7BA1\u7406\uFF1Alist \u5217\u51FA\u547D\u4EE4\uFF0Cpath \u663E\u793A\u672C\u5730\u63D2\u4EF6\u76EE\u5F55",
|
|
145
|
+
register(program, ctx) {
|
|
146
|
+
program.command("list").description("\u5217\u51FA\u5185\u7F6E\u547D\u4EE4\u4E0E\u672C\u5730\u63D2\u4EF6").action(async () => {
|
|
147
|
+
const all = await loadAllCommands(ctx);
|
|
148
|
+
const rows = all.map((item) => ({
|
|
149
|
+
source: item.source,
|
|
150
|
+
name: item.command?.name ?? "(\u52A0\u8F7D\u5931\u8D25)",
|
|
151
|
+
description: item.command?.description ?? item.error ?? "",
|
|
152
|
+
file: item.file ?? ""
|
|
153
|
+
}));
|
|
154
|
+
if (ctx.json) {
|
|
155
|
+
ctx.log.result(rows);
|
|
156
|
+
} else {
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
ctx.log.result(`[${row.source}] ${row.name}: ${row.description}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
program.command("path").description("\u6253\u5370\u672C\u5730\u63D2\u4EF6\u76EE\u5F55\uFF08\u653E\u5165 .mjs \u6587\u4EF6\u5373\u751F\u6548\uFF09").action(() => {
|
|
163
|
+
const dir = path3.join(ctx.homeDir, "commands");
|
|
164
|
+
if (ctx.json) {
|
|
165
|
+
ctx.log.result({ dir });
|
|
166
|
+
} else {
|
|
167
|
+
ctx.log.result(dir);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/commands/ts.ts
|
|
174
|
+
function formatTime(ms) {
|
|
175
|
+
const d = new Date(ms);
|
|
176
|
+
return d.toISOString().replace("T", " ").replace("Z", "");
|
|
177
|
+
}
|
|
178
|
+
function parseInput(input) {
|
|
179
|
+
if (/^\d+$/.test(input)) {
|
|
180
|
+
const n = Number(input);
|
|
181
|
+
if (n > 1e12) {
|
|
182
|
+
return { seconds: Math.floor(n / 1e3), milliseconds: n, raw: input };
|
|
183
|
+
}
|
|
184
|
+
return { seconds: n, milliseconds: n * 1e3, raw: input };
|
|
185
|
+
}
|
|
186
|
+
const parsed = Date.parse(input);
|
|
187
|
+
if (Number.isNaN(parsed)) {
|
|
188
|
+
throw new Error(`\u65E0\u6CD5\u89E3\u6790\u65F6\u95F4: ${input}`);
|
|
189
|
+
}
|
|
190
|
+
return { seconds: Math.floor(parsed / 1e3), milliseconds: parsed, raw: input };
|
|
191
|
+
}
|
|
192
|
+
var ts = {
|
|
193
|
+
name: "ts",
|
|
194
|
+
description: "\u65F6\u95F4\u6233\u5DE5\u5177\uFF1A\u65F6\u95F4\u6233 <-> \u53EF\u8BFB\u65F6\u95F4\u4E92\u8F6C",
|
|
195
|
+
register(program, ctx) {
|
|
196
|
+
program.argument("[value]", "\u6570\u5B57\u65F6\u95F4\u6233\uFF08\u79D2\u6216\u6BEB\u79D2\uFF0C\u81EA\u52A8\u8BC6\u522B\uFF09\u6216\u65E5\u671F\u5B57\u7B26\u4E32").action((value) => {
|
|
197
|
+
if (!value || value === "now") {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const report2 = {
|
|
200
|
+
seconds: Math.floor(now / 1e3),
|
|
201
|
+
milliseconds: now,
|
|
202
|
+
iso: new Date(now).toISOString(),
|
|
203
|
+
local: new Date(now).toString()
|
|
204
|
+
};
|
|
205
|
+
ctx.log.result(ctx.json ? report2 : report2.iso);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const result = parseInput(value);
|
|
209
|
+
const report = {
|
|
210
|
+
input: result.raw,
|
|
211
|
+
seconds: result.seconds,
|
|
212
|
+
milliseconds: result.milliseconds,
|
|
213
|
+
iso: new Date(result.milliseconds).toISOString(),
|
|
214
|
+
local: formatTime(result.milliseconds)
|
|
215
|
+
};
|
|
216
|
+
ctx.log.result(ctx.json ? report : report.local);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/commands/index.ts
|
|
222
|
+
var builtinCommands = [doctor, plugin, ts];
|
|
223
|
+
|
|
224
|
+
// src/core/loader.ts
|
|
225
|
+
async function loadBuiltinCommands() {
|
|
226
|
+
return builtinCommands.map((command) => ({ command, source: "builtin" }));
|
|
227
|
+
}
|
|
228
|
+
async function loadLocalPlugins(ctx) {
|
|
229
|
+
const dir = path4.join(ctx.homeDir, "commands");
|
|
230
|
+
const loaded = [];
|
|
231
|
+
if (!fs3.existsSync(dir)) return loaded;
|
|
232
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".mjs")).sort();
|
|
233
|
+
for (const file of files) {
|
|
234
|
+
const abs = path4.join(dir, file);
|
|
235
|
+
try {
|
|
236
|
+
const mod = await import(
|
|
237
|
+
/* @vite-ignore */
|
|
238
|
+
pathToFileURL(abs).href
|
|
239
|
+
);
|
|
240
|
+
const cmd = mod.default ?? mod;
|
|
241
|
+
if (!isCliCommand(cmd)) {
|
|
242
|
+
loaded.push({ source: "local", file: abs, error: "\u9ED8\u8BA4\u5BFC\u51FA\u4E0D\u7B26\u5408 CliCommand \u5951\u7EA6" });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
loaded.push({ command: cmd, source: "local", file: abs });
|
|
246
|
+
} catch (err) {
|
|
247
|
+
loaded.push({
|
|
248
|
+
source: "local",
|
|
249
|
+
file: abs,
|
|
250
|
+
error: err instanceof Error ? err.message : String(err)
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return loaded;
|
|
255
|
+
}
|
|
256
|
+
async function loadAllCommands(ctx) {
|
|
257
|
+
return [...await loadBuiltinCommands(), ...await loadLocalPlugins(ctx)];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/cli.ts
|
|
261
|
+
async function main() {
|
|
262
|
+
const rawArgs = process.argv.slice(2);
|
|
263
|
+
const json = rawArgs.includes("--json");
|
|
264
|
+
const args = rawArgs.filter((a) => a !== "--json");
|
|
265
|
+
const ctx = createContext(defaultHomeDir(), json);
|
|
266
|
+
const program = new Command();
|
|
267
|
+
program.name("v-cli").description("kevlns \u7684\u4E2A\u4EBA\u5DE5\u5177\u7BB1").version(VERSION).option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").showHelpAfterError();
|
|
268
|
+
const loaded = await loadAllCommands(ctx);
|
|
269
|
+
for (const item of loaded) {
|
|
270
|
+
if (!item.command) {
|
|
271
|
+
ctx.log.warn(`${item.file ?? "?"} \u52A0\u8F7D\u5931\u8D25: ${item.error ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const cmd = item.command;
|
|
275
|
+
if (cmd.hidden) continue;
|
|
276
|
+
const sub = program.command(cmd.name).description(cmd.description);
|
|
277
|
+
cmd.register(sub, ctx);
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
await program.parseAsync(args, { from: "user" });
|
|
281
|
+
return 0;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (err instanceof CommanderError) {
|
|
284
|
+
return err.exitCode ?? 1;
|
|
285
|
+
}
|
|
286
|
+
ctx.log.error(err instanceof Error ? err.message : String(err));
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
main().then(
|
|
291
|
+
(code) => process.exit(code),
|
|
292
|
+
(err) => {
|
|
293
|
+
process.stderr.write(`[v-cli] \u672A\u6355\u83B7\u5F02\u5E38: ${err instanceof Error ? err.stack ?? err.message : String(err)}
|
|
294
|
+
`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kevlns/v-cli",
|
|
3
|
+
"version": "0.1.1-beta.1",
|
|
4
|
+
"description": "kevlns 的个人工具箱 CLI(插件化架构,内置 + 本地插件)+ doctor/plugin/ts 命令",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"v-cli": "dist/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"prepack": "npm run build",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"check": "npm run build && npm run typecheck && npm test"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "kevlns <936277610@qq.com>",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/kevlns/v-cli.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/kevlns/v-cli/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/kevlns/v-cli#readme",
|
|
32
|
+
"private": false,
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"cli",
|
|
38
|
+
"toolbox",
|
|
39
|
+
"personal"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^12.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.14.0",
|
|
46
|
+
"tsup": "^8.3.0",
|
|
47
|
+
"typescript": "^5.6.0",
|
|
48
|
+
"vitest": "^4.1.10"
|
|
49
|
+
},
|
|
50
|
+
"allowScripts": {
|
|
51
|
+
"esbuild@0.27.2": true
|
|
52
|
+
}
|
|
53
|
+
}
|