ccmanager 2.11.6 → 3.1.0
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/dist/components/Configuration.js +14 -0
- package/dist/components/ConfigureCustomCommand.d.ts +9 -0
- package/dist/components/ConfigureCustomCommand.js +44 -0
- package/dist/components/ConfigureOther.d.ts +6 -0
- package/dist/components/ConfigureOther.js +113 -0
- package/dist/components/ConfigureOther.test.d.ts +1 -0
- package/dist/components/ConfigureOther.test.js +80 -0
- package/dist/components/ConfigureStatusHooks.js +7 -1
- package/dist/components/ConfigureTimeout.d.ts +9 -0
- package/dist/components/ConfigureTimeout.js +42 -0
- package/dist/components/CustomCommandSummary.d.ts +6 -0
- package/dist/components/CustomCommandSummary.js +10 -0
- package/dist/components/Menu.recent-projects.test.js +2 -0
- package/dist/components/Menu.test.js +2 -0
- package/dist/components/Session.d.ts +2 -2
- package/dist/components/Session.js +67 -4
- package/dist/constants/statusIcons.d.ts +3 -1
- package/dist/constants/statusIcons.js +3 -0
- package/dist/services/autoApprovalVerifier.d.ts +25 -0
- package/dist/services/autoApprovalVerifier.js +272 -0
- package/dist/services/autoApprovalVerifier.test.d.ts +1 -0
- package/dist/services/autoApprovalVerifier.test.js +120 -0
- package/dist/services/configurationManager.d.ts +8 -0
- package/dist/services/configurationManager.js +56 -0
- package/dist/services/sessionManager.autoApproval.test.d.ts +1 -0
- package/dist/services/sessionManager.autoApproval.test.js +160 -0
- package/dist/services/sessionManager.d.ts +5 -0
- package/dist/services/sessionManager.js +149 -1
- package/dist/services/sessionManager.statePersistence.test.js +2 -0
- package/dist/services/sessionManager.test.js +6 -0
- package/dist/types/index.d.ts +15 -1
- package/dist/utils/hookExecutor.test.js +8 -0
- package/dist/utils/logger.d.ts +83 -14
- package/dist/utils/logger.js +218 -17
- package/dist/utils/worktreeUtils.test.js +1 -0
- package/package.json +1 -1
package/dist/utils/logger.js
CHANGED
|
@@ -1,21 +1,222 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { format } from 'util';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
import os from 'os';
|
|
5
|
+
/**
|
|
6
|
+
* Log level enum for structured logging
|
|
7
|
+
*/
|
|
8
|
+
var LogLevel;
|
|
9
|
+
(function (LogLevel) {
|
|
10
|
+
LogLevel["DEBUG"] = "DEBUG";
|
|
11
|
+
LogLevel["INFO"] = "INFO";
|
|
12
|
+
LogLevel["WARN"] = "WARN";
|
|
13
|
+
LogLevel["ERROR"] = "ERROR";
|
|
14
|
+
LogLevel["LOG"] = "LOG";
|
|
15
|
+
})(LogLevel || (LogLevel = {}));
|
|
16
|
+
/**
|
|
17
|
+
* CLI-optimized logger with size management and rotation
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Automatic log rotation when file exceeds max size
|
|
21
|
+
* - Configurable retention (3 rotated files by default)
|
|
22
|
+
* - Atomic write operations to prevent corruption
|
|
23
|
+
* - Platform-aware log location (respects XDG_STATE_HOME on Linux)
|
|
24
|
+
* - Detailed timestamps and structured log lines
|
|
25
|
+
* - Sensitive information filtering on console output
|
|
26
|
+
*/
|
|
27
|
+
class Logger {
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
Object.defineProperty(this, "logFile", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "config", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "writeQueue", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: []
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(this, "isWriting", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: false
|
|
52
|
+
});
|
|
53
|
+
this.config = {
|
|
54
|
+
maxSizeBytes: config.maxSizeBytes ?? 5 * 1024 * 1024, // 5MB default
|
|
55
|
+
maxRotatedFiles: config.maxRotatedFiles ?? 3,
|
|
56
|
+
logErrorsToConsole: config.logErrorsToConsole ?? true,
|
|
57
|
+
};
|
|
58
|
+
this.logFile = this.resolveLogPath();
|
|
59
|
+
this.initializeLogFile();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve log file path following XDG Base Directory specification
|
|
63
|
+
* and respecting environment overrides for testing
|
|
64
|
+
*/
|
|
65
|
+
resolveLogPath() {
|
|
66
|
+
// Allow environment override for testing
|
|
67
|
+
if (process.env['CCMANAGER_LOG_FILE']) {
|
|
68
|
+
return process.env['CCMANAGER_LOG_FILE'];
|
|
69
|
+
}
|
|
70
|
+
// Use XDG_STATE_HOME if available (Linux/macOS standard)
|
|
71
|
+
const xdgStateHome = process.env['XDG_STATE_HOME'];
|
|
72
|
+
if (xdgStateHome) {
|
|
73
|
+
const logDir = path.join(xdgStateHome, 'ccmanager');
|
|
74
|
+
return path.join(logDir, 'ccmanager.log');
|
|
75
|
+
}
|
|
76
|
+
// Fallback to ~/.local/state/ccmanager on Linux, ~/Library/Logs on macOS
|
|
77
|
+
const homeDir = os.homedir();
|
|
78
|
+
if (process.platform === 'darwin') {
|
|
79
|
+
return path.join(homeDir, 'Library', 'Logs', 'ccmanager', 'ccmanager.log');
|
|
80
|
+
}
|
|
81
|
+
// Linux and others
|
|
82
|
+
return path.join(homeDir, '.local', 'state', 'ccmanager', 'ccmanager.log');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Initialize log file and ensure directory exists
|
|
86
|
+
*/
|
|
87
|
+
initializeLogFile() {
|
|
88
|
+
try {
|
|
89
|
+
const logDir = path.dirname(this.logFile);
|
|
90
|
+
if (!fs.existsSync(logDir)) {
|
|
91
|
+
fs.mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
92
|
+
}
|
|
93
|
+
// Only clear log on first startup (check if file exists)
|
|
94
|
+
if (!fs.existsSync(this.logFile)) {
|
|
95
|
+
fs.writeFileSync(this.logFile, '', 'utf8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (_error) {
|
|
99
|
+
// Silently fail if we can't initialize - don't crash the app
|
|
100
|
+
// This ensures CLI operation is not disrupted by logging issues
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if log file exceeds size limit and rotate if needed
|
|
105
|
+
*/
|
|
106
|
+
rotateLogIfNeeded() {
|
|
107
|
+
try {
|
|
108
|
+
const stats = fs.statSync(this.logFile);
|
|
109
|
+
if (stats.size < this.config.maxSizeBytes) {
|
|
110
|
+
return; // No rotation needed
|
|
111
|
+
}
|
|
112
|
+
// Rotate old logs: ccmanager.log.3 -> removed, .2 -> .3, .1 -> .2, .log -> .1
|
|
113
|
+
for (let i = this.config.maxRotatedFiles; i > 0; i--) {
|
|
114
|
+
const oldName = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;
|
|
115
|
+
const newName = `${this.logFile}.${i}`;
|
|
116
|
+
if (fs.existsSync(oldName)) {
|
|
117
|
+
fs.renameSync(oldName, newName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Remove the oldest log file if it exists
|
|
121
|
+
const oldestLog = `${this.logFile}.${this.config.maxRotatedFiles}`;
|
|
122
|
+
if (fs.existsSync(oldestLog)) {
|
|
123
|
+
fs.unlinkSync(oldestLog);
|
|
124
|
+
}
|
|
125
|
+
// Start fresh log file
|
|
126
|
+
fs.writeFileSync(this.logFile, '', 'utf8');
|
|
127
|
+
}
|
|
128
|
+
catch (_error) {
|
|
129
|
+
// Silently fail - don't disrupt app operation
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Queue write operation to prevent concurrent writes
|
|
134
|
+
* This ensures log file integrity with atomic operations
|
|
135
|
+
*/
|
|
136
|
+
queueWrite(callback) {
|
|
137
|
+
this.writeQueue.push(callback);
|
|
138
|
+
this.processQueue();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Process write queue sequentially to prevent concurrent writes
|
|
142
|
+
*/
|
|
143
|
+
processQueue() {
|
|
144
|
+
if (this.isWriting || this.writeQueue.length === 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.isWriting = true;
|
|
148
|
+
const callback = this.writeQueue.shift();
|
|
149
|
+
try {
|
|
150
|
+
if (callback) {
|
|
151
|
+
callback();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (_error) {
|
|
155
|
+
// Silently fail - don't crash the app
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
this.isWriting = false;
|
|
159
|
+
this.processQueue();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Write log entry with level and formatted message
|
|
164
|
+
*/
|
|
165
|
+
writeLog(level, args) {
|
|
166
|
+
this.queueWrite(() => {
|
|
167
|
+
try {
|
|
168
|
+
this.rotateLogIfNeeded();
|
|
169
|
+
const timestamp = new Date().toISOString();
|
|
170
|
+
const message = format(...args);
|
|
171
|
+
const logLine = `[${timestamp}] [${level}] ${message}\n`;
|
|
172
|
+
fs.appendFileSync(this.logFile, logLine, 'utf8');
|
|
173
|
+
// Also output errors to console for immediate visibility
|
|
174
|
+
if (level === LogLevel.ERROR && this.config.logErrorsToConsole) {
|
|
175
|
+
console.error(`[${level}]`, ...args);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (_error) {
|
|
179
|
+
// Silently fail - don't disrupt app operation
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get the path to the current log file
|
|
185
|
+
* Useful for users to locate and inspect logs
|
|
186
|
+
*/
|
|
187
|
+
getLogPath() {
|
|
188
|
+
return this.logFile;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Log entry at LOG level (general information)
|
|
192
|
+
*/
|
|
193
|
+
log(...args) {
|
|
194
|
+
this.writeLog(LogLevel.LOG, args);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Log entry at INFO level (significant events)
|
|
198
|
+
*/
|
|
199
|
+
info(...args) {
|
|
200
|
+
this.writeLog(LogLevel.INFO, args);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Log entry at WARN level (potentially harmful situations)
|
|
204
|
+
*/
|
|
205
|
+
warn(...args) {
|
|
206
|
+
this.writeLog(LogLevel.WARN, args);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Log entry at ERROR level (error conditions)
|
|
210
|
+
*/
|
|
211
|
+
error(...args) {
|
|
212
|
+
this.writeLog(LogLevel.ERROR, args);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Log entry at DEBUG level (detailed diagnostic information)
|
|
216
|
+
* Only written to file, not to console
|
|
217
|
+
*/
|
|
218
|
+
debug(...args) {
|
|
219
|
+
this.writeLog(LogLevel.DEBUG, args);
|
|
220
|
+
}
|
|
12
221
|
}
|
|
13
|
-
export const
|
|
14
|
-
log: (...args) => writeLog('LOG', args),
|
|
15
|
-
info: (...args) => writeLog('INFO', args),
|
|
16
|
-
warn: (...args) => writeLog('WARN', args),
|
|
17
|
-
error: (...args) => writeLog('ERROR', args),
|
|
18
|
-
debug: (...args) => writeLog('DEBUG', args),
|
|
19
|
-
};
|
|
20
|
-
// Alias for console.log style usage
|
|
21
|
-
export const logger = log;
|
|
222
|
+
export const logger = new Logger();
|
|
@@ -135,6 +135,7 @@ describe('prepareWorktreeItems', () => {
|
|
|
135
135
|
devcontainerConfig: undefined,
|
|
136
136
|
pendingState: undefined,
|
|
137
137
|
pendingStateStart: undefined,
|
|
138
|
+
autoApprovalFailed: false,
|
|
138
139
|
};
|
|
139
140
|
it('should prepare basic worktree without git status', () => {
|
|
140
141
|
const items = prepareWorktreeItems([mockWorktree], []);
|