buildsight-collector 1.0.2 → 1.0.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.
Files changed (2) hide show
  1. package/index.js +156 -11
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -5,6 +5,65 @@ import chalk from "chalk";
5
5
  import ora from "ora";
6
6
  import simpleGit from "simple-git";
7
7
  import { spawn } from 'child_process';
8
+ import os from "os";
9
+ import { execSync } from "child_process";
10
+
11
+ function getCurrentCron() {
12
+ try {
13
+ return execSync("crontab -l").toString();
14
+ } catch {
15
+ return "";
16
+ }
17
+ }
18
+
19
+ function detectOS() {
20
+ const platform = os.platform();
21
+
22
+ if (platform === "linux") return "linux";
23
+ if (platform === "darwin") return "mac";
24
+ if (platform === "win32") return "windows";
25
+
26
+ return "unknown";
27
+ }
28
+
29
+ async function setupAutomaticRun(token, intervalMinutes) {
30
+ const platform = detectOS();
31
+ const cronLine = `*/${intervalMinutes} * * * * npx buildsight-collector ${token}`;
32
+
33
+ console.log(chalk.cyan("\n⚙ Configuração de execução automática detectada."));
34
+
35
+ if (platform === "linux" || platform === "mac") {
36
+ console.log(chalk.gray("➡ Sistema detected: Linux/Mac"));
37
+
38
+ try {
39
+ const current = getCurrentCron();
40
+ if (current.includes(cronLine)) {
41
+ console.log(chalk.yellow("⚠ Cron já configurada anteriormente."));
42
+ return;
43
+ }
44
+
45
+ execSync(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`);
46
+
47
+ console.log(chalk.green("✓ Cron adicionada com sucesso!"));
48
+ } catch (err) {
49
+ console.log(chalk.red("❌ Falha ao configurar cron:", err.message));
50
+ }
51
+ } else if (platform === "windows") {
52
+ console.log(chalk.gray("➡ Sistema detected: Windows"));
53
+
54
+ const taskName = "BuildSightCollector";
55
+ const taskCmd = `schtasks /Create /SC MINUTE /MO ${intervalMinutes} /TN "${taskName}" /TR "npx buildsight-collector ${token}" /F`;
56
+
57
+ try {
58
+ execSync(taskCmd);
59
+ console.log(chalk.green("✓ Tarefa agendada criada com sucesso!"));
60
+ } catch (err) {
61
+ console.log(chalk.red("❌ Falha ao criar tarefa agendada:", err.message));
62
+ }
63
+ } else {
64
+ console.log(chalk.red("❌ Sistema operacional não suportado para execução automática."));
65
+ }
66
+ }
8
67
 
9
68
  const spinner = ora();
10
69
 
@@ -36,9 +95,17 @@ async function main() {
36
95
  process.exit(0);
37
96
  }
38
97
 
39
- spinner.succeed(`Foram encontrados ${data.data.length} paths.`);
98
+ const paths = data?.data || [];
99
+ const autoRunEnabled = data?.autoRunEnabled ?? false;
100
+ const intervalMinutes = data?.intervalMinutes ?? 15;
40
101
 
41
- for (const repo of data.data) {
102
+ if (autoRunEnabled) {
103
+ await setupAutomaticRun(token, intervalMinutes);
104
+ }
105
+
106
+ spinner.succeed(`Foram encontrados ${paths.length} paths.`);
107
+
108
+ for (const repo of paths) {
42
109
  console.log(chalk.blue(`\n📂 Processando repositório:`), chalk.white(repo.name));
43
110
  const git = simpleGit(repo.path);
44
111
  const DAYS_RANGE = repo.periodDays || 1;
@@ -79,27 +146,103 @@ async function main() {
79
146
  // --- 🔹 3. Merges locais ---
80
147
  const merges = await git.log({ '--merges': null });
81
148
 
82
- // --- 🔹 4. Arquivos mais modificados ---
149
+ // --- 🔹 4. Arquivos mais modificados + Code Churn + Hot Spots ---
83
150
  const fileChangeCounts = {};
151
+ const codeChurn = {}; // { file: { additions, deletions, commits, authors: Set } }
152
+ const hotSpots = {}; // { file: { totalChanges, bugFixCount, authors: Set, lastModified } }
153
+
84
154
  for (const commit of log.all) {
85
155
  try {
156
+ // Obter estatísticas detalhadas do commit (linhas +/-)
86
157
  const show = await git.raw(['show', '--stat', '--oneline', commit.hash]);
87
- const files = Array.from(show.matchAll(/ ([^\s]+)\s+\|\s+\d+/g)).map(m => m[1]);
88
- for (const file of files) {
89
- fileChangeCounts[file] = (fileChangeCounts[file] || 0) + 1;
158
+ const fileMatches = Array.from(show.matchAll(/ ([^\s]+)\s+\|\s+(\d+)/g));
159
+
160
+ // Obter detalhes de adições/remoções por arquivo
161
+ const numstat = await git.raw(['show', '--numstat', '--oneline', commit.hash]);
162
+ const numstatLines = numstat.split('\n').slice(1).filter(line => line.trim());
163
+
164
+ const isBugFix = /^fix|bug|hotfix|urgent|patch/i.test(commit.message);
165
+
166
+ for (const line of numstatLines) {
167
+ const match = line.match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
168
+ if (match) {
169
+ const additions = match[1] === '-' ? 0 : parseInt(match[1], 10);
170
+ const deletions = match[2] === '-' ? 0 : parseInt(match[2], 10);
171
+ const file = match[3].trim();
172
+
173
+ // Atualizar fileChangeCounts
174
+ fileChangeCounts[file] = (fileChangeCounts[file] || 0) + 1;
175
+
176
+ // Atualizar Code Churn
177
+ if (!codeChurn[file]) {
178
+ codeChurn[file] = {
179
+ additions: 0,
180
+ deletions: 0,
181
+ commits: 0,
182
+ authors: new Set()
183
+ };
184
+ }
185
+ codeChurn[file].additions += additions;
186
+ codeChurn[file].deletions += deletions;
187
+ codeChurn[file].commits += 1;
188
+ codeChurn[file].authors.add(commit.author_email);
189
+
190
+ // Atualizar Hot Spots
191
+ if (!hotSpots[file]) {
192
+ hotSpots[file] = {
193
+ totalChanges: 0,
194
+ bugFixCount: 0,
195
+ authors: new Set(),
196
+ lastModified: commit.date
197
+ };
198
+ }
199
+ hotSpots[file].totalChanges += 1;
200
+ hotSpots[file].authors.add(commit.author_email);
201
+ if (isBugFix) {
202
+ hotSpots[file].bugFixCount += 1;
203
+ }
204
+ // Atualizar última modificação se mais recente
205
+ if (new Date(commit.date) > new Date(hotSpots[file].lastModified)) {
206
+ hotSpots[file].lastModified = commit.date;
207
+ }
208
+ }
90
209
  }
91
210
  } catch { }
92
211
  }
93
212
 
213
+ // Converter Sets para arrays e calcular métricas finais
214
+ const codeChurnData = Object.entries(codeChurn).map(([file, data]) => ({
215
+ file,
216
+ additions: data.additions,
217
+ deletions: data.deletions,
218
+ totalChurn: data.additions + data.deletions,
219
+ commits: data.commits,
220
+ churnRate: data.commits > 0 ? (data.additions + data.deletions) / data.commits : 0,
221
+ authors: Array.from(data.authors),
222
+ authorCount: data.authors.size
223
+ })).sort((a, b) => b.totalChurn - a.totalChurn);
224
+
225
+ const hotSpotsData = Object.entries(hotSpots).map(([file, data]) => ({
226
+ file,
227
+ totalChanges: data.totalChanges,
228
+ bugFixCount: data.bugFixCount,
229
+ bugFixRatio: data.totalChanges > 0 ? data.bugFixCount / data.totalChanges : 0,
230
+ authors: Array.from(data.authors),
231
+ authorCount: data.authors.size,
232
+ lastModified: data.lastModified,
233
+ // Score composto: frequência de mudanças + proporção de bug fixes + número de autores
234
+ hotSpotScore: (data.totalChanges * 0.4) + (data.bugFixCount * 0.4) + (data.authors.size * 0.2)
235
+ })).sort((a, b) => b.hotSpotScore - a.hotSpotScore);
236
+
94
237
  // --- 🔹 5. Montar dados crus por commit ---
95
238
  const commits = await Promise.all(
96
239
  log.all.map(async (c) => {
97
240
  const isMerge = c.message.startsWith("Merge");
98
- const isRevert = /revert:/i.test(c.message);
99
- const isFix = /^fix:/i.test(c.message);
100
- const isFeat = /^feat:/i.test(c.message);
101
- const isRefactor = /^refactor:/i.test(c.message);
102
- const isHotfix = /hotfix|urgent/i.test(c.message);
241
+ const isRevert = /revert/i.test(c.message);
242
+ const isFix = /^fix/i.test(c.message);
243
+ const isFeat = /^(feat|feature)/i.test(c.message);
244
+ const isRefactor = /^refactor/i.test(c.message);
245
+ const isHotfix = /^(hotfix|urgent)/i.test(c.message);
103
246
 
104
247
  let filesChanged = [];
105
248
  try {
@@ -163,6 +306,8 @@ async function main() {
163
306
  message: m.message,
164
307
  })),
165
308
  fileChangeCounts, // 🔥 arquivos mais alterados
309
+ codeChurn: codeChurnData, // 🔄 detecção de code churn
310
+ hotSpots: hotSpotsData, // 🔥 identificação de hot spots
166
311
  },
167
312
  commits: batch,
168
313
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildsight-collector",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "CLI do BuildSight para coleta automatizada de commits e métricas de repositórios.",
5
5
  "main": "index.js",
6
6
  "bin": {