├── .gitignore ├── README.md ├── config ├── config.js └── default.template.md ├── doc.md ├── example ├── gitwork.out.month-week.html ├── gitwork.out.month-week.md ├── gitwork.out.week.html └── gitwork.out.week.md ├── index.js ├── lib └── opener.js ├── package.json ├── util.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .history 3 | *.out.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitwork 2 | 一秒写出漂亮的工作报告! 3 | 4 | **效果演示** 5 | 6 | - [👉 多项目 week 格式的报告](https://wll8.github.io/gitday/example/gitwork.out.week.html) 7 | - [👉 多项目 month-week 格式的报告](https://wll8.github.io/gitday/example/gitwork.out.month-week.html) 8 | 9 | ## 为什么开发它 10 | 程序员的工作基本都在编码, 所以我们可以直接读取避免记录生成工作报告. 对于每周都在开几次任务进度会, 还得每周都要写工作报告, 在工作报告中, 有注意到大家的工作报告基本都是千篇一律: 11 | 12 | 测试: 13 | > 本周创建了 x 个 bug, 测试了 x 个bug, 关闭了 x 个 bug... 下周计划: 编写测试报告, 继续测试. 14 | 15 | 开发: 16 | > 本周修复 xx bug, 开发了 xx 功能. 下周计划: 修复 xx bug, 开发 xx 功能. 17 | 18 | 周某: 19 | > 意义在哪里? 这得人工写? 我周某人就是不惯着! 20 | 21 | ## 功能 22 | - 支持任意时间段的报告 23 | - 高度可自定义, 例如模板/变量/git作者名/项目名转换... 24 | - 支持根据 commit message 规范转换任务风格, 例如 `fix(cli): xxx` 将被转换为 `🐛修复cli中的缺陷: xxx` 25 | - 支持模板插槽 26 | - 支持从多个项目目录中读取数据生成一个工作报告 27 | - 支持自动过滤较高相似并的任务(避免看起来一个任务在报告中出现了几次) 28 | - [ ] 支持生成工作小结 29 | - [ ] 支持直接转换为 pdf/邮件/html/jpeg/... 30 | 31 | ## 使用 32 | ``` sh 33 | # 安装 34 | npm i -g gitwork 35 | 36 | # 生成当月工作报告 37 | gitwork 38 | 39 | # 指定开始时间生成报告 40 | gitwork after=2021-01-01 41 | 42 | # 生成周报 43 | gitwork template=week 44 | 45 | # 查看使用说明 46 | gitwork --help 47 | 48 | # 查看版本号 49 | gitwork -v 50 | 51 | # 打开配置文件所在位置 52 | gitwork --config 53 | ``` 54 | 55 | ## 命令行参数 56 | - -v, --version 显示程序版本号 57 | - --help 显示帮助页面 58 | - --config 打开配置文件所在位置 59 | - --select 选择报告配置 60 | 61 | ## 可配置项 62 | 这些选项来自配置文件,你可以使用 `gitwork --config` 打开配置文件所在位置,也可以在使用时通过命令行设置报告参数, 例如 `gitwork author=wll8` 。 63 | 64 | - select 65 | - [x] 你可以使用配置文件保存多个报告,批量生成它们,多个使用逗号分割。 66 | - 可选值 67 | - default 程序默认的报告配置 68 | - 其他自己在配置文件中添加的模板标志 69 | - 默认值 `default` 70 | - layout 71 | - [ ] 布局方式 72 | - 可选值 73 | - repository-time 先仓库后时间 74 | - time-repository 先时间后仓库 75 | - 默认值 `repository-time` 76 | - author 77 | - [x] 指定要生成报告的 git 用户名 78 | - 可选值 79 | - 其他你自己指定的用户名,多个使用逗号分割 80 | - 默认值 `当前 git 用户`, 即 `git config user.name` 的值 81 | - ignoreAuthor 82 | - 是否忽略按用户名进行过滤 83 | - 可选值 84 | - true 85 | - false 86 | - 默认值 false 87 | - authorName 88 | - [x] 实际输出到报告中的名称, 例如 git 用户名和报告中所需姓名不同时 89 | - 默认值 `当前 git 用户` 90 | - template 91 | - [x] 报告的时间编排方式 92 | - 可选值 93 | - month 94 | - month-week 95 | - month-week-day 96 | - week 97 | - week-day 98 | - day 99 | - 默认值 `month-week` 100 | - messageBody 101 | - 如何处理 git commit message 的 body 部分, 由于它的内容不可控, 可能会破坏报告 102 | - 可选值 103 | - raw 原样使用 104 | - none 不使用 105 | - compatible 当含有可能破坏报告的内容时不使用 106 | - 默认值: `compatible` 107 | - useFile 108 | - [x] 使用文件模板, 相对于配置文件目录 109 | - 默认值: `./default.template.md` 110 | - after 111 | - 开始时间, YYYY-MM-DD 格式 112 | - 默认值 `根据 template 转换` 113 | - before 114 | - 结束时间, YYYY-MM-DD 格式 115 | - 默认值 `根据 template 转换` 116 | - outFile 117 | - [ ] 输出文件, 支持 .md .html .pdf .jpeg .word .xlsx, 相对于运行目录 118 | - 默认值: `./gitwork.out.md` 119 | - insertFile 120 | - [ ] 插入文件, 相对于运行目录 121 | - 默认值: `./todo.md` 122 | - rootLevel 123 | - [x] 从多少个#号开始表示生成内容中最高级别标题, 不包含文档根结点标题, 根节点标题的级别应在 report.title 中添加 124 | - 默认值: 2 125 | - repository 126 | - [x] 从哪些仓库中生成报告 127 | - 可选值 128 | - string 本地仓库绝对路径, 多个时使用逗号分割 129 | - array 130 | - path 本地仓库绝对路径 131 | - name 项目名称 132 | - 默认值: 当前仓库 133 | - noEqualMsg 134 | - [x] 过滤掉重复的 msg 135 | - 可选值 136 | - true 137 | - false 138 | - 默认值: true 139 | - similarity 140 | - [x] 过滤掉大于给定相似值的 msg 141 | - 可选值 142 | - 0 到 1 之前的值 143 | - 默认值: 0.75 144 | - messageTypeSimilarity 145 | - [x] 配置 message type 的相似程度 146 | - 可选值 147 | - 0 到 1 之前的值 148 | - 默认值: 0.8 149 | - noUnknownType 150 | - [x] 是否移除未知的 type 151 | - 可选值 152 | - true 153 | - false 154 | - 默认值: false 155 | - messageTypeTemplate 156 | - [x] 配置 message 的生成模板 157 | - 可选值 158 | - 可使用字符串模板, 支持使用 messageConvert[type].des 中的变量 159 | - 默认值: `#{emoji}#{text}` 160 | - messageConvert 161 | - [x] 配置 message 的详细生成规则 162 | - 可选值 163 | - false 不进行转换 164 | - object [自定义请参考](./config/config.js) 165 | - [type] git commit message type 标志 166 | - alias array type 别名 167 | - des type 转换配置 168 | - emoji 表情 169 | - text type 和 scope 的转换模板 170 | - 0 没有 scope 时的模板 171 | - 1 有 scope 时的模板 172 | - 默认值: [参考 config.js](./config/config.js) 173 | 174 | ## todo 175 | - [ ] fix: 应使用保险的方式检查 messageBody 中的内容 176 | - [ ] feat: 如果只有一个时间结点时, 则不显示它. 比如下面内容, 当为本周周报时, 重复显示 `2022年07月 第4周` 是没有意义的. 177 | - [ ] feat: 格式化输出 178 | - [x] 每个 commit 前插入一个空行 179 | - [ ] 多行 commit 消息时再每行后面添加两个空格, 这样默认 markdown 才会显示换行效果 180 | - [x] 移除 commit title 与 body 之前的空行 181 | - [x] 移除 body 后面多于的空行 182 | - [ ] 合并两个空行为一个 183 | - [x] 转换 msg 提交标志, 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx` -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | report: [ 3 | { 4 | select: `default`, // 默认配置, 不可删除 5 | layout: `repository-time`, // 6 | author: [], // 当前用户名 7 | authorName: undefined, // 当前用户名 8 | template: `month-week`, 9 | messageBody: `compatible`, 10 | useFile: `./default.template.md`, 11 | after: ``, 12 | before: ``, 13 | outFile: `./${GET(`package`).name}.out.md`, 14 | insertFile: undefined, 15 | noEqualMsg: true, 16 | rootLevel: 2, 17 | similarity: 0.75, 18 | repository: [], // 当前项目 19 | messageTypeSimilarity: 0.8, 20 | messageTypeTemplate: `@{emoji}@{text}`, 21 | useMessageConvert: true, 22 | messageConvert: { 23 | feat: { 24 | alias: [`feature`], 25 | des: { 26 | emoji: `✨`, 27 | text: [`添加功能: `, `在@{scope}中添加新功能: `], 28 | }, 29 | }, 30 | fix: { 31 | alias: [`fixed`], 32 | des: { 33 | emoji: `🐛`, 34 | text: [`修复缺陷: `, `修复@{scope}中的缺陷: `], 35 | }, 36 | }, 37 | doc: { 38 | alias: [`docs`], 39 | des: { 40 | emoji: `📝`, 41 | text: [`更新文档: `, `更新@{scope}中的文档: `], 42 | }, 43 | }, 44 | style: { 45 | alias: [`styles`], 46 | des: { 47 | emoji: `🌈`, 48 | text: [`优化代码可读性: `, `优化@{scope}中的代码可读性: `], 49 | }, 50 | }, 51 | build: { 52 | alias: [], 53 | des: { 54 | emoji: `📦`, 55 | text: [`更新构建工具: `, `更新@{scope}中的构建工具: `], 56 | }, 57 | }, 58 | refactor: { 59 | alias: [], 60 | des: { 61 | emoji: `♻️`, 62 | text: [`重构代码: `, `重构@{scope}中的代码: `], 63 | }, 64 | }, 65 | revert: { 66 | alias: [], 67 | des: { 68 | emoji: `⏪`, 69 | text: [`回退代码: `, `回退@{scope}中的代码: `], 70 | }, 71 | }, 72 | test: { 73 | alias: [], 74 | des: { 75 | emoji: `🚨`, 76 | text: [`更新测试逻辑: `, `更新@{scope}中的测试逻辑: `], 77 | }, 78 | }, 79 | perf: { 80 | alias: [], 81 | des: { 82 | emoji: `🚀`, 83 | text: [`提升性能: `, `提升@{scope}中的性能: `], 84 | }, 85 | }, 86 | ci: { 87 | alias: [], 88 | des: { 89 | emoji: `💚`, 90 | text: [`更新持续集成服务: `, `更新@{scope}中的持续集成服务: `], 91 | }, 92 | }, 93 | chore: { 94 | alias: [], 95 | des: { 96 | emoji: `🔧`, 97 | text: [`更新辅助工具: `, `更新@{scope}中的辅助工具: `], 98 | }, 99 | }, 100 | }, 101 | ignoreAuthor: false, 102 | }, 103 | ], 104 | } -------------------------------------------------------------------------------- /config/default.template.md: -------------------------------------------------------------------------------- 1 | # @{authorName}的工作报告(@{after} 至 @{before}) 2 | 3 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。 4 | 5 | ## 工作报告 6 | 小结:在这个阶段中,主要精力集中在[XXX]这个项目,以及[修复缺陷]。 7 | 处理[xxx]这项工作消耗了比预期较多的时间,主要原因是[在实现功能上需要处理比较多的逻辑]。使用[xxx这种方案]获取可以得到更好的收益。 8 | 9 | 10 | ### 项目一 11 | 12 | - fix: xxx 13 | des... 14 | 15 | - fix: xxx 16 | 17 | 18 | 19 | ### 项目二 20 | 21 | - fix: xxx 22 | des... 23 | 24 | - fix: xxx 25 | 26 | 27 | 28 | 29 | ## 后续计划 30 | 31 | - item... 32 | - item... 33 | 34 | -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | ## git log 2 | https://git-scm.com/docs/git-log 3 | 4 | - --author 作者 5 | - --all 显示所有分支 6 | - --after=1.months 显示某个时间之后, 如果 authorDate 在两周前, 但 commitDate 在一周前, 也会被查出. 7 | - --no-merges 不包括 merges 8 | 9 | 10 | 11 | - 作者日期:最初创作提交的时间。通常,当有人第一次运行git commit. 12 | - 提交日期:将提交应用于分支的时间。在许多情况下,它与作者日期相同。有时它会有所不同:如果提交被修改、重新设置或由作者以外的人作为补丁的一部分应用。在这些情况下,日期将是变基发生或应用补丁的日期。 13 | 14 | 15 | ## git commit 16 | https://github.com/dannyfritz/commit-message-emoji 17 | https://medium.com/walmartglobaltech/semantic-commit-messages-with-emojis-dba2541cea9a 18 | https://babakks.github.io/article/2020/07/03/emojis-in-git-commit-messages.html 19 | https://gist.github.com/parmentf/035de27d6ed1dce0b36a 20 | https://wilsonmar.github.io/git-messages/ -------------------------------------------------------------------------------- /example/gitwork.out.month-week.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gitwork.out.month-week.md 5 | 6 | 7 | 228 | 229 | 304 | 305 | 356 | 357 | 358 | 359 | 360 | 368 |

王小花的工作报告(2022-08-01 至 2022-08-31)

369 | 411 |
412 |

大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。

413 |

1. 工作报告

414 |

小结:在这个阶段中,主要精力集中在 工作报告生成器 这个项目,以及 添加功能 。 处理 支持过滤掉相似度较高的 msg 这项工作消耗了比预期较多的时间,主要原因是 在实现功能上需要处理比较多的逻辑

415 |

1.1. 工作报告生成器

416 |

1.1.1. 2022年08月

417 |
1.1.1.1. 第3周
418 | 431 |
1.1.1.2. 第1周
432 | 466 |

1.2. markdown 转换工具

467 |

1.2.1. 2022年08月

468 |
1.2.1.1. 第3周
469 | 473 |
1.2.1.2. 第2周
474 | 497 |
1.2.1.3. 第1周
498 | 516 |

1.3. 接口伴侣

517 |

1.3.1. 2022年08月

518 |
1.3.1.1. 第3周
519 | 522 |
1.3.1.2. 第1周
523 | 537 |

2. 后续计划

538 |

2.1. 工作报告生成器

539 | 543 | 544 | 545 | 546 | -------------------------------------------------------------------------------- /example/gitwork.out.month-week.md: -------------------------------------------------------------------------------- 1 | 王小花的工作报告(2022-08-01 至 2022-08-31) 2 | 3 | - [1. 工作报告](#1-工作报告) 4 | - [1.1. 工作报告生成器](#11-工作报告生成器) 5 | - [1.1.1. 2022年08月](#111-2022年08月) 6 | - [1.1.1.1. 第3周](#1111-第3周) 7 | - [1.1.1.2. 第1周](#1112-第1周) 8 | - [1.2. markdown 转换工具](#12-markdown-转换工具) 9 | - [1.2.1. 2022年08月](#121-2022年08月) 10 | - [1.2.1.1. 第3周](#1211-第3周) 11 | - [1.2.1.2. 第2周](#1212-第2周) 12 | - [1.2.1.3. 第1周](#1213-第1周) 13 | - [1.3. 接口伴侣](#13-接口伴侣) 14 | - [1.3.1. 2022年08月](#131-2022年08月) 15 | - [1.3.1.1. 第3周](#1311-第3周) 16 | - [1.3.1.2. 第1周](#1312-第1周) 17 | - [2. 后续计划](#2-后续计划) 18 | - [2.1. 工作报告生成器](#21-工作报告生成器) 19 | 20 | --- 21 | 22 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。 23 | 24 | ## 1. 工作报告 25 | 26 | 小结:在这个阶段中,主要精力集中在 `工作报告生成器` 这个项目,以及 `添加功能` 。 处理 `支持过滤掉相似度较高的 msg` 这项工作消耗了比预期较多的时间,主要原因是 `在实现功能上需要处理比较多的逻辑` 。 27 | 28 | 29 | ### 1.1. 工作报告生成器 30 | #### 1.1.1. 2022年08月 31 | ##### 1.1.1.1. 第3周 32 | - 🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出 33 | - ✨添加功能: 更新默认模板 34 | - 🐛修复缺陷: git log 结果仅有一条时不应出现错误 35 | 36 | ``` 37 | Error: Bad arguments: First argument should be a string, second should be an array of strings 38 | ``` 39 | 40 | ##### 1.1.1.2. 第1周 41 | - ✨添加功能: 比较 commit msg 时忽略大小写 42 | - ✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤 43 | - ✨添加功能: 使用 --branches 参数而不是 --all 参数 44 | - 🐛修复缺陷: 修复 --debug 参数失效 45 | - ✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀 46 | - 🐛修复缺陷: 处理 --help 报错 47 | - 🐛修复缺陷: 需要显示当天内容 48 | 49 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行 `after=2022-08-01 before=2022-08-31` 没有任何输出. 运行 `after=2022-07-31 before=2022-07-31` 也没有输出. 50 | - 应变更为包含当天 51 | - ✨添加功能: 转换 msg 提交标志 52 | 53 | 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx` 54 | - ✨添加功能: 支持过滤掉相似度较高的 msg 55 | 56 | 57 | ### 1.2. markdown 转换工具 58 | #### 1.2.1. 2022年08月 59 | ##### 1.2.1.1. 第3周 60 | - 📝更新文档: update readme 61 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误 62 | 63 | ##### 1.2.1.2. 第2周 64 | - ✨添加功能: 简化输入参数 65 | - 🔧更新辅助工具: 更新文档, 修剪日志 66 | - ✨添加功能: 简化输入的命令 67 | 68 | - 前: extension.js cmd=extension.markdown-pdf.pdf 69 | - 后: extension.js out=pdf 70 | - ✨添加功能: 优化 input 参数合法性判断 71 | - ✨添加功能: 实现从命令行导出 pdf 72 | 73 | ``` sh 74 | extension.js cmd=extension.markdown-pdf.pdf input=D:/README.zh.md 75 | ``` 76 | 77 | ##### 1.2.1.3. 第1周 78 | - ✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常 79 | - ✨添加功能: 使用自己实现的方法简单实现拦截 80 | - ✨添加功能: 在 node 环境创建 vscode proxy 81 | 82 | 但是在 vscode 环境会导致 proxy 失败. 83 | 84 | 如果对 vscode 对象进行代理, 会报以下错误: 85 | ``` txt 86 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}') 87 | ``` 88 | - 🔧更新辅助工具: 添加 vsce package 和 npm build 命令 89 | 90 | 91 | ### 1.3. 接口伴侣 92 | #### 1.3.1. 2022年08月 93 | ##### 1.3.1.1. 第3周 94 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误 95 | 96 | ##### 1.3.1.2. 第1周 97 | - ✨添加功能: 调整一些依赖为可选项 98 | 99 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件 100 | - ✨添加功能: 把动态依赖放置于 optionalDependencies 101 | 102 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系 103 | - 🐛修复缺陷: hasPackage 不应只检测子级 node_modules 104 | 105 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules . 106 | 107 | 108 | ## 2. 后续计划 109 | ### 2.1. 工作报告生成器 110 | - [ ] feat: 支持生成 html/pdf/jpeg... 111 | - [ ] feat: 支持作为邮件自动发送 -------------------------------------------------------------------------------- /example/gitwork.out.week.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gitwork.out.week.md 5 | 6 | 7 | 228 | 229 | 304 | 305 | 356 | 357 | 358 | 359 | 360 | 368 |

王小花的工作报告(2022-08-01 至 2022-08-21)

369 | 399 |
400 |

大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。

401 |

1. 工作报告

402 |

小结:在这个阶段中,主要精力集中在 工作报告生成器 这个项目,以及 添加功能 。 处理 支持过滤掉相似度较高的 msg 这项工作消耗了比预期较多的时间,主要原因是 在实现功能上需要处理比较多的逻辑

403 |

1.1. 工作报告生成器

404 |

1.1.1. 2022年08月 第3周

405 | 418 |

1.1.2. 2022年08月 第1周

419 | 453 |

1.2. markdown 转换工具

454 |

1.2.1. 2022年08月 第3周

455 | 459 |

1.2.2. 2022年08月 第2周

460 | 483 |

1.2.3. 2022年08月 第1周

484 | 502 |

1.3. 接口伴侣

503 |

1.3.1. 2022年08月 第3周

504 | 507 |

1.3.2. 2022年08月 第1周

508 | 522 |

2. 后续计划

523 |

2.1. 工作报告生成器

524 | 528 | 529 | 530 | 531 | -------------------------------------------------------------------------------- /example/gitwork.out.week.md: -------------------------------------------------------------------------------- 1 | 王小花的工作报告(2022-08-01 至 2022-08-21) 2 | 3 | - [1. 工作报告](#1-工作报告) 4 | - [1.1. 工作报告生成器](#11-工作报告生成器) 5 | - [1.1.1. 2022年08月 第3周](#111-2022年08月-第3周) 6 | - [1.1.2. 2022年08月 第1周](#112-2022年08月-第1周) 7 | - [1.2. markdown 转换工具](#12-markdown-转换工具) 8 | - [1.2.1. 2022年08月 第3周](#121-2022年08月-第3周) 9 | - [1.2.2. 2022年08月 第2周](#122-2022年08月-第2周) 10 | - [1.2.3. 2022年08月 第1周](#123-2022年08月-第1周) 11 | - [1.3. 接口伴侣](#13-接口伴侣) 12 | - [1.3.1. 2022年08月 第3周](#131-2022年08月-第3周) 13 | - [1.3.2. 2022年08月 第1周](#132-2022年08月-第1周) 14 | - [2. 后续计划](#2-后续计划) 15 | - [2.1. 工作报告生成器](#21-工作报告生成器) 16 | 17 | --- 18 | 19 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。 20 | 21 | 22 | ## 1. 工作报告 23 | 24 | 小结:在这个阶段中,主要精力集中在 `工作报告生成器` 这个项目,以及 `添加功能` 。 处理 `支持过滤掉相似度较高的 msg` 这项工作消耗了比预期较多的时间,主要原因是 `在实现功能上需要处理比较多的逻辑` 。 25 | 26 | 27 | ### 1.1. 工作报告生成器 28 | #### 1.1.1. 2022年08月 第3周 29 | - 🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出 30 | - ✨添加功能: 更新默认模板 31 | - 🐛修复缺陷: git log 结果仅有一条时不应出现错误 32 | 33 | ``` 34 | Error: Bad arguments: First argument should be a string, second should be an array of strings 35 | ``` 36 | 37 | #### 1.1.2. 2022年08月 第1周 38 | - ✨添加功能: 比较 commit msg 时忽略大小写 39 | - ✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤 40 | - ✨添加功能: 使用 --branches 参数而不是 --all 参数 41 | - 🐛修复缺陷: 修复 --debug 参数失效 42 | - ✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀 43 | - 🐛修复缺陷: 处理 --help 报错 44 | - 🐛修复缺陷: 需要显示当天内容 45 | 46 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行 `after=2022-08-01 before=2022-08-31` 没有任何输出. 运行 `after=2022-07-31 before=2022-07-31` 也没有输出. 47 | - 应变更为包含当天 48 | - ✨添加功能: 转换 msg 提交标志 49 | 50 | 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx` 51 | - ✨添加功能: 支持过滤掉相似度较高的 msg 52 | 53 | 54 | ### 1.2. markdown 转换工具 55 | #### 1.2.1. 2022年08月 第3周 56 | - 📝更新文档: update readme 57 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误 58 | 59 | #### 1.2.2. 2022年08月 第2周 60 | - ✨添加功能: 简化输入参数 61 | - 🔧更新辅助工具: 更新文档, 修剪日志 62 | - ✨添加功能: 简化输入的命令 63 | 64 | - 前: extension.js cmd=extension.markdown-pdf.pdf 65 | - 后: extension.js out=pdf 66 | - ✨添加功能: 优化 input 参数合法性判断 67 | - ✨添加功能: 实现从命令行导出 pdf 68 | 69 | ``` sh 70 | extension.js cmd=extension.markdown-pdf.pdf input=D:/README.zh.md 71 | ``` 72 | 73 | #### 1.2.3. 2022年08月 第1周 74 | - ✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常 75 | - ✨添加功能: 使用自己实现的方法简单实现拦截 76 | - ✨添加功能: 在 node 环境创建 vscode proxy 77 | 78 | 但是在 vscode 环境会导致 proxy 失败. 79 | 80 | 如果对 vscode 对象进行代理, 会报以下错误: 81 | ``` txt 82 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}') 83 | ``` 84 | - 🔧更新辅助工具: 添加 vsce package 和 npm build 命令 85 | 86 | 87 | ### 1.3. 接口伴侣 88 | #### 1.3.1. 2022年08月 第3周 89 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误 90 | 91 | #### 1.3.2. 2022年08月 第1周 92 | - ✨添加功能: 调整一些依赖为可选项 93 | 94 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件 95 | - ✨添加功能: 把动态依赖放置于 optionalDependencies 96 | 97 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系 98 | - 🐛修复缺陷: hasPackage 不应只检测子级 node_modules 99 | 100 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules . 101 | 102 | 103 | ## 2. 后续计划 104 | ### 2.1. 工作报告生成器 105 | - [ ] feat: 支持生成 html/pdf/jpeg... 106 | - [ ] feat: 支持作为邮件自动发送 -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console._log = console.log 4 | console.log = () => {} 5 | const util = require(`./util.js`) 6 | util.init() 7 | const { 8 | parseArgv, 9 | execSync, 10 | help, 11 | paserLogToList, 12 | toMd, 13 | } = util 14 | const argv = parseArgv() 15 | const cli = { 16 | ...argv, 17 | '--debug': argv[`--debug`] || false, 18 | '--select': argv[`--select`] ? argv[`--select`].split(`,`) : [`default`], 19 | } 20 | 21 | { // 关联参数特殊处理 22 | if(cli['--help'] || cli['-h']) { 23 | help() 24 | process.exit() 25 | } 26 | if(cli['--config']) { 27 | util.opener(GET(`configdir`)) 28 | process.exit() 29 | } 30 | if(cli['-v'] || cli['--version']) { 31 | util.print(GET(`package`).version) 32 | process.exit() 33 | } 34 | } 35 | 36 | SET(`cli`, cli) 37 | 38 | const config = GET(`config`) 39 | let reportList = config.report.filter(item => cli[`--select`].includes(item.select)) 40 | if(reportList.length === 0) { 41 | util.print(`所指定的 --select 值不存在, 将使用默认值`) 42 | reportList = config.report.filter(item => [`default`].includes(item.select)) 43 | } 44 | reportList.forEach(reportItem => { 45 | const newReportItem = util.handleReportConfig({reportItem, query: cli}) 46 | SET(`curReport`, newReportItem) 47 | const repository = newReportItem.repository.map(repositoryItem => { 48 | let cmdRes = `` 49 | try { 50 | cmdRes = execSync( 51 | util.logLine(newReportItem), 52 | { 53 | cwd: repositoryItem.path, 54 | } 55 | ) 56 | } catch (error) { 57 | util.errExit(`获取 git 工作记录失败`, error) 58 | } 59 | const list = paserLogToList({str: cmdRes}) 60 | const toMdRes = toMd({ 61 | list, 62 | rootLevel: newReportItem.rootLevel, 63 | }) 64 | const obj = { 65 | name: `${`#`.repeat(util.getTitleLevel({type: `repository`, rootLevel: newReportItem.rootLevel}))} ${repositoryItem.name}`, 66 | toMdRes 67 | } 68 | return [ 69 | obj.name, 70 | obj.toMdRes, 71 | `\n`, 72 | ].join(`\n`) 73 | }).join(`\n`).trim() 74 | const out = util.handleTemplateFile({report: reportItem, body: repository}) 75 | require(`fs`).writeFileSync(newReportItem.outFile, out) 76 | }) -------------------------------------------------------------------------------- /lib/opener.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // from: https://unpkg.com/opener@1.5.1/lib/opener.js 4 | 5 | var childProcess = require("child_process"); 6 | var os = require("os"); 7 | 8 | module.exports = function opener(args, options, callback) { 9 | var platform = process.platform; 10 | 11 | // Attempt to detect Windows Subystem for Linux (WSL). WSL itself as Linux (which works in most cases), but in 12 | // this specific case we need to treat it as actually being Windows. The "Windows-way" of opening things through 13 | // cmd.exe works just fine here, whereas using xdg-open does not, since there is no X Windows in WSL. 14 | if (platform === "linux" && os.release().indexOf("Microsoft") !== -1) { 15 | platform = "win32"; 16 | } 17 | 18 | // http://stackoverflow.com/q/1480971/3191, but see below for Windows. 19 | var command; 20 | switch (platform) { 21 | case "win32": { 22 | command = "cmd.exe"; 23 | break; 24 | } 25 | case "darwin": { 26 | command = "open"; 27 | break; 28 | } 29 | default: { 30 | command = "xdg-open"; 31 | break; 32 | } 33 | } 34 | 35 | if (typeof args === "string") { 36 | args = [args]; 37 | } 38 | 39 | if (typeof options === "function") { 40 | callback = options; 41 | options = {}; 42 | } 43 | 44 | if (options && typeof options === "object" && options.command) { 45 | if (platform === "win32") { 46 | // *always* use cmd on windows 47 | args = [options.command].concat(args); 48 | } else { 49 | command = options.command; 50 | } 51 | } 52 | 53 | if (platform === "win32") { 54 | // On Windows, we really want to use the "start" command. But, the rules regarding arguments with spaces, and 55 | // escaping them with quotes, can get really arcane. So the easiest way to deal with this is to pass off the 56 | // responsibility to "cmd /c", which has that logic built in. 57 | // 58 | // Furthermore, if "cmd /c" double-quoted the first parameter, then "start" will interpret it as a window title, 59 | // so we need to add a dummy empty-string window title: http://stackoverflow.com/a/154090/3191 60 | // 61 | // Additionally, on Windows ampersand needs to be escaped when passed to "start" 62 | args = args.map(function (value) { 63 | return value.replace(/&/g, "^&"); 64 | }); 65 | args = ["/c", "start", "\"\""].concat(args); 66 | } 67 | 68 | return childProcess.execFileSync(command, args, options, callback); 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitwork", 3 | "version": "2.0.6", 4 | "description": "", 5 | "bin": { 6 | "gitwork": "index.js" 7 | }, 8 | "scripts": { 9 | "dev": "node index.js template=week messageBody=none after=2022-06-01 before=2022-07-31 --select=default" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/wll8/gitday.git" 14 | }, 15 | "homepage": "https://wll8.github.io/gitday/", 16 | "keywords": [], 17 | "author": "xw", 18 | "license": "ISC", 19 | "dependencies": { 20 | "lodash.unionby": "^4.8.0", 21 | "moment": "^2.29.4", 22 | "mustache": "^4.2.0", 23 | "opener": "^1.5.2", 24 | "string-similarity": "^4.0.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const os = require('os') 3 | const cp = require('child_process') 4 | const path = require('path') 5 | const moment = require('moment') 6 | const mustache = require('mustache') 7 | mustache.escape = text => text // 不转义字符 8 | const stringSimilarity = require('string-similarity') 9 | const opener = require('./lib/opener.js') 10 | const unionby = require('lodash.unionby') 11 | const pkg = require(`./package.json`) 12 | 13 | 14 | function removeLeft(str) { 15 | const lines = str.split(`\n`) 16 | // 获取应该删除的空白符数量 17 | const minSpaceNum = lines.filter(item => item.trim()) 18 | .map(item => item.match(/(^\s+)?/)[0].length) 19 | .sort((a, b) => a - b)[0] 20 | // 删除空白符 21 | const newStr = lines 22 | .map(item => item.slice(minSpaceNum)) 23 | .join(`\n`) 24 | return newStr 25 | } 26 | 27 | function print(...arg) { 28 | return console._log(...arg) 29 | } 30 | 31 | /** 32 | * 获取所使用的 git log 命令 33 | * @returns string 34 | */ 35 | function logLine({after, before}) { 36 | // 由于 git --before 包含当天, 但 --after 不包含当天, 所以 after 需要再推前 37 | const newAfter = moment(after).add({day: -1}).format(`YYYY-MM-DD`) 38 | const str = [ 39 | `git`, 40 | `log`, 41 | `--branches`, 42 | `--after=${newAfter}`, 43 | `--before=${before}`, 44 | `--no-merges`, 45 | `--format=fuller`, 46 | ].join(` `) 47 | return str 48 | } 49 | 50 | function help() { 51 | print(fs.readFileSync(`${__dirname}/README.md`, `utf8`)) 52 | print(`\n访问 ${pkg.homepage} 查看详情`) 53 | // opener(pkg.homepage) 54 | } 55 | 56 | function execSync(cmd, option) { 57 | console.log(`>`, cmd) 58 | return cp.execSync(cmd, option).toString().trim() 59 | } 60 | 61 | function dateFormater(t, formater) { // 时间格式化 62 | let date = t ? new Date(t) : new Date() 63 | return moment(date).format(formater) 64 | } 65 | 66 | function parseArgv(arr) { // 解析命令行参数 67 | return (arr || process.argv.slice(2)).reduce((acc, arg) => { 68 | let [k, ...v] = arg.split(`=`) 69 | v = v.join(`=`) // 把带有 = 的值合并为字符串 70 | acc[k] = v === `` // 没有值时, 则表示为 true 71 | ? true 72 | : ( 73 | /^(true|false)$/.test(v) // 转换指明的 true/false 74 | ? v === `true` 75 | : ( 76 | /[\d|.]+/.test(v) 77 | ? (isNaN(Number(v)) ? v : Number(v)) // 如果转换为数字失败, 则使用原始字符 78 | : v 79 | ) 80 | ) 81 | return acc 82 | }, {}) 83 | } 84 | 85 | function handleMsg(rawMsg) { 86 | let msg = ((rawMsg.match(/(\n\n)([\s\S]+?$)/) || [])[2] || ``) 87 | msg = removeLeft(msg) 88 | let [, one, body] = msg.trim().match(/(.*)[\n]?([\s\S]*)/) 89 | one = one.trim() 90 | body = body.trim() 91 | one = handleOneMsg(one) 92 | let newMsg = `` 93 | if(GET(`curReport`).messageBody === `none`) { // 不使用 body 94 | newMsg = one 95 | } 96 | if(GET(`curReport`).messageBody === `compatible`) { // 当 body 含有可能破坏报告的内容时不使用 97 | if( 98 | body.match(/^#{1,6}\s+/gm) // 含有 # 标题 99 | ) { 100 | newMsg = one 101 | } else { 102 | newMsg = `${one}\n${body}` 103 | } 104 | } 105 | if(GET(`curReport`).messageBody === `raw`) { // 原样使用 body 106 | newMsg = `${one}\n${body}` 107 | } 108 | newMsg = newMsg.trim() 109 | return newMsg 110 | } 111 | 112 | /** 113 | * 根据 git commit 规范转换 msg 风格 114 | */ 115 | function handleOneMsg(msg) { 116 | const {type, scope, subject} = parseMsg(msg) 117 | let newMsg = msg 118 | const [key, val] = Object.entries(GET(`curReport`).messageConvert).find(([key, val]) => { 119 | const {rating} = stringSimilarity.findBestMatch(type.toLocaleLowerCase(), [key, ...val.alias].map(item => item.toLocaleLowerCase())).bestMatch 120 | return (rating > GET(`curReport`).messageTypeSimilarity) 121 | }) || [] 122 | if(key) { 123 | const template = scope ? val.des.text[1] : val.des.text[0] 124 | const text = render( [template, subject].join(``), {scope}) 125 | const emoji = val.des.emoji 126 | const newText = render(GET(`curReport`).messageTypeTemplate, {text, emoji}) 127 | newMsg = newText 128 | } 129 | return newMsg 130 | } 131 | 132 | /** 133 | * 解析 git msg 中的 type scope subject 134 | * arg0 string 135 | * @param {*} msg 136 | * @returns 137 | */ 138 | function parseMsg(msg) { 139 | let type, scope, subject 140 | ;[, type = ``, subject = ``] = msg.trim().match(/(.+?)[::][\s+](.*$)/) || [] 141 | ;[, type = ``, scope = ``] = type.trim().match(/\s{0}(.+?)\s{0}\(\s{0,}(.*)\s{0,}\)$/) || [, type] 142 | type = type.trim() 143 | scope = scope.trim() 144 | subject = subject.trim() 145 | const res = {type, scope, subject} 146 | return res 147 | } 148 | 149 | /** 150 | * 把 git log 的内容解析为数组 151 | * @param {*} str 152 | * @returns 153 | */ 154 | function paserLogToList({str}) { 155 | const authorList = GET(`curReport`).author || [] 156 | str = `\n${str}` 157 | const tag = /\ncommit / 158 | let list = str.split(tag).filter(item => item.trim()).map(rawMsg => { 159 | rawMsg = `commit ${rawMsg}` 160 | const obj = {} 161 | obj.raw = rawMsg 162 | // 使用 CommitDate 而不是 AuthorDate 163 | // CommitDate: 被再次修改的时间或作为补丁使用的时间 164 | obj.date = (dateFormater((rawMsg.match(/CommitDate:(.*)/) || [])[1].trim(), 'YYYY-MM-DD HH:mm:ss')) 165 | // commit sha 166 | obj.commit = (rawMsg.match(/(.*)\n/) || [])[1].trim() 167 | // 作者及邮箱 168 | obj.author = (rawMsg.match(/Author:(.*)/) || [])[1].trim() 169 | obj.msg = handleMsg(rawMsg) 170 | return obj 171 | }).filter(item => { 172 | return ( 173 | ( // 时间范围 174 | moment(item.date).isSameOrBefore(GET(`curReport`).before) 175 | && moment(item.date).isSameOrAfter(GET(`curReport`).after) 176 | ) 177 | && ( // 作者 178 | GET(`curReport`).ignoreAuthor || 179 | authorList.some(author => { 180 | return item.author.startsWith(`${author} <`) 181 | } ) 182 | ) 183 | ) 184 | }) 185 | // 去除重复的 msg 186 | GET(`curReport`).noEqualMsg && (list = unionby(list, `msg`)); 187 | // 去除大于指定相似度的 msg 188 | GET(`curReport`).similarity && (list = list = removeSimilarity({list, key: `msg`, similarity: GET(`curReport`).similarity})); 189 | return list 190 | } 191 | 192 | /** 193 | * 删除数组中指定字段的相似度大于指定值的数组 194 | * arg.list 提供的数组 195 | * arg.key 要比较相似度的字段 196 | * arg.similarity 相似值 197 | */ 198 | function removeSimilarity({list, key = `msg`, similarity = 0.75}) { 199 | if(list.length < 2) { 200 | return list 201 | } 202 | let msgList = list.map(item => item[key]) 203 | let res = [] 204 | msgList.forEach((msg, index) => { 205 | let newList = [...msgList] 206 | newList.splice(index, 1) 207 | const diff = stringSimilarity.findBestMatch(msg, newList) 208 | .ratings 209 | .filter(item => { 210 | return item.rating > similarity 211 | }).map(item => item.target) 212 | const select = diff.length ? diff[0] : msg 213 | res = res.includes(select) ? res : res.concat(msg) 214 | }) 215 | const newList = list.filter(item => res.includes(item[key])) 216 | return newList 217 | } 218 | 219 | 220 | /** 221 | * 根据指定的 key 排序 222 | * @param {boolean} showNew false 降序 true 升序 223 | * @param {string} key 要排序的 key 224 | * @returns function 225 | */ 226 | function sort(showNew, key) { 227 | return (a, b) => showNew ? (b[key] - a[key]) : (a[key] - b[key]) 228 | } 229 | 230 | /** 231 | * 根据 title 标记输出 md 232 | * @param {object} param0 233 | * @param {array} param0.list log 数据 234 | * @returns string 235 | */ 236 | function create({list, rootLevel}) { 237 | const template = GET(`curReport`).template 238 | const titleMap = { 239 | 'month': [ 240 | '@{year}年@{month}月', 241 | ], 242 | 'month-week': [ 243 | '@{year}年@{month}月', 244 | '第@{week}周', 245 | ], 246 | 'month-week-day': [ 247 | '@{year}年@{month}月', 248 | '第@{week}周', 249 | '@{day}日 星期@{weekDay}', 250 | ], 251 | 'week': [ 252 | '@{year}年@{month}月 第@{week}周', 253 | ], 254 | 'week-day': [ 255 | '@{year}年@{month}月 第@{week}周', 256 | '@{day}日 星期@{weekDay}', 257 | ], 258 | 'day': [ 259 | '@{year}年@{month}月@{day}日', 260 | ], 261 | } 262 | let titleList = titleMap[template] 263 | if(!titleList) { 264 | new Error(`不支持的标记`) 265 | return process.exit() 266 | } else { // 添加 # 标题标志 267 | titleList = titleList.map((item, index) => { 268 | const level = getTitleLevel({type: `date`, rootLevel}) + index 269 | return `${`#`.repeat(level)} ${item}` 270 | }) 271 | } 272 | let oldTitle = [] 273 | const str = list.map(item => { 274 | let titleStr = [] 275 | const newTitle = titleList.map((title, index) => { 276 | // 模拟一段代码实现简单的模板语法 277 | title = render(`\n${title}`, item.dateObj) 278 | titleStr[index] = oldTitle[index] === title ? `` : title 279 | return title 280 | } ) 281 | const res = [ 282 | titleStr.join(``), 283 | `\n- ${debug({item})}${ // 多行 msg 的时候在行前面加空格, 以处理缩进关系 284 | item.msg.split(`\n`).map((msgLine) => ` ${msgLine}`).join(`\n`).trim() 285 | }` 286 | ].filter(item => item).join(`\n`) 287 | oldTitle = newTitle 288 | return res 289 | }).join(`\n`).trim() 290 | return str 291 | } 292 | 293 | function debug({item}) { 294 | if(GET(`cli`)[`--debug`]) { 295 | return `${item.date} ${item.commit} ${item.author}\n ` 296 | } else { 297 | return `` 298 | } 299 | } 300 | 301 | /** 302 | * 根据标志对应的时间转换为 markdown 格式 303 | * @param {object} param0 304 | * @param {string} [param0.tag = day] - 标记 305 | * @param {array} param0.list - 数据 306 | * @param {boolean} [param0.showNew = true] - 是否把新时间排到前面 307 | */ 308 | function toMd({ 309 | list = [], 310 | showNew = true, 311 | rootLevel = 1, 312 | }){ 313 | list = list.map(obj => { // 先把每个类型的时间取出来方便使用 314 | const date = new Date(obj.date) 315 | obj.timeStamp = date.getTime() 316 | obj.dateObj = { 317 | year: dateFormater(obj.date, `YYYY`), 318 | month: dateFormater(obj.date, `MM`), 319 | day: dateFormater(obj.date, `DD`), 320 | week: Math.ceil((date.getDate() + 6 - date.getDay()) / 7), // 第几周 321 | weekDay: [`日`, `一`, `二`, `三`, `四`, `五`, `六`][date.getDay()], // 星期几 322 | } 323 | return obj 324 | }).sort(sort(showNew, `timeStamp`)) 325 | const res = create({ 326 | list, 327 | rootLevel, 328 | }) 329 | return res 330 | } 331 | 332 | /** 333 | * 获取模板对应的时间 334 | */ 335 | function handleLogTime({template}) { 336 | const tag = template.split(`-`).shift() // month week day 337 | // 处理 moment 是以周日作为每周的开始, 但现实中是以周一作为开始 338 | const offset = tag === `week` ? {day: 1} : {} 339 | const after = moment().startOf(tag).add(offset).format(`YYYY-MM-DD`) 340 | const before = moment().endOf(tag).add(offset).format(`YYYY-MM-DD`) 341 | return {after, before} 342 | } 343 | 344 | /** 345 | * 获取标题级别 346 | * 假设 rootLevel = 1, 效果为: 347 | * # 文档名称 348 | * ## 项目名称 349 | * ### 时间级别1 350 | * #### 时间级别2 351 | */ 352 | function getTitleLevel({type, rootLevel = 1}) { 353 | return { 354 | repository: rootLevel + 1, 355 | date: rootLevel + 2, 356 | }[type] 357 | } 358 | 359 | /** 360 | * 模板处理器 361 | * report 报告配置 362 | */ 363 | function handleTemplateFile({report, body}) { 364 | const insertBody = render(``, GET(`curReport`)) 365 | const useFilePath = `${GET(`configdir`)}/${report.useFile}` 366 | const useFilePathDefault = `${GET(`configdir`)}/default.template.md` 367 | let mdBody = fs.existsSync(useFilePath) ? fs.readFileSync(useFilePath, `utf8`) : (print(`所指定的 useFile 值不存在, 将使用默认值`), fs.readFileSync(useFilePathDefault, `utf8`)) 368 | mdBody = render(mdBody, GET(`curReport`)) 369 | mdBody = mdBody.replace(/([\s\S]+?)/, ($0, $1) => (body || $1)) 370 | mdBody = mdBody.replace(/([\s\S]+?)/, ($0, $1) => (insertBody || $1)) 371 | return mdBody 372 | } 373 | 374 | /** 375 | * 渲染模板 376 | * @param {*} template 377 | * @returns 378 | */ 379 | function render(template, view) { 380 | return mustache.render(template, view, {}, [`@{`, `}`]) 381 | } 382 | 383 | /** 384 | * 处理报告配置,例如使用命令行参数覆盖报告中的配置 385 | */ 386 | function handleReportConfig({reportItem: cfg, query: cli}) { 387 | const curPath = process.cwd() 388 | const curPathName = path.parse(curPath).name 389 | const newReport = { 390 | // 默认值 391 | ...GET(`reportDefault`), 392 | // 配置值 393 | ...cfg, 394 | // 命令行值 395 | ...Object.entries(cli).reduce((acc, [key, val]) => ({...acc, [key]: val}), {}) 396 | } 397 | // 根据 template 预处理时间 398 | newReport.after = newReport.after || handleLogTime({template: newReport.template}).after 399 | newReport.before = newReport.before || handleLogTime({template: newReport.template}).before 400 | 401 | newReport.author = cli[`author`] 402 | ? cli[`author`].split(`,`) 403 | : ( 404 | (cfg.author && cfg.author.length) 405 | ? cfg.author 406 | : [getDefaultGitName()] 407 | ); 408 | 409 | newReport.authorName = newReport.authorName || newReport.author[0] 410 | newReport.repository = cli[`repository`] 411 | ? cli[`author`].split(`,`).map(item => ({path: item, name: path.parse(item).name})) 412 | : ( 413 | (cfg.repository && cfg.repository.length) 414 | ? cfg.repository 415 | : [{path: curPath, name: curPathName}] 416 | ); 417 | return newReport 418 | } 419 | 420 | function getDefaultGitName() { 421 | try { 422 | return execSync(`git config user.name`) 423 | } catch (error) { 424 | errExit(`获取 git 的默认用户名失败`, error) 425 | } 426 | } 427 | 428 | function errExit(msg, error) { 429 | print(msg) 430 | print(String(error)) 431 | process.exit() 432 | } 433 | 434 | /** 435 | * 初始化程序 436 | */ 437 | function init() { 438 | global.SET = (key, val) => { 439 | console.log(`SET`, key, val) 440 | global[`${pkg.name}_${key}`] = val 441 | return val 442 | } 443 | global.GET = (key) => { 444 | return global[`${pkg.name}_${key}`] 445 | } 446 | global.SET(`package`, pkg) 447 | const configdir = `${os.homedir()}/.${pkg.name}/` 448 | global.SET(`configdir`, configdir) 449 | global.SET(`configFilePath`, `${configdir}/config.js`) 450 | global.SET(`reportDefault`, require(`./config/config.js`).report.find(item => item.select === `default`)) 451 | if(fs.existsSync(configdir) === false) { // 创建配置文件目录 452 | fs.mkdirSync(configdir, {recursive: true}) 453 | } 454 | 455 | // 如果文件不存在, 则创建它们 456 | [ 457 | [`${__dirname}/config/config.js`, GET(`configFilePath`)], 458 | [`${__dirname}/config/default.template.md`, `${GET(`configdir`)}/default.template.md`], 459 | ].forEach(([form, to]) => { 460 | if( 461 | (fs.existsSync(to) === false) 462 | || (fs.readFileSync(to, `utf8`).trim() === ``) 463 | ) { // 创建配置文件 464 | fs.copyFileSync(form, to) 465 | } 466 | }) 467 | global.SET(`config`, require(GET(`configFilePath`))) 468 | } 469 | 470 | module.exports = { 471 | init, 472 | opener, 473 | errExit, 474 | handleReportConfig, 475 | handleTemplateFile, 476 | getTitleLevel, 477 | handleLogTime, 478 | logLine, 479 | toMd, 480 | create, 481 | sort, 482 | help, 483 | print, 484 | removeLeft, 485 | execSync, 486 | parseArgv, 487 | paserLogToList, 488 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | lodash.unionby@^4.8.0: 6 | version "4.8.0" 7 | resolved "https://registry.npmmirror.com/lodash.unionby/-/lodash.unionby-4.8.0.tgz#883f098ff78f564a727b7508e09cdd539734bb83" 8 | integrity sha512-e60kn4GJIunNkw6v9MxRnUuLYI/Tyuanch7ozoCtk/1irJTYBj+qNTxr5B3qVflmJhwStJBv387Cb+9VOfABMg== 9 | 10 | moment@^2.29.4: 11 | version "2.29.4" 12 | resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" 13 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== 14 | 15 | mustache@^4.2.0: 16 | version "4.2.0" 17 | resolved "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" 18 | integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== 19 | 20 | opener@^1.5.2: 21 | version "1.5.2" 22 | resolved "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" 23 | integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== 24 | 25 | string-similarity@^4.0.4: 26 | version "4.0.4" 27 | resolved "https://registry.npmmirror.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" 28 | integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== 29 | --------------------------------------------------------------------------------