@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.
- package/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
- package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
- package/.beads/config.yaml +4 -0
- package/.beads/issues.jsonl +8 -0
- package/.beads/metadata.json +4 -0
- package/.env.example +8 -0
- package/autochangelog/.env.example +36 -0
- package/autochangelog/README.md +412 -0
- package/autochangelog/config/database.js +27 -0
- package/autochangelog/package.json +47 -0
- package/autochangelog/public/landing.html +581 -0
- package/autochangelog/server.js +104 -0
- package/autochangelog/src/app.js +181 -0
- package/autochangelog/src/config/database.js +26 -0
- package/autochangelog/src/controllers/auth.js +488 -0
- package/autochangelog/src/controllers/changelog.js +682 -0
- package/autochangelog/src/controllers/project.js +580 -0
- package/autochangelog/src/controllers/repository.js +780 -0
- package/autochangelog/src/middleware/auth.js +386 -0
- package/autochangelog/src/models/Changelog.js +443 -0
- package/autochangelog/src/models/Project.js +226 -0
- package/autochangelog/src/models/Repository.js +366 -0
- package/autochangelog/src/models/User.js +223 -0
- package/autochangelog/src/routes/auth.routes.js +32 -0
- package/autochangelog/src/routes/changelog.routes.js +42 -0
- package/autochangelog/src/routes/github-auth.routes.js +102 -0
- package/autochangelog/src/routes/project.routes.js +50 -0
- package/autochangelog/src/routes/repository.routes.js +54 -0
- package/autochangelog/src/services/changelog.js +722 -0
- package/autochangelog/src/services/github.js +243 -0
- package/autochangelog/utils/logger.js +77 -0
- package/autochangelog/views/404.ejs +18 -0
- package/autochangelog/views/dashboard.ejs +596 -0
- package/autochangelog/views/index.ejs +231 -0
- package/autochangelog/views/layouts/main.ejs +44 -0
- package/autochangelog/views/login.ejs +104 -0
- package/autochangelog/views/partials/footer.ejs +20 -0
- package/autochangelog/views/partials/navbar.ejs +51 -0
- package/autochangelog/views/register.ejs +109 -0
- package/autochangelog-cli/README.md +266 -0
- package/autochangelog-cli/bin/autochangelog +120 -0
- package/autochangelog-cli/package.json +46 -0
- package/autochangelog-cli/src/cli/commands/auth.js +291 -0
- package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
- package/autochangelog-cli/src/cli/commands/project.js +427 -0
- package/autochangelog-cli/src/cli/commands/repo.js +557 -0
- package/autochangelog-cli/src/cli/commands/stats.js +706 -0
- package/autochangelog-cli/src/cli/utils/config.js +277 -0
- package/autochangelog-cli/src/cli/utils/errors.js +307 -0
- package/autochangelog-cli/src/cli/utils/logger.js +75 -0
- package/autochangelog-cli/src/cli/utils/output.js +357 -0
- package/package.json +8 -3
- package/plugins/supercli/README.md +108 -0
- package/plugins/supercli/plugin.json +123 -0
- package/server.js +1 -1
- package/src/cli/api.js +380 -0
- package/src/cli/direct/agent-utils.js +61 -0
- package/src/cli/direct/cli-utils.js +112 -0
- package/src/cli/direct/data-seeding.js +307 -0
- package/src/cli/direct/db-admin.js +84 -0
- package/src/cli/direct/db-advanced.js +372 -0
- package/src/cli/direct/db-utils.js +558 -0
- package/src/cli/direct/help.js +195 -0
- package/src/cli/direct/migration.js +107 -0
- package/src/cli/direct/rbac-advanced.js +132 -0
- package/src/cli/direct/resources-additional.js +400 -0
- package/src/cli/direct/resources-cms-advanced.js +173 -0
- package/src/cli/direct/resources-cms.js +247 -0
- package/src/cli/direct/resources-core.js +253 -0
- package/src/cli/direct/resources-execution.js +367 -0
- package/src/cli/direct/resources-health.js +152 -0
- package/src/cli/direct/resources-integrations.js +182 -0
- package/src/cli/direct/resources-logs.js +204 -0
- package/src/cli/direct/resources-org-rbac.js +187 -0
- package/src/cli/direct/resources-system.js +236 -0
- package/src/cli/direct.js +556 -0
- package/src/controllers/admin.controller.js +4 -0
- package/src/controllers/auth.controller.js +148 -1
- package/src/controllers/waitingList.controller.js +130 -1
- package/src/models/RbacRole.js +1 -1
- package/src/models/User.js +39 -5
- package/src/routes/auth.routes.js +6 -0
- package/src/routes/waitingList.routes.js +12 -2
- package/src/routes/waitingListAdmin.routes.js +3 -0
- package/src/services/email.service.js +1 -0
- package/src/services/github.service.js +255 -0
- package/src/services/rateLimiter.service.js +29 -1
- package/src/services/waitingListJson.service.js +32 -3
- 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 };
|