git-cracked 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import { existsSync } from 'fs';
3
3
  import { loadConfig } from './config.js';
4
4
  import { runCommit } from './committer.js';
5
5
  import { startDashboard } from './dashboard.js';
6
+ import { getActivity } from './logger.js';
6
7
  import { CONFIG_PATH } from './paths.js';
7
8
 
8
9
  const args = process.argv.slice(2);
@@ -21,13 +22,86 @@ if (runNow) {
21
22
  process.exit(0);
22
23
  }
23
24
 
25
+ // How often the catch-up checker runs (ms). Catches scheduled slots that were
26
+ // missed because the machine was asleep / shut down at the exact cron minute.
27
+ const CATCHUP_INTERVAL_MS = 15 * 60 * 1000;
28
+
24
29
  let scheduled = false;
30
+ let committing = false;
31
+ let activeConfig = null;
25
32
  const activeTasks = [];
33
+ let catchupTimer = null;
34
+
35
+ // Single-flight wrapper so the cron jobs and the catch-up checker can never
36
+ // run a commit at the same time (which would risk a double commit).
37
+ async function safeCommit(config, reason) {
38
+ if (committing) return;
39
+ committing = true;
40
+ try {
41
+ await runCommit(config);
42
+ } catch (err) {
43
+ console.error(`${reason} failed:`, err.message);
44
+ } finally {
45
+ committing = false;
46
+ }
47
+ }
48
+
49
+ // Parse a 5-field cron expression into { h, m, dows } for catch-up math.
50
+ // Returns null for expressions with ranges/lists in the minute or hour field,
51
+ // which the catch-up checker simply skips (the live cron job still covers them).
52
+ function parseExpr(expr) {
53
+ const parts = expr.trim().split(/\s+/);
54
+ if (parts.length !== 5) return null;
55
+ const [min, hour, , , dow] = parts;
56
+ const m = parseInt(min, 10);
57
+ const h = parseInt(hour, 10);
58
+ if (Number.isNaN(m) || Number.isNaN(h)) return null;
59
+
60
+ const dows = new Set();
61
+ if (dow === '*') {
62
+ for (let i = 0; i <= 6; i++) dows.add(i);
63
+ } else {
64
+ for (const p of dow.split(',')) {
65
+ if (p.includes('-')) {
66
+ const [a, b] = p.split('-').map(Number);
67
+ for (let i = a; i <= b; i++) dows.add(i % 7);
68
+ } else {
69
+ dows.add(Number(p) % 7);
70
+ }
71
+ }
72
+ }
73
+ return { h, m, dows };
74
+ }
75
+
76
+ // If a scheduled slot has already passed today (on a matching weekday) and no
77
+ // commit has happened since, commit once to catch up.
78
+ async function checkMissedCommits(config) {
79
+ const now = new Date();
80
+ let latestPassed = null;
81
+
82
+ for (const expr of config.schedule) {
83
+ const p = parseExpr(expr);
84
+ if (!p || !p.dows.has(now.getDay())) continue;
85
+ const slot = new Date(now);
86
+ slot.setHours(p.h, p.m, 0, 0);
87
+ if (slot <= now && (!latestPassed || slot > latestPassed)) latestPassed = slot;
88
+ }
89
+
90
+ if (!latestPassed) return; // nothing scheduled has passed today
91
+
92
+ const commits = getActivity().commits;
93
+ const last = commits[0] ? new Date(commits[0].timestamp) : null;
94
+ if (last && last >= latestPassed) return; // already committed since that slot
95
+
96
+ console.log(`[${now.toISOString()}] Catching up missed scheduled commit`);
97
+ await safeCommit(config, 'Catch-up commit');
98
+ }
26
99
 
27
100
  function startSchedule(config) {
28
101
  // Replace any previously registered jobs (e.g. after a settings change)
29
102
  for (const task of activeTasks) task.stop();
30
103
  activeTasks.length = 0;
104
+ activeConfig = config;
31
105
 
32
106
  for (const expression of config.schedule) {
33
107
  if (!cron.validate(expression)) {
@@ -36,16 +110,21 @@ function startSchedule(config) {
36
110
  }
37
111
  const task = cron.schedule(expression, async () => {
38
112
  console.log(`[${new Date().toISOString()}] Cron triggered`);
39
- try {
40
- await runCommit(config);
41
- } catch (err) {
42
- console.error('Commit failed:', err.message);
43
- }
113
+ await safeCommit(config, 'Scheduled commit');
44
114
  });
45
115
  activeTasks.push(task);
46
116
  }
117
+
47
118
  scheduled = true;
48
119
  console.log('Schedule active:', config.schedule.join(', '));
120
+
121
+ // Catch up anything missed while the machine was off, then poll periodically.
122
+ checkMissedCommits(config);
123
+ if (!catchupTimer) {
124
+ catchupTimer = setInterval(() => {
125
+ if (activeConfig) checkMissedCommits(activeConfig);
126
+ }, CATCHUP_INTERVAL_MS);
127
+ }
49
128
  }
50
129
 
51
130
  console.log('Git Cracked running.');