UNPKG

git-yike-logger-hook

Version:

A TypeScript Git hook plugin for automatically generating commit logs with TODO/WIP comment scanning

3 lines (2 loc) 6.08 kB
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("fs"),e=require("path"),s=require("simple-git");function i(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach(function(s){if("default"!==s){var i=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,i.get?i:{enumerable:!0,get:function(){return t[s]}})}}),e.default=t,Object.freeze(e)}var r=i(t),n=i(e);class o{constructor(){this.git=s.simpleGit()}async getCurrentCommitInfo(){try{const t=(await this.git.log({maxCount:1})).latest;return t?{commitHash:t.hash,message:t.message,author:{name:t.author_name,email:t.author_email},timestamp:t.date}:{}}catch(t){if(t instanceof Error&&t.message.includes("does not have any commits yet"))return{};throw new Error(`获取提交信息失败: ${t}`)}}async getCurrentBranch(){try{return(await this.git.revparse(["--abbrev-ref","HEAD"])).trim()}catch(t){return"unknown"}}async getRemoteInfo(){try{const t=(await this.git.getRemotes(!0)).find(t=>"origin"===t.name);if(t)return{name:t.name,url:t.refs.fetch||t.refs.push}}catch(t){}}async getStagedFiles(){try{return(await this.git.status()).staged}catch(t){throw new Error(`获取暂存区文件失败: ${t}`)}}async getWorkingDirectoryStatus(){try{const t=await this.git.status();return{staged:t.staged,unstaged:t.modified.concat(t.not_added),untracked:t.untracked||[]}}catch(t){throw new Error(`获取工作区状态失败: ${t}`)}}async getFileChanges(){try{const t=await this.git.status();return{added:t.created,modified:t.modified,deleted:t.deleted,renamed:t.renamed.map(t=>({from:t.from,to:t.to}))}}catch(t){throw new Error(`获取文件变更失败: ${t}`)}}async isGitRepository(){try{return await this.git.status(),!0}catch(t){return!1}}}class a{constructor(){this.supportedExtensions=[".js",".jsx",".ts",".tsx",".vue",".svelte"],this.todoPatterns=[/\/\/\s*TODO\s*:?\s*(.+)/gi,/\/\*\s*TODO\s*:?\s*(.+?)\s*\*\//gi,/\/\/\s*@todo\s*:?\s*(.+)/gi,/\/\*\s*@todo\s*:?\s*(.+?)\s*\*\//gi],this.wipPatterns=[/\/\/\s*WIP\s*:?\s*(.+)/gi,/\/\*\s*WIP\s*:?\s*(.+?)\s*\*\//gi,/\/\/\s*@wip\s*:?\s*(.+)/gi,/\/\*\s*@wip\s*:?\s*(.+?)\s*\*\//gi,/\/\/\s*WORK\s*IN\s*PROGRESS\s*:?\s*(.+)/gi,/\/\*\s*WORK\s*IN\s*PROGRESS\s*:?\s*(.+?)\s*\*\//gi]}async scanDirectory(t){const e=[],s=[];try{const i=await this.getAllJavaScriptFiles(t);for(const t of i){const i=await this.scanFile(t);e.push(...i.todos),s.push(...i.wips)}}catch(t){}return{todos:e,wips:s}}async scanFile(t){const e=[],s=[];try{if(!r.existsSync(t))return{todos:e,wips:s};const i=r.readFileSync(t,"utf8").split("\n");for(let r=0;r<i.length;r++){const n=i[r],o=r+1;for(const s of this.todoPatterns){const i=this.extractMatches(n,s,"TODO");for(const s of i)e.push({type:"TODO",content:s.content,filePath:this.normalizePath(t),lineNumber:o,fullLine:n.trim()})}for(const e of this.wipPatterns){const i=this.extractMatches(n,e,"WIP");for(const e of i)s.push({type:"WIP",content:e.content,filePath:this.normalizePath(t),lineNumber:o,fullLine:n.trim()})}}}catch(t){}return{todos:e,wips:s}}async getAllJavaScriptFiles(t){const e=[],s=t=>{try{const i=r.readdirSync(t);for(const o of i){const i=n.join(t,o),a=r.statSync(i);if(a.isDirectory()){if(this.shouldSkipDirectory(o))continue;s(i)}else if(a.isFile()){const t=n.extname(o).toLowerCase();this.supportedExtensions.includes(t)&&e.push(i)}}}catch(t){}};return s(t),e}shouldSkipDirectory(t){return["node_modules",".git","dist","build","coverage",".next",".nuxt",".vuepress",".vite","target","out",".idea",".vscode",".vs"].includes(t)||t.startsWith(".")}extractMatches(t,e,s){const i=[];let r;for(e.lastIndex=0;null!==(r=e.exec(t));){const t=r[1]?r[1].trim():"";t&&i.push({content:t})}return i}normalizePath(t){return t.replace(/\\/g,"/")}async scanChangedFiles(t){const e=[],s=[],i=[...t.added,...t.modified].filter(t=>this.isJavaScriptFile(t));for(const t of i)try{const i=await this.scanFile(t);e.push(...i.todos),s.push(...i.wips)}catch(t){}return{todos:e,wips:s}}isJavaScriptFile(t){const e=n.extname(t).toLowerCase();return this.supportedExtensions.includes(e)}}class c{constructor(t={}){this.gitUtils=new o,this.commentScanner=new a,this.config={logDir:".git-logs",includeUnstaged:!1,includeUntracked:!1,...t}}async generateLog(){try{if(!await this.gitUtils.isGitRepository())throw new Error("当前目录不是 Git 仓库");const t=await this.gitUtils.getCurrentCommitInfo();if(!t.commitHash){const e=(new Date).toISOString();t.timestamp=e,t.message="首次提交",t.commitHash="initial-commit",t.author={name:"Unknown",email:"unknown@example.com"}}const e=await this.gitUtils.getCurrentBranch(),s=await this.gitUtils.getRemoteInfo(),i=await this.gitUtils.getFileChanges(),r=await this.commentScanner.scanChangedFiles(i),o={author:t.author,timestamp:t.timestamp,message:t.message,commitHash:t.commitHash,changedFiles:i,branch:e,remote:s,comments:{todos:r.todos,wips:r.wips}};await this.ensureLogDirectory();const a=this.generateLogFileName(o),c=n.join(this.config.logDir,a);await this.writeLogFile(c,o)}catch(t){process.exit(1)}}async ensureLogDirectory(){r.existsSync(this.config.logDir)||r.mkdirSync(this.config.logDir,{recursive:!0})}generateLogFileName(t){const e=new Date(t.timestamp);return`${e.toISOString().split("T")[0]}_${e.toTimeString().split(" ")[0].replace(/:/g,"-")}_${t.commitHash.substring(0,8)}.json`}async writeLogFile(t,e){const s=JSON.stringify(e,null,2);r.writeFileSync(t,s,"utf8")}async getAllLogs(){try{if(!r.existsSync(this.config.logDir))return[];const t=r.readdirSync(this.config.logDir).filter(t=>t.endsWith(".json")).sort().reverse(),e=[];for(const s of t)try{const t=n.join(this.config.logDir,s),i=r.readFileSync(t,"utf8"),o=JSON.parse(i);e.push(o)}catch(t){}return e}catch(t){return[]}}async cleanupLogs(t=100){try{const e=await this.getAllLogs();if(e.length<=t)return;const s=e.slice(t);for(const t of s){const e=this.generateLogFileName(t),s=n.join(this.config.logDir,e);r.existsSync(s)&&r.unlinkSync(s)}}catch(t){}}}exports.CommentScanner=a,exports.GitLogger=c,exports.GitUtils=o,exports.default=c; //# sourceMappingURL=index.js.map