@pubinfo/commitlint 2.0.0-rc.3 → 2.0.0-rc.4

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.
@@ -1,587 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { execSync, spawn } from "node:child_process";
3
- import fs from "node:fs";
4
- import { resolve } from "node:path";
5
- import process from "node:process";
6
- import { defineCommand, runMain } from "citty";
7
-
8
- //#region src/config.ts
9
- function isMainBranch() {
10
- try {
11
- try {
12
- const currentBranch = execSync("git branch --show-current", { encoding: "utf8" }).trim();
13
- if (currentBranch) return [
14
- "main",
15
- "master",
16
- "dev",
17
- "test"
18
- ].includes(currentBranch);
19
- } catch {
20
- try {
21
- const currentBranch = execSync("git symbolic-ref --short HEAD", { encoding: "utf8" }).trim();
22
- return [
23
- "main",
24
- "master",
25
- "dev",
26
- "test"
27
- ].includes(currentBranch);
28
- } catch {
29
- return false;
30
- }
31
- }
32
- } catch {
33
- return false;
34
- }
35
- return false;
36
- }
37
- function generateCommitTypes() {
38
- const allTypes = [
39
- "feat",
40
- "fix",
41
- "docs",
42
- "style",
43
- "refactor",
44
- "perf",
45
- "test",
46
- "build",
47
- "ci",
48
- "chore",
49
- "revert",
50
- "types",
51
- "release",
52
- "init",
53
- "wip"
54
- ];
55
- if (isMainBranch()) return allTypes.filter((type$1) => type$1 !== "wip");
56
- return allTypes;
57
- }
58
- function generateCzGitTypes() {
59
- const all = [
60
- {
61
- value: "feat",
62
- name: "feat: ✨ 新功能",
63
- emoji: ":sparkles:"
64
- },
65
- {
66
- value: "fix",
67
- name: "fix: 🐛 修复缺陷",
68
- emoji: ":bug:"
69
- },
70
- {
71
- value: "types",
72
- name: "types: 🎨 类型相关",
73
- emoji: ":art:"
74
- },
75
- {
76
- value: "docs",
77
- name: "docs: 📝 文档更新",
78
- emoji: ":memo:"
79
- },
80
- {
81
- value: "style",
82
- name: "style: 💄 代码格式",
83
- emoji: ":lipstick:"
84
- },
85
- {
86
- value: "refactor",
87
- name: "refactor: ♻️ 代码重构",
88
- emoji: ":recycle:"
89
- },
90
- {
91
- value: "perf",
92
- name: "perf: ⚡️ 性能提升",
93
- emoji: ":zap:"
94
- },
95
- {
96
- value: "test",
97
- name: "test: ✅ 测试相关",
98
- emoji: ":white_check_mark:"
99
- },
100
- {
101
- value: "build",
102
- name: "build: 📦️ 构建相关",
103
- emoji: ":package:"
104
- },
105
- {
106
- value: "release",
107
- name: "release: 🔖 版本提升",
108
- emoji: ":bookmark:"
109
- },
110
- {
111
- value: "ci",
112
- name: "ci: 🎡 持续集成",
113
- emoji: ":ferris_wheel:"
114
- },
115
- {
116
- value: "revert",
117
- name: "revert: ⏪️ 回退代码",
118
- emoji: ":rewind:"
119
- },
120
- {
121
- value: "chore",
122
- name: "chore: 🔨 其他修改",
123
- emoji: ":hammer:"
124
- },
125
- {
126
- value: "init",
127
- name: "init: 🎉 初始化",
128
- emoji: ":tada:"
129
- },
130
- {
131
- value: "wip",
132
- name: "wip: 🚧 工作进行中",
133
- emoji: ":construction:"
134
- }
135
- ];
136
- if (isMainBranch()) return all.filter((t) => t.value !== "wip");
137
- return all;
138
- }
139
- const commitPreset = {
140
- extends: ["@commitlint/config-conventional"],
141
- rules: {
142
- "scope-enum": [0],
143
- "type-enum": [
144
- 2,
145
- "always",
146
- generateCommitTypes()
147
- ],
148
- "type-empty": [2, "never"],
149
- "type-case": [
150
- 2,
151
- "always",
152
- "lower-case"
153
- ],
154
- "subject-empty": [2, "never"],
155
- "subject-full-stop": [
156
- 2,
157
- "never",
158
- "."
159
- ],
160
- "subject-case": [
161
- 2,
162
- "never",
163
- [
164
- "sentence-case",
165
- "start-case",
166
- "pascal-case",
167
- "upper-case"
168
- ]
169
- ],
170
- "header-max-length": [
171
- 2,
172
- "always",
173
- 100
174
- ],
175
- "body-leading-blank": [1, "always"],
176
- "footer-leading-blank": [1, "always"]
177
- },
178
- prompt: {
179
- alias: { fd: "docs: fix typos" },
180
- messages: {
181
- scope: "选择一个提交范围 (可回车跳过):",
182
- customScope: "请输入自定义的提交范围 :",
183
- type: "选择你要提交的类型 :",
184
- subject: "填写简短精炼的变更描述 :\n",
185
- body: "填写更加详细的变更描述(可选)。使用 \"|\" 换行 :\n",
186
- breaking: "列举非兼容性重大的变更(可选)。使用 \"|\" 换行 :\n",
187
- footerPrefixsSelect: "选择关联issue前缀(可选):",
188
- customFooterPrefixs: "输入自定义issue前缀 :",
189
- footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
190
- confirmCommit: "是否提交或修改commit ?"
191
- },
192
- types: generateCzGitTypes(),
193
- useEmoji: false,
194
- emojiAlign: "center",
195
- themeColorCode: "",
196
- enableMultipleScopes: true,
197
- skipQuestions: ["scope"],
198
- defaultScope: "___CUSTOM___:",
199
- customScopesAlign: "bottom",
200
- customScopesAlias: "custom",
201
- emptyScopesAlias: "empty",
202
- upperCaseSubject: false,
203
- markBreakingChangeMode: true,
204
- allowBreakingChanges: ["feat", "fix"],
205
- breaklineNumber: 100,
206
- breaklineChar: "|",
207
- issuePrefixs: [{
208
- value: "link",
209
- name: "link: 链接 ISSUES 进行中"
210
- }, {
211
- value: "closed",
212
- name: "closed: 标记 ISSUES 已完成"
213
- }],
214
- customIssuePrefixsAlign: "top",
215
- emptyIssuePrefixsAlias: "skip",
216
- customIssuePrefixsAlias: "custom",
217
- allowCustomIssuePrefixs: true,
218
- allowEmptyIssuePrefixs: true,
219
- confirmColorize: true,
220
- maxHeaderLength: Number.POSITIVE_INFINITY,
221
- maxSubjectLength: Number.POSITIVE_INFINITY,
222
- minSubjectLength: 0,
223
- scopeOverrides: void 0,
224
- defaultBody: "",
225
- defaultIssues: "",
226
- defaultSubject: ""
227
- }
228
- };
229
- function loadCommitConfig() {
230
- return commitPreset;
231
- }
232
-
233
- //#endregion
234
- //#region src/prompt.ts
235
- async function runPrompt(cfg, opts) {
236
- try {
237
- const require = createRequire(import.meta.url);
238
- try {
239
- const czg = require("czg");
240
- if (czg && typeof czg.default === "function") {
241
- const api = await czg.default({ emoji: opts.emoji });
242
- if (api && api.raw) return { raw: api.raw };
243
- }
244
- } catch {}
245
- const bin$1 = resolve(process.cwd(), "node_modules/.bin/czg");
246
- if (fs.existsSync(bin$1)) {
247
- await new Promise((res) => {
248
- const child = spawn(bin$1, opts.emoji ? ["emoji"] : [], { stdio: "inherit" });
249
- child.on("exit", () => res());
250
- child.on("error", () => res());
251
- });
252
- const msgPath = resolve(process.cwd(), ".git/COMMIT_EDITMSG");
253
- if (fs.existsSync(msgPath)) {
254
- const raw = fs.readFileSync(msgPath, "utf8").trim();
255
- return { raw };
256
- }
257
- }
258
- return null;
259
- } catch {
260
- return null;
261
- }
262
- }
263
-
264
- //#endregion
265
- //#region package.json
266
- var name = "@pubinfo/commitlint";
267
- var type = "module";
268
- var version = "2.0.0-rc.3";
269
- var description = "commitlint config for Pubinfo projects";
270
- var exports = { ".": "./dist/index.js" };
271
- var main$1 = "./dist/index.js";
272
- var module = "./dist/index.js";
273
- var types = "./dist/index.d.ts";
274
- var typesVersions = { "*": { "*": ["./dist/*", "./*"] } };
275
- var bin = { "pubinfo-commit": "./bin/commit.js" };
276
- var files = ["bin", "dist"];
277
- var scripts = {
278
- "build": "tsdown",
279
- "stub": "tsdown --watch"
280
- };
281
- var dependencies = {
282
- "@commitlint/config-conventional": "catalog:commit",
283
- "@commitlint/lint": "catalog:commit",
284
- "@commitlint/load": "catalog:commit",
285
- "citty": "catalog:node",
286
- "czg": "catalog:commit"
287
- };
288
- var package_default = {
289
- name,
290
- type,
291
- version,
292
- description,
293
- exports,
294
- main: main$1,
295
- module,
296
- types,
297
- typesVersions,
298
- bin,
299
- files,
300
- scripts,
301
- dependencies
302
- };
303
-
304
- //#endregion
305
- //#region src/runner.ts
306
- async function lintMessage(message) {
307
- try {
308
- const lintMod = await import("@commitlint/lint");
309
- const result = await lintMod.default(message, commitPreset.rules, commitPreset);
310
- if (!result.valid) {
311
- console.error("✖ Commit message failed lint:\n");
312
- for (const err of result.errors) console.error(` • ${err.message}`);
313
- process.exit(1);
314
- }
315
- } catch (e) {
316
- console.error("[pubinfo-commit] lint failed to run:", e);
317
- }
318
- return true;
319
- }
320
- async function lintAndReturn(message) {
321
- await lintMessage(message);
322
- return message;
323
- }
324
-
325
- //#endregion
326
- //#region src/setup.ts
327
- /**
328
- * 生成 cz-git 配置文件 (cz.config.cjs)。
329
- * 用途:
330
- * - 让 cz-git 交互界面显示中文提示、动态类型/范围。
331
- * - 保持与内置 commitPreset.prompt 同步。
332
- * 行为:始终覆盖写入(只要目录存在/可创建),不再跳过已存在文件。
333
- */
334
- function createCzConfigFile(cwd = process.cwd()) {
335
- const dir = resolve(cwd, ".pubinfo");
336
- const target = resolve(dir, "cz.config.cjs");
337
- try {
338
- fs.mkdirSync(dir, { recursive: true });
339
- const cfg = {
340
- $schema: "https://raw.githubusercontent.com/Zhengqbbb/cz-git/refs/tags/v1.12.0/docs/public/schema/cz-git.json",
341
- ...commitPreset.prompt,
342
- types: commitPreset.prompt?.types?.map((t) => ({
343
- value: t.value,
344
- name: t.name,
345
- emoji: t.emoji
346
- })),
347
- scopes: commitPreset.prompt?.scopes
348
- };
349
- const content = `module.exports = ${JSON.stringify(cfg, null, 2)}\n`;
350
- fs.writeFileSync(target, content, "utf8");
351
- return true;
352
- } catch {
353
- return false;
354
- }
355
- }
356
- /**
357
- * 生成 .gitmessage 模板,便于 IDE / Git 原生命令在编辑器中提供格式示例。
358
- * 若用户已自定义则不覆盖。
359
- */
360
- function createCommitTemplate(cwd = process.cwd()) {
361
- const target = resolve(cwd, ".gitmessage");
362
- if (fs.existsSync(target)) return false;
363
- const types$1 = (commitPreset.prompt?.types || []).map((t) => t.value).join(", ");
364
- const scopes = (commitPreset.prompt?.scopes || []).join(", ");
365
- const tpl = `# Commit Message 模板 (首行: <type>(<scope>): <subject>)\n# 可用类型: ${types$1}\n# 可用范围: ${scopes}\n# 示例: feat(core): add http client abstraction\n# 空行后可写入 body, 使用 | 换行 (交互模式会自动处理)\n`;
366
- fs.writeFileSync(target, tpl, "utf8");
367
- return true;
368
- }
369
- /**
370
- * 将 Git 全局/仓库级 commit.template 指向 .gitmessage(若尚未设置)。
371
- * 失败会静默忽略,不影响主流程。
372
- */
373
- function ensureGitTemplate(cwd = process.cwd()) {
374
- try {
375
- const existing = execSync("git config --get commit.template", {
376
- cwd,
377
- stdio: "pipe"
378
- }).toString().trim();
379
- if (existing) return false;
380
- } catch {}
381
- try {
382
- execSync("git config commit.template .gitmessage", {
383
- cwd,
384
- stdio: "ignore"
385
- });
386
- return true;
387
- } catch {
388
- return false;
389
- }
390
- }
391
- /**
392
- * 确保 package.json 中 simple-git-hooks 配置包含:
393
- * - pre-commit: 仅 lint-staged 处理已暂存文件 + 全量 lint 兜底
394
- * - commit-msg: 动态选择使用统一 CLI(pubinfo) 还是回退二进制(pubinfo-commit)
395
- * 逻辑:
396
- * 1. 根据依赖判断是否有 pubinfo / @pubinfo/cli
397
- * 2. 幂等写入,无差异则不改动文件
398
- */
399
- function ensurePackageGitHooks(cwd = process.cwd()) {
400
- const pkgPath = resolve(cwd, "package.json");
401
- if (!fs.existsSync(pkgPath)) return false;
402
- try {
403
- const raw = fs.readFileSync(pkgPath, "utf8");
404
- const json = JSON.parse(raw);
405
- json["simple-git-hooks"] = json["simple-git-hooks"] || {};
406
- const desiredPre = "pnpm lint-staged && pnpm run lint";
407
- if (json["simple-git-hooks"]["pre-commit"] !== desiredPre) json["simple-git-hooks"]["pre-commit"] = desiredPre;
408
- const hasPubinfo = Boolean(json.dependencies && (json.dependencies.pubinfo || json.dependencies["@pubinfo/cli"]) || json.devDependencies && (json.devDependencies.pubinfo || json.devDependencies["@pubinfo/cli"]));
409
- const desiredCommitMsg = hasPubinfo ? "pnpm exec pubinfo commit --edit $1" : "pnpm exec pubinfo-commit --edit $1";
410
- if (json["simple-git-hooks"]["commit-msg"] !== desiredCommitMsg) json["simple-git-hooks"]["commit-msg"] = desiredCommitMsg;
411
- fs.writeFileSync(pkgPath, `${JSON.stringify(json, null, 2)}\n`, "utf8");
412
- return true;
413
- } catch {
414
- return false;
415
- }
416
- }
417
- /**
418
- * 确保 package.json 中的 commitizen 配置指向迁移后的 .pubinfo/cz.config.cjs。
419
- * 适用场景:用户仍然使用 git-cz / czg / commitizen 入口时,能复用同一份配置。
420
- * 规则:
421
- * - 若无 config.commitizen,则创建 { path: 'node_modules/cz-git', czConfig: '.pubinfo/cz.config.cjs' }
422
- * - 若已有但未声明 czConfig 或旧路径 (如 cz.config.cjs / ./cz.config.cjs),更新为新路径。
423
- * - 不主动覆盖指向其它自定义文件的 czConfig(若用户显式设置且非根默认文件名)。
424
- */
425
- function ensureCommitizenConfig(cwd = process.cwd()) {
426
- const pkgPath = resolve(cwd, "package.json");
427
- if (!fs.existsSync(pkgPath)) return false;
428
- try {
429
- const raw = fs.readFileSync(pkgPath, "utf8");
430
- const json = JSON.parse(raw);
431
- const desiredPath = "node_modules/cz-git";
432
- const desiredCzConfig = ".pubinfo/cz.config.cjs";
433
- json.config = json.config || {};
434
- json.config.commitizen = json.config.commitizen || {};
435
- let changed = false;
436
- if (json.config.commitizen.path !== desiredPath) {
437
- json.config.commitizen.path = desiredPath;
438
- changed = true;
439
- }
440
- const currentCz = json.config.commitizen.czConfig;
441
- const normalize = (p) => p ? p.replace(/^\.\/?/, "") : p;
442
- const isOldRoot = normalize(currentCz) === "cz.config.cjs" || normalize(currentCz) === "cz.config.js";
443
- if (!currentCz || isOldRoot || normalize(currentCz) === "cz.config.mjs") {
444
- if (currentCz !== desiredCzConfig) {
445
- json.config.commitizen.czConfig = desiredCzConfig;
446
- changed = true;
447
- }
448
- }
449
- if (!changed) return false;
450
- fs.writeFileSync(pkgPath, `${JSON.stringify(json, null, 2)}\n`, "utf8");
451
- return true;
452
- } catch {
453
- return false;
454
- }
455
- }
456
- /**
457
- * 调用 simple-git-hooks 安装脚本,生成 .git/hooks/* 实体文件。
458
- * 使用 npx 以兼容未在 PATH 的场景。
459
- */
460
- function runSimpleGitHooksInstall(cwd = process.cwd()) {
461
- try {
462
- execSync("npx simple-git-hooks", {
463
- cwd,
464
- stdio: "ignore"
465
- });
466
- return true;
467
- } catch {
468
- return false;
469
- }
470
- }
471
- /**
472
- * 一键初始化提交环境:
473
- * - 生成 .gitmessage / cz.config.cjs
474
- * - 设置 commit.template
475
- * - 写入 simple-git-hooks 配置并安装
476
- * 返回各步骤布尔状态,便于上层打印调试。
477
- */
478
- function initCommitEnvironment(cwd = process.cwd()) {
479
- const createdTpl = createCommitTemplate(cwd);
480
- const createdCz = createCzConfigFile(cwd);
481
- const setTpl = ensureGitTemplate(cwd);
482
- const updatedPkg = ensurePackageGitHooks(cwd);
483
- const updatedCommitizen = ensureCommitizenConfig(cwd);
484
- const installed = runSimpleGitHooksInstall(cwd);
485
- return {
486
- createdTpl,
487
- createdCz,
488
- setTpl,
489
- updatedPkg,
490
- updatedCommitizen,
491
- installed
492
- };
493
- }
494
-
495
- //#endregion
496
- //#region src/cli.ts
497
- /**
498
- * 本文件提供 `pubinfo-commit` 可执行入口(由 package.json bin 指向)。
499
- * 作用:
500
- * 1. 在交互模式下通过 cz-git 生成符合约定式规范的提交信息(中文提示)。
501
- * 2. 在 `commit-msg` 钩子中以 `--edit` 方式读取 Git 临时文件并进行 lint 校验。
502
- * 3. 通过 `--init` 一键生成交互配置、提交模板与 hook 配置。
503
- * 4. 通过 `-m` 直接传入消息字符串做快速校验,不真正执行 git commit。
504
- *
505
- * 注意:不直接调用 `git commit`,保持透明度,让调用方自主执行提交动作。
506
- */
507
- const main = defineCommand({
508
- meta: {
509
- name: package_default.name,
510
- version: package_default.version,
511
- description: package_default.description
512
- },
513
- args: {
514
- "message": {
515
- type: "string",
516
- description: "直接提交的 commit message (跳过交互)",
517
- alias: "m"
518
- },
519
- "edit": {
520
- type: "string",
521
- description: "Git 钩子传入的 commit message 文件路径 (--edit $1)"
522
- },
523
- "init": {
524
- type: "boolean",
525
- description: "初始化 commit 环境 (模板 / cz / git hooks)"
526
- },
527
- "signoff": {
528
- type: "boolean",
529
- description: "Add Signed-off-by line (占位)"
530
- },
531
- "amend": {
532
- type: "boolean",
533
- description: "Amend previous commit (占位)"
534
- },
535
- "no-verify": {
536
- type: "boolean",
537
- description: "Bypass pre-commit and commit-msg hooks (占位)"
538
- },
539
- "emoji": {
540
- type: "boolean",
541
- description: "输出带 emoji 的 message (交互模式)"
542
- }
543
- },
544
- run: async ({ args }) => {
545
- const { message, edit, init } = args;
546
- if (init) {
547
- const r = initCommitEnvironment();
548
- console.log("[pubinfo-commit] init result:", r);
549
- return;
550
- }
551
- if (edit) {
552
- try {
553
- const raw = fs.readFileSync(String(edit), "utf8").trim();
554
- if (!raw) {
555
- console.error("[pubinfo-commit] 空的 commit message 文件");
556
- return;
557
- }
558
- await lintMessage(raw);
559
- } catch (e) {
560
- console.error("[pubinfo-commit] 读取 --edit 文件失败", e);
561
- }
562
- return;
563
- }
564
- if (message) {
565
- await lintMessage(String(message));
566
- console.log("\n✔ 已通过 lint,可执行: git commit -m %s");
567
- return;
568
- }
569
- const cfg = loadCommitConfig();
570
- createCzConfigFile();
571
- const result = await runPrompt(cfg, { emoji: !!args.emoji });
572
- if (!result) {
573
- console.error("[pubinfo-commit] 未找到 cz-git 或交互被中断");
574
- return;
575
- }
576
- await lintMessage(result.raw);
577
- }
578
- });
579
-
580
- //#endregion
581
- //#region src/run.ts
582
- function runMain$1() {
583
- return runMain(main);
584
- }
585
-
586
- //#endregion
587
- export { commitPreset, createCommitTemplate, createCzConfigFile, ensureGitTemplate, ensurePackageGitHooks, initCommitEnvironment, lintAndReturn, lintMessage, loadCommitConfig, runMain$1 as runMain, runPrompt };