standards-cli 1.0.0

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 ADDED
@@ -0,0 +1,257 @@
1
+ # standards-cli
2
+
3
+ 一键初始化前端项目提交规范链路(cz-git + commitlint + husky + lint-staged)
4
+
5
+ ## 功能特性
6
+
7
+ - 🚀 一键初始化提交规范链路
8
+ - 📦 自动安装所需依赖
9
+ - 🔧 智能检测包管理器(pnpm/bun/yarn/npm)
10
+ - 🤖 智能检测 eslint/prettier,自动选择合适的 lint-staged 配置
11
+ - 📝 生成标准配置文件(引用模板,不复制)
12
+ - 🎯 支持 Vue3/ESM 项目
13
+ - ⚙️ husky hooks 自动配置
14
+
15
+ ## 使用方式
16
+
17
+ ### 方式一:npx(推荐)- 无需全局安装
18
+
19
+ ```bash
20
+ npx standards-cli init
21
+ ```
22
+
23
+ `npx` 会从 npm registry 临时下载包并运行,适合临时使用或测试。
24
+
25
+ ### 方式二:作为项目依赖安装 - 推荐用于团队项目
26
+
27
+ ```bash
28
+ # 在项目根目录下安装
29
+ pnpm add -D standards-cli
30
+ # 或
31
+ npm install -D standards-cli
32
+ # 或
33
+ yarn add -D standards-cli
34
+ # 或
35
+ bun add -d standards-cli
36
+ ```
37
+
38
+ 安装后,通过 npm scripts 或 pnpm exec 运行:
39
+
40
+ ```bash
41
+ # 使用 npm scripts
42
+ npm run standards init
43
+
44
+ # 或使用 pnpm exec
45
+ pnpm exec standards init
46
+ # 或使用 yarn
47
+ yarn standards init
48
+ # 或使用 bunx
49
+ bunx standards init
50
+ ```
51
+
52
+ **优点:**
53
+
54
+ - 所有团队成员自动获得相同版本
55
+ - 无需全局安装
56
+ - 版本锁定在 package.json 中
57
+
58
+ ### 方式三:全局安装 - 可在任何目录运行
59
+
60
+ ```bash
61
+ npm install -g standards-cli
62
+ # 或
63
+ pnpm add -g standards-cli
64
+ # 或
65
+ yarn global add standards-cli
66
+ # 或
67
+ bun add -g standards-cli
68
+ ```
69
+
70
+ 全局安装后,可以在任何目录直接运行 `standards init`,适合频繁使用。
71
+
72
+ ## 命令参数
73
+
74
+ ```bash
75
+ standards init [选项]
76
+
77
+ 选项:
78
+ --pm <pnpm|bun|yarn|npm> 指定包管理器,跳过选择
79
+ --yes 默认 yes,跳过安装确认
80
+ --no-install 只生成文件,不安装依赖
81
+
82
+ 示例:
83
+ standards init
84
+ standards init --pm pnpm
85
+ standards init --yes
86
+ standards init --no-install
87
+ ```
88
+
89
+ ## 生成的文件
90
+
91
+ 运行 `standards init` 后,会在项目根目录生成以下文件:
92
+
93
+ ```
94
+ ├── commitlint.config.cjs # commitlint 配置(引用模板)
95
+ ├── cz.config.cjs # cz-git 配置(引用模板)
96
+ ├── .lintstagedrc.cjs # lint-staged 配置(根据检测自动选择)
97
+ └── .husky/
98
+ ├── pre-commit # pre-commit hook
99
+ └── commit-msg # commit-msg hook
100
+ ```
101
+
102
+ ### package.json 变更
103
+
104
+ ```json
105
+ {
106
+ "scripts": {
107
+ "prepare": "husky install",
108
+ "cz": "cz",
109
+ "commit": "cz"
110
+ },
111
+ "config": {
112
+ "commitizen": {
113
+ "path": "cz-git"
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## 安装的依赖
120
+
121
+ - `husky` - Git hooks 管理
122
+ - `lint-staged` - 暂存区文件检查
123
+ - `@commitlint/cli` - 提交信息检查
124
+ - `@commitlint/config-conventional` - 提交信息规范
125
+ - `cz-git` - 交互式提交工具
126
+ - `commitizen` - 提交工具框架
127
+
128
+ ## 使用方式
129
+
130
+ 初始化完成后,可以使用以下两种方式提交:
131
+
132
+ ### 方式一:交互式提交(推荐)
133
+
134
+ ```bash
135
+ pnpm commit
136
+ # 或
137
+ pnpm cz
138
+ ```
139
+
140
+ 使用交互式界面选择提交类型、填写描述等,自动生成符合规范的提交信息。
141
+
142
+ **交互流程:**
143
+
144
+ 1. 选择提交类型(feat/fix/docs/...)
145
+ 2. **填写 scope(对应代码模块)** - 必填,手动输入如 `auth`
146
+ 3. 填写简短描述(subject)- 必填
147
+ 4. 确认提交
148
+
149
+ **最终提交信息格式:** `feat (auth): add login feature`
150
+
151
+ ### 方式二:直接提交
152
+
153
+ ```bash
154
+ git commit -m "feat: add new feature"
155
+ ```
156
+
157
+ 直接使用符合规范的提交信息格式提交,commit-msg hook 会自动验证。
158
+
159
+ **注意:** 无论使用哪种方式,commit-msg hook 都会拦截不符合规范的提交信息。
160
+
161
+ ## lint-staged 智能配置
162
+
163
+ CLI 会自动检测项目是否已安装 `eslint` 和 `prettier`,并生成对应的 lint-staged 配置:
164
+
165
+ **场景一:项目没有安装 eslint/prettier**
166
+
167
+ - lint-staged 配置为**简单模式**(仅 echo,不运行 eslint/prettier)
168
+ - 目的:只规范提交信息,避免 lint-staged 报错
169
+
170
+ **场景二:项目已安装 eslint/prettier**
171
+
172
+ - lint-staged 配置为**完整模式**(包含 `eslint --fix` 和 `prettier --write`)
173
+ - 目的:完整的代码检查和格式化
174
+
175
+ **如需切换模式:**
176
+
177
+ - 安装 eslint/prettier 后重新运行 `standards init`
178
+ - 或手动修改 `.lintstagedrc.cjs` 配置
179
+
180
+ ## 提交类型
181
+
182
+ 支持以下提交类型:
183
+
184
+ | 类型 | 说明 |
185
+ | -------- | -------------------------- |
186
+ | feat | 新功能 |
187
+ | fix | 修复 bug |
188
+ | docs | 文档更新 |
189
+ | style | 代码格式调整(不影响功能) |
190
+ | refactor | 重构 |
191
+ | perf | 性能优化 |
192
+ | test | 测试相关 |
193
+ | build | 构建系统或外部依赖变动 |
194
+ | ci | CI 配置变动 |
195
+ | chore | 其他杂项 |
196
+ | revert | 回滚提交 |
197
+
198
+ ## 提交信息格式
199
+
200
+ 提交信息必须符合以下格式:
201
+
202
+ ```
203
+ <type>(<scope>): <subject>
204
+
205
+ <body>
206
+
207
+ <footer>
208
+ ```
209
+
210
+ - `type`: 必选,提交类型(见上表)
211
+ - `scope`: 必选,影响范围(对应代码模块,如 `auth`, `user`)
212
+ - `subject`: 必选,简短描述(最多 72 字符)
213
+ - `body`: 可选,详细描述
214
+ - `footer`: 可选,关联 issue 等
215
+
216
+ **示例:**
217
+
218
+ ```bash
219
+ # 正确
220
+ git commit -m "feat (auth): add login"
221
+ git commit -m "fix (api): resolve login bug"
222
+ git commit -m "docs: update README"
223
+
224
+ # 错误(会被拦截)
225
+ git commit -m "add login"
226
+ git commit -m "fix bug"
227
+ git commit -m "update"
228
+ git commit -m "feat: add login" # 缺少 scope
229
+ ```
230
+
231
+ ## 注意事项
232
+
233
+ 1. **eslint/prettier 依赖**:本 CLI 不安装 eslint 和 prettier,请根据项目需要自行安装
234
+ 2. **lint-staged 配置**:CLI 会自动检测并生成合适的配置
235
+ 3. **Node 版本**:需要 Node.js >= 18
236
+ 4. **提交方式**:`git commit` 和 `pnpm commit` 都会被 commit-msg hook 拦截验证
237
+
238
+ ## 开发
239
+
240
+ ```bash
241
+ # 本地测试
242
+ node src/index.js init
243
+
244
+ # 或使用 npm link
245
+ npm link
246
+ standards init
247
+ ```
248
+
249
+ ## 发布
250
+
251
+ ```bash
252
+ npm publish
253
+ ```
254
+
255
+ ## License
256
+
257
+ MIT
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "standards-cli",
3
+ "version": "1.0.0",
4
+ "description": "一键初始化前端项目提交规范链路(cz-git + commitlint + husky + lint-staged)",
5
+ "type": "module",
6
+ "bin": {
7
+ "standards": "./src/index.js"
8
+ },
9
+ "exports": {
10
+ "./templates/*": "./templates/*"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "templates"
15
+ ],
16
+ "scripts": {
17
+ "test": "node src/index.js init"
18
+ },
19
+ "keywords": [
20
+ "commitlint",
21
+ "husky",
22
+ "lint-staged",
23
+ "cz-git",
24
+ "commitizen",
25
+ "git",
26
+ "conventional-commits",
27
+ "cli"
28
+ ],
29
+ "author": "",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git@github.com:ChenyCHENYU/standards-cli.git"
34
+ },
35
+ "homepage": "https://github.com/yourusername/standards-cli#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/yourusername/standards-cli/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
@@ -0,0 +1,404 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "process";
3
+ import {
4
+ exists,
5
+ readJson,
6
+ writeJson,
7
+ ensureDir,
8
+ writeFileIfMissing,
9
+ writeFile,
10
+ chmodSafe,
11
+ } from "../lib/fs.js";
12
+ import {
13
+ detectPackageManager,
14
+ getInstallCommand,
15
+ getExecCommand,
16
+ isValidPm,
17
+ SUPPORTED_PMS,
18
+ } from "../lib/pm.js";
19
+ import { execLive } from "../lib/exec.js";
20
+
21
+ const rl = readline.createInterface({ input, output });
22
+
23
+ /**
24
+ * 需要安装的依赖列表
25
+ */
26
+ const REQUIRED_DEPS = [
27
+ "husky",
28
+ "lint-staged",
29
+ "@commitlint/cli",
30
+ "@commitlint/config-conventional",
31
+ "cz-git",
32
+ "commitizen",
33
+ ];
34
+
35
+ /**
36
+ * 解析命令行参数
37
+ */
38
+ function parseArgs(args) {
39
+ const result = {
40
+ pm: null,
41
+ yes: false,
42
+ noInstall: false,
43
+ };
44
+
45
+ for (let i = 0; i < args.length; i++) {
46
+ const arg = args[i];
47
+ if (arg === "--pm" && args[i + 1]) {
48
+ result.pm = args[i + 1];
49
+ i++;
50
+ } else if (arg === "--yes") {
51
+ result.yes = true;
52
+ } else if (arg === "--no-install") {
53
+ result.noInstall = true;
54
+ }
55
+ }
56
+
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * 询问用户是否安装依赖
62
+ */
63
+ async function askInstallDeps() {
64
+ const answer = await rl.question("是否安装缺失依赖? (Y/n): ");
65
+ const normalized = answer.trim().toLowerCase();
66
+ return normalized === "" || normalized === "y" || normalized === "yes";
67
+ }
68
+
69
+ /**
70
+ * 询问用户选择包管理器
71
+ */
72
+ async function askPackageManager(defaultPm) {
73
+ const options = SUPPORTED_PMS.map((pm, index) => `${index + 1}. ${pm}`).join(
74
+ " "
75
+ );
76
+ const answer = await rl.question(
77
+ `请选择包管理器 (${options}) [默认: ${defaultPm}]: `
78
+ );
79
+ const normalized = answer.trim().toLowerCase();
80
+
81
+ if (normalized === "") {
82
+ return defaultPm;
83
+ }
84
+
85
+ // 支持直接输入名称
86
+ if (isValidPm(normalized)) {
87
+ return normalized;
88
+ }
89
+
90
+ // 支持输入数字
91
+ const index = parseInt(normalized, 10);
92
+ if (!isNaN(index) && index >= 1 && index <= SUPPORTED_PMS.length) {
93
+ return SUPPORTED_PMS[index - 1];
94
+ }
95
+
96
+ return defaultPm;
97
+ }
98
+
99
+ /**
100
+ * 检查项目是否已安装 eslint/prettier
101
+ */
102
+ function hasLintTools(pkg) {
103
+ const allDeps = {
104
+ ...(pkg.dependencies || {}),
105
+ ...(pkg.devDependencies || {}),
106
+ };
107
+ return !!allDeps.eslint && !!allDeps.prettier;
108
+ }
109
+
110
+ /**
111
+ * 生成配置文件
112
+ */
113
+ async function generateConfigFiles(pkg) {
114
+ console.log("\n📝 生成配置文件...");
115
+
116
+ // commitlint.config.cjs
117
+ const commitlintCreated = await writeFileIfMissing(
118
+ "commitlint.config.cjs",
119
+ `module.exports = require('standards-cli/templates/commitlint/config.cjs');\n`
120
+ );
121
+ if (commitlintCreated) {
122
+ console.log(" ✔ commitlint.config.cjs");
123
+ } else {
124
+ console.log(" ⚠ commitlint.config.cjs (已存在,跳过)");
125
+ }
126
+
127
+ // cz.config.cjs
128
+ const czConfigCreated = await writeFileIfMissing(
129
+ "cz.config.cjs",
130
+ `module.exports = require('standards-cli/templates/commitlint/cz.config.cjs');\n`
131
+ );
132
+ if (czConfigCreated) {
133
+ console.log(" ✔ cz.config.cjs");
134
+ } else {
135
+ console.log(" ⚠ cz.config.cjs (已存在,跳过)");
136
+ }
137
+
138
+ // .lintstagedrc.cjs - 根据是否有 eslint/prettier 选择模板
139
+ const hasLintTools = hasLintTools(pkg);
140
+ const lintstagedTemplate = hasLintTools
141
+ ? "require('standards-cli/templates/lint-staged/config.cjs')"
142
+ : "require('standards-cli/templates/lint-staged/config-simple.cjs')";
143
+ const lintstagedrcCreated = await writeFileIfMissing(
144
+ ".lintstagedrc.cjs",
145
+ `module.exports = ${lintstagedTemplate};\n`
146
+ );
147
+ if (lintstagedrcCreated) {
148
+ console.log(" ✔ .lintstagedrc.cjs");
149
+ } else {
150
+ console.log(" ⚠ .lintstagedrc.cjs (已存在,跳过)");
151
+ }
152
+
153
+ // .husky 目录
154
+ await ensureDir(".husky");
155
+
156
+ // .husky/pre-commit
157
+ const preCommitContent = `#!/bin/sh
158
+ . "$(dirname "$0")/_/husky.sh"
159
+ npx --no-install lint-staged
160
+ `;
161
+ await writeFile(".husky/pre-commit", preCommitContent);
162
+ await chmodSafe(".husky/pre-commit");
163
+ console.log(" ✔ .husky/pre-commit");
164
+
165
+ // .husky/commit-msg
166
+ const commitMsgContent = `#!/bin/sh
167
+ . "$(dirname "$0")/_/husky.sh"
168
+ npx --no-install commitlint --edit "$1"
169
+ `;
170
+ await writeFile(".husky/commit-msg", commitMsgContent);
171
+ await chmodSafe(".husky/commit-msg");
172
+ console.log(" ✔ .husky/commit-msg");
173
+ }
174
+
175
+ /**
176
+ * 修改 package.json
177
+ */
178
+ async function updatePackageJson() {
179
+ console.log("\n📦 更新 package.json...");
180
+
181
+ const pkg = await readJson("package.json");
182
+
183
+ let modified = false;
184
+
185
+ // 确保 scripts 存在
186
+ if (!pkg.scripts) {
187
+ pkg.scripts = {};
188
+ }
189
+
190
+ // 添加 prepare(如果不存在)
191
+ if (!pkg.scripts.prepare) {
192
+ pkg.scripts.prepare = "husky install";
193
+ console.log(' ✔ 添加 scripts.prepare = "husky install"');
194
+ modified = true;
195
+ } else {
196
+ console.log(" ⚠ scripts.prepare (已存在,跳过)");
197
+ }
198
+
199
+ // 添加 cz(如果不存在)
200
+ if (!pkg.scripts.cz) {
201
+ pkg.scripts.cz = "cz";
202
+ console.log(' ✔ 添加 scripts.cz = "cz"');
203
+ modified = true;
204
+ } else {
205
+ console.log(" ⚠ scripts.cz (已存在,跳过)");
206
+ }
207
+
208
+ // 添加 commit(如果不存在)
209
+ if (!pkg.scripts.commit) {
210
+ pkg.scripts.commit = "cz";
211
+ console.log(' ✔ 添加 scripts.commit = "cz"');
212
+ modified = true;
213
+ } else {
214
+ console.log(" ⚠ scripts.commit (已存在,跳过)");
215
+ }
216
+
217
+ // 确保 config 存在
218
+ if (!pkg.config) {
219
+ pkg.config = {};
220
+ }
221
+
222
+ // 添加 commitizen.path(如果不存在)
223
+ if (!pkg.config.commitizen) {
224
+ pkg.config.commitizen = {};
225
+ }
226
+ if (!pkg.config.commitizen.path) {
227
+ pkg.config.commitizen.path = "cz-git";
228
+ console.log(' ✔ 添加 config.commitizen.path = "cz-git"');
229
+ modified = true;
230
+ } else {
231
+ console.log(" ⚠ config.commitizen.path (已存在,跳过)");
232
+ }
233
+
234
+ if (modified) {
235
+ await writeJson("package.json", pkg);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 检查缺失的依赖
241
+ */
242
+ function checkMissingDeps(pkg) {
243
+ const allDeps = {
244
+ ...(pkg.dependencies || {}),
245
+ ...(pkg.devDependencies || {}),
246
+ };
247
+
248
+ const missing = REQUIRED_DEPS.filter((dep) => !allDeps[dep]);
249
+ return missing;
250
+ }
251
+
252
+ /**
253
+ * 安装依赖
254
+ */
255
+ async function installDependencies(pm, missingDeps) {
256
+ console.log("\n📥 安装依赖...");
257
+ const command = getInstallCommand(pm, missingDeps);
258
+ console.log(` 执行: ${command}`);
259
+ const code = await execLive(command);
260
+ if (code !== 0) {
261
+ throw new Error(`依赖安装失败,退出码: ${code}`);
262
+ }
263
+ console.log(" ✔ 依赖安装成功");
264
+ }
265
+
266
+ /**
267
+ * 执行 husky install
268
+ */
269
+ async function runHuskyInstall(pm) {
270
+ console.log("\n🔧 初始化 husky...");
271
+ const command = getExecCommand(pm, "husky install");
272
+ console.log(` 执行: ${command}`);
273
+ const code = await execLive(command);
274
+ if (code !== 0) {
275
+ throw new Error(`husky install 失败,退出码: ${code}`);
276
+ }
277
+ console.log(" ✔ husky 初始化成功");
278
+ }
279
+
280
+ /**
281
+ * 打印成功信息
282
+ */
283
+ function printSuccess(pm, hasLintTools) {
284
+ console.log("\n✅ 提交规范链路初始化完成!\n");
285
+ console.log("📝 使用方式:");
286
+ console.log(` 运行 ${pm} commit 进行交互式提交`);
287
+ console.log(` 或运行 ${pm} cz 进行交互式提交\n`);
288
+ console.log("💡 提示:");
289
+ if (hasLintTools) {
290
+ console.log(" - lint-staged 已配置为完整模式(包含 eslint 和 prettier)");
291
+ console.log(" - 如需调整,请修改 .lintstagedrc.cjs 配置\n");
292
+ } else {
293
+ console.log(" - lint-staged 已配置为简单模式(仅规范提交信息)");
294
+ console.log(" - 如需启用 eslint/prettier,请先安装工具后重新运行 init\n");
295
+ }
296
+ }
297
+
298
+ /**
299
+ * 打印安装命令
300
+ */
301
+ function printInstallCommand(missingDeps, defaultPm) {
302
+ console.log("\n📋 请手动执行以下命令安装依赖:\n");
303
+ for (const pm of SUPPORTED_PMS) {
304
+ const command = getInstallCommand(pm, missingDeps);
305
+ const marker = pm === defaultPm ? " [推荐]" : "";
306
+ console.log(` ${command}${marker}`);
307
+ }
308
+ console.log("\n安装后请执行: husky install\n");
309
+ }
310
+
311
+ /**
312
+ * 主函数
313
+ */
314
+ export async function init(args) {
315
+ try {
316
+ console.log("🚀 standards-cli - 提交规范链路初始化\n");
317
+
318
+ // 解析参数
319
+ const options = parseArgs(args);
320
+
321
+ // 1. 检查 package.json 是否存在
322
+ if (!(await exists("package.json"))) {
323
+ console.error("❌ 错误: 当前目录未找到 package.json");
324
+ console.log(" 请在业务仓库根目录下运行此命令");
325
+ rl.close();
326
+ process.exit(1);
327
+ }
328
+ console.log("✔ 检测到 package.json");
329
+
330
+ // 读取 package.json
331
+ const pkg = await readJson("package.json");
332
+
333
+ // 2. 生成配置文件
334
+ await generateConfigFiles(pkg);
335
+
336
+ // 3. 修改 package.json
337
+ await updatePackageJson();
338
+
339
+ // 4. 检查缺失依赖
340
+ const missingDeps = checkMissingDeps(pkg);
341
+ if (missingDeps.length > 0) {
342
+ console.log(`\n📦 缺失依赖: ${missingDeps.join(", ")}`);
343
+
344
+ if (options.noInstall) {
345
+ printInstallCommand(missingDeps, "pnpm");
346
+ rl.close();
347
+ return;
348
+ }
349
+
350
+ // 询问是否安装
351
+ const shouldInstall = options.yes || (await askInstallDeps());
352
+
353
+ if (shouldInstall) {
354
+ // 确定包管理器
355
+ let pm = options.pm || (await detectPackageManager());
356
+ if (options.pm && !isValidPm(options.pm)) {
357
+ console.warn(`⚠ 不支持的包管理器: ${options.pm},将使用默认值`);
358
+ pm = await detectPackageManager();
359
+ }
360
+ // 只在非 yes 模式下询问包管理器
361
+ if (!options.pm && !options.yes) {
362
+ pm = await askPackageManager(pm);
363
+ }
364
+
365
+ // 安装依赖
366
+ await installDependencies(pm, missingDeps);
367
+
368
+ // 6. 执行 husky install
369
+ await runHuskyInstall(pm);
370
+
371
+ // 7. 打印成功信息
372
+ const hasLintTools = hasLintTools(pkg);
373
+ printSuccess(pm, hasLintTools);
374
+ } else {
375
+ printInstallCommand(missingDeps, "pnpm");
376
+ }
377
+ } else {
378
+ console.log("\n✔ 所有依赖已安装");
379
+
380
+ // 检测包管理器
381
+ const pm = options.pm || (await detectPackageManager());
382
+ if (options.pm && !isValidPm(options.pm)) {
383
+ console.warn(`⚠ 不支持的包管理器: ${options.pm}`);
384
+ }
385
+
386
+ // 执行 husky install
387
+ try {
388
+ await runHuskyInstall(pm);
389
+ } catch (error) {
390
+ console.warn(`⚠ husky install 失败: ${error.message}`);
391
+ }
392
+
393
+ // 打印成功信息
394
+ const hasLintTools = hasLintTools(pkg);
395
+ printSuccess(pm, hasLintTools);
396
+ }
397
+
398
+ rl.close();
399
+ } catch (error) {
400
+ console.error(`\n❌ 初始化失败: ${error.message}`);
401
+ rl.close();
402
+ process.exit(1);
403
+ }
404
+ }
package/src/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { init } from "./commands/init.js";
4
+
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+
8
+ const helpText = `
9
+ standards-cli - 一键初始化前端项目提交规范链路
10
+
11
+ 用法:
12
+ standards init [选项]
13
+
14
+ 选项:
15
+ --pm <pnpm|bun|yarn|npm> 指定包管理器,跳过选择
16
+ --yes 默认 yes,跳过安装确认
17
+ --no-install 只生成文件,不安装依赖
18
+
19
+ 示例:
20
+ standards init
21
+ standards init --pm pnpm
22
+ standards init --yes
23
+ standards init --no-install
24
+ `;
25
+
26
+ async function main() {
27
+ if (
28
+ !command ||
29
+ command === "-h" ||
30
+ command === "--help" ||
31
+ command === "help"
32
+ ) {
33
+ console.log(helpText);
34
+ process.exit(0);
35
+ }
36
+
37
+ if (command === "init") {
38
+ await init(args.slice(1));
39
+ } else {
40
+ console.error(`未知命令: ${command}`);
41
+ console.log(helpText);
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ main().catch((error) => {
47
+ console.error("错误:", error.message);
48
+ process.exit(1);
49
+ });
@@ -0,0 +1,27 @@
1
+ import { spawn } from "child_process";
2
+
3
+ /**
4
+ * 执行命令并打印输出
5
+ * @param {string} command - 命令字符串
6
+ * @param {object} options - 选项
7
+ * @returns {Promise<number>} - 退出码
8
+ */
9
+ export function execLive(command, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ const [cmd, ...args] = command.split(" ");
12
+
13
+ const proc = spawn(cmd, args, {
14
+ shell: true,
15
+ stdio: "inherit",
16
+ ...options,
17
+ });
18
+
19
+ proc.on("close", (code) => {
20
+ resolve(code);
21
+ });
22
+
23
+ proc.on("error", (error) => {
24
+ reject(error);
25
+ });
26
+ });
27
+ }
package/src/lib/fs.js ADDED
@@ -0,0 +1,75 @@
1
+ import { promises as fs } from "fs";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname, join } from "path";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ /**
9
+ * 检查文件或目录是否存在
10
+ */
11
+ export async function exists(path) {
12
+ try {
13
+ await fs.access(path);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * 读取 JSON 文件
22
+ */
23
+ export async function readJson(path) {
24
+ const content = await fs.readFile(path, "utf-8");
25
+ return JSON.parse(content);
26
+ }
27
+
28
+ /**
29
+ * 写入 JSON 文件
30
+ */
31
+ export async function writeJson(path, data) {
32
+ await fs.writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
33
+ }
34
+
35
+ /**
36
+ * 确保目录存在,不存在则创建
37
+ */
38
+ export async function ensureDir(path) {
39
+ if (!(await exists(path))) {
40
+ await fs.mkdir(path, { recursive: true });
41
+ }
42
+ }
43
+
44
+ /**
45
+ * 写入文件(如果不存在)
46
+ * 返回 true 表示写入成功,false 表示已存在跳过
47
+ */
48
+ export async function writeFileIfMissing(path, content) {
49
+ if (await exists(path)) {
50
+ return false;
51
+ }
52
+ await fs.writeFile(path, content, "utf-8");
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * 写入文件(总是写入,覆盖)
58
+ */
59
+ export async function writeFile(path, content) {
60
+ await fs.writeFile(path, content, "utf-8");
61
+ }
62
+
63
+ /**
64
+ * 安全设置文件可执行权限(仅在非 Windows 系统)
65
+ */
66
+ export async function chmodSafe(path) {
67
+ if (process.platform === "win32") {
68
+ return; // Windows 不需要 chmod
69
+ }
70
+ try {
71
+ await fs.chmod(path, 0o755);
72
+ } catch (error) {
73
+ // 失败不致命,忽略
74
+ }
75
+ }
package/src/lib/pm.js ADDED
@@ -0,0 +1,76 @@
1
+
2
+ import { exists } from "./fs.js";
3
+
4
+ /**
5
+ * 支持的包管理器列表
6
+ */
7
+ export const SUPPORTED_PMS = ["pnpm", "bun", "yarn", "npm"];
8
+
9
+ /**
10
+ * 检测当前项目使用的包管理器
11
+ * 通过检查 lock 文件判断
12
+ */
13
+ export async function detectPackageManager() {
14
+ // 检查 pnpm-lock.yaml
15
+ if (await exists("pnpm-lock.yaml")) {
16
+ return "pnpm";
17
+ }
18
+ // 检查 bun.lockb
19
+ if (await exists("bun.lockb")) {
20
+ return "bun";
21
+ }
22
+ // 检查 yarn.lock
23
+ if (await exists("yarn.lock")) {
24
+ return "yarn";
25
+ }
26
+ // 检查 package-lock.json
27
+ if (await exists("package-lock.json")) {
28
+ return "npm";
29
+ }
30
+ // 默认返回 pnpm
31
+ return "pnpm";
32
+ }
33
+
34
+ /**
35
+ * 获取安装命令
36
+ */
37
+ export function getInstallCommand(pm, packages) {
38
+ const deps = packages.join(" ");
39
+ switch (pm) {
40
+ case "pnpm":
41
+ return `pnpm add -D ${deps}`;
42
+ case "bun":
43
+ return `bun add -d ${deps}`;
44
+ case "yarn":
45
+ return `yarn add -D ${deps}`;
46
+ case "npm":
47
+ return `npm i -D ${deps}`;
48
+ default:
49
+ return `pnpm add -D ${deps}`;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 获取执行命令(用于运行本地安装的包)
55
+ */
56
+ export function getExecCommand(pm, command) {
57
+ switch (pm) {
58
+ case "pnpm":
59
+ return `pnpm exec ${command}`;
60
+ case "bun":
61
+ return `bunx ${command}`;
62
+ case "yarn":
63
+ return `yarn ${command}`;
64
+ case "npm":
65
+ return `npx ${command}`;
66
+ default:
67
+ return `npx ${command}`;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 验证包管理器是否支持
73
+ */
74
+ export function isValidPm(pm) {
75
+ return SUPPORTED_PMS.includes(pm);
76
+ }
@@ -0,0 +1,25 @@
1
+
2
+ module.exports = {
3
+ extends: ["@commitlint/config-conventional"],
4
+ rules: {
5
+ "type-enum": [
6
+ 2,
7
+ "always",
8
+ [
9
+ "feat", // 新功能
10
+ "fix", // 修复 bug
11
+ "docs", // 文档更新
12
+ "style", // 代码格式调整(不影响功能)
13
+ "refactor", // 重构
14
+ "perf", // 性能优化
15
+ "test", // 测试相关
16
+ "build", // 构建系统或外部依赖变动
17
+ "ci", // CI 配置变动
18
+ "chore", // 其他杂项
19
+ "revert", // 回滚提交
20
+ ],
21
+ ],
22
+ "subject-empty": [2, "never"],
23
+ "subject-max-length": [2, "always", 72],
24
+ },
25
+ };
@@ -0,0 +1,45 @@
1
+ module.exports = {
2
+ types: [
3
+ { value: "feat", name: "feat: 新功能 (feature)" },
4
+ { value: "fix", name: "fix: 修复 bug (fix)" },
5
+ { value: "docs", name: "docs: 文档更新 (documentation)" },
6
+ { value: "style", name: "style: 代码格式调整(不影响功能)(style)" },
7
+ { value: "refactor", name: "refactor: 重构 (refactor)" },
8
+ { value: "perf", name: "perf: 性能优化 (performance)" },
9
+ { value: "test", name: "test: 测试相关 (test)" },
10
+ { value: "build", name: "build: 构建系统或外部依赖变动 (build)" },
11
+ { value: "ci", name: "ci: CI 配置变动 (ci)" },
12
+ { value: "chore", name: "chore: 其他杂项 (chore)" },
13
+ { value: "revert", name: "revert: 回滚提交 (revert)" },
14
+ ],
15
+
16
+ // 交互提示信息
17
+ messages: {
18
+ type: "选择你要提交的类型:",
19
+ scope: "填写 scope (对应代码模块):",
20
+ customScope: "请输入自定义 scope:",
21
+ subject: "填写简短描述 (必填):",
22
+ confirmCommit: "确认提交?",
23
+ },
24
+
25
+ // 是否允许自定义 scope
26
+ allowCustomScopes: true,
27
+
28
+ // 是否允许空 scope(false 表示必须填写)
29
+ allowEmptyScopes: false,
30
+
31
+ // scope 是否从枚举列表选择(false 表示手动输入)
32
+ scopeEnum: false,
33
+
34
+ // 是否允许自定义 type
35
+ allowCustomTypes: false,
36
+
37
+ // 是否允许空 subject
38
+ allowEmptySubject: false,
39
+
40
+ // subject 最大长度
41
+ subjectMaxLength: 72,
42
+
43
+ // 是否允许 breaking 变更
44
+ allowBreakingChanges: ["feat", "fix"],
45
+ };
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ ###
3
+ # @Author: ChenYu ycyplus@gmail.com
4
+ # @Date: 2026-01-11 22:50:56
5
+ # @LastEditors: ChenYu ycyplus@gmail.com
6
+ # @LastEditTime: 2026-01-11 22:50:58
7
+ # @FilePath: \frontend-standards\templates\husky\commit-msg.sh
8
+ # @Description:
9
+ # Copyright (c) 2026 by CHENY, All Rights Reserved 😎.
10
+ ###
11
+ . "$(dirname "$0")/_/husky.sh"
12
+ npx --no-install commitlint --edit "$1"
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ ###
3
+ # @Author: ChenYu ycyplus@gmail.com
4
+ # @Date: 2026-01-11 22:50:49
5
+ # @LastEditors: ChenYu ycyplus@gmail.com
6
+ # @LastEditTime: 2026-01-11 22:50:52
7
+ # @FilePath: \frontend-standards\templates\husky\pre-commit.sh
8
+ # @Description:
9
+ # Copyright (c) 2026 by CHENY, All Rights Reserved 😎.
10
+ ###
11
+ . "$(dirname "$0")/_/husky.sh"
12
+ npx --no-install lint-staged
@@ -0,0 +1,7 @@
1
+
2
+ module.exports = {
3
+ "*.{js,jsx,ts,tsx,vue}": ['echo "Checking JS/Vue files..."'],
4
+ "*.{js,jsx,ts,tsx,vue,css,scss,less,md,json,yml,yaml}": [
5
+ 'echo "Formatting files..."',
6
+ ],
7
+ };
@@ -0,0 +1,13 @@
1
+ /*
2
+ * @Author: ChenYu ycyplus@gmail.com
3
+ * @Date: 2026-01-11 22:50:42
4
+ * @LastEditors: ChenYu ycyplus@gmail.com
5
+ * @LastEditTime: 2026-01-11 22:50:45
6
+ * @FilePath: \frontend-standards\templates\lint-staged\config.cjs
7
+ * @Description:
8
+ * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.
9
+ */
10
+ module.exports = {
11
+ "*.{js,jsx,ts,tsx,vue}": ["eslint --fix"],
12
+ "*.{js,jsx,ts,tsx,vue,css,scss,less,md,json,yml,yaml}": ["prettier --write"],
13
+ };