zen-gitsync 1.4.0 → 1.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-gitsync",
3
- "version": "1.4.0",
3
+ "version": "1.5.2",
4
4
  "description": "一个 git 自动查看差异并提交的工具",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -32,7 +32,10 @@
32
32
  "type": "git",
33
33
  "url": "git+https://github.com/xz333221/zen-gitsync.git"
34
34
  },
35
- "keywords": ["gitsync", "git"],
35
+ "keywords": [
36
+ "gitsync",
37
+ "git"
38
+ ],
36
39
  "author": "",
37
40
  "license": "MIT",
38
41
  "bugs": {
@@ -43,6 +46,8 @@
43
46
  "boxen": "^8.0.1",
44
47
  "chalk": "^5.4.1",
45
48
  "cli-table3": "^0.6.5",
49
+ "date-fns": "^4.1.0",
50
+ "log-update": "^6.1.0",
46
51
  "ora": "^8.1.1",
47
52
  "string-width": "^7.2.0"
48
53
  }
package/src/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ import chalk from 'chalk';
4
5
 
5
6
  const configPath = path.join(os.homedir(), '.git-commit-tool.json');
6
7
 
@@ -23,8 +24,27 @@ async function loadConfig() {
23
24
  async function saveConfig(config) {
24
25
  await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
25
26
  }
27
+ // 添加配置管理函数
28
+ async function handleConfigCommands() {
29
+ if (process.argv.includes('get-config')) {
30
+ const currentConfig = await loadConfig();
31
+ console.log('Current configuration:');
32
+ console.log(currentConfig);
33
+ process.exit();
34
+ }
26
35
 
36
+ const setMsgArg = process.argv.find(arg => arg.startsWith('--set-default-message='));
37
+ if (setMsgArg) {
38
+ const newMessage = setMsgArg.split('=')[1];
39
+ const currentConfig = await loadConfig();
40
+ currentConfig.defaultCommitMessage = newMessage;
41
+ await saveConfig(currentConfig);
42
+ console.log(chalk.green(`✓ 默认提交信息已设置为: "${newMessage}"`));
43
+ process.exit();
44
+ }
45
+ }
27
46
  export default {
28
47
  loadConfig,
29
- saveConfig
48
+ saveConfig,
49
+ handleConfigCommands
30
50
  };
package/src/gitCommit.js CHANGED
@@ -1,427 +1,241 @@
1
1
  #!/usr/bin/env node
2
-
3
- import {exec, execSync} from 'child_process'
4
- import os from 'os'
5
- import {coloredLog, errorLog} from './utils/index.js';
2
+ import {
3
+ coloredLog, errorLog, execGitCommand, execSyncGitCommand, showHelp,
4
+ getCwd, judgePlatform, judgeLog, judgeHelp, exec_exit, judgeUnmerged, formatDuration,
5
+ exec_push, execPull, judgeRemote, execDiff, execAddAndCommit, delay
6
+ } from './utils/index.js';
6
7
  import readline from 'readline'
7
8
  import ora from 'ora';
8
9
  import chalk from 'chalk';
9
10
  import boxen from 'boxen';
10
11
  import config from './config.js';
11
- const { loadConfig, saveConfig } = config;
12
- const {defaultCommitMessage} = config
13
-
14
- let timer = null
15
- const showHelp = () => {
16
- const helpMessage = `
17
- Usage: g [options]
12
+ import dateFormat from 'date-fns/format';
13
+ import logUpdate from 'log-update';
18
14
 
19
- Options:
20
- -h, --help Show this help message
21
- --set-default-message=<msg> Set default commit message
22
- get-config Show current configuration
23
- -y Auto commit with default message
24
- -m <message> Commit message (use quotes if message contains spaces)
25
- -m=<message> Commit message (use this form without spaces around '=')
26
- --path=<path> Set custom working directory
27
- --cwd=<path> Set custom working directory
28
- --interval=<seconds> Set interval time for automatic commits (in seconds)
29
- log Show git commit logs
30
- --n=<number> Number of commits to show with --log
31
- --no-diff Skip displaying git diff
15
+ let countdownInterval = null;
32
16
 
33
- Example:
34
- g -m "Initial commit" Commit with a custom message
35
- g -m=Fix-bug Commit with a custom message (no spaces around '=')
36
- g -y Auto commit with the default message
37
- g -y --interval=600 Commit every 10 minutes (600 seconds)
38
- g --path=/path/to/repo Specify a custom working directory
39
- g log Show recent commit logs
40
- g log --n=5 Show the last 5 commits with --log
17
+ function startCountdown(interval) {
18
+ let remaining = interval;
41
19
 
42
- Add auto submit in package.json:
43
- "scripts": {
44
- "g:y": "g -y"
20
+ // 清除旧的倒计时
21
+ if (countdownInterval) {
22
+ clearInterval(countdownInterval);
45
23
  }
46
24
 
47
- Start a background process for automatic commits:
48
- start /min cmd /k "g -y --path=your-folder --interval"
49
- `;
25
+ const render = () => {
26
+ const nextTime = Date.now() + remaining;
27
+ const formattedTime = dateFormat(nextTime, 'yyyy-MM-dd HH:mm:ss');
28
+ const duration = formatDuration(remaining);
50
29
 
51
- console.log(helpMessage);
52
- process.exit();
53
- };
54
- // 添加配置管理函数
55
- async function handleConfigCommands() {
56
- if (process.argv.includes('get-config')) {
57
- const currentConfig = await loadConfig();
58
- console.log('Current configuration:');
59
- console.log(currentConfig);
60
- process.exit();
61
- }
30
+ const message = [
31
+ chalk.green.bold('🕒 倒计时'),
32
+ chalk.cyan(`下次提交: ${formattedTime}`),
33
+ chalk.yellow(`剩余时间: ${duration}`),
34
+ chalk.dim('按 Ctrl+C 终止进程')
35
+ ].join('\n');
62
36
 
63
- const setMsgArg = process.argv.find(arg => arg.startsWith('--set-default-message='));
64
- if (setMsgArg) {
65
- const newMessage = setMsgArg.split('=')[1];
66
- const currentConfig = await loadConfig();
67
- currentConfig.defaultCommitMessage = newMessage;
68
- await saveConfig(currentConfig);
69
- console.log(chalk.green(`✓ 默认提交信息已设置为: "${newMessage}"`));
70
- process.exit();
71
- }
72
- }
73
- const judgePlatform = () => {
74
- // 判断是否是 Windows 系统
75
- if (os.platform() === 'win32') {
76
- try {
77
- // 设置终端字符编码为 UTF-8
78
- execSync('chcp 65001');
79
- } catch (e) {
80
- console.error('设置字符编码失败:', e.message);
81
- }
82
- }
83
- };
84
- const getCwd = () => {
85
- const cwdArg = process.argv.find(arg => arg.startsWith('--path')) || process.argv.find(arg => arg.startsWith('--cwd'));
86
- if (cwdArg) {
87
- // console.log(`cwdArg ==> `, cwdArg)
88
- const [, value] = cwdArg.split('=')
89
- // console.log(`value ==> `, value)
90
- return value || process.cwd()
91
- }
92
- return process.cwd()
93
- }
94
-
95
- class GitCommit {
96
- constructor(options) {
97
- this.statusOutput = null
98
- this.exit = options.exit
99
- // 从配置加载默认提交信息
100
- loadConfig().then(config => {
101
- this.commitMessage = config.defaultCommitMessage;
102
- this.init();
37
+ const box = boxen(message, {
38
+ padding: 1,
39
+ margin: 1,
40
+ borderColor: 'cyan',
41
+ borderStyle: 'round'
103
42
  });
104
- }
105
43
 
106
- exec_exit() {
107
- if (this.exit) {
108
- process.exit()
109
- }
110
- }
111
-
112
- judgeLog() {
113
- const logArg = process.argv.find(arg => arg === 'log');
114
- if (logArg) {
115
- this.printGitLog(); // 如果有 log 参数,打印 Git 提交记录
116
- return true;
117
- }
118
- }
44
+ setTimeout(() => {
45
+ logUpdate(box);
46
+ }, 200);
47
+ // logUpdate(box);
48
+ };
119
49
 
120
- judgeHelp() {
121
- if (process.argv.includes('-h') || process.argv.includes('--help')) {
122
- showHelp();
123
- return true;
124
- }
125
- }
50
+ // 立即渲染一次
51
+ render();
126
52
 
127
- execDiff() {
128
- const no_diff = process.argv.find(arg => arg.startsWith('--no-diff'))
129
- if (!no_diff) {
130
- this.execSyncGitCommand('git diff --color=always', {
131
- head: `git diff`
132
- })
133
- }
134
- }
53
+ // 每秒更新
54
+ countdownInterval = setInterval(() => {
55
+ remaining -= 1000;
135
56
 
136
- async execAddAndCommit() {
137
- // 检查 -m 参数(提交信息)
138
- const commitMessageArg = process.argv.find(arg => arg.startsWith('-m'));
139
- if (commitMessageArg) {
140
- if (commitMessageArg.includes('=')) {
141
- // 处理 -m=<message> 的情况
142
- this.commitMessage = commitMessageArg.split('=')[1]?.replace(/^['"]|['"]$/g, '') || defaultCommitMessage;
143
- } else {
144
- // 处理 -m <message> 的情况
145
- const index = process.argv.indexOf(commitMessageArg);
146
- if (index !== -1 && process.argv[index + 1]) {
147
- this.commitMessage = process.argv[index + 1]?.replace(/^['"]|['"]$/g, '') || defaultCommitMessage;
148
- }
149
- }
57
+ if (remaining <= 0) {
58
+ clearInterval(countdownInterval);
59
+ logUpdate.clear();
60
+ return;
150
61
  }
151
62
 
152
- // 检查命令行参数,判断是否有 -y 参数
153
- const autoCommit = process.argv.includes('-y');
154
-
155
- if (!autoCommit && !commitMessageArg) {
156
- // 如果没有 -y 参数,则等待用户输入提交信息
157
- const rl = readline.createInterface({
158
- input: process.stdin,
159
- output: process.stdout
160
- })
161
-
162
- function rlPromisify(fn) {
163
- return async (...args) => {
164
- return new Promise((resolve, reject) => fn(...args, resolve, reject))
165
- }
166
- }
63
+ render();
64
+ }, 1000);
65
+ }
167
66
 
168
- const question = rlPromisify(rl.question.bind(rl))
169
- this.commitMessage = await question('请输入提交信息:');
170
- }
67
+ const {loadConfig, saveConfig, handleConfigCommands} = config;
68
+ const {defaultCommitMessage} = config
171
69
 
172
- this.statusOutput.includes('(use "git add') && this.execSyncGitCommand('git add .')
70
+ let timer = null
173
71
 
174
- // 执行 git commit
175
- if (this.statusOutput.includes('Untracked files:') || this.statusOutput.includes('Changes not staged for commit') || this.statusOutput.includes('Changes to be committed')) {
176
- this.execSyncGitCommand(`git commit -m "${this.commitMessage || defaultCommitMessage}"`)
177
- }
178
- }
72
+ async function createGitCommit(options) {
73
+ // console.log(`自动提交流程开始=====================>`)
74
+ try {
75
+ let statusOutput = null
76
+ let exit = options ? !!options.exit : true
77
+ const config = await loadConfig()
78
+ let commitMessage = config.defaultCommitMessage
79
+ let {stdout} = await execGitCommand('git status')
80
+ statusOutput = stdout
81
+ judgeUnmerged(statusOutput)
82
+ // 先检查本地是否有未提交的更改
83
+ const hasLocalChanges = !statusOutput.includes('nothing to commit, working tree clean');
84
+ if (hasLocalChanges) {
85
+ // 检查是否有 --no-diff 参数
86
+ await execDiff()
87
+ await execAddAndCommit({statusOutput, commitMessage, exit})
88
+ statusOutput.includes('use "git pull') && await execPull()
179
89
 
180
- async judgeRemote() {
181
- try {
182
- const spinner = ora('正在检查远程更新...').start();
183
90
  // 检查是否有远程更新
184
- // 先获取远程最新状态
185
- await this.execGitCommand('git remote update', {
186
- head: 'Fetching remote updates',
187
- log: false
188
- });
189
- // 检查是否需要 pull
190
- const res = await this.execGitCommand('git rev-list HEAD..@{u} --count', {
191
- head: 'Checking if behind remote',
192
- log: false
193
- });
194
- const behindCount = res.stdout.trim()
195
- // 如果本地落后于远程
196
- if (parseInt(behindCount) > 0) {
197
- try {
198
- spinner.stop();
199
- // const spinner_pull = ora('发现远程更新,正在拉取...').start();
200
- await this.execPull()
201
-
202
- // // 尝试使用 --ff-only 拉取更新
203
- // const res = await this.execGitCommand('git pull --ff-only', {
204
- // spinner: spinner_pull,
205
- // head: 'Pulling updates'
206
- // });
207
- console.log(chalk.green('✓ 已成功同步远程更新'));
208
- } catch (pullError) {
209
- // // 如果 --ff-only 拉取失败,尝试普通的 git pull
210
- // console.log(chalk.yellow('⚠️ 无法快进合并,尝试普通合并...'));
211
- // await this.execPull()
212
- throw new Error(pullError)
213
- }
91
+ await judgeRemote() // 等待 judgeRemote 完成
92
+
93
+ await exec_push({exit, commitMessage})
94
+ } else {
95
+ if (statusOutput.includes('use "git push')) {
96
+ await exec_push({exit, commitMessage})
97
+ } else if (statusOutput.includes('use "git pull')) {
98
+ await execPull()
214
99
  } else {
215
- spinner.stop();
216
- console.log(chalk.green('✓ 本地已是最新'));
100
+ await judgeRemote() // 等待 judgeRemote 完成
101
+ exec_exit(exit);
217
102
  }
218
- } catch (e) {
219
- // console.log(`e ==> `, e)
220
- spinner.stop();
221
- throw new Error(e)
222
- }
223
- }
224
-
225
- async execPull() {
226
- try {
227
- // 检查是否需要拉取更新
228
- const spinner = ora('正在拉取代码...').start();
229
- await this.execGitCommand('git pull', {
230
- spinner
231
- })
232
- } catch (e) {
233
- console.log(chalk.yellow('⚠️ 拉取远程更新合并失败,可能存在冲突,请手动处理'));
234
- throw Error(e)
235
103
  }
104
+ } catch (e) {
105
+ console.error(chalk.red.bold('提交流程错误:'));
106
+ console.error(chalk.dim(e.stack)); // 打印完整错误堆栈
107
+ throw e; // 继续向上抛出错误
236
108
  }
109
+ }
110
+ async function main() {
111
+ judgePlatform()
237
112
 
238
- async init() {
239
- try {
240
- judgePlatform()
113
+ // 检查是否有 log 参数
114
+ judgeLog()
241
115
 
242
- // 检查是否有 log 参数
243
- if (this.judgeLog()) return
116
+ // 检查帮助参数
117
+ judgeHelp()
244
118
 
245
- // 检查帮助参数
246
- if (this.judgeHelp()) return
119
+ await handleConfigCommands();
247
120
 
248
- this.statusOutput = this.execSyncGitCommand('git status')
249
- const hasUnmerged = this.statusOutput.includes('You have unmerged paths');
250
- if (hasUnmerged) {
251
- errorLog('错误', '存在未合并的文件,请先解决冲突')
252
- process.exit(1);
253
- }
254
- // 先检查本地是否有未提交的更改
255
- const hasLocalChanges = !this.statusOutput.includes('nothing to commit, working tree clean');
256
- if (hasLocalChanges) {
257
- // 检查是否有 --no-diff 参数
258
- this.execDiff()
121
+ judgeInterval();
122
+ }
259
123
 
260
- await this.execAddAndCommit()
124
+ const showStartInfo = (interval) => {
125
+ const cwd = getCwd();
126
+ const intervalSeconds = interval / 1000;
127
+ const startTime = new Date().toLocaleString();
261
128
 
262
- this.statusOutput.includes('use "git pull') && await this.execPull()
129
+ const head = `⏰ 定时提交任务已启动`;
263
130
 
264
- // 检查是否有远程更新
265
- await this.judgeRemote() // 等待 judgeRemote 完成
131
+ const message = chalk.green.bold([
132
+ `开始时间: ${chalk.yellow(startTime)}`,
133
+ `工作目录: ${chalk.cyan(cwd)}`,
134
+ `提交间隔: ${chalk.magenta(formatDuration(interval))}`,
135
+ ].join("\n"));
266
136
 
267
- this.exec_push()
268
- } else {
269
- if (this.statusOutput.includes('use "git push')) {
270
- this.exec_push()
271
- } else if (this.statusOutput.includes('use "git pull')) {
272
- await this.execPull()
273
- } else {
274
- await this.judgeRemote() // 等待 judgeRemote 完成
275
- this.exec_exit();
276
- }
277
- }
278
- } catch (e) {
279
- // console.log(`e ==> `, e)
280
- // console.log(`e.message ==> `, e.message)
281
- // 应该提供更具体的错误信息
282
- // console.error('Git operation failed:', e.message);
283
- // 考虑不同错误类型的处理
284
- if (e.message.includes('not a git repository')) {
285
- errorLog('错误', '当前目录不是git仓库')
286
- } else if (e.message.includes('Permission denied')) {
287
- errorLog('错误', '权限不足,请检查git配置')
288
- }
289
- process.exit(1);
290
- }
291
- }
292
-
293
- async printGitLog() {
294
- let n = 20;
295
- let logArg = process.argv.find(arg => arg.startsWith('--n='));
296
- if (logArg) {
297
- n = parseInt(logArg.split('=')[1], 10);
298
- }
299
- const logCommand = `git log -n ${n} --pretty=format:"%C(green)%h%C(reset) | %C(cyan)%an%C(reset) | %C(yellow)%ad%C(reset) | %C(blue)%D%C(reset) | %C(magenta)%s%C(reset)" --date=format:"%Y-%m-%d %H:%M" --graph --decorate --color`
300
- try {
301
- const logOutput = this.execSyncGitCommand(logCommand, {
302
- head: `git log`
303
- });
304
- // // 格式化输出 Git 提交记录
305
- // const box = boxen(chalk.green.bold(logOutput), {
306
- // borderStyle: 'round',
307
- // borderColor: 'cyan',
308
- // backgroundColor: 'black',
309
- // });
310
- // console.log(box); // 打印优雅的 Git 提交记录
311
- } catch (error) {
312
- console.error('无法获取 Git 提交记录:', error.message);
313
- }
314
- this.exec_exit(); // 打印完成后退出
137
+ coloredLog(head, message)
138
+ // console.log('\n'.repeat(6));
139
+ }
140
+ const commitAndSchedule = async (interval) => {
141
+ try {
142
+ await createGitCommit({exit: false});
143
+ // await delay(2000)
144
+ startCountdown(interval); // 启动倒计时
145
+
146
+ // 设置定时提交
147
+ timer = setTimeout(async () => {
148
+ await commitAndSchedule(interval);
149
+ }, interval + 100);
150
+ } catch (error) {
151
+ console.error('提交出错:', error.message);
152
+ clearTimeout(timer);
153
+ clearInterval(countdownInterval);
154
+ process.exit(1);
315
155
  }
156
+ };
157
+ const judgeInterval = async () => {
158
+ const intervalArg = process.argv.find(arg => arg.startsWith('--interval'));
159
+ if (intervalArg) {
160
+ let interval = parseInt(intervalArg.split('=')[1] || '3600', 10) * 1000;
316
161
 
317
- exec_push() {
318
- // 执行 git push
319
- // this.execSyncGitCommand(`git push`);
320
- const spinner = ora('正在推送代码...').start();
321
- this.execGitCommand('git push', {
322
- spinner
323
- }, (error, stdout, stderr) => {
324
-
325
- // 使用 boxen 绘制带边框的消息
326
- let msg = ` SUCCESS: 提交完成
327
- message: ${this.commitMessage || defaultCommitMessage}
328
- time: ${new Date().toLocaleString()} `
329
- const message = chalk.green.bold(msg);
330
- const box = boxen(message, {
331
- // borderStyle: 'round', // 方框的样式
332
- // borderColor: 'whiteBright', // 边框颜色
333
- // backgroundColor: 'black', // 背景颜色
334
- });
335
-
336
- console.log(box); // 打印带有边框的消息
337
- // this.execSyncGitCommand(`git log -n 1 --pretty=format:"%B%n%h %d%n%ad" --date=iso`)
338
- this.exec_exit();
339
- })
340
- }
162
+ showStartInfo(interval);
341
163
 
342
- execSyncGitCommand(command, options = {}) {
343
- let {encoding = 'utf-8', maxBuffer = 30 * 1024 * 1024, head = command, log = true} = options
344
164
  try {
345
- let cwd = getCwd()
346
- const output = execSync(command, {env: { ...process.env, LANG: 'C.UTF-8' },encoding, maxBuffer, cwd})
347
- if (options.spinner) {
348
- options.spinner.stop();
349
- }
350
- let result = output.trim()
351
- log && coloredLog(head, result)
352
- return result
353
- } catch (e) {
354
- // console.log(`执行命令出错 ==> `, command, e)
355
- log && coloredLog(command, e, 'error')
356
- throw new Error(e)
357
- }
358
- }
359
-
360
- execGitCommand(command, options = {}, callback) {
361
- return new Promise((resolve, reject) => {
362
- let {encoding = 'utf-8', maxBuffer = 30 * 1024 * 1024, head = command, log = true} = options
363
- let cwd = getCwd()
364
- exec(command, {env: { ...process.env, LANG: 'C.UTF-8' },encoding, maxBuffer, cwd}, (error, stdout, stderr) => {
365
- if (options.spinner) {
366
- options.spinner.stop();
367
- }
368
- if (error) {
369
- log && coloredLog(head, error, 'error')
370
- reject(error)
371
- return
372
- }
373
- if (stdout) {
374
- log && coloredLog(head, stdout)
375
- }
376
- if (stderr) {
377
- log && coloredLog(head, stderr)
378
- }
379
- resolve({
380
- stdout,
381
- stderr
382
- })
383
- callback && callback(error, stdout, stderr)
384
- })
385
- })
386
- }
387
- }
388
- // 在 judgeInterval 函数前添加配置命令处理
389
- async function main() {
390
- await handleConfigCommands();
391
- judgeInterval();
392
- }
393
- const judgeInterval = () => {
394
- // 判断是否有 --interval 参数
395
- const intervalArg = process.argv.find(arg => arg.startsWith('--interval'));
396
- if (intervalArg) {
397
- // console.log(`intervalArg ==> `, intervalArg)
398
- let interval = intervalArg.split('=')[1] || 60 * 60; // 默认间隔为1小时
399
- // console.log(`interval ==> `, interval)
400
- interval = parseInt(interval, 10) * 1000; // 将间隔时间转换为毫秒
401
- // console.log(`interval ==> `, interval)
402
- if (isNaN(interval)) {
403
- console.log('无效的间隔时间,请使用 --interval=秒数');
165
+ await commitAndSchedule(interval);
166
+ } catch (error) {
167
+ console.error(chalk.red.bold('定时提交致命错误:'), error.message);
404
168
  process.exit(1);
405
169
  }
406
- if (timer) {
407
- console.log(`清空定时器`)
408
- clearInterval(timer);
409
- timer = null;
410
- }
411
- new GitCommit({
412
- exit: false
413
- })
414
- timer = setInterval(() => {
415
- // console.log(`定时执行`)
416
- new GitCommit({
417
- exit: false
418
- })
419
- }, interval)
170
+
171
+ // 处理退出清理
172
+ process.on('SIGINT', () => {
173
+ logUpdate.clear();
174
+ clearTimeout(timer);
175
+ clearInterval(countdownInterval);
176
+ console.log(chalk.yellow('\n🛑 定时任务已终止'));
177
+ process.exit();
178
+ });
420
179
  } else {
421
- new GitCommit({
422
- exit: true
423
- })
180
+ createGitCommit({exit: false});
424
181
  }
425
182
  };
183
+ // const judgeInterval = async () => {
184
+ // // 判断是否有 --interval 参数
185
+ // const intervalArg = process.argv.find(arg => arg.startsWith('--interval'));
186
+ // if (intervalArg) {
187
+ // // // console.log(`intervalArg ==> `, intervalArg)
188
+ // // let interval = intervalArg.split('=')[1] || 60 * 60; // 默认间隔为1小时
189
+ // // // console.log(`interval ==> `, interval)
190
+ // // interval = parseInt(interval, 10) * 1000; // 将间隔时间转换为毫秒
191
+ // // // console.log(`interval ==> `, interval)
192
+ // // if (isNaN(interval)) {
193
+ // // console.log('无效的间隔时间,请使用 --interval=秒数');
194
+ // // process.exit(1);
195
+ // // }
196
+ // // if (timer) {
197
+ // // console.log(`清空定时器`)
198
+ // // clearInterval(timer);
199
+ // // timer = null;
200
+ // // }
201
+ // // showStartInfo(interval);
202
+ // // await createGitCommit({exit: false})
203
+ // //
204
+ // // timer = setInterval(() => {
205
+ // // // console.log(`定时执行`)
206
+ // // createGitCommit({exit: false})
207
+ // // }, interval)
208
+ //
209
+ // let interval = parseInt(intervalArg.split('=')[1] || '3600', 10) * 1000;
210
+ // // const showUpdates = () => {
211
+ // // showNextCommitTime(interval);
212
+ // // // 每小时更新一次时间显示
213
+ // // timer = setTimeout(() => {
214
+ // // showUpdates();
215
+ // // }, 20000); // 每小时更新一次
216
+ // // };
217
+ //
218
+ // const commitAndSchedule = async (interval) => {
219
+ // try {
220
+ // await createGitCommit({exit: false});
221
+ // startCountdown(interval); // 启动倒计时
222
+ //
223
+ // // 设置定时提交
224
+ // timer = setTimeout(async () => {
225
+ // await commitAndSchedule(interval);
226
+ // }, interval);
227
+ // } catch (error) {
228
+ // console.error('提交出错:', error.message);
229
+ // }
230
+ // };
231
+ //
232
+ // await commitAndSchedule();
233
+ //
234
+ // // 设置定时提交
235
+ // timer = setInterval(commitAndSchedule, interval);
236
+ // } else {
237
+ // createGitCommit({exit: false})
238
+ // }
239
+ // };
426
240
 
427
241
  main()
@@ -18,6 +18,12 @@ import stringWidth from 'string-width';
18
18
  import Table from 'cli-table3';
19
19
  import chalk from 'chalk';
20
20
  import boxen from "boxen";
21
+ import {exec, execSync} from 'child_process'
22
+ import os from 'os'
23
+ import ora from "ora";
24
+ import readline from 'readline'
25
+ import path from 'path'
26
+
21
27
 
22
28
  const printTableWithHeaderUnderline = (head, content, style) => {
23
29
  // 获取终端的列数(宽度)
@@ -43,6 +49,14 @@ const printTableWithHeaderUnderline = (head, content, style) => {
43
49
  style: style,
44
50
  wordWrap: true, // 启用自动换行
45
51
  // chars: {
52
+ // 'top': '─',
53
+ // 'top-mid': '┬',
54
+ // 'bottom': '─',
55
+ // 'mid': '─',
56
+ // 'left': '│',
57
+ // 'right': '│'
58
+ // },
59
+ // chars: {
46
60
  // 'top': '═', // 顶部边框使用长横线
47
61
  // 'top-mid': '╤', // 顶部连接符
48
62
  // 'top-left': '╔', // 左上角
@@ -94,6 +108,7 @@ function getRandomColor() {
94
108
  function resetColor() {
95
109
  return '\x1b[0m';
96
110
  }
111
+
97
112
  const calcColor = (commandLine, str) => {
98
113
  let color = 'reset'
99
114
  switch (commandLine) {
@@ -159,6 +174,9 @@ const coloredLog = (...args) => {
159
174
  const commandLine = args[0];
160
175
  const content = args[1];
161
176
  const type = args[2] || 'common';
177
+ // console.log(`commandLine ==> `, commandLine)
178
+ // console.log(`content ==> `, content)
179
+ // console.log(`type ==> `, type)
162
180
  tableLog(commandLine, content, type);
163
181
  }
164
182
  const errorLog = (commandLine, content) => {
@@ -169,4 +187,410 @@ const errorLog = (commandLine, content) => {
169
187
  const box = boxen(message);
170
188
  console.log(box); // 打印带有边框的消息
171
189
  }
172
- export {coloredLog, errorLog};
190
+
191
+ function execSyncGitCommand(command, options = {}) {
192
+ let {encoding = 'utf-8', maxBuffer = 30 * 1024 * 1024, head = command, log = true} = options
193
+ try {
194
+ let cwd = getCwd()
195
+ const output = execSync(command, {
196
+ env: {
197
+ ...process.env,
198
+ // LANG: 'en_US.UTF-8', // Linux/macOS
199
+ // LC_ALL: 'en_US.UTF-8', // Linux/macOS
200
+ GIT_CONFIG_PARAMETERS: "'core.quotepath=false'" // 关闭路径转义
201
+ }, encoding, maxBuffer, cwd
202
+ })
203
+ if (options.spinner) {
204
+ options.spinner.stop();
205
+ }
206
+ let result = output.trim()
207
+ log && coloredLog(head, result)
208
+ return result
209
+ } catch (e) {
210
+ // console.log(`执行命令出错 ==> `, command, e)
211
+ log && coloredLog(command, e, 'error')
212
+ throw new Error(e)
213
+ }
214
+ }
215
+
216
+ function execGitCommand(command, options = {}) {
217
+ return new Promise((resolve, reject) => {
218
+ let {encoding = 'utf-8', maxBuffer = 30 * 1024 * 1024, head = command, log = true} = options
219
+ let cwd = getCwd()
220
+
221
+ // setTimeout(() => {
222
+ exec(command, {
223
+ env: {
224
+ ...process.env,
225
+ // LANG: 'en_US.UTF-8', // Linux/macOS
226
+ // LC_ALL: 'en_US.UTF-8', // Linux/macOS
227
+ GIT_CONFIG_PARAMETERS: "'core.quotepath=false'" // 关闭路径转义
228
+ },
229
+ encoding,
230
+ maxBuffer,
231
+ cwd
232
+ }, (error, stdout, stderr) => {
233
+ if (options.spinner) {
234
+ options.spinner.stop();
235
+ }
236
+
237
+ if (stdout) {
238
+ log && coloredLog(head, stdout)
239
+ }
240
+ if (stderr) {
241
+ log && coloredLog(head, stderr)
242
+ }
243
+ if (error) {
244
+ log && coloredLog(head, error, 'error')
245
+ reject(error)
246
+ return
247
+ }
248
+ resolve({
249
+ stdout,
250
+ stderr
251
+ })
252
+ })
253
+ // }, 1000)
254
+
255
+ })
256
+ }
257
+
258
+ const getCwd = () => {
259
+ const cwdArg = process.argv.find(arg => arg.startsWith('--path')) || process.argv.find(arg => arg.startsWith('--cwd'));
260
+ if (cwdArg) {
261
+ // console.log(`cwdArg ==> `, cwdArg)
262
+ const [, value] = cwdArg.split('=')
263
+ // console.log(`value ==> `, value)
264
+ return value || process.cwd()
265
+ }
266
+ return process.cwd()
267
+ }
268
+ const judgePlatform = () => {
269
+ // 判断是否是 Windows 系统
270
+ if (os.platform() === 'win32') {
271
+ try {
272
+ // 设置终端字符编码为 UTF-8
273
+ execSync('chcp 65001');
274
+ } catch (e) {
275
+ console.error('设置字符编码失败:', e.message);
276
+ }
277
+ }
278
+ };
279
+ const showHelp = () => {
280
+ const helpMessage = `
281
+ Usage: g [options]
282
+
283
+ Options:
284
+ -h, --help Show this help message
285
+ --set-default-message=<msg> Set default commit message
286
+ get-config Show current configuration
287
+ -y Auto commit with default message
288
+ -m <message> Commit message (use quotes if message contains spaces)
289
+ -m=<message> Commit message (use this form without spaces around '=')
290
+ --path=<path> Set custom working directory
291
+ --cwd=<path> Set custom working directory
292
+ --interval=<seconds> Set interval time for automatic commits (in seconds)
293
+ log Show git commit logs
294
+ --n=<number> Number of commits to show with --log
295
+ --no-diff Skip displaying git diff
296
+
297
+ Example:
298
+ g -m "Initial commit" Commit with a custom message
299
+ g -m=Fix-bug Commit with a custom message (no spaces around '=')
300
+ g -y Auto commit with the default message
301
+ g -y --interval=600 Commit every 10 minutes (600 seconds)
302
+ g --path=/path/to/repo Specify a custom working directory
303
+ g log Show recent commit logs
304
+ g log --n=5 Show the last 5 commits with --log
305
+
306
+ Add auto submit in package.json:
307
+ "scripts": {
308
+ "g:y": "g -y"
309
+ }
310
+
311
+ Run in the background across platforms:
312
+ Windows:
313
+ start /min cmd /k "g -y --path=your-folder --interval=600"
314
+
315
+ Linux/macOS:
316
+ nohup g -y --path=your-folder --interval=600 > git-autocommit.log 2>&1 &
317
+
318
+ Stop all monitoring processes:
319
+ Windows: Terminate the Node.js process in the Task Manager.
320
+ Linux/macOS:
321
+ pkill -f "g -y" # Terminate all auto-commit processes
322
+ ps aux | grep "g -y" # Find the specific process ID
323
+ kill [PID] # Terminate the specified process
324
+ `;
325
+
326
+ console.log(helpMessage);
327
+ process.exit();
328
+ };
329
+
330
+ function judgeLog() {
331
+ const logArg = process.argv.find(arg => arg === 'log');
332
+ if (logArg) {
333
+ printGitLog(); // 如果有 log 参数,打印 Git 提交记录
334
+ // 打印完成后退出
335
+ process.exit();
336
+ }
337
+ }
338
+
339
+ function judgeHelp() {
340
+ if (process.argv.includes('-h') || process.argv.includes('--help')) {
341
+ showHelp();
342
+ }
343
+ }
344
+
345
+ async function printGitLog() {
346
+ let n = 20;
347
+ let logArg = process.argv.find(arg => arg.startsWith('--n='));
348
+ if (logArg) {
349
+ n = parseInt(logArg.split('=')[1], 10);
350
+ }
351
+ const logCommand = `git log -n ${n} --pretty=format:"%C(green)%h%C(reset) | %C(cyan)%an%C(reset) | %C(yellow)%ad%C(reset) | %C(blue)%D%C(reset) | %C(magenta)%s%C(reset)" --date=format:"%Y-%m-%d %H:%M" --graph --decorate --color`
352
+ try {
353
+ const logOutput = execSyncGitCommand(logCommand, {
354
+ head: `git log`
355
+ });
356
+ } catch (error) {
357
+ console.error('无法获取 Git 提交记录:', error.message);
358
+ }
359
+ // 打印完成后退出
360
+ process.exit();
361
+ }
362
+
363
+ function exec_exit(exit) {
364
+ if (exit) {
365
+ process.exit()
366
+ }
367
+ }
368
+
369
+ function judgeUnmerged(statusOutput) {
370
+ const hasUnmerged = statusOutput.includes('You have unmerged paths');
371
+ if (hasUnmerged) {
372
+ errorLog('错误', '存在未合并的文件,请先解决冲突')
373
+ process.exit(1);
374
+ }
375
+ }
376
+
377
+ function exec_push({exit, commitMessage}) {
378
+ // 执行 git push
379
+ // execSyncGitCommand(`git push`);
380
+ return new Promise((resolve, reject) => {
381
+ const spinner = ora('正在推送代码...').start();
382
+ execGitCommand('git push', {
383
+ spinner
384
+ }).then(({stdout, stderr}) => {
385
+ printCommitLog({commitMessage})
386
+ resolve({stdout, stderr})
387
+ })
388
+ });
389
+ }
390
+
391
+ function printCommitLog({commitMessage}) {
392
+ try {
393
+ // 获取项目名称(取git仓库根目录名)
394
+ const projectRoot = execSyncGitCommand('git rev-parse --show-toplevel', {log: false});
395
+ const projectName = chalk.blueBright(path.basename(projectRoot.trim()));
396
+
397
+ // 获取当前提交hash(取前7位)
398
+ const commitHash = execSyncGitCommand('git rev-parse --short HEAD', {log: false}).trim();
399
+ const hashDisplay = chalk.yellow(commitHash);
400
+
401
+ // 获取分支信息
402
+ const branch = execSyncGitCommand('git branch --show-current', {log: false}).trim();
403
+ const branchDisplay = chalk.magenta(branch);
404
+
405
+ // 构建信息内容
406
+ const message = [
407
+ `${chalk.cyan.bold('Project:')} ${projectName}`,
408
+ `${chalk.cyan.bold('Commit:')} ${hashDisplay} ${chalk.dim('on')} ${branchDisplay}`,
409
+ `${chalk.cyan.bold('Message:')} ${chalk.reset(commitMessage)}`,
410
+ `${chalk.cyan.bold('Time:')} ${new Date().toLocaleString()}`
411
+ ].join('\n');
412
+
413
+ // 使用boxen创建装饰框
414
+ const box = boxen(message, {
415
+ padding: 1,
416
+ margin: 1,
417
+ borderStyle: 'round',
418
+ borderColor: 'green',
419
+ title: chalk.bold.green('✅ COMMIT SUCCESS'),
420
+ titleAlignment: 'center',
421
+ float: 'left',
422
+ textAlignment: 'left'
423
+ });
424
+
425
+ console.log(box);
426
+ } catch (error) {
427
+ // 异常处理
428
+ const errorBox = boxen(chalk.red(`Failed to get commit details: ${error.message}`), {
429
+ borderColor: 'red',
430
+ padding: 1
431
+ });
432
+ console.log(errorBox);
433
+ }
434
+ }
435
+
436
+ async function execPull() {
437
+ try {
438
+ // 检查是否需要拉取更新
439
+ const spinner = ora('正在拉取代码...').start();
440
+ await execGitCommand('git pull', {
441
+ spinner
442
+ })
443
+ } catch (e) {
444
+ console.log(chalk.yellow('⚠️ 拉取远程更新合并失败,可能存在冲突,请手动处理'));
445
+ throw Error(e)
446
+ }
447
+ }
448
+
449
+ function delay(timeout) {
450
+ return new Promise(resolve => setTimeout(resolve, timeout));
451
+ }
452
+
453
+ async function judgeRemote() {
454
+ const spinner = ora('正在检查远程更新...').start();
455
+ try {
456
+ // 检查是否有远程更新
457
+ // 先获取远程最新状态
458
+ await execGitCommand('git remote update', {
459
+ head: 'Fetching remote updates',
460
+ log: false
461
+ });
462
+ // 检查是否需要 pull
463
+ const res = await execGitCommand('git rev-list HEAD..@{u} --count', {
464
+ head: 'Checking if behind remote',
465
+ log: false
466
+ });
467
+ const behindCount = res.stdout.trim()
468
+ const { green, black, bgGreen, white } = chalk;
469
+ // 如果本地落后于远程
470
+ if (parseInt(behindCount) > 0) {
471
+ try {
472
+ spinner.stop();
473
+ // const spinner_pull = ora('发现远程更新,正在拉取...').start();
474
+ await execPull()
475
+
476
+ // // 尝试使用 --ff-only 拉取更新
477
+ // const res = await execGitCommand('git pull --ff-only', {
478
+ // spinner: spinner_pull,
479
+ // head: 'Pulling updates'
480
+ // });
481
+ // console.log(
482
+ // bgGreen.white.bold(' SYNC ') +
483
+ // green` ➔ ` +
484
+ // chalk.blue.bold('远程仓库已同步') +
485
+ // green(' ✔')
486
+ // );
487
+ const message = '已成功同步远程更新'.split('').map((char, i) =>
488
+ chalk.rgb(0, 255 - i*10, 0)(char)
489
+ ).join('');
490
+
491
+ console.log(chalk.bold(`✅ ${message}`));
492
+ } catch (pullError) {
493
+ // // 如果 --ff-only 拉取失败,尝试普通的 git pull
494
+ // console.log(chalk.yellow('⚠️ 无法快进合并,尝试普通合并...'));
495
+ // await this.execPull()
496
+ throw new Error(pullError)
497
+ }
498
+ } else {
499
+ spinner.stop();
500
+ const message = '本地已是最新'.split('').map((char, i) =>
501
+ chalk.rgb(0, 255 - i*10, 0)(char)
502
+ ).join('');
503
+ console.log(chalk.bold(`✅ ${message}`));
504
+ }
505
+ } catch (e) {
506
+ // console.log(`e ==> `, e)
507
+ spinner.stop();
508
+ throw new Error(e)
509
+ }
510
+ }
511
+
512
+ async function execDiff() {
513
+ const no_diff = process.argv.find(arg => arg.startsWith('--no-diff'))
514
+ if (!no_diff) {
515
+ await execGitCommand('git diff --color=always', {
516
+ head: `git diff`
517
+ })
518
+ }
519
+ }
520
+
521
+ async function execAddAndCommit({statusOutput, commitMessage, exit}) {
522
+ // 检查 -m 参数(提交信息)
523
+ const commitMessageArg = process.argv.find(arg => arg.startsWith('-m'));
524
+ if (commitMessageArg) {
525
+ if (commitMessageArg.includes('=')) {
526
+ // 处理 -m=<message> 的情况
527
+ commitMessage = commitMessageArg.split('=')[1]?.replace(/^['"]|['"]$/g, '');
528
+ } else {
529
+ // 处理 -m <message> 的情况
530
+ const index = process.argv.indexOf(commitMessageArg);
531
+ if (index !== -1 && process.argv[index + 1]) {
532
+ commitMessage = process.argv[index + 1]?.replace(/^['"]|['"]$/g, '');
533
+ }
534
+ }
535
+ }
536
+
537
+ // 检查命令行参数,判断是否有 -y 参数
538
+ const autoCommit = process.argv.includes('-y');
539
+
540
+ if (!autoCommit && !commitMessageArg) {
541
+ // 如果没有 -y 参数,则等待用户输入提交信息
542
+ const rl = readline.createInterface({
543
+ input: process.stdin,
544
+ output: process.stdout
545
+ })
546
+
547
+ function rlPromisify(fn) {
548
+ return async (...args) => {
549
+ return new Promise((resolve, reject) => fn(...args, resolve, reject))
550
+ }
551
+ }
552
+
553
+ const question = rlPromisify(rl.question.bind(rl))
554
+ commitMessage = await question('请输入提交信息:') || commitMessage;
555
+ }
556
+
557
+ statusOutput.includes('(use "git add') && await execGitCommand('git add .')
558
+ // 强制添加所有变更
559
+ // await execGitCommand('git add -A .');
560
+
561
+ // 提交前二次校验
562
+ const checkStatus = await execGitCommand('git status --porcelain', {log: false});
563
+ if (!checkStatus.stdout.trim()) {
564
+ console.log(chalk.yellow('⚠️ 没有检测到可提交的变更'));
565
+ // exec_exit(exit)
566
+ return;
567
+ }
568
+
569
+ // 执行 git commit
570
+ if (statusOutput.includes('Untracked files:') || statusOutput.includes('Changes not staged for commit') || statusOutput.includes('Changes to be committed')) {
571
+ await execGitCommand(`git commit -m "${commitMessage}"`)
572
+ }
573
+ }
574
+
575
+ // 添加时间格式化函数
576
+ function formatDuration(ms) {
577
+ const totalSeconds = Math.floor(ms / 1000);
578
+ const days = Math.floor(totalSeconds / (3600 * 24));
579
+ const hours = Math.floor((totalSeconds % (3600 * 24)) / 3600);
580
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
581
+ const seconds = totalSeconds % 60;
582
+
583
+ return [
584
+ days && `${days}天`,
585
+ hours && `${hours}小时`,
586
+ minutes && `${minutes}分`,
587
+ `${seconds}秒`
588
+ ].filter(Boolean).join('');
589
+ }
590
+
591
+ export {
592
+ coloredLog, errorLog, execSyncGitCommand,
593
+ execGitCommand, getCwd, judgePlatform, showHelp, judgeLog, printGitLog,
594
+ judgeHelp, exec_exit, judgeUnmerged, delay, formatDuration,
595
+ exec_push, execPull, judgeRemote, execDiff, execAddAndCommit
596
+ };