@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,277 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * Configuration utilities for CLI operations
7
+ * Handles environment variables, config files, and global settings
8
+ */
9
+
10
+ // Default configuration
11
+ const DEFAULT_CONFIG = {
12
+ // Database
13
+ mongodb_uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/autochangelog',
14
+
15
+ // GitHub
16
+ github_client_id: process.env.GITHUB_CLIENT_ID,
17
+ github_client_secret: process.env.GITHUB_CLIENT_SECRET,
18
+ github_token: process.env.GITHUB_TOKEN,
19
+
20
+ // Authentication
21
+ jwt_secret: process.env.JWT_SECRET || 'autochangelog-cli-secret',
22
+
23
+ // API
24
+ api_base_url: process.env.API_BASE_URL || 'http://localhost:3000',
25
+
26
+ // CLI
27
+ output_format: process.env.OUTPUT_FORMAT || 'auto', // auto, json, text
28
+ color_enabled: process.env.COLOR !== 'false',
29
+ verbose: process.env.VERBOSE === 'true',
30
+ timeout: parseInt(process.env.TIMEOUT) || 30000,
31
+
32
+ // Paths
33
+ config_path: process.env.CONFIG_PATH || path.join(os.homedir(), '.autochangelog', 'config.json'),
34
+ cache_path: process.env.CACHE_PATH || path.join(os.homedir(), '.autochangelog', 'cache'),
35
+ };
36
+
37
+ /**
38
+ * Get configuration value with environment variable override
39
+ * @param {string} key - Configuration key
40
+ * @param {string} envVar - Environment variable name
41
+ * @param {*} defaultValue - Default value
42
+ * @returns {*} Configuration value
43
+ */
44
+ function getConfigValue(key, envVar, defaultValue) {
45
+ const envValue = process.env[envVar];
46
+ if (envValue !== undefined) {
47
+ return envValue;
48
+ }
49
+
50
+ // Try to load from config file
51
+ try {
52
+ const configPath = DEFAULT_CONFIG.config_path;
53
+ if (fs.existsSync(configPath)) {
54
+ const config = fs.readJsonSync(configPath);
55
+ return config[key] !== undefined ? config[key] : defaultValue;
56
+ }
57
+ } catch (error) {
58
+ // Config file exists but is invalid, ignore and use defaults
59
+ }
60
+
61
+ return defaultValue;
62
+ }
63
+
64
+ /**
65
+ * Load configuration from environment and config file
66
+ * @returns {Object} Configuration object
67
+ */
68
+ function loadConfig() {
69
+ const config = { ...DEFAULT_CONFIG };
70
+
71
+ // Load from config file if exists
72
+ try {
73
+ if (fs.existsSync(config.config_path)) {
74
+ const fileConfig = fs.readJsonSync(config.config_path);
75
+ Object.assign(config, fileConfig);
76
+ }
77
+ } catch (error) {
78
+ // Config file exists but is invalid, ignore and use defaults
79
+ }
80
+
81
+ // Override with environment variables
82
+ config.mongodb_uri = getConfigValue('mongodb_uri', 'MONGODB_URI');
83
+ config.github_client_id = getConfigValue('github_client_id', 'GITHUB_CLIENT_ID');
84
+ config.github_client_secret = getConfigValue('github_client_secret', 'GITHUB_CLIENT_SECRET');
85
+ config.github_token = getConfigValue('github_token', 'GITHUB_TOKEN');
86
+ config.jwt_secret = getConfigValue('jwt_secret', 'JWT_SECRET');
87
+ config.api_base_url = getConfigValue('api_base_url', 'API_BASE_URL');
88
+ config.output_format = getConfigValue('output_format', 'OUTPUT_FORMAT');
89
+ config.color_enabled = getConfigValue('color_enabled', 'COLOR') !== false;
90
+ config.verbose = getConfigValue('verbose', 'VERBOSE');
91
+ config.timeout = parseInt(getConfigValue('timeout', 'TIMEOUT', '30000'));
92
+
93
+ return config;
94
+ }
95
+
96
+ /**
97
+ * Save configuration to file
98
+ * @param {Object} updates - Configuration updates to save
99
+ */
100
+ function saveConfig(updates) {
101
+ const configPath = DEFAULT_CONFIG.config_path;
102
+
103
+ // Ensure config directory exists
104
+ fs.ensureDirSync(path.dirname(configPath));
105
+
106
+ // Load existing config
107
+ let config = {};
108
+ try {
109
+ if (fs.existsSync(configPath)) {
110
+ config = fs.readJsonSync(configPath);
111
+ }
112
+ } catch (error) {
113
+ // Config file exists but is invalid, start fresh
114
+ }
115
+
116
+ // Apply updates
117
+ Object.assign(config, updates);
118
+
119
+ // Save config
120
+ fs.writeJsonSync(configPath, config, { spaces: 2 });
121
+ }
122
+
123
+ /**
124
+ * Clear saved configuration
125
+ */
126
+ function clearConfig() {
127
+ const configPath = DEFAULT_CONFIG.config_path;
128
+ if (fs.existsSync(configPath)) {
129
+ fs.removeSync(configPath);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Get authentication token from various sources
135
+ * @returns {string|null} Authentication token
136
+ */
137
+ function getAuthToken() {
138
+ // Check environment variable first
139
+ if (process.env.GITHUB_TOKEN) {
140
+ return process.env.GITHUB_TOKEN;
141
+ }
142
+
143
+ // Check config file
144
+ try {
145
+ if (fs.existsSync(DEFAULT_CONFIG.config_path)) {
146
+ const config = fs.readJsonSync(DEFAULT_CONFIG.config_path);
147
+ if (config.github_token) {
148
+ return config.github_token;
149
+ }
150
+ }
151
+ } catch (error) {
152
+ // Config file exists but is invalid
153
+ }
154
+
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Set authentication token
160
+ * @param {string} token - GitHub token
161
+ */
162
+ function setAuthToken(token) {
163
+ saveConfig({ github_token: token });
164
+ }
165
+
166
+ /**
167
+ * Clear authentication token
168
+ */
169
+ function clearAuthToken() {
170
+ const config = loadConfig();
171
+ delete config.github_token;
172
+ saveConfig(config);
173
+ }
174
+
175
+ /**
176
+ * Check if user is authenticated
177
+ * @returns {boolean} True if authenticated
178
+ */
179
+ function isAuthenticated() {
180
+ return !!getAuthToken();
181
+ }
182
+
183
+ /**
184
+ * Setup global options for all commands
185
+ * @param {Object} program - Commander program instance
186
+ */
187
+ function setupGlobalOptions(program) {
188
+ program
189
+ .option('--json', 'Output in JSON format (default in non-TTY)')
190
+ .option('--verbose', 'Enable verbose logging')
191
+ .option('--no-color', 'Disable colored output')
192
+ .option('--config <path>', 'Path to config file')
193
+ .option('--timeout <ms>', 'Request timeout in milliseconds', parseInt);
194
+ }
195
+
196
+ /**
197
+ * Handle global errors and exit gracefully
198
+ * @param {Object} logger - Logger instance
199
+ */
200
+ function handleGlobalErrors(logger) {
201
+ // Handle uncaught exceptions
202
+ process.on('uncaughtException', (error) => {
203
+ logger.error('Uncaught Exception:', error);
204
+ process.exit(1);
205
+ });
206
+
207
+ // Handle unhandled promise rejections
208
+ process.on('unhandledRejection', (reason, promise) => {
209
+ logger.error('Unhandled Promise Rejection at:', promise, 'reason:', reason);
210
+ process.exit(1);
211
+ });
212
+
213
+ // Handle SIGINT (Ctrl+C)
214
+ process.on('SIGINT', () => {
215
+ logger.info('Received SIGINT, exiting...');
216
+ process.exit(0);
217
+ });
218
+
219
+ // Handle SIGTERM
220
+ process.on('SIGTERM', () => {
221
+ logger.info('Received SIGTERM, exiting...');
222
+ process.exit(0);
223
+ });
224
+ }
225
+
226
+ /**
227
+ * Validate required environment variables
228
+ * @param {Array} required - Array of required environment variable names
229
+ * @returns {Array} Array of missing environment variables
230
+ */
231
+ function validateEnvironment(required = []) {
232
+ const missing = [];
233
+
234
+ required.forEach(envVar => {
235
+ if (!process.env[envVar]) {
236
+ missing.push(envVar);
237
+ }
238
+ });
239
+
240
+ return missing;
241
+ }
242
+
243
+ /**
244
+ * Get cache directory path
245
+ * @returns {string} Cache directory path
246
+ */
247
+ function getCacheDir() {
248
+ const cachePath = DEFAULT_CONFIG.cache_path;
249
+ fs.ensureDirSync(cachePath);
250
+ return cachePath;
251
+ }
252
+
253
+ /**
254
+ * Clear cache directory
255
+ */
256
+ function clearCache() {
257
+ const cachePath = DEFAULT_CONFIG.cache_path;
258
+ if (fs.existsSync(cachePath)) {
259
+ fs.emptyDirSync(cachePath);
260
+ }
261
+ }
262
+
263
+ module.exports = {
264
+ DEFAULT_CONFIG,
265
+ loadConfig,
266
+ saveConfig,
267
+ clearConfig,
268
+ getAuthToken,
269
+ setAuthToken,
270
+ clearAuthToken,
271
+ isAuthenticated,
272
+ setupGlobalOptions,
273
+ handleGlobalErrors,
274
+ validateEnvironment,
275
+ getCacheDir,
276
+ clearCache,
277
+ };
@@ -0,0 +1,307 @@
1
+ const chalk = require('chalk');
2
+
3
+ /**
4
+ * Error handling utilities for CLI operations
5
+ * Provides structured error responses following agent-friendly patterns
6
+ */
7
+
8
+ // Error code constants following the documented pattern
9
+ const ERROR_CODES = {
10
+ // User errors (80-89)
11
+ INVALID_ARGUMENT: 85,
12
+ RESOURCE_NOT_FOUND: 92,
13
+ AUTHENTICATION_FAILED: 93,
14
+ PERMISSION_DENIED: 94,
15
+
16
+ // Resource/state errors (90-99)
17
+ RESOURCE_EXISTS: 95,
18
+ CONFLICT: 96,
19
+
20
+ // Integration/external errors (100-109)
21
+ GITHUB_API_ERROR: 105,
22
+ NETWORK_ERROR: 106,
23
+ TIMEOUT_ERROR: 107,
24
+ RATE_LIMIT_EXCEEDED: 108,
25
+
26
+ // Internal software errors (110-119)
27
+ INTERNAL_ERROR: 110,
28
+ DATABASE_ERROR: 111,
29
+ VALIDATION_ERROR: 112,
30
+ };
31
+
32
+ /**
33
+ * Create a structured error response
34
+ * @param {string} message - Human-readable error message
35
+ * @param {string} type - Machine-readable error type
36
+ * @param {number} code - Semantic error code
37
+ * @param {Object} details - Additional error details
38
+ * @param {Array} suggestions - Array of suggested next actions
39
+ * @param {boolean} recoverable - Whether the error is recoverable
40
+ * @param {number} retryAfter - Seconds to wait before retry (for rate limits/timeouts)
41
+ * @returns {Object} Structured error object
42
+ */
43
+ function createError(message, type, code, details = {}, suggestions = [], recoverable = false, retryAfter = null) {
44
+ return {
45
+ error: {
46
+ message,
47
+ type,
48
+ code,
49
+ details,
50
+ suggestions,
51
+ recoverable,
52
+ retry_after: retryAfter,
53
+ timestamp: new Date().toISOString(),
54
+ }
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Handle CLI errors with appropriate exit codes and output
60
+ * @param {Error} error - The error to handle
61
+ * @param {Object} output - Output manager instance
62
+ */
63
+ function handleCliError(error, output) {
64
+ let structuredError;
65
+
66
+ // Handle different error types
67
+ if (error.code && error.type) {
68
+ // Already structured error
69
+ structuredError = error;
70
+ } else if (error.name === 'ValidationError') {
71
+ structuredError = createError(
72
+ error.message || 'Validation failed',
73
+ 'validation_error',
74
+ ERROR_CODES.VALIDATION_ERROR,
75
+ error.details || {},
76
+ ['Check input parameters and try again'],
77
+ false
78
+ );
79
+ } else if (error.name === 'MongoError' || error.name === 'MongooseError') {
80
+ structuredError = createError(
81
+ error.message || 'Database operation failed',
82
+ 'database_error',
83
+ ERROR_CODES.DATABASE_ERROR,
84
+ { originalError: error.message },
85
+ ['Check database connection and try again'],
86
+ true,
87
+ 5
88
+ );
89
+ } else if (error.response && error.response.status) {
90
+ // HTTP error from GitHub API
91
+ const status = error.response.status;
92
+ if (status === 401) {
93
+ structuredError = createError(
94
+ 'Authentication failed. Check GitHub token permissions.',
95
+ 'authentication_failed',
96
+ ERROR_CODES.AUTHENTICATION_FAILED,
97
+ { status, message: error.message },
98
+ ['Run "autochangelog auth login" to re-authenticate'],
99
+ false
100
+ );
101
+ } else if (status === 403) {
102
+ structuredError = createError(
103
+ 'Access denied. Check repository permissions.',
104
+ 'permission_denied',
105
+ ERROR_CODES.PERMISSION_DENIED,
106
+ { status, message: error.message },
107
+ ['Check GitHub token has repo access', 'Verify repository visibility'],
108
+ false
109
+ );
110
+ } else if (status === 404) {
111
+ structuredError = createError(
112
+ 'Resource not found',
113
+ 'resource_not_found',
114
+ ERROR_CODES.RESOURCE_NOT_FOUND,
115
+ { status, message: error.message },
116
+ ['Check resource ID or name', 'Verify resource exists'],
117
+ false
118
+ );
119
+ } else if (status === 429) {
120
+ structuredError = createError(
121
+ 'Rate limit exceeded',
122
+ 'rate_limit_exceeded',
123
+ ERROR_CODES.RATE_LIMIT_EXCEEDED,
124
+ { status, message: error.message },
125
+ ['Wait before retrying', 'Check GitHub rate limit status'],
126
+ true,
127
+ 60
128
+ );
129
+ } else {
130
+ structuredError = createError(
131
+ `GitHub API error: ${error.message}`,
132
+ 'github_api_error',
133
+ ERROR_CODES.GITHUB_API_ERROR,
134
+ { status, message: error.message },
135
+ ['Check GitHub API status', 'Verify API permissions'],
136
+ true,
137
+ 10
138
+ );
139
+ }
140
+ } else {
141
+ // Generic error
142
+ structuredError = createError(
143
+ error.message || 'An unexpected error occurred',
144
+ 'internal_error',
145
+ ERROR_CODES.INTERNAL_ERROR,
146
+ { stack: error.stack },
147
+ ['Check logs for more details', 'Report issue if problem persists'],
148
+ false
149
+ );
150
+ }
151
+
152
+ // Output error
153
+ if (output.isJsonMode()) {
154
+ console.error(JSON.stringify(structuredError, null, 2));
155
+ } else {
156
+ output.printError(structuredError.error);
157
+ }
158
+
159
+ // Set appropriate exit code
160
+ process.exit(structuredError.error.code);
161
+ }
162
+
163
+ /**
164
+ * Validate required arguments
165
+ * @param {Object} args - Arguments object
166
+ * @param {Array} required - Array of required argument names
167
+ * @param {Object} output - Output manager instance
168
+ */
169
+ function validateRequiredArgs(args, required, output) {
170
+ const missing = required.filter(arg => !args[arg]);
171
+
172
+ if (missing.length > 0) {
173
+ const error = createError(
174
+ `Missing required argument(s): ${missing.join(', ')}`,
175
+ 'invalid_argument',
176
+ ERROR_CODES.INVALID_ARGUMENT,
177
+ { missing },
178
+ missing.map(arg => `Provide --${arg} or ${arg} parameter`),
179
+ false
180
+ );
181
+
182
+ if (output.isJsonMode()) {
183
+ console.error(JSON.stringify(error, null, 2));
184
+ } else {
185
+ output.printError(error.error);
186
+ }
187
+
188
+ process.exit(ERROR_CODES.INVALID_ARGUMENT);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Validate date parameters
194
+ * @param {string} month - Month parameter
195
+ * @param {string} year - Year parameter
196
+ * @param {Object} output - Output manager instance
197
+ */
198
+ function validateDateParams(month, year, output) {
199
+ const monthNum = parseInt(month);
200
+ const yearNum = parseInt(year);
201
+
202
+ if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
203
+ const error = createError(
204
+ `Invalid month: ${month}. Must be 1-12.`,
205
+ 'invalid_argument',
206
+ ERROR_CODES.INVALID_ARGUMENT,
207
+ { month },
208
+ ['Use month number 1-12'],
209
+ false
210
+ );
211
+
212
+ if (output.isJsonMode()) {
213
+ console.error(JSON.stringify(error, null, 2));
214
+ } else {
215
+ output.printError(error.error);
216
+ }
217
+
218
+ process.exit(ERROR_CODES.INVALID_ARGUMENT);
219
+ }
220
+
221
+ if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
222
+ const error = createError(
223
+ `Invalid year: ${year}. Must be 2000-2100.`,
224
+ 'invalid_argument',
225
+ ERROR_CODES.INVALID_ARGUMENT,
226
+ { year },
227
+ ['Use 4-digit year format'],
228
+ false
229
+ );
230
+
231
+ if (output.isJsonMode()) {
232
+ console.error(JSON.stringify(error, null, 2));
233
+ } else {
234
+ output.printError(error.error);
235
+ }
236
+
237
+ process.exit(ERROR_CODES.INVALID_ARGUMENT);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Check if error is recoverable (should trigger retry logic)
243
+ * @param {Object} error - Error object
244
+ * @returns {boolean} True if error is recoverable
245
+ */
246
+ function isRecoverableError(error) {
247
+ if (!error || !error.error) {
248
+ return false;
249
+ }
250
+
251
+ const { code, recoverable } = error.error;
252
+
253
+ // Explicitly recoverable
254
+ if (recoverable) {
255
+ return true;
256
+ }
257
+
258
+ // Check error codes that are typically recoverable
259
+ const recoverableCodes = [
260
+ ERROR_CODES.GITHUB_API_ERROR,
261
+ ERROR_CODES.NETWORK_ERROR,
262
+ ERROR_CODES.TIMEOUT_ERROR,
263
+ ERROR_CODES.RATE_LIMIT_EXCEEDED,
264
+ ERROR_CODES.DATABASE_ERROR,
265
+ ];
266
+
267
+ return recoverableCodes.includes(code);
268
+ }
269
+
270
+ /**
271
+ * Get retry delay in milliseconds for recoverable errors
272
+ * @param {Object} error - Error object
273
+ * @returns {number} Delay in milliseconds
274
+ */
275
+ function getRetryDelay(error) {
276
+ if (!error || !error.error) {
277
+ return 5000; // Default 5 seconds
278
+ }
279
+
280
+ const { retry_after, code } = error.error;
281
+
282
+ if (retry_after) {
283
+ return retry_after * 1000; // Convert to milliseconds
284
+ }
285
+
286
+ // Default delays based on error type
287
+ switch (code) {
288
+ case ERROR_CODES.RATE_LIMIT_EXCEEDED:
289
+ return 60000; // 1 minute
290
+ case ERROR_CODES.TIMEOUT_ERROR:
291
+ return 10000; // 10 seconds
292
+ case ERROR_CODES.NETWORK_ERROR:
293
+ return 5000; // 5 seconds
294
+ default:
295
+ return 5000; // 5 seconds
296
+ }
297
+ }
298
+
299
+ module.exports = {
300
+ ERROR_CODES,
301
+ createError,
302
+ handleCliError,
303
+ validateRequiredArgs,
304
+ validateDateParams,
305
+ isRecoverableError,
306
+ getRetryDelay,
307
+ };
@@ -0,0 +1,75 @@
1
+ const winston = require('winston');
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Logger utility for CLI operations
6
+ * Provides structured logging with different levels and formats
7
+ */
8
+
9
+ function setupLogger() {
10
+ // Determine log level from environment or default to info
11
+ const logLevel = process.env.LOG_LEVEL || 'info';
12
+
13
+ // Check if output should be in JSON format (non-TTY or --json flag)
14
+ const isJsonOutput = process.env.FORCE_JSON === 'true' ||
15
+ (!process.stdout.isTTY && process.env.FORCE_TEXT !== 'true');
16
+
17
+ // Define log format
18
+ const logFormat = isJsonOutput
19
+ ? winston.format.json()
20
+ : winston.format.combine(
21
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
22
+ winston.format.errors({ stack: true }),
23
+ winston.format.printf(({ timestamp, level, message, ...meta }) => {
24
+ let logMessage = `${timestamp} [${level.toUpperCase()}]: ${message}`;
25
+
26
+ // Add metadata if present
27
+ if (Object.keys(meta).length > 0) {
28
+ logMessage += ` ${JSON.stringify(meta)}`;
29
+ }
30
+
31
+ return logMessage;
32
+ })
33
+ );
34
+
35
+ // Create logger instance
36
+ const logger = winston.createLogger({
37
+ level: logLevel,
38
+ format: logFormat,
39
+ transports: [
40
+ new winston.transports.Console({
41
+ stderrLevels: ['error', 'warn'],
42
+ handleExceptions: true,
43
+ handleRejections: true,
44
+ }),
45
+ ],
46
+ });
47
+
48
+ // Add color support for human-readable output
49
+ if (!isJsonOutput) {
50
+ logger.format = winston.format.combine(
51
+ winston.format.colorize(),
52
+ logger.format
53
+ );
54
+ }
55
+
56
+ // Override console methods to use winston
57
+ const originalConsole = { ...console };
58
+
59
+ console.log = (...args) => logger.info(args.join(' '));
60
+ console.error = (...args) => logger.error(args.join(' '));
61
+ console.warn = (...args) => logger.warn(args.join(' '));
62
+ console.info = (...args) => logger.info(args.join(' '));
63
+
64
+ // Restore original console for direct output
65
+ logger.restoreConsole = () => {
66
+ console.log = originalConsole.log;
67
+ console.error = originalConsole.error;
68
+ console.warn = originalConsole.warn;
69
+ console.info = originalConsole.info;
70
+ };
71
+
72
+ return logger;
73
+ }
74
+
75
+ module.exports = { setupLogger };