@howlil/ez-agents 3.1.0 → 3.4.2
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/LICENSE +21 -21
- package/README.md +288 -718
- package/bin/install.js +438 -71
- package/commands/ez/auth.md +87 -0
- package/commands/ez/join-discord.md +18 -18
- package/ez-agents/bin/ez-tools.cjs +120 -2
- package/ez-agents/bin/lib/assistant-adapter.cjs +264 -205
- package/ez-agents/bin/lib/audit-exec.cjs +26 -9
- package/ez-agents/bin/lib/auth.cjs +2 -1
- package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
- package/ez-agents/bin/lib/commands.cjs +42 -23
- package/ez-agents/bin/lib/config.cjs +190 -183
- package/ez-agents/bin/lib/core.cjs +42 -25
- package/ez-agents/bin/lib/file-lock.cjs +236 -236
- package/ez-agents/bin/lib/frontmatter.cjs +299 -299
- package/ez-agents/bin/lib/fs-utils.cjs +153 -153
- package/ez-agents/bin/lib/git-utils.cjs +203 -203
- package/ez-agents/bin/lib/health-check.cjs +2 -3
- package/ez-agents/bin/lib/index.cjs +113 -113
- package/ez-agents/bin/lib/init.cjs +757 -710
- package/ez-agents/bin/lib/logger.cjs +52 -15
- package/ez-agents/bin/lib/milestone.cjs +241 -241
- package/ez-agents/bin/lib/model-provider.cjs +241 -146
- package/ez-agents/bin/lib/phase.cjs +925 -908
- package/ez-agents/bin/lib/planning-write.cjs +107 -0
- package/ez-agents/bin/lib/retry.cjs +119 -119
- package/ez-agents/bin/lib/roadmap.cjs +306 -305
- package/ez-agents/bin/lib/safe-exec.cjs +91 -5
- package/ez-agents/bin/lib/safe-path.cjs +130 -130
- package/ez-agents/bin/lib/state.cjs +736 -721
- package/ez-agents/bin/lib/temp-file.cjs +239 -239
- package/ez-agents/bin/lib/template.cjs +223 -222
- package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
- package/ez-agents/bin/lib/test-graceful.cjs +93 -93
- package/ez-agents/bin/lib/test-logger.cjs +60 -60
- package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
- package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
- package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
- package/ez-agents/bin/lib/timeout-exec.cjs +63 -62
- package/ez-agents/bin/lib/verify.cjs +69 -26
- package/ez-agents/references/checkpoints.md +776 -776
- package/ez-agents/references/continuation-format.md +249 -249
- package/ez-agents/references/questioning.md +162 -162
- package/ez-agents/references/tdd.md +263 -263
- package/ez-agents/templates/codebase/concerns.md +310 -310
- package/ez-agents/templates/codebase/conventions.md +307 -307
- package/ez-agents/templates/codebase/integrations.md +280 -280
- package/ez-agents/templates/codebase/stack.md +186 -186
- package/ez-agents/templates/codebase/testing.md +480 -480
- package/ez-agents/templates/config.json +37 -37
- package/ez-agents/templates/continue-here.md +78 -78
- package/ez-agents/templates/milestone-archive.md +123 -123
- package/ez-agents/templates/milestone.md +115 -115
- package/ez-agents/templates/requirements.md +231 -231
- package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
- package/ez-agents/templates/research-project/FEATURES.md +147 -147
- package/ez-agents/templates/research-project/PITFALLS.md +200 -200
- package/ez-agents/templates/research-project/STACK.md +120 -120
- package/ez-agents/templates/research-project/SUMMARY.md +170 -170
- package/ez-agents/templates/retrospective.md +54 -54
- package/ez-agents/templates/roadmap.md +202 -202
- package/ez-agents/templates/summary-minimal.md +41 -41
- package/ez-agents/templates/summary-standard.md +48 -48
- package/ez-agents/templates/summary.md +248 -248
- package/ez-agents/templates/user-setup.md +311 -311
- package/ez-agents/templates/verification-report.md +322 -322
- package/ez-agents/workflows/add-phase.md +112 -112
- package/ez-agents/workflows/add-tests.md +351 -351
- package/ez-agents/workflows/add-todo.md +158 -158
- package/ez-agents/workflows/audit-milestone.md +332 -332
- package/ez-agents/workflows/autonomous.md +743 -743
- package/ez-agents/workflows/check-todos.md +177 -177
- package/ez-agents/workflows/cleanup.md +152 -152
- package/ez-agents/workflows/complete-milestone.md +766 -766
- package/ez-agents/workflows/diagnose-issues.md +219 -219
- package/ez-agents/workflows/discovery-phase.md +289 -289
- package/ez-agents/workflows/discuss-phase.md +762 -762
- package/ez-agents/workflows/execute-phase.md +468 -468
- package/ez-agents/workflows/execute-plan.md +483 -483
- package/ez-agents/workflows/health.md +159 -159
- package/ez-agents/workflows/help.md +492 -492
- package/ez-agents/workflows/insert-phase.md +130 -130
- package/ez-agents/workflows/list-phase-assumptions.md +178 -178
- package/ez-agents/workflows/map-codebase.md +316 -316
- package/ez-agents/workflows/new-milestone.md +384 -384
- package/ez-agents/workflows/new-project.md +1113 -1111
- package/ez-agents/workflows/node-repair.md +92 -92
- package/ez-agents/workflows/pause-work.md +122 -122
- package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
- package/ez-agents/workflows/plan-phase.md +651 -651
- package/ez-agents/workflows/progress.md +382 -382
- package/ez-agents/workflows/quick.md +610 -610
- package/ez-agents/workflows/remove-phase.md +155 -155
- package/ez-agents/workflows/research-phase.md +74 -74
- package/ez-agents/workflows/resume-project.md +307 -307
- package/ez-agents/workflows/set-profile.md +81 -81
- package/ez-agents/workflows/settings.md +242 -242
- package/ez-agents/workflows/stats.md +57 -57
- package/ez-agents/workflows/transition.md +544 -544
- package/ez-agents/workflows/ui-phase.md +290 -290
- package/ez-agents/workflows/ui-review.md +157 -157
- package/ez-agents/workflows/update.md +320 -320
- package/ez-agents/workflows/validate-phase.md +167 -167
- package/ez-agents/workflows/verify-phase.md +243 -243
- package/ez-agents/workflows/verify-work.md +584 -584
- package/package.json +2 -3
- package/scripts/build-hooks.js +43 -43
- package/scripts/fix-qwen-installation.js +144 -0
- package/scripts/run-tests.cjs +29 -29
- package/README.zh-CN.md +0 -702
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Creates secure temp files with fs.mkdtemp(), automatic cleanup on exit,
|
|
7
|
-
* and path validation to prevent traversal attacks.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* const { createTempFile, cleanupAll } = require('./temp-file.cjs');
|
|
11
|
-
* const tempFile = await createTempFile('prefix-');
|
|
12
|
-
* // ... use temp file ...
|
|
13
|
-
* // Automatically cleaned up on process exit
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const os = require('os');
|
|
19
|
-
const crypto = require('crypto');
|
|
20
|
-
const Logger = require('./logger.cjs');
|
|
21
|
-
const logger = new Logger();
|
|
22
|
-
|
|
23
|
-
// Track all created temp resources for cleanup
|
|
24
|
-
const tempResources = new Set();
|
|
25
|
-
let cleanupRegistered = false;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Generate a secure random suffix for temp names
|
|
29
|
-
* @returns {string} - Random hex string
|
|
30
|
-
*/
|
|
31
|
-
function generateSecureSuffix() {
|
|
32
|
-
return crypto.randomBytes(8).toString('hex');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Validate that a path is safe (no traversal attacks)
|
|
37
|
-
* @param {string} basePath - Base directory
|
|
38
|
-
* @param {string} targetPath - Target path to validate
|
|
39
|
-
* @returns {boolean} - True if safe
|
|
40
|
-
*/
|
|
41
|
-
function isPathSafe(basePath, targetPath) {
|
|
42
|
-
const resolvedBase = path.resolve(basePath);
|
|
43
|
-
const resolvedTarget = path.resolve(targetPath);
|
|
44
|
-
|
|
45
|
-
// Ensure target starts with base (is inside base)
|
|
46
|
-
const normalizedBase = resolvedBase + path.sep;
|
|
47
|
-
return resolvedTarget === resolvedBase ||
|
|
48
|
-
resolvedTarget.startsWith(normalizedBase);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Create a secure temporary directory
|
|
53
|
-
* @param {string} prefix - Prefix for temp directory name
|
|
54
|
-
* @param {string} customBase - Custom base directory (default: os.tmpdir())
|
|
55
|
-
* @returns {Promise<string>} - Path to created temp directory
|
|
56
|
-
*/
|
|
57
|
-
async function createTempDir(prefix = 'ez-', customBase = null) {
|
|
58
|
-
const baseDir = customBase || os.tmpdir();
|
|
59
|
-
const tempPath = await fs.promises.mkdtemp(path.join(baseDir, prefix));
|
|
60
|
-
|
|
61
|
-
logger.debug(`Created temp directory: ${tempPath}`);
|
|
62
|
-
tempResources.add({ type: 'dir', path: tempPath });
|
|
63
|
-
|
|
64
|
-
registerCleanup();
|
|
65
|
-
|
|
66
|
-
return tempPath;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create a secure temporary file
|
|
71
|
-
* @param {string} prefix - Prefix for temp file name
|
|
72
|
-
* @param {string} customBase - Custom base directory (default: os.tmpdir())
|
|
73
|
-
* @param {string} content - Optional initial content
|
|
74
|
-
* @returns {Promise<string>} - Path to created temp file
|
|
75
|
-
*/
|
|
76
|
-
async function createTempFile(prefix = 'ez-', customBase = null, content = '') {
|
|
77
|
-
const baseDir = customBase || os.tmpdir();
|
|
78
|
-
const fileName = `${prefix}${generateSecureSuffix()}`;
|
|
79
|
-
const tempPath = path.join(baseDir, fileName);
|
|
80
|
-
|
|
81
|
-
await fs.promises.writeFile(tempPath, content, 'utf-8');
|
|
82
|
-
|
|
83
|
-
logger.debug(`Created temp file: ${tempPath}`);
|
|
84
|
-
tempResources.add({ type: 'file', path: tempPath });
|
|
85
|
-
|
|
86
|
-
registerCleanup();
|
|
87
|
-
|
|
88
|
-
return tempPath;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Write content to a temp file safely
|
|
93
|
-
* @param {string} tempPath - Path to temp file
|
|
94
|
-
* @param {string} content - Content to write
|
|
95
|
-
* @param {Object} options - Write options
|
|
96
|
-
*/
|
|
97
|
-
async function writeToTemp(tempPath, content, options = {}) {
|
|
98
|
-
const { validateBase = os.tmpdir() } = options;
|
|
99
|
-
|
|
100
|
-
if (!isPathSafe(validateBase, tempPath)) {
|
|
101
|
-
throw new Error(`Path traversal detected: ${tempPath}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
await fs.promises.writeFile(tempPath, content, 'utf-8');
|
|
105
|
-
logger.debug(`Written to temp file: ${tempPath}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Read content from a temp file safely
|
|
110
|
-
* @param {string} tempPath - Path to temp file
|
|
111
|
-
* @param {Object} options - Read options
|
|
112
|
-
* @returns {Promise<string>} - File content
|
|
113
|
-
*/
|
|
114
|
-
async function readFromTemp(tempPath, options = {}) {
|
|
115
|
-
const { validateBase = os.tmpdir() } = options;
|
|
116
|
-
|
|
117
|
-
if (!isPathSafe(validateBase, tempPath)) {
|
|
118
|
-
throw new Error(`Path traversal detected: ${tempPath}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const content = await fs.promises.readFile(tempPath, 'utf-8');
|
|
122
|
-
logger.debug(`Read from temp file: ${tempPath}`);
|
|
123
|
-
|
|
124
|
-
return content;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Clean up a specific temp resource
|
|
129
|
-
* @param {string} tempPath - Path to temp file or directory
|
|
130
|
-
*/
|
|
131
|
-
async function cleanupTemp(tempPath) {
|
|
132
|
-
const resolvedPath = path.resolve(tempPath);
|
|
133
|
-
|
|
134
|
-
for (const resource of tempResources) {
|
|
135
|
-
if (path.resolve(resource.path) === resolvedPath) {
|
|
136
|
-
try {
|
|
137
|
-
if (resource.type === 'dir') {
|
|
138
|
-
await fs.promises.rm(resolvedPath, { recursive: true, force: true });
|
|
139
|
-
logger.debug(`Cleaned up temp directory: ${resolvedPath}`);
|
|
140
|
-
} else {
|
|
141
|
-
await fs.promises.unlink(resolvedPath);
|
|
142
|
-
logger.debug(`Cleaned up temp file: ${resolvedPath}`);
|
|
143
|
-
}
|
|
144
|
-
tempResources.delete(resource);
|
|
145
|
-
} catch (err) {
|
|
146
|
-
logger.warn(`Failed to cleanup temp: ${resolvedPath}`, {
|
|
147
|
-
error: err.message
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Clean up all tracked temp resources
|
|
157
|
-
*/
|
|
158
|
-
async function cleanupAll() {
|
|
159
|
-
logger.info(`Cleaning up ${tempResources.size} temp resources...`);
|
|
160
|
-
|
|
161
|
-
const cleanupPromises = [];
|
|
162
|
-
for (const resource of tempResources) {
|
|
163
|
-
cleanupPromises.push(
|
|
164
|
-
cleanupTemp(resource.path).catch(err => {
|
|
165
|
-
logger.warn(`Cleanup failed for ${resource.path}: ${err.message}`);
|
|
166
|
-
})
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await Promise.all(cleanupPromises);
|
|
171
|
-
logger.info('Temp cleanup complete');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Register cleanup handlers (called automatically)
|
|
176
|
-
*/
|
|
177
|
-
function registerCleanup() {
|
|
178
|
-
if (cleanupRegistered) return;
|
|
179
|
-
cleanupRegistered = true;
|
|
180
|
-
|
|
181
|
-
const cleanupHandler = async () => {
|
|
182
|
-
if (tempResources.size > 0) {
|
|
183
|
-
await cleanupAll();
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// Register for various exit scenarios
|
|
188
|
-
process.on('exit', () => {
|
|
189
|
-
// Synchronous cleanup for exit event
|
|
190
|
-
if (tempResources.size > 0) {
|
|
191
|
-
logger.debug('Synchronous temp cleanup on exit');
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
process.on('SIGINT', async () => {
|
|
196
|
-
await cleanupHandler();
|
|
197
|
-
process.exit(130);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
process.on('SIGTERM', async () => {
|
|
201
|
-
await cleanupHandler();
|
|
202
|
-
process.exit(143);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
process.on('beforeExit', async () => {
|
|
206
|
-
await cleanupHandler();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Handle uncaught errors
|
|
210
|
-
process.on('uncaughtException', async (err) => {
|
|
211
|
-
logger.error('Uncaught exception, cleaning up temps...', {
|
|
212
|
-
error: err.message
|
|
213
|
-
});
|
|
214
|
-
await cleanupHandler();
|
|
215
|
-
process.exit(1);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
logger.debug('Temp cleanup handlers registered');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get list of tracked temp resources
|
|
223
|
-
* @returns {Array} - Array of {type, path} objects
|
|
224
|
-
*/
|
|
225
|
-
function getTrackedTemps() {
|
|
226
|
-
return Array.from(tempResources);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
module.exports = {
|
|
230
|
-
createTempDir,
|
|
231
|
-
createTempFile,
|
|
232
|
-
writeToTemp,
|
|
233
|
-
readFromTemp,
|
|
234
|
-
cleanupTemp,
|
|
235
|
-
cleanupAll,
|
|
236
|
-
getTrackedTemps,
|
|
237
|
-
isPathSafe,
|
|
238
|
-
generateSecureSuffix
|
|
239
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EZ Temp File — Secure temporary file handler
|
|
5
|
+
*
|
|
6
|
+
* Creates secure temp files with fs.mkdtemp(), automatic cleanup on exit,
|
|
7
|
+
* and path validation to prevent traversal attacks.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const { createTempFile, cleanupAll } = require('./temp-file.cjs');
|
|
11
|
+
* const tempFile = await createTempFile('prefix-');
|
|
12
|
+
* // ... use temp file ...
|
|
13
|
+
* // Automatically cleaned up on process exit
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const Logger = require('./logger.cjs');
|
|
21
|
+
const logger = new Logger();
|
|
22
|
+
|
|
23
|
+
// Track all created temp resources for cleanup
|
|
24
|
+
const tempResources = new Set();
|
|
25
|
+
let cleanupRegistered = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a secure random suffix for temp names
|
|
29
|
+
* @returns {string} - Random hex string
|
|
30
|
+
*/
|
|
31
|
+
function generateSecureSuffix() {
|
|
32
|
+
return crypto.randomBytes(8).toString('hex');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a path is safe (no traversal attacks)
|
|
37
|
+
* @param {string} basePath - Base directory
|
|
38
|
+
* @param {string} targetPath - Target path to validate
|
|
39
|
+
* @returns {boolean} - True if safe
|
|
40
|
+
*/
|
|
41
|
+
function isPathSafe(basePath, targetPath) {
|
|
42
|
+
const resolvedBase = path.resolve(basePath);
|
|
43
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
44
|
+
|
|
45
|
+
// Ensure target starts with base (is inside base)
|
|
46
|
+
const normalizedBase = resolvedBase + path.sep;
|
|
47
|
+
return resolvedTarget === resolvedBase ||
|
|
48
|
+
resolvedTarget.startsWith(normalizedBase);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a secure temporary directory
|
|
53
|
+
* @param {string} prefix - Prefix for temp directory name
|
|
54
|
+
* @param {string} customBase - Custom base directory (default: os.tmpdir())
|
|
55
|
+
* @returns {Promise<string>} - Path to created temp directory
|
|
56
|
+
*/
|
|
57
|
+
async function createTempDir(prefix = 'ez-', customBase = null) {
|
|
58
|
+
const baseDir = customBase || os.tmpdir();
|
|
59
|
+
const tempPath = await fs.promises.mkdtemp(path.join(baseDir, prefix));
|
|
60
|
+
|
|
61
|
+
logger.debug(`Created temp directory: ${tempPath}`);
|
|
62
|
+
tempResources.add({ type: 'dir', path: tempPath });
|
|
63
|
+
|
|
64
|
+
registerCleanup();
|
|
65
|
+
|
|
66
|
+
return tempPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a secure temporary file
|
|
71
|
+
* @param {string} prefix - Prefix for temp file name
|
|
72
|
+
* @param {string} customBase - Custom base directory (default: os.tmpdir())
|
|
73
|
+
* @param {string} content - Optional initial content
|
|
74
|
+
* @returns {Promise<string>} - Path to created temp file
|
|
75
|
+
*/
|
|
76
|
+
async function createTempFile(prefix = 'ez-', customBase = null, content = '') {
|
|
77
|
+
const baseDir = customBase || os.tmpdir();
|
|
78
|
+
const fileName = `${prefix}${generateSecureSuffix()}`;
|
|
79
|
+
const tempPath = path.join(baseDir, fileName);
|
|
80
|
+
|
|
81
|
+
await fs.promises.writeFile(tempPath, content, 'utf-8');
|
|
82
|
+
|
|
83
|
+
logger.debug(`Created temp file: ${tempPath}`);
|
|
84
|
+
tempResources.add({ type: 'file', path: tempPath });
|
|
85
|
+
|
|
86
|
+
registerCleanup();
|
|
87
|
+
|
|
88
|
+
return tempPath;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Write content to a temp file safely
|
|
93
|
+
* @param {string} tempPath - Path to temp file
|
|
94
|
+
* @param {string} content - Content to write
|
|
95
|
+
* @param {Object} options - Write options
|
|
96
|
+
*/
|
|
97
|
+
async function writeToTemp(tempPath, content, options = {}) {
|
|
98
|
+
const { validateBase = os.tmpdir() } = options;
|
|
99
|
+
|
|
100
|
+
if (!isPathSafe(validateBase, tempPath)) {
|
|
101
|
+
throw new Error(`Path traversal detected: ${tempPath}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await fs.promises.writeFile(tempPath, content, 'utf-8');
|
|
105
|
+
logger.debug(`Written to temp file: ${tempPath}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read content from a temp file safely
|
|
110
|
+
* @param {string} tempPath - Path to temp file
|
|
111
|
+
* @param {Object} options - Read options
|
|
112
|
+
* @returns {Promise<string>} - File content
|
|
113
|
+
*/
|
|
114
|
+
async function readFromTemp(tempPath, options = {}) {
|
|
115
|
+
const { validateBase = os.tmpdir() } = options;
|
|
116
|
+
|
|
117
|
+
if (!isPathSafe(validateBase, tempPath)) {
|
|
118
|
+
throw new Error(`Path traversal detected: ${tempPath}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const content = await fs.promises.readFile(tempPath, 'utf-8');
|
|
122
|
+
logger.debug(`Read from temp file: ${tempPath}`);
|
|
123
|
+
|
|
124
|
+
return content;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clean up a specific temp resource
|
|
129
|
+
* @param {string} tempPath - Path to temp file or directory
|
|
130
|
+
*/
|
|
131
|
+
async function cleanupTemp(tempPath) {
|
|
132
|
+
const resolvedPath = path.resolve(tempPath);
|
|
133
|
+
|
|
134
|
+
for (const resource of tempResources) {
|
|
135
|
+
if (path.resolve(resource.path) === resolvedPath) {
|
|
136
|
+
try {
|
|
137
|
+
if (resource.type === 'dir') {
|
|
138
|
+
await fs.promises.rm(resolvedPath, { recursive: true, force: true });
|
|
139
|
+
logger.debug(`Cleaned up temp directory: ${resolvedPath}`);
|
|
140
|
+
} else {
|
|
141
|
+
await fs.promises.unlink(resolvedPath);
|
|
142
|
+
logger.debug(`Cleaned up temp file: ${resolvedPath}`);
|
|
143
|
+
}
|
|
144
|
+
tempResources.delete(resource);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.warn(`Failed to cleanup temp: ${resolvedPath}`, {
|
|
147
|
+
error: err.message
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clean up all tracked temp resources
|
|
157
|
+
*/
|
|
158
|
+
async function cleanupAll() {
|
|
159
|
+
logger.info(`Cleaning up ${tempResources.size} temp resources...`);
|
|
160
|
+
|
|
161
|
+
const cleanupPromises = [];
|
|
162
|
+
for (const resource of tempResources) {
|
|
163
|
+
cleanupPromises.push(
|
|
164
|
+
cleanupTemp(resource.path).catch(err => {
|
|
165
|
+
logger.warn(`Cleanup failed for ${resource.path}: ${err.message}`);
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await Promise.all(cleanupPromises);
|
|
171
|
+
logger.info('Temp cleanup complete');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Register cleanup handlers (called automatically)
|
|
176
|
+
*/
|
|
177
|
+
function registerCleanup() {
|
|
178
|
+
if (cleanupRegistered) return;
|
|
179
|
+
cleanupRegistered = true;
|
|
180
|
+
|
|
181
|
+
const cleanupHandler = async () => {
|
|
182
|
+
if (tempResources.size > 0) {
|
|
183
|
+
await cleanupAll();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Register for various exit scenarios
|
|
188
|
+
process.on('exit', () => {
|
|
189
|
+
// Synchronous cleanup for exit event
|
|
190
|
+
if (tempResources.size > 0) {
|
|
191
|
+
logger.debug('Synchronous temp cleanup on exit');
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
process.on('SIGINT', async () => {
|
|
196
|
+
await cleanupHandler();
|
|
197
|
+
process.exit(130);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
process.on('SIGTERM', async () => {
|
|
201
|
+
await cleanupHandler();
|
|
202
|
+
process.exit(143);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
process.on('beforeExit', async () => {
|
|
206
|
+
await cleanupHandler();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Handle uncaught errors
|
|
210
|
+
process.on('uncaughtException', async (err) => {
|
|
211
|
+
logger.error('Uncaught exception, cleaning up temps...', {
|
|
212
|
+
error: err.message
|
|
213
|
+
});
|
|
214
|
+
await cleanupHandler();
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
logger.debug('Temp cleanup handlers registered');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get list of tracked temp resources
|
|
223
|
+
* @returns {Array} - Array of {type, path} objects
|
|
224
|
+
*/
|
|
225
|
+
function getTrackedTemps() {
|
|
226
|
+
return Array.from(tempResources);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
createTempDir,
|
|
231
|
+
createTempFile,
|
|
232
|
+
writeToTemp,
|
|
233
|
+
readFromTemp,
|
|
234
|
+
cleanupTemp,
|
|
235
|
+
cleanupAll,
|
|
236
|
+
getTrackedTemps,
|
|
237
|
+
isPathSafe,
|
|
238
|
+
generateSecureSuffix
|
|
239
|
+
};
|