@intranefr/superbackend 1.7.7 → 1.7.9

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 (119) hide show
  1. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
  2. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
  3. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
  4. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
  5. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
  6. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
  7. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
  8. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
  9. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
  10. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
  11. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
  12. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
  13. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
  14. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
  15. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
  16. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
  17. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
  18. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
  19. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
  20. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
  21. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
  22. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
  23. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
  24. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
  25. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
  26. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
  27. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
  28. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
  29. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
  30. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
  31. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
  32. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
  33. package/.beads/config.yaml +4 -0
  34. package/.beads/issues.jsonl +8 -0
  35. package/.beads/metadata.json +4 -0
  36. package/.env.example +8 -0
  37. package/autochangelog/.env.example +36 -0
  38. package/autochangelog/README.md +412 -0
  39. package/autochangelog/config/database.js +27 -0
  40. package/autochangelog/package.json +47 -0
  41. package/autochangelog/public/landing.html +581 -0
  42. package/autochangelog/server.js +104 -0
  43. package/autochangelog/src/app.js +181 -0
  44. package/autochangelog/src/config/database.js +26 -0
  45. package/autochangelog/src/controllers/auth.js +488 -0
  46. package/autochangelog/src/controllers/changelog.js +682 -0
  47. package/autochangelog/src/controllers/project.js +580 -0
  48. package/autochangelog/src/controllers/repository.js +780 -0
  49. package/autochangelog/src/middleware/auth.js +386 -0
  50. package/autochangelog/src/models/Changelog.js +443 -0
  51. package/autochangelog/src/models/Project.js +226 -0
  52. package/autochangelog/src/models/Repository.js +366 -0
  53. package/autochangelog/src/models/User.js +223 -0
  54. package/autochangelog/src/routes/auth.routes.js +32 -0
  55. package/autochangelog/src/routes/changelog.routes.js +42 -0
  56. package/autochangelog/src/routes/github-auth.routes.js +102 -0
  57. package/autochangelog/src/routes/project.routes.js +50 -0
  58. package/autochangelog/src/routes/repository.routes.js +54 -0
  59. package/autochangelog/src/services/changelog.js +722 -0
  60. package/autochangelog/src/services/github.js +243 -0
  61. package/autochangelog/utils/logger.js +77 -0
  62. package/autochangelog/views/404.ejs +18 -0
  63. package/autochangelog/views/dashboard.ejs +596 -0
  64. package/autochangelog/views/index.ejs +231 -0
  65. package/autochangelog/views/layouts/main.ejs +44 -0
  66. package/autochangelog/views/login.ejs +104 -0
  67. package/autochangelog/views/partials/footer.ejs +20 -0
  68. package/autochangelog/views/partials/navbar.ejs +51 -0
  69. package/autochangelog/views/register.ejs +109 -0
  70. package/autochangelog-cli/README.md +266 -0
  71. package/autochangelog-cli/bin/autochangelog +120 -0
  72. package/autochangelog-cli/package.json +46 -0
  73. package/autochangelog-cli/src/cli/commands/auth.js +291 -0
  74. package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
  75. package/autochangelog-cli/src/cli/commands/project.js +427 -0
  76. package/autochangelog-cli/src/cli/commands/repo.js +557 -0
  77. package/autochangelog-cli/src/cli/commands/stats.js +706 -0
  78. package/autochangelog-cli/src/cli/utils/config.js +277 -0
  79. package/autochangelog-cli/src/cli/utils/errors.js +307 -0
  80. package/autochangelog-cli/src/cli/utils/logger.js +75 -0
  81. package/autochangelog-cli/src/cli/utils/output.js +357 -0
  82. package/package.json +8 -3
  83. package/plugins/supercli/README.md +108 -0
  84. package/plugins/supercli/plugin.json +123 -0
  85. package/server.js +1 -1
  86. package/src/cli/api.js +380 -0
  87. package/src/cli/direct/agent-utils.js +61 -0
  88. package/src/cli/direct/cli-utils.js +112 -0
  89. package/src/cli/direct/data-seeding.js +307 -0
  90. package/src/cli/direct/db-admin.js +84 -0
  91. package/src/cli/direct/db-advanced.js +372 -0
  92. package/src/cli/direct/db-utils.js +558 -0
  93. package/src/cli/direct/help.js +195 -0
  94. package/src/cli/direct/migration.js +107 -0
  95. package/src/cli/direct/rbac-advanced.js +132 -0
  96. package/src/cli/direct/resources-additional.js +400 -0
  97. package/src/cli/direct/resources-cms-advanced.js +173 -0
  98. package/src/cli/direct/resources-cms.js +247 -0
  99. package/src/cli/direct/resources-core.js +253 -0
  100. package/src/cli/direct/resources-execution.js +367 -0
  101. package/src/cli/direct/resources-health.js +152 -0
  102. package/src/cli/direct/resources-integrations.js +182 -0
  103. package/src/cli/direct/resources-logs.js +204 -0
  104. package/src/cli/direct/resources-org-rbac.js +187 -0
  105. package/src/cli/direct/resources-system.js +236 -0
  106. package/src/cli/direct.js +556 -0
  107. package/src/controllers/admin.controller.js +4 -0
  108. package/src/controllers/auth.controller.js +148 -1
  109. package/src/controllers/waitingList.controller.js +130 -1
  110. package/src/models/RbacRole.js +1 -1
  111. package/src/models/User.js +39 -5
  112. package/src/routes/auth.routes.js +6 -0
  113. package/src/routes/waitingList.routes.js +12 -2
  114. package/src/routes/waitingListAdmin.routes.js +3 -0
  115. package/src/services/email.service.js +1 -0
  116. package/src/services/github.service.js +255 -0
  117. package/src/services/rateLimiter.service.js +29 -1
  118. package/src/services/waitingListJson.service.js +32 -3
  119. package/views/admin-waiting-list.ejs +386 -3
@@ -0,0 +1,706 @@
1
+ const { Command } = require('commander');
2
+ const chalk = require('chalk');
3
+ const { output } = require('../utils/output');
4
+ const { handleCliError, validateRequiredArgs, ERROR_CODES } = require('../utils/errors');
5
+ const { loadConfig, getAuthToken } = require('../utils/config');
6
+ const { setupLogger } = require('../utils/logger');
7
+
8
+ /**
9
+ * Statistics and utility commands for AutoChangelog CLI
10
+ * Handles usage statistics, subscription info, and system utilities
11
+ */
12
+
13
+ function setupStatsCommands(program) {
14
+ const stats = program
15
+ .command('stats')
16
+ .description('Show project statistics and usage');
17
+
18
+ // Project statistics command
19
+ stats
20
+ .command('project [project-id]')
21
+ .description('Show project statistics')
22
+ .option('--detailed', 'Show detailed statistics')
23
+ .action(async (projectId, options) => {
24
+ const logger = setupLogger();
25
+
26
+ try {
27
+ const token = getAuthToken();
28
+ if (!token) {
29
+ output.error('Not authenticated. Run "autochangelog auth login" first.');
30
+ process.exit(ERROR_CODES.AUTHENTICATION_FAILED);
31
+ }
32
+
33
+ if (!projectId) {
34
+ output.error('Project ID is required');
35
+ process.exit(ERROR_CODES.INVALID_ARGUMENT);
36
+ }
37
+
38
+ const result = await getProjectStats(projectId, options.detailed, token, logger);
39
+
40
+ if (output.isJsonMode()) {
41
+ output.output({
42
+ success: true,
43
+ data: result,
44
+ metadata: {
45
+ projectId,
46
+ detailed: options.detailed,
47
+ timestamp: new Date().toISOString(),
48
+ }
49
+ });
50
+ } else {
51
+ displayProjectStats(result, options.detailed);
52
+ }
53
+
54
+ } catch (error) {
55
+ handleCliError(error, output);
56
+ }
57
+ });
58
+
59
+ // Usage command
60
+ stats
61
+ .command('usage')
62
+ .description('Show subscription usage')
63
+ .action(async () => {
64
+ const logger = setupLogger();
65
+
66
+ try {
67
+ const token = getAuthToken();
68
+ if (!token) {
69
+ output.error('Not authenticated. Run "autochangelog auth login" first.');
70
+ process.exit(ERROR_CODES.AUTHENTICATION_FAILED);
71
+ }
72
+
73
+ const result = await getUsageStats(token, logger);
74
+
75
+ if (output.isJsonMode()) {
76
+ output.output({
77
+ success: true,
78
+ data: result,
79
+ metadata: {
80
+ timestamp: new Date().toISOString(),
81
+ }
82
+ });
83
+ } else {
84
+ displayUsageStats(result);
85
+ }
86
+
87
+ } catch (error) {
88
+ handleCliError(error, output);
89
+ }
90
+ });
91
+
92
+ // System info command
93
+ stats
94
+ .command('system')
95
+ .description('Show system information')
96
+ .action(async () => {
97
+ const logger = setupLogger();
98
+
99
+ try {
100
+ const config = loadConfig();
101
+ const token = getAuthToken();
102
+
103
+ const result = await getSystemInfo(config, token, logger);
104
+
105
+ if (output.isJsonMode()) {
106
+ output.output({
107
+ success: true,
108
+ data: result,
109
+ metadata: {
110
+ timestamp: new Date().toISOString(),
111
+ }
112
+ });
113
+ } else {
114
+ displaySystemInfo(result);
115
+ }
116
+
117
+ } catch (error) {
118
+ handleCliError(error, output);
119
+ }
120
+ });
121
+
122
+ // Health check command
123
+ stats
124
+ .command('health')
125
+ .description('Check system health')
126
+ .action(async () => {
127
+ const logger = setupLogger();
128
+
129
+ try {
130
+ const config = loadConfig();
131
+ const token = getAuthToken();
132
+
133
+ const result = await checkHealth(config, token, logger);
134
+
135
+ if (output.isJsonMode()) {
136
+ output.output({
137
+ success: true,
138
+ data: result,
139
+ metadata: {
140
+ timestamp: new Date().toISOString(),
141
+ }
142
+ });
143
+ } else {
144
+ displayHealthCheck(result);
145
+ }
146
+
147
+ } catch (error) {
148
+ handleCliError(error, output);
149
+ }
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Get project statistics
155
+ * @param {string} projectId - Project ID
156
+ * @param {boolean} detailed - Show detailed stats
157
+ * @param {string} token - Authentication token
158
+ * @param {Object} logger - Logger instance
159
+ * @returns {Promise<Object>} Project statistics
160
+ */
161
+ async function getProjectStats(projectId, detailed, token, logger) {
162
+ try {
163
+ const mongoose = require('mongoose');
164
+ const Project = require('../../autochangelog/src/models/Project');
165
+ const Repository = require('../../autochangelog/src/models/Repository');
166
+ const Changelog = require('../../autochangelog/src/models/Changelog');
167
+
168
+ await mongoose.connect(loadConfig().mongodb_uri);
169
+
170
+ // Get project info
171
+ const project = await Project.findById(projectId).lean();
172
+ if (!project) {
173
+ const error = new Error('Project not found');
174
+ error.code = ERROR_CODES.RESOURCE_NOT_FOUND;
175
+ error.type = 'resource_not_found';
176
+ throw error;
177
+ }
178
+
179
+ // Get repository count
180
+ const repositoryCount = await Repository.countDocuments({ projectId });
181
+
182
+ // Get changelog stats
183
+ const changelogStats = await Changelog.aggregate([
184
+ { $match: { projectId } },
185
+ {
186
+ $group: {
187
+ _id: null,
188
+ total: { $sum: 1 },
189
+ generated: { $sum: { $cond: [{ $eq: ['$status', 'generated'] }, 1, 0] } },
190
+ public: { $sum: { $cond: ['$public', 1, 0] } },
191
+ totalSize: { $sum: { $ifNull: [{ $strLenBytes: '$content' }, 0] } },
192
+ }
193
+ }
194
+ ]);
195
+
196
+ const changelogData = changelogStats[0] || { total: 0, generated: 0, public: 0, totalSize: 0 };
197
+
198
+ // Get recent activity
199
+ const recentChangelogs = await Changelog.find({ projectId })
200
+ .sort({ createdAt: -1 })
201
+ .limit(5)
202
+ .lean();
203
+
204
+ const stats = {
205
+ project: {
206
+ id: project._id,
207
+ name: project.name,
208
+ plan: project.plan,
209
+ createdAt: project.createdAt,
210
+ },
211
+ repositories: {
212
+ total: repositoryCount,
213
+ },
214
+ changelogs: {
215
+ total: changelogData.total,
216
+ generated: changelogData.generated,
217
+ public: changelogData.public,
218
+ totalSize: changelogData.totalSize,
219
+ averageSize: changelogData.total > 0 ? Math.round(changelogData.totalSize / changelogData.total) : 0,
220
+ },
221
+ recentActivity: recentChangelogs.map(changelog => ({
222
+ id: changelog._id,
223
+ title: changelog.title,
224
+ format: changelog.format,
225
+ createdAt: changelog.createdAt,
226
+ })),
227
+ };
228
+
229
+ if (detailed) {
230
+ // Add detailed repository stats
231
+ const repositories = await Repository.find({ projectId }).lean();
232
+ stats.repositories.details = repositories.map(repo => ({
233
+ id: repo._id,
234
+ fullName: repo.fullName,
235
+ status: repo.status,
236
+ lastSync: repo.lastSync,
237
+ private: repo.private,
238
+ }));
239
+
240
+ // Add monthly breakdown
241
+ const monthlyStats = await Changelog.aggregate([
242
+ { $match: { projectId } },
243
+ {
244
+ $group: {
245
+ _id: { year: '$year', month: '$month' },
246
+ count: { $sum: 1 },
247
+ size: { $sum: { $ifNull: [{ $strLenBytes: '$content' }, 0] } },
248
+ }
249
+ },
250
+ { $sort: { '_id.year': -1, '_id.month': -1 } }
251
+ ]);
252
+
253
+ stats.monthlyBreakdown = monthlyStats;
254
+ }
255
+
256
+ logger.info(`Project stats retrieved: ${project.name}`);
257
+
258
+ return stats;
259
+
260
+ } catch (error) {
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Get usage statistics
267
+ * @param {string} token - Authentication token
268
+ * @param {Object} logger - Logger instance
269
+ * @returns {Promise<Object>} Usage statistics
270
+ */
271
+ async function getUsageStats(token, logger) {
272
+ try {
273
+ const mongoose = require('mongoose');
274
+ const Project = require('../../autochangelog/src/models/Project');
275
+ const Repository = require('../../autochangelog/src/models/Repository');
276
+ const Changelog = require('../../autochangelog/src/models/Changelog');
277
+
278
+ await mongoose.connect(loadConfig().mongodb_uri);
279
+
280
+ // Get all projects for the authenticated user
281
+ const projects = await Project.find().lean();
282
+
283
+ const usage = {
284
+ projects: {
285
+ total: projects.length,
286
+ byPlan: {},
287
+ },
288
+ repositories: {
289
+ total: 0,
290
+ },
291
+ changelogs: {
292
+ total: 0,
293
+ generated: 0,
294
+ public: 0,
295
+ totalSize: 0,
296
+ },
297
+ limits: {
298
+ projects: { current: projects.length, limit: 100 },
299
+ repositories: { current: 0, limit: 1000 },
300
+ changelogs: { current: 0, limit: 10000 },
301
+ }
302
+ };
303
+
304
+ // Count by plan
305
+ projects.forEach(project => {
306
+ usage.projects.byPlan[project.plan] = (usage.projects.byPlan[project.plan] || 0) + 1;
307
+ });
308
+
309
+ // Get repository count
310
+ usage.repositories.total = await Repository.countDocuments();
311
+ usage.limits.repositories.current = usage.repositories.total;
312
+
313
+ // Get changelog stats
314
+ const changelogStats = await Changelog.aggregate([
315
+ {
316
+ $group: {
317
+ _id: null,
318
+ total: { $sum: 1 },
319
+ generated: { $sum: { $cond: [{ $eq: ['$status', 'generated'] }, 1, 0] } },
320
+ public: { $sum: { $cond: ['$public', 1, 0] } },
321
+ totalSize: { $sum: { $ifNull: [{ $strLenBytes: '$content' }, 0] } },
322
+ }
323
+ }
324
+ ]);
325
+
326
+ const changelogData = changelogStats[0] || { total: 0, generated: 0, public: 0, totalSize: 0 };
327
+ usage.changelogs = {
328
+ total: changelogData.total,
329
+ generated: changelogData.generated,
330
+ public: changelogData.public,
331
+ totalSize: changelogData.totalSize,
332
+ };
333
+ usage.limits.changelogs.current = changelogData.total;
334
+
335
+ logger.info('Usage stats retrieved');
336
+
337
+ return usage;
338
+
339
+ } catch (error) {
340
+ throw error;
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Get system information
346
+ * @param {Object} config - Configuration object
347
+ * @param {string} token - Authentication token
348
+ * @param {Object} logger - Logger instance
349
+ * @returns {Promise<Object>} System information
350
+ */
351
+ async function getSystemInfo(config, token, logger) {
352
+ try {
353
+ const os = require('os');
354
+ const mongoose = require('mongoose');
355
+
356
+ // Test database connection
357
+ let dbStatus = 'unknown';
358
+ try {
359
+ await mongoose.connect(config.mongodb_uri);
360
+ dbStatus = 'connected';
361
+ } catch (error) {
362
+ dbStatus = 'disconnected';
363
+ }
364
+
365
+ const systemInfo = {
366
+ cli: {
367
+ version: require('../../../package.json').version,
368
+ nodeVersion: process.version,
369
+ platform: process.platform,
370
+ arch: process.arch,
371
+ cwd: process.cwd(),
372
+ },
373
+ database: {
374
+ status: dbStatus,
375
+ uri: config.mongodb_uri ? config.mongodb_uri.replace(/\/\/.*@/, '//***:***@') : 'Not configured',
376
+ },
377
+ authentication: {
378
+ authenticated: !!token,
379
+ tokenPreview: token ? token.substring(0, 10) + '...' : 'Not set',
380
+ },
381
+ environment: {
382
+ nodeEnv: process.env.NODE_ENV || 'development',
383
+ homeDir: os.homedir(),
384
+ tempDir: os.tmpdir(),
385
+ uptime: process.uptime(),
386
+ },
387
+ paths: {
388
+ config: config.config_path,
389
+ cache: config.cache_path,
390
+ }
391
+ };
392
+
393
+ logger.info('System info retrieved');
394
+
395
+ return systemInfo;
396
+
397
+ } catch (error) {
398
+ throw error;
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Check system health
404
+ * @param {Object} config - Configuration object
405
+ * @param {string} token - Authentication token
406
+ * @param {Object} logger - Logger instance
407
+ * @returns {Promise<Object>} Health check results
408
+ */
409
+ async function checkHealth(config, token, logger) {
410
+ try {
411
+ const mongoose = require('mongoose');
412
+ const fs = require('fs-extra');
413
+
414
+ const health = {
415
+ status: 'healthy',
416
+ checks: [],
417
+ timestamp: new Date().toISOString(),
418
+ };
419
+
420
+ // Check database connection
421
+ try {
422
+ await mongoose.connect(config.mongodb_uri);
423
+ health.checks.push({
424
+ name: 'database',
425
+ status: 'healthy',
426
+ message: 'Database connection successful',
427
+ });
428
+ } catch (error) {
429
+ health.status = 'unhealthy';
430
+ health.checks.push({
431
+ name: 'database',
432
+ status: 'unhealthy',
433
+ message: `Database connection failed: ${error.message}`,
434
+ });
435
+ }
436
+
437
+ // Check authentication
438
+ if (token) {
439
+ try {
440
+ const { Octokit } = require('@octokit/rest');
441
+ const octokit = new Octokit({ auth: token });
442
+ await octokit.rest.users.getAuthenticated();
443
+ health.checks.push({
444
+ name: 'authentication',
445
+ status: 'healthy',
446
+ message: 'GitHub authentication valid',
447
+ });
448
+ } catch (error) {
449
+ health.status = 'unhealthy';
450
+ health.checks.push({
451
+ name: 'authentication',
452
+ status: 'unhealthy',
453
+ message: 'GitHub authentication failed',
454
+ });
455
+ }
456
+ } else {
457
+ health.checks.push({
458
+ name: 'authentication',
459
+ status: 'warning',
460
+ message: 'No authentication token set',
461
+ });
462
+ }
463
+
464
+ // Check file system permissions
465
+ try {
466
+ const configDir = require('path').dirname(config.config_path);
467
+ await fs.ensureDir(configDir);
468
+ health.checks.push({
469
+ name: 'filesystem',
470
+ status: 'healthy',
471
+ message: 'Configuration directory accessible',
472
+ });
473
+ } catch (error) {
474
+ health.status = 'unhealthy';
475
+ health.checks.push({
476
+ name: 'filesystem',
477
+ status: 'unhealthy',
478
+ message: `File system access failed: ${error.message}`,
479
+ });
480
+ }
481
+
482
+ // Check GitHub API rate limit
483
+ if (token) {
484
+ try {
485
+ const { Octokit } = require('@octokit/rest');
486
+ const octokit = new Octokit({ auth: token });
487
+ const { data } = await octokit.rest.rateLimit.get();
488
+
489
+ const remaining = data.rate.remaining;
490
+ const limit = data.rate.limit;
491
+ const resetTime = new Date(data.rate.reset * 1000);
492
+
493
+ health.checks.push({
494
+ name: 'github_rate_limit',
495
+ status: remaining > 100 ? 'healthy' : 'warning',
496
+ message: `Rate limit: ${remaining}/${limit} remaining, resets at ${resetTime.toISOString()}`,
497
+ details: {
498
+ remaining,
499
+ limit,
500
+ resetTime: resetTime.toISOString(),
501
+ }
502
+ });
503
+ } catch (error) {
504
+ health.checks.push({
505
+ name: 'github_rate_limit',
506
+ status: 'unknown',
507
+ message: 'Could not check GitHub rate limit',
508
+ });
509
+ }
510
+ }
511
+
512
+ logger.info(`Health check completed: ${health.status}`);
513
+
514
+ return health;
515
+
516
+ } catch (error) {
517
+ throw error;
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Display project statistics in human-readable format
523
+ * @param {Object} stats - Project statistics
524
+ * @param {boolean} detailed - Whether to show detailed stats
525
+ */
526
+ function displayProjectStats(stats, detailed) {
527
+ console.log(chalk.cyan.bold(`📊 Project Statistics: ${stats.project.name}`));
528
+ console.log('');
529
+
530
+ // Basic info
531
+ console.log(chalk.blue('📋 Basic Information:'));
532
+ console.log(` Project ID: ${stats.project.id}`);
533
+ console.log(` Plan: ${chalk.yellow(stats.project.plan)}`);
534
+ console.log(` Created: ${new Date(stats.project.createdAt).toLocaleDateString()}`);
535
+ console.log('');
536
+
537
+ // Repositories
538
+ console.log(chalk.blue('📦 Repositories:'));
539
+ console.log(` Total: ${chalk.yellow(stats.repositories.total)}`);
540
+ console.log('');
541
+
542
+ // Changelogs
543
+ console.log(chalk.blue('📝 Changelogs:'));
544
+ console.log(` Total: ${chalk.yellow(stats.changelogs.total)}`);
545
+ console.log(` Generated: ${chalk.yellow(stats.changelogs.generated)}`);
546
+ console.log(` Public: ${chalk.yellow(stats.changelogs.public)}`);
547
+ console.log(` Average Size: ${chalk.yellow(formatBytes(stats.changelogs.averageSize))}`);
548
+ console.log(` Total Size: ${chalk.yellow(formatBytes(stats.changelogs.totalSize))}`);
549
+ console.log('');
550
+
551
+ // Recent activity
552
+ console.log(chalk.blue('🕐 Recent Activity:'));
553
+ if (stats.recentActivity.length === 0) {
554
+ console.log(' No recent changelogs');
555
+ } else {
556
+ stats.recentActivity.forEach((changelog, index) => {
557
+ console.log(` ${index + 1}. ${changelog.title} (${changelog.format}) - ${new Date(changelog.createdAt).toLocaleDateString()}`);
558
+ });
559
+ }
560
+ console.log('');
561
+
562
+ // Detailed stats
563
+ if (detailed) {
564
+ if (stats.repositories.details && stats.repositories.details.length > 0) {
565
+ console.log(chalk.blue('🔍 Repository Details:'));
566
+ stats.repositories.details.forEach(repo => {
567
+ const statusColor = repo.status === 'active' ? chalk.green : chalk.yellow;
568
+ console.log(` • ${repo.fullName} ${statusColor(`[${repo.status}]`)} ${repo.private ? chalk.red('[PRIVATE]') : ''}`);
569
+ });
570
+ console.log('');
571
+ }
572
+
573
+ if (stats.monthlyBreakdown && stats.monthlyBreakdown.length > 0) {
574
+ console.log(chalk.blue('📅 Monthly Breakdown:'));
575
+ stats.monthlyBreakdown.forEach(month => {
576
+ console.log(` ${month._id.year}-${month._id.month}: ${month.count} changelogs, ${formatBytes(month.size)} total`);
577
+ });
578
+ console.log('');
579
+ }
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Display usage statistics in human-readable format
585
+ * @param {Object} usage - Usage statistics
586
+ */
587
+ function displayUsageStats(usage) {
588
+ console.log(chalk.cyan.bold('📈 Subscription Usage'));
589
+ console.log('');
590
+
591
+ // Projects
592
+ console.log(chalk.blue('🏗️ Projects:'));
593
+ console.log(` Total: ${chalk.yellow(usage.projects.total)} / ${usage.limits.projects.limit}`);
594
+ console.log(` By Plan:`);
595
+ Object.entries(usage.projects.byPlan).forEach(([plan, count]) => {
596
+ console.log(` ${plan}: ${chalk.yellow(count)}`);
597
+ });
598
+ console.log('');
599
+
600
+ // Repositories
601
+ console.log(chalk.blue('📦 Repositories:'));
602
+ console.log(` Total: ${chalk.yellow(usage.repositories.total)} / ${usage.limits.repositories.limit}`);
603
+ console.log('');
604
+
605
+ // Changelogs
606
+ console.log(chalk.blue('📝 Changelogs:'));
607
+ console.log(` Total: ${chalk.yellow(usage.changelogs.total)} / ${usage.limits.changelogs.limit}`);
608
+ console.log(` Generated: ${chalk.yellow(usage.changelogs.generated)}`);
609
+ console.log(` Public: ${chalk.yellow(usage.changelogs.public)}`);
610
+ console.log(` Total Size: ${chalk.yellow(formatBytes(usage.changelogs.totalSize))}`);
611
+ console.log('');
612
+ }
613
+
614
+ /**
615
+ * Display system information in human-readable format
616
+ * @param {Object} systemInfo - System information
617
+ */
618
+ function displaySystemInfo(systemInfo) {
619
+ console.log(chalk.cyan.bold('🖥️ System Information'));
620
+ console.log('');
621
+
622
+ // CLI Info
623
+ console.log(chalk.blue('🔧 CLI:'));
624
+ console.log(` Version: ${chalk.yellow(systemInfo.cli.version)}`);
625
+ console.log(` Node.js: ${chalk.yellow(systemInfo.cli.nodeVersion)}`);
626
+ console.log(` Platform: ${chalk.yellow(systemInfo.cli.platform)} ${chalk.yellow(systemInfo.cli.arch)}`);
627
+ console.log(` Working Directory: ${chalk.yellow(systemInfo.cli.cwd)}`);
628
+ console.log('');
629
+
630
+ // Database
631
+ console.log(chalk.blue('🗄️ Database:'));
632
+ const dbStatusColor = systemInfo.database.status === 'connected' ? chalk.green : chalk.red;
633
+ console.log(` Status: ${dbStatusColor(systemInfo.database.status)}`);
634
+ console.log(` URI: ${chalk.yellow(systemInfo.database.uri)}`);
635
+ console.log('');
636
+
637
+ // Authentication
638
+ console.log(chalk.blue('🔐 Authentication:'));
639
+ const authStatusColor = systemInfo.authentication.authenticated ? chalk.green : chalk.red;
640
+ console.log(` Status: ${authStatusColor(systemInfo.authentication.authenticated ? 'Authenticated' : 'Not Authenticated')}`);
641
+ console.log(` Token: ${chalk.yellow(systemInfo.authentication.tokenPreview)}`);
642
+ console.log('');
643
+
644
+ // Environment
645
+ console.log(chalk.blue('🌍 Environment:'));
646
+ console.log(` Node Environment: ${chalk.yellow(systemInfo.environment.nodeEnv)}`);
647
+ console.log(` Home Directory: ${chalk.yellow(systemInfo.environment.homeDir)}`);
648
+ console.log(` Temp Directory: ${chalk.yellow(systemInfo.environment.tempDir)}`);
649
+ console.log(` Uptime: ${chalk.yellow(formatUptime(systemInfo.environment.uptime))}`);
650
+ console.log('');
651
+
652
+ // Paths
653
+ console.log(chalk.blue('📁 Paths:'));
654
+ console.log(` Config: ${chalk.yellow(systemInfo.paths.config)}`);
655
+ console.log(` Cache: ${chalk.yellow(systemInfo.paths.cache)}`);
656
+ console.log('');
657
+ }
658
+
659
+ /**
660
+ * Display health check results in human-readable format
661
+ * @param {Object} health - Health check results
662
+ */
663
+ function displayHealthCheck(health) {
664
+ const statusColor = health.status === 'healthy' ? chalk.green : chalk.red;
665
+ console.log(chalk.cyan.bold(`🏥 System Health: ${statusColor(health.status.toUpperCase())}`));
666
+ console.log('');
667
+
668
+ health.checks.forEach(check => {
669
+ const statusColor = check.status === 'healthy' ? chalk.green : check.status === 'warning' ? chalk.yellow : chalk.red;
670
+ console.log(`${statusColor(`• ${check.name}: ${check.status.toUpperCase()}`)}`);
671
+ console.log(` ${chalk.gray(check.message)}`);
672
+ console.log('');
673
+ });
674
+
675
+ console.log(chalk.gray(`Last checked: ${new Date(health.timestamp).toLocaleString()}`));
676
+ console.log('');
677
+ }
678
+
679
+ /**
680
+ * Format bytes to human-readable format
681
+ * @param {number} bytes - Number of bytes
682
+ * @returns {string} Formatted string
683
+ */
684
+ function formatBytes(bytes) {
685
+ if (bytes === 0) return '0 B';
686
+ const k = 1024;
687
+ const sizes = ['B', 'KB', 'MB', 'GB'];
688
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
689
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
690
+ }
691
+
692
+ /**
693
+ * Format uptime to human-readable format
694
+ * @param {number} seconds - Uptime in seconds
695
+ * @returns {string} Formatted string
696
+ */
697
+ function formatUptime(seconds) {
698
+ const days = Math.floor(seconds / 86400);
699
+ const hours = Math.floor((seconds % 86400) / 3600);
700
+ const minutes = Math.floor((seconds % 3600) / 60);
701
+ const secs = Math.floor(seconds % 60);
702
+
703
+ return `${days}d ${hours}h ${minutes}m ${secs}s`;
704
+ }
705
+
706
+ module.exports = setupStatsCommands;