-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutility-functions.js
More file actions
463 lines (372 loc) Β· 17.1 KB
/
utility-functions.js
File metadata and controls
463 lines (372 loc) Β· 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { spawn } = require('child_process');
// Import the core class
const { AIEngineerActivityGenerator } = require('./activity-generator.js');
// Secure command execution using spawn
function executeCommand(command, args, options = {}) {
return new Promise((resolve, reject) => {
const proc = spawn(command, args, { stdio: 'pipe', ...options });
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
if (code === 0) {
resolve(stdout);
} else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
proc.on('error', (err) => {
reject(err);
});
});
}
// Add the missing methods directly to the prototype
AIEngineerActivityGenerator.prototype.generateUniformCommitDates = function() {
const commitSchedule = [];
let currentColumn = 0;
for (let char of this.version) {
if (!this.letterPatterns[char]) {
console.warn(`Character '${char}' not supported, skipping...`);
continue;
}
const pattern = this.letterPatterns[char];
const letterWidth = pattern[0].length;
for (let col = 0; col < letterWidth; col++) {
for (let row = 0; row < 7; row++) {
if (pattern[row][col] === 1) {
const date = new Date(this.startDate);
// Fix alignment issue by ensuring consistent day calculation
date.setDate(date.getDate() + (currentColumn + col) * 7 + row);
// Create uniform commits spread evenly throughout the day
for (let commitNum = 0; commitNum < this.options.commitsPerDay; commitNum++) {
const commitDate = new Date(date);
// Spread commits evenly across 24 hours for maximum uniformity
const hourSpread = 24 / this.options.commitsPerDay;
const baseHour = Math.floor(commitNum * hourSpread);
const minuteVariation = Math.floor(Math.random() * 60);
const secondVariation = Math.floor(Math.random() * 60);
commitDate.setHours(baseHour, minuteVariation, secondVariation, 0);
commitSchedule.push(commitDate);
}
}
}
}
currentColumn += letterWidth + 1;
}
commitSchedule.sort((a, b) => a.getTime() - b.getTime());
return commitSchedule;
};
AIEngineerActivityGenerator.prototype.forceReplacePattern = async function() {
console.log('π Force replacing existing pattern...');
try {
// Reset git history to remove old commits using secure command execution
await executeCommand('git', ['checkout', '--orphan', 'temp-new-pattern'], { cwd: this.repoPath });
await executeCommand('git', ['rm', '-rf', '.'], { cwd: this.repoPath });
console.log('β
Cleared existing pattern');
} catch (error) {
console.log('π‘ Creating fresh pattern (no existing pattern found)');
}
};
AIEngineerActivityGenerator.prototype.previewPattern = function() {
console.log(`π PREVIEW: "${this.version}" Pattern`);
console.log("=".repeat(50));
const { startDate: windowStart, endDate: windowEnd } = this.getCurrentGitHubWindow();
console.log(`π
Current GitHub window: ${windowStart.toDateString()} to ${windowEnd.toDateString()}`);
if (this.recommendedVersions[this.version]) {
const info = this.recommendedVersions[this.version];
console.log(`π ${info.description}`);
console.log(`π Width: ${info.width} weeks | Visibility: ${info.visibility}`);
}
console.log(`π₯ Intensity: ${this.options.intensity} (${this.options.commitsPerDay} commits per pixel)`);
console.log(`π― Positioning: ${this.options.centerMessage ? 'Centered' : 'Left-aligned'}`);
console.log(`ποΈ Pattern start: ${this.startDate.toDateString()}\n`);
// Get the commit dates that will be used for the actual GitHub activity
const commitDates = this.generateUniformCommitDates();
const uniqueDates = [...new Set(commitDates.map(d => d.toDateString()))];
// Create a grid with exact GitHub dimensions (7 days x 53 weeks)
const grid = Array(7).fill().map(() => Array(53).fill('Β·'));
// Create a map of dates to make lookup faster
const dateMap = new Set(uniqueDates);
// Fill the grid by simulating GitHub's rendering approach
// This ensures the preview matches GitHub's actual rendering
const startOfWindow = new Date(windowStart);
// Adjust the window start to match GitHub's alignment
// GitHub's contribution graph always starts on Sunday
while (startOfWindow.getDay() !== 0) {
startOfWindow.setDate(startOfWindow.getDate() - 1);
}
// Fill the grid by checking each cell
for (let week = 0; week < 53; week++) {
for (let day = 0; day < 7; day++) {
const cellDate = new Date(startOfWindow);
cellDate.setDate(cellDate.getDate() + (week * 7) + day);
// Check if this date has commits
if (dateMap.has(cellDate.toDateString())) {
grid[day][week] = 'β';
}
}
}
// ANSI color codes for green text
const GREEN = "\x1b[32m";
const RESET = "\x1b[0m";
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
console.log('GitHub Activity Graph Preview (53-week window):');
grid.forEach((row, index) => {
// Convert the row to a string with green color for filled squares
const coloredRow = row.map(cell => cell === 'β' ? `${GREEN}β${RESET}` : 'Β·').join('');
console.log(`${dayNames[index]}: ${coloredRow}`);
});
console.log(`\nπ Unique days with activity: ${uniqueDates.length}`);
console.log(`π₯ Total commits: ${commitDates.length} (${this.options.commitsPerDay} per day)`);
console.log(`π
Pattern span: ${this.calculateMessageWidth()} weeks`);
if (this.options.keepInView) {
const refreshDates = this.calculateRefreshSchedule();
console.log(`π Next refresh needed: ${refreshDates[0]?.toDateString() || 'Not scheduled'}`);
}
};
AIEngineerActivityGenerator.prototype.createCommits = function() {
return new Promise(async (resolve, reject) => {
try {
// Force replace if requested
if (this.options.forceReplace) {
await this.forceReplacePattern();
}
const commitDates = this.generateUniformCommitDates();
const activityFile = path.join(this.repoPath, 'ai-engineer-activity.txt');
const configFile = path.join(this.repoPath, '.github-activity-config.json');
const activityReadmeFile = path.join(this.repoPath, 'ACTIVITY-PATTERN.md');
// Check if original README exists and preserve it
const originalReadmeFile = path.join(this.repoPath, 'README.md');
console.log(`π€ Creating AI Engineer activity pattern: "${this.version}"`);
console.log(`π₯ Intensity: ${this.options.intensity} (${this.options.commitsPerDay} commits per pixel)`);
console.log(`π
${commitDates.length} total commits scheduled`);
console.log(`π Message width: ${this.calculateMessageWidth()} weeks`);
console.log(`π― Message positioning: ${this.options.centerMessage ? 'Centered' : 'Left-aligned'}`);
const { startDate: windowStart, endDate: windowEnd } = this.getCurrentGitHubWindow();
console.log(`π
Current visible window: ${windowStart.toDateString()} to ${windowEnd.toDateString()}`);
if (this.recommendedVersions[this.version]) {
const info = this.recommendedVersions[this.version];
console.log(`π‘ ${info.description}`);
}
if (this.options.keepInView) {
const refreshSchedule = this.calculateRefreshSchedule();
console.log(`π Auto-refresh enabled - ${refreshSchedule.length} refresh points scheduled`);
}
console.log();
process.chdir(this.repoPath);
const config = {
version: this.version,
options: this.options,
created: new Date().toISOString(),
messageWidth: this.calculateMessageWidth(),
refreshSchedule: this.options.keepInView ? this.calculateRefreshSchedule() : [],
windowInfo: this.getCurrentGitHubWindow()
};
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
const activityReadmeContent = `# AI Engineer GitHub Activity Display
## Current Message: "${this.version}"
This repository creates a GitHub activity graph pattern displaying "${this.version}".
### Configuration
- **Message**: ${this.version}
- **Width**: ${this.calculateMessageWidth()} weeks
- **Intensity**: ${this.options.intensity} (${this.options.commitsPerDay} commits per pixel)
- **Total Commits**: ${commitDates.length}
- **Positioning**: ${this.options.centerMessage ? 'Centered' : 'Left-aligned'}
- **Auto-Refresh**: ${this.options.keepInView ? 'Enabled' : 'Disabled'}
### Current GitHub Window
- **Start**: ${windowStart.toDateString()}
- **End**: ${windowEnd.toDateString()}
- **Pattern Start**: ${this.startDate.toDateString()}
### Pattern Details
${this.recommendedVersions[this.version] ? `- **Visibility**: ${this.recommendedVersions[this.version].visibility}
- **Description**: ${this.recommendedVersions[this.version].description}` : '- **Custom Pattern**'}
### Message Replacement
To replace this message with a new one:
\`\`\`bash
node cli.js replace "NEW MESSAGE" --intensity=ultra --force-replace
\`\`\`
### Created: ${new Date().toLocaleDateString()}
*Generated by AI Engineer Activity Generator v3.0*
`;
// Write the activity pattern info to a separate file
fs.writeFileSync(activityReadmeFile, activityReadmeContent);
// NEVER modify the original README.md file
// Only create a new README.md if one doesn't exist at all
if (!fs.existsSync(originalReadmeFile)) {
fs.writeFileSync(originalReadmeFile, activityReadmeContent);
console.log('π Created new README.md with activity pattern information');
} else {
console.log('π Created ACTIVITY-PATTERN.md with pattern information (README.md preserved)');
}
// Ensure .gitignore is preserved
const gitignorePath = path.join(this.repoPath, '.gitignore');
const defaultGitignore = `node_modules/
.DS_Store
*.log
.env
.vscode/
coverage/
dist/
tmp/`;
// Create .gitignore if it doesn't exist, otherwise preserve it
if (!fs.existsSync(gitignorePath)) {
fs.writeFileSync(gitignorePath, defaultGitignore);
console.log('π Created default .gitignore file');
} else {
console.log('π Preserved existing .gitignore file');
}
if (this.options.keepInView) {
const refreshScript = `#!/usr/bin/env node
const { refreshAIEngineerDisplay } = require('./utility-functions.js');
refreshAIEngineerDisplay(__dirname)
.then(() => console.log('β
Pattern refreshed successfully!'))
.catch(console.error);
`;
fs.writeFileSync(path.join(this.repoPath, 'refresh-activity.js'), refreshScript);
try {
await executeCommand('chmod', ['+x', 'refresh-activity.js'], { cwd: this.repoPath });
} catch (e) {
// Ignore chmod errors on Windows
}
}
const batchSize = 100; // Larger batches for efficiency
const batches = [];
for (let i = 0; i < commitDates.length; i += batchSize) {
batches.push(commitDates.slice(i, i + batchSize));
}
console.log(`β‘ Creating commits in ${batches.length} batches for maximum uniformity...`);
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
const batch = batches[batchIndex];
for (let i = 0; i < batch.length; i++) {
const date = batch[i];
const dateString = date.toISOString().split('T')[0];
const timeString = date.toISOString().split('T')[1].split('.')[0];
const activityLine = `${this.version}: ${dateString} ${timeString} | Commit ${batchIndex * batchSize + i + 1}\n`;
fs.writeFileSync(activityFile, activityLine, { flag: 'a' });
await executeCommand('git', ['add', '.'], { cwd: this.repoPath });
const commitMessage = `${this.version}: Uniform activity ${batchIndex * batchSize + i + 1}`;
await executeCommand('git', ['commit', '-m', commitMessage, '--date', date.toISOString()], { cwd: this.repoPath });
}
const overallProgress = Math.round(((batchIndex + 1) / batches.length) * 100);
console.log(`π Batch ${batchIndex + 1}/${batches.length} complete (${overallProgress}%)`);
}
// If we used force replace, clean up the branch
if (this.options.forceReplace) {
try {
await executeCommand('git', ['branch', '-D', 'main'], { cwd: this.repoPath });
await executeCommand('git', ['branch', '-m', 'main'], { cwd: this.repoPath });
console.log('β
Updated main branch with new pattern');
} catch (error) {
console.log('π‘ Branch cleanup completed');
}
}
console.log('\nβ
AI Engineer activity pattern created successfully!');
console.log('\nπ Next steps:');
console.log('1. Push to GitHub: git push -f origin main');
console.log('2. Wait 5-10 minutes for GitHub to update the activity graph');
console.log('3. Visit your GitHub profile to see the result');
console.log(`4. Your activity graph will display: "${this.version}"`);
if (this.options.keepInView) {
console.log('\nπ Auto-refresh setup:');
console.log('- Configuration saved in .github-activity-config.json');
console.log('- Run "node refresh-activity.js" to refresh the pattern');
}
console.log(`\nπ₯ Pattern intensity: ${this.options.intensity} (uniform distribution)`);
console.log(`π‘ Total commits created: ${commitDates.length}`);
console.log(`π― Pattern is centered in current GitHub window`);
resolve();
} catch (error) {
reject(error);
}
});
};
function setupAIEngineerDisplay(repoPath, version = 'AI', options = {}) {
return new Promise((resolve, reject) => {
console.log("π AI Engineer GitHub Activity Display Setup v3.0");
console.log("=".repeat(60));
if (!version || version === 'help') {
AIEngineerActivityGenerator.showAvailableVersions();
resolve();
return;
}
const defaultOptions = {
intensity: 'ultra',
keepInView: false,
centerMessage: true,
forceReplace: false,
commitsPerDay: null
};
const finalOptions = { ...defaultOptions, ...options };
console.log(`π― Setting up "${version}" with options:`);
console.log(` β’ Intensity: ${finalOptions.intensity}`);
console.log(` β’ Positioning: ${finalOptions.centerMessage ? 'Centered' : 'Left-aligned'}`);
console.log(` β’ Keep in view: ${finalOptions.keepInView ? 'Yes' : 'No'}`);
console.log(` β’ Force replace: ${finalOptions.forceReplace ? 'Yes' : 'No'}`);
console.log('');
const generator = new AIEngineerActivityGenerator(repoPath, version, finalOptions);
generator.previewPattern();
console.log("\nβ³ Creating commits...");
generator.createCommits()
.then(() => resolve())
.catch(error => reject(error));
});
}
function refreshAIEngineerDisplay(repoPath) {
return new Promise((resolve, reject) => {
const configPath = path.join(repoPath, '.github-activity-config.json');
if (!fs.existsSync(configPath)) {
reject(new Error('No configuration found. Run initial setup first.'));
return;
}
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
console.log(`π Refreshing "${config.version}" pattern...`);
const generator = new AIEngineerActivityGenerator(repoPath, config.version, {
...config.options,
startDate: null, // Recalculate centered position
forceReplace: true // Always force replace on refresh
});
generator.createCommits()
.then(() => resolve())
.catch(error => reject(error));
});
}
// New function to replace existing message with a new one
function replaceMessage(repoPath, newVersion, options = {}) {
return new Promise((resolve, reject) => {
console.log(`π Replacing existing message with "${newVersion}"`);
console.log("=".repeat(50));
// Force replace by default when replacing messages
const replaceOptions = {
...options,
forceReplace: true,
centerMessage: true,
intensity: options.intensity || 'ultra'
};
const generator = new AIEngineerActivityGenerator(repoPath, newVersion, replaceOptions);
generator.previewPattern();
console.log("\nβ³ Replacing message...");
generator.createCommits()
.then(() => {
console.log('\nβ
Message replacement complete!');
console.log('π Next steps:');
console.log('1. Push with force: git push -f origin main');
console.log('2. Wait 5-10 minutes for GitHub to update');
console.log(`3. Your new message "${newVersion}" will be displayed`);
resolve();
})
.catch(error => reject(error));
});
}
module.exports = {
setupAIEngineerDisplay,
refreshAIEngineerDisplay,
replaceMessage
};