steroids-api 0.2.7
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/API/src/index.d.ts +10 -0
- package/dist/API/src/index.d.ts.map +1 -0
- package/dist/API/src/index.js +130 -0
- package/dist/API/src/index.js.map +1 -0
- package/dist/API/src/routes/activity.d.ts +7 -0
- package/dist/API/src/routes/activity.d.ts.map +1 -0
- package/dist/API/src/routes/activity.js +252 -0
- package/dist/API/src/routes/activity.js.map +1 -0
- package/dist/API/src/routes/config.d.ts +7 -0
- package/dist/API/src/routes/config.d.ts.map +1 -0
- package/dist/API/src/routes/config.js +521 -0
- package/dist/API/src/routes/config.js.map +1 -0
- package/dist/API/src/routes/health.d.ts +7 -0
- package/dist/API/src/routes/health.d.ts.map +1 -0
- package/dist/API/src/routes/health.js +172 -0
- package/dist/API/src/routes/health.js.map +1 -0
- package/dist/API/src/routes/incidents.d.ts +7 -0
- package/dist/API/src/routes/incidents.d.ts.map +1 -0
- package/dist/API/src/routes/incidents.js +117 -0
- package/dist/API/src/routes/incidents.js.map +1 -0
- package/dist/API/src/routes/projects.d.ts +7 -0
- package/dist/API/src/routes/projects.d.ts.map +1 -0
- package/dist/API/src/routes/projects.js +398 -0
- package/dist/API/src/routes/projects.js.map +1 -0
- package/dist/API/src/routes/runners.d.ts +7 -0
- package/dist/API/src/routes/runners.d.ts.map +1 -0
- package/dist/API/src/routes/runners.js +242 -0
- package/dist/API/src/routes/runners.js.map +1 -0
- package/dist/API/src/routes/tasks.d.ts +7 -0
- package/dist/API/src/routes/tasks.d.ts.map +1 -0
- package/dist/API/src/routes/tasks.js +1007 -0
- package/dist/API/src/routes/tasks.js.map +1 -0
- package/dist/API/src/utils/validation.d.ts +22 -0
- package/dist/API/src/utils/validation.d.ts.map +1 -0
- package/dist/API/src/utils/validation.js +50 -0
- package/dist/API/src/utils/validation.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +184 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/activity.d.ts +7 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +252 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/config.d.ts +7 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +647 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/credit-alerts.d.ts +2 -0
- package/dist/routes/credit-alerts.d.ts.map +1 -0
- package/dist/routes/credit-alerts.js +97 -0
- package/dist/routes/credit-alerts.js.map +1 -0
- package/dist/routes/health.d.ts +7 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +200 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/incidents.d.ts +7 -0
- package/dist/routes/incidents.d.ts.map +1 -0
- package/dist/routes/incidents.js +117 -0
- package/dist/routes/incidents.js.map +1 -0
- package/dist/routes/projects.d.ts +7 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +643 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/runners.d.ts +7 -0
- package/dist/routes/runners.d.ts.map +1 -0
- package/dist/routes/runners.js +299 -0
- package/dist/routes/runners.js.map +1 -0
- package/dist/routes/skills.d.ts +3 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +109 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/storage.d.ts +7 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +93 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/tasks.d.ts +7 -0
- package/dist/routes/tasks.d.ts.map +1 -0
- package/dist/routes/tasks.js +1145 -0
- package/dist/routes/tasks.js.map +1 -0
- package/dist/src/cleanup/invocation-logs.d.ts +30 -0
- package/dist/src/cleanup/invocation-logs.d.ts.map +1 -0
- package/dist/src/cleanup/invocation-logs.js +66 -0
- package/dist/src/cleanup/invocation-logs.js.map +1 -0
- package/dist/src/commands/loop-phases.d.ts +11 -0
- package/dist/src/commands/loop-phases.d.ts.map +1 -0
- package/dist/src/commands/loop-phases.js +304 -0
- package/dist/src/commands/loop-phases.js.map +1 -0
- package/dist/src/config/loader.d.ts +160 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +276 -0
- package/dist/src/config/loader.js.map +1 -0
- package/dist/src/database/connection.d.ts +35 -0
- package/dist/src/database/connection.d.ts.map +1 -0
- package/dist/src/database/connection.js +197 -0
- package/dist/src/database/connection.js.map +1 -0
- package/dist/src/database/queries.d.ts +220 -0
- package/dist/src/database/queries.d.ts.map +1 -0
- package/dist/src/database/queries.js +589 -0
- package/dist/src/database/queries.js.map +1 -0
- package/dist/src/database/schema.d.ts +8 -0
- package/dist/src/database/schema.d.ts.map +1 -0
- package/dist/src/database/schema.js +184 -0
- package/dist/src/database/schema.js.map +1 -0
- package/dist/src/git/push.d.ts +26 -0
- package/dist/src/git/push.d.ts.map +1 -0
- package/dist/src/git/push.js +91 -0
- package/dist/src/git/push.js.map +1 -0
- package/dist/src/git/status.d.ts +83 -0
- package/dist/src/git/status.d.ts.map +1 -0
- package/dist/src/git/status.js +315 -0
- package/dist/src/git/status.js.map +1 -0
- package/dist/src/health/stuck-task-detector.d.ts +131 -0
- package/dist/src/health/stuck-task-detector.d.ts.map +1 -0
- package/dist/src/health/stuck-task-detector.js +233 -0
- package/dist/src/health/stuck-task-detector.js.map +1 -0
- package/dist/src/health/stuck-task-recovery.d.ts +45 -0
- package/dist/src/health/stuck-task-recovery.d.ts.map +1 -0
- package/dist/src/health/stuck-task-recovery.js +309 -0
- package/dist/src/health/stuck-task-recovery.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +130 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/locking/queries.d.ts +116 -0
- package/dist/src/locking/queries.d.ts.map +1 -0
- package/dist/src/locking/queries.js +232 -0
- package/dist/src/locking/queries.js.map +1 -0
- package/dist/src/locking/section-lock.d.ts +74 -0
- package/dist/src/locking/section-lock.d.ts.map +1 -0
- package/dist/src/locking/section-lock.js +196 -0
- package/dist/src/locking/section-lock.js.map +1 -0
- package/dist/src/locking/task-lock.d.ts +92 -0
- package/dist/src/locking/task-lock.d.ts.map +1 -0
- package/dist/src/locking/task-lock.js +233 -0
- package/dist/src/locking/task-lock.js.map +1 -0
- package/dist/src/migrations/index.d.ts +7 -0
- package/dist/src/migrations/index.d.ts.map +1 -0
- package/dist/src/migrations/index.js +9 -0
- package/dist/src/migrations/index.js.map +1 -0
- package/dist/src/migrations/manifest.d.ts +92 -0
- package/dist/src/migrations/manifest.d.ts.map +1 -0
- package/dist/src/migrations/manifest.js +255 -0
- package/dist/src/migrations/manifest.js.map +1 -0
- package/dist/src/migrations/runner.d.ts +84 -0
- package/dist/src/migrations/runner.d.ts.map +1 -0
- package/dist/src/migrations/runner.js +338 -0
- package/dist/src/migrations/runner.js.map +1 -0
- package/dist/src/orchestrator/coder.d.ts +32 -0
- package/dist/src/orchestrator/coder.d.ts.map +1 -0
- package/dist/src/orchestrator/coder.js +170 -0
- package/dist/src/orchestrator/coder.js.map +1 -0
- package/dist/src/orchestrator/coordinator.d.ts +28 -0
- package/dist/src/orchestrator/coordinator.d.ts.map +1 -0
- package/dist/src/orchestrator/coordinator.js +252 -0
- package/dist/src/orchestrator/coordinator.js.map +1 -0
- package/dist/src/orchestrator/fallback-handler.d.ts +24 -0
- package/dist/src/orchestrator/fallback-handler.d.ts.map +1 -0
- package/dist/src/orchestrator/fallback-handler.js +280 -0
- package/dist/src/orchestrator/fallback-handler.js.map +1 -0
- package/dist/src/orchestrator/invoke.d.ts +14 -0
- package/dist/src/orchestrator/invoke.d.ts.map +1 -0
- package/dist/src/orchestrator/invoke.js +76 -0
- package/dist/src/orchestrator/invoke.js.map +1 -0
- package/dist/src/orchestrator/post-coder.d.ts +10 -0
- package/dist/src/orchestrator/post-coder.d.ts.map +1 -0
- package/dist/src/orchestrator/post-coder.js +198 -0
- package/dist/src/orchestrator/post-coder.js.map +1 -0
- package/dist/src/orchestrator/post-reviewer.d.ts +10 -0
- package/dist/src/orchestrator/post-reviewer.d.ts.map +1 -0
- package/dist/src/orchestrator/post-reviewer.js +199 -0
- package/dist/src/orchestrator/post-reviewer.js.map +1 -0
- package/dist/src/orchestrator/reviewer.d.ts +35 -0
- package/dist/src/orchestrator/reviewer.d.ts.map +1 -0
- package/dist/src/orchestrator/reviewer.js +237 -0
- package/dist/src/orchestrator/reviewer.js.map +1 -0
- package/dist/src/orchestrator/schemas.d.ts +10 -0
- package/dist/src/orchestrator/schemas.d.ts.map +1 -0
- package/dist/src/orchestrator/schemas.js +81 -0
- package/dist/src/orchestrator/schemas.js.map +1 -0
- package/dist/src/orchestrator/task-selector.d.ts +102 -0
- package/dist/src/orchestrator/task-selector.d.ts.map +1 -0
- package/dist/src/orchestrator/task-selector.js +326 -0
- package/dist/src/orchestrator/task-selector.js.map +1 -0
- package/dist/src/orchestrator/types.d.ts +74 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +5 -0
- package/dist/src/orchestrator/types.js.map +1 -0
- package/dist/src/prompts/coder.d.ts +36 -0
- package/dist/src/prompts/coder.d.ts.map +1 -0
- package/dist/src/prompts/coder.js +303 -0
- package/dist/src/prompts/coder.js.map +1 -0
- package/dist/src/prompts/prompt-helpers.d.ts +51 -0
- package/dist/src/prompts/prompt-helpers.d.ts.map +1 -0
- package/dist/src/prompts/prompt-helpers.js +299 -0
- package/dist/src/prompts/prompt-helpers.js.map +1 -0
- package/dist/src/prompts/reviewer.d.ts +40 -0
- package/dist/src/prompts/reviewer.d.ts.map +1 -0
- package/dist/src/prompts/reviewer.js +416 -0
- package/dist/src/prompts/reviewer.js.map +1 -0
- package/dist/src/providers/claude.d.ts +53 -0
- package/dist/src/providers/claude.d.ts.map +1 -0
- package/dist/src/providers/claude.js +227 -0
- package/dist/src/providers/claude.js.map +1 -0
- package/dist/src/providers/codex.d.ts +53 -0
- package/dist/src/providers/codex.d.ts.map +1 -0
- package/dist/src/providers/codex.js +253 -0
- package/dist/src/providers/codex.js.map +1 -0
- package/dist/src/providers/gemini.d.ts +58 -0
- package/dist/src/providers/gemini.d.ts.map +1 -0
- package/dist/src/providers/gemini.js +240 -0
- package/dist/src/providers/gemini.js.map +1 -0
- package/dist/src/providers/interface.d.ts +185 -0
- package/dist/src/providers/interface.d.ts.map +1 -0
- package/dist/src/providers/interface.js +92 -0
- package/dist/src/providers/interface.js.map +1 -0
- package/dist/src/providers/invocation-logger.d.ts +97 -0
- package/dist/src/providers/invocation-logger.d.ts.map +1 -0
- package/dist/src/providers/invocation-logger.js +378 -0
- package/dist/src/providers/invocation-logger.js.map +1 -0
- package/dist/src/providers/openai.d.ts +53 -0
- package/dist/src/providers/openai.d.ts.map +1 -0
- package/dist/src/providers/openai.js +230 -0
- package/dist/src/providers/openai.js.map +1 -0
- package/dist/src/providers/registry.d.ts +100 -0
- package/dist/src/providers/registry.d.ts.map +1 -0
- package/dist/src/providers/registry.js +170 -0
- package/dist/src/providers/registry.js.map +1 -0
- package/dist/src/routes/activity.d.ts +7 -0
- package/dist/src/routes/activity.d.ts.map +1 -0
- package/dist/src/routes/activity.js +252 -0
- package/dist/src/routes/activity.js.map +1 -0
- package/dist/src/routes/config.d.ts +7 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +521 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/health.d.ts +7 -0
- package/dist/src/routes/health.d.ts.map +1 -0
- package/dist/src/routes/health.js +172 -0
- package/dist/src/routes/health.js.map +1 -0
- package/dist/src/routes/incidents.d.ts +7 -0
- package/dist/src/routes/incidents.d.ts.map +1 -0
- package/dist/src/routes/incidents.js +117 -0
- package/dist/src/routes/incidents.js.map +1 -0
- package/dist/src/routes/projects.d.ts +7 -0
- package/dist/src/routes/projects.d.ts.map +1 -0
- package/dist/src/routes/projects.js +398 -0
- package/dist/src/routes/projects.js.map +1 -0
- package/dist/src/routes/runners.d.ts +7 -0
- package/dist/src/routes/runners.d.ts.map +1 -0
- package/dist/src/routes/runners.js +242 -0
- package/dist/src/routes/runners.js.map +1 -0
- package/dist/src/routes/tasks.d.ts +7 -0
- package/dist/src/routes/tasks.d.ts.map +1 -0
- package/dist/src/routes/tasks.js +1007 -0
- package/dist/src/routes/tasks.js.map +1 -0
- package/dist/src/runners/activity-log.d.ts +65 -0
- package/dist/src/runners/activity-log.d.ts.map +1 -0
- package/dist/src/runners/activity-log.js +140 -0
- package/dist/src/runners/activity-log.js.map +1 -0
- package/dist/src/runners/cron.d.ts +30 -0
- package/dist/src/runners/cron.d.ts.map +1 -0
- package/dist/src/runners/cron.js +333 -0
- package/dist/src/runners/cron.js.map +1 -0
- package/dist/src/runners/daemon.d.ts +71 -0
- package/dist/src/runners/daemon.d.ts.map +1 -0
- package/dist/src/runners/daemon.js +233 -0
- package/dist/src/runners/daemon.js.map +1 -0
- package/dist/src/runners/global-db.d.ts +31 -0
- package/dist/src/runners/global-db.d.ts.map +1 -0
- package/dist/src/runners/global-db.js +220 -0
- package/dist/src/runners/global-db.js.map +1 -0
- package/dist/src/runners/hang-detector.d.ts +38 -0
- package/dist/src/runners/hang-detector.d.ts.map +1 -0
- package/dist/src/runners/hang-detector.js +130 -0
- package/dist/src/runners/hang-detector.js.map +1 -0
- package/dist/src/runners/heartbeat.d.ts +39 -0
- package/dist/src/runners/heartbeat.d.ts.map +1 -0
- package/dist/src/runners/heartbeat.js +71 -0
- package/dist/src/runners/heartbeat.js.map +1 -0
- package/dist/src/runners/lock.d.ts +47 -0
- package/dist/src/runners/lock.d.ts.map +1 -0
- package/dist/src/runners/lock.js +140 -0
- package/dist/src/runners/lock.js.map +1 -0
- package/dist/src/runners/orchestrator-loop.d.ts +20 -0
- package/dist/src/runners/orchestrator-loop.d.ts.map +1 -0
- package/dist/src/runners/orchestrator-loop.js +208 -0
- package/dist/src/runners/orchestrator-loop.js.map +1 -0
- package/dist/src/runners/projects.d.ts +96 -0
- package/dist/src/runners/projects.d.ts.map +1 -0
- package/dist/src/runners/projects.js +243 -0
- package/dist/src/runners/projects.js.map +1 -0
- package/dist/src/runners/wakeup.d.ts +37 -0
- package/dist/src/runners/wakeup.d.ts.map +1 -0
- package/dist/src/runners/wakeup.js +355 -0
- package/dist/src/runners/wakeup.js.map +1 -0
- package/dist/src/utils/validation.d.ts +22 -0
- package/dist/src/utils/validation.d.ts.map +1 -0
- package/dist/src/utils/validation.js +50 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/dist/utils/sqlite.d.ts +17 -0
- package/dist/utils/sqlite.d.ts.map +1 -0
- package/dist/utils/sqlite.js +27 -0
- package/dist/utils/sqlite.js.map +1 -0
- package/dist/utils/storage-cache.d.ts +33 -0
- package/dist/utils/storage-cache.d.ts.map +1 -0
- package/dist/utils/storage-cache.js +81 -0
- package/dist/utils/storage-cache.js.map +1 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +51 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +39 -0
- package/src/index.ts +199 -0
- package/src/routes/activity.ts +302 -0
- package/src/routes/config.ts +723 -0
- package/src/routes/credit-alerts.ts +73 -0
- package/src/routes/health.ts +219 -0
- package/src/routes/incidents.ts +131 -0
- package/src/routes/projects.ts +854 -0
- package/src/routes/runners.ts +357 -0
- package/src/routes/skills.ts +127 -0
- package/src/routes/storage.ts +108 -0
- package/src/routes/tasks.ts +1372 -0
- package/src/utils/sqlite.ts +36 -0
- package/src/utils/storage-cache.ts +107 -0
- package/src/utils/validation.ts +61 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron wake-up command for restarting stale/dead runners
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { checkLockStatus, removeLock } from './lock.js';
|
|
8
|
+
import { openGlobalDatabase } from './global-db.js';
|
|
9
|
+
import { findStaleRunners } from './heartbeat.js';
|
|
10
|
+
import { getRegisteredProjects } from './projects.js';
|
|
11
|
+
import { openDatabase } from '../database/connection.js';
|
|
12
|
+
import { loadConfig } from '../config/loader.js';
|
|
13
|
+
import { recoverStuckTasks } from '../health/stuck-task-recovery.js';
|
|
14
|
+
import { cleanupInvocationLogs } from '../cleanup/invocation-logs.js';
|
|
15
|
+
/**
|
|
16
|
+
* Check if a project has pending work
|
|
17
|
+
*/
|
|
18
|
+
async function projectHasPendingWork(projectPath) {
|
|
19
|
+
const dbPath = join(projectPath, '.steroids', 'steroids.db');
|
|
20
|
+
if (!existsSync(dbPath)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
// Use dynamic import for ESM compatibility
|
|
25
|
+
const { default: Database } = await import('better-sqlite3');
|
|
26
|
+
const db = new Database(dbPath, { readonly: true });
|
|
27
|
+
const result = db
|
|
28
|
+
.prepare(`SELECT COUNT(*) as count FROM tasks
|
|
29
|
+
WHERE status IN ('pending', 'in_progress', 'review')`)
|
|
30
|
+
.get();
|
|
31
|
+
db.close();
|
|
32
|
+
return result.count > 0;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if there's an active runner for a specific project
|
|
40
|
+
* Exported for use in daemon startup checks
|
|
41
|
+
*/
|
|
42
|
+
export function hasActiveRunnerForProject(projectPath) {
|
|
43
|
+
const { db, close } = openGlobalDatabase();
|
|
44
|
+
try {
|
|
45
|
+
const row = db
|
|
46
|
+
.prepare(`SELECT 1 FROM runners
|
|
47
|
+
WHERE project_path = ?
|
|
48
|
+
AND status != 'stopped'
|
|
49
|
+
AND heartbeat_at > datetime('now', '-5 minutes')`)
|
|
50
|
+
.get(projectPath);
|
|
51
|
+
return row !== undefined;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Kill a process by PID
|
|
59
|
+
*/
|
|
60
|
+
function killProcess(pid) {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 'SIGTERM');
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start a new runner daemon
|
|
71
|
+
* Uses 'steroids runners start --detach' so the runner registers in the global DB
|
|
72
|
+
* and updates heartbeat, allowing hasActiveRunnerForProject() to detect it
|
|
73
|
+
*/
|
|
74
|
+
function startRunner(projectPath) {
|
|
75
|
+
try {
|
|
76
|
+
// Use runners start --detach so the daemon registers itself in the global DB
|
|
77
|
+
// This is critical: hasActiveRunnerForProject() checks the runners table,
|
|
78
|
+
// and only the daemon (not loop directly) writes to that table
|
|
79
|
+
const args = ['runners', 'start', '--detach', '--project', projectPath];
|
|
80
|
+
const child = spawn('steroids', args, {
|
|
81
|
+
detached: true,
|
|
82
|
+
stdio: 'ignore',
|
|
83
|
+
});
|
|
84
|
+
child.unref();
|
|
85
|
+
return { pid: child.pid ?? 0 };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Main wake-up function
|
|
93
|
+
* Called by cron every minute to ensure runners are healthy
|
|
94
|
+
* Iterates over ALL registered projects and starts runners as needed
|
|
95
|
+
* Returns per-project results
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Record the last wakeup invocation time
|
|
99
|
+
*/
|
|
100
|
+
function recordWakeupTime() {
|
|
101
|
+
const { db, close } = openGlobalDatabase();
|
|
102
|
+
try {
|
|
103
|
+
db.prepare(`INSERT INTO _global_schema (key, value) VALUES ('last_wakeup_at', datetime('now'))
|
|
104
|
+
ON CONFLICT(key) DO UPDATE SET value = datetime('now')`).run();
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the last wakeup invocation time
|
|
112
|
+
*/
|
|
113
|
+
export function getLastWakeupTime() {
|
|
114
|
+
const { db, close } = openGlobalDatabase();
|
|
115
|
+
try {
|
|
116
|
+
const row = db
|
|
117
|
+
.prepare("SELECT value FROM _global_schema WHERE key = 'last_wakeup_at'")
|
|
118
|
+
.get();
|
|
119
|
+
return row?.value ?? null;
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export async function wakeup(options = {}) {
|
|
126
|
+
const { quiet = false, dryRun = false } = options;
|
|
127
|
+
const results = [];
|
|
128
|
+
const log = (msg) => {
|
|
129
|
+
if (!quiet)
|
|
130
|
+
console.log(msg);
|
|
131
|
+
};
|
|
132
|
+
// Record wakeup invocation time (even for dry runs)
|
|
133
|
+
if (!dryRun) {
|
|
134
|
+
recordWakeupTime();
|
|
135
|
+
}
|
|
136
|
+
// Step 1: Clean up stale runners first
|
|
137
|
+
const global = openGlobalDatabase();
|
|
138
|
+
try {
|
|
139
|
+
try {
|
|
140
|
+
const staleRunners = findStaleRunners(global.db);
|
|
141
|
+
if (staleRunners.length > 0) {
|
|
142
|
+
log(`Found ${staleRunners.length} stale runner(s), cleaning up...`);
|
|
143
|
+
if (!dryRun) {
|
|
144
|
+
for (const runner of staleRunners) {
|
|
145
|
+
if (runner.pid) {
|
|
146
|
+
killProcess(runner.pid);
|
|
147
|
+
}
|
|
148
|
+
global.db.prepare('DELETE FROM runners WHERE id = ?').run(runner.id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
results.push({
|
|
152
|
+
action: 'cleaned',
|
|
153
|
+
reason: `Cleaned ${staleRunners.length} stale runner(s)`,
|
|
154
|
+
staleRunners: staleRunners.length,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// ignore global DB issues; wakeup will still attempt per-project checks
|
|
160
|
+
}
|
|
161
|
+
// Step 2: Clean zombie lock if present
|
|
162
|
+
const lockStatus = checkLockStatus();
|
|
163
|
+
if (lockStatus.isZombie && lockStatus.pid) {
|
|
164
|
+
log(`Found zombie lock (PID: ${lockStatus.pid}), cleaning...`);
|
|
165
|
+
if (!dryRun) {
|
|
166
|
+
removeLock();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Step 3: Get all registered projects from global registry
|
|
170
|
+
const registeredProjects = getRegisteredProjects(false); // enabled only
|
|
171
|
+
if (registeredProjects.length === 0) {
|
|
172
|
+
log('No registered projects found');
|
|
173
|
+
log('Run "steroids projects add <path>" to register a project');
|
|
174
|
+
results.push({
|
|
175
|
+
action: 'none',
|
|
176
|
+
reason: 'No registered projects',
|
|
177
|
+
pendingTasks: 0,
|
|
178
|
+
});
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
log(`Checking ${registeredProjects.length} registered project(s)...`);
|
|
182
|
+
// Step 4: Check each project and start runners as needed
|
|
183
|
+
for (const project of registeredProjects) {
|
|
184
|
+
// Skip if project directory doesn't exist
|
|
185
|
+
if (!existsSync(project.path)) {
|
|
186
|
+
log(`Skipping ${project.path}: directory not found`);
|
|
187
|
+
results.push({
|
|
188
|
+
action: 'none',
|
|
189
|
+
reason: 'Directory not found',
|
|
190
|
+
projectPath: project.path,
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
// Phase 6 (live monitoring): best-effort retention cleanup of invocation activity logs.
|
|
195
|
+
// This is safe to run even if the project has no pending tasks.
|
|
196
|
+
let deletedInvocationLogs = 0;
|
|
197
|
+
try {
|
|
198
|
+
const cleanup = cleanupInvocationLogs(project.path, { retentionDays: 7, dryRun });
|
|
199
|
+
deletedInvocationLogs = cleanup.deletedFiles;
|
|
200
|
+
if (cleanup.deletedFiles > 0 && !quiet) {
|
|
201
|
+
log(`Cleaned ${cleanup.deletedFiles} old invocation log(s) in ${project.path}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Ignore cleanup errors; wakeup must remain robust.
|
|
206
|
+
}
|
|
207
|
+
// Skip if project already has an active runner
|
|
208
|
+
// Check for pending work
|
|
209
|
+
const hasWork = await projectHasPendingWork(project.path);
|
|
210
|
+
if (!hasWork) {
|
|
211
|
+
log(`Skipping ${project.path}: no pending tasks`);
|
|
212
|
+
results.push({
|
|
213
|
+
action: 'none',
|
|
214
|
+
reason: 'No pending tasks',
|
|
215
|
+
projectPath: project.path,
|
|
216
|
+
deletedInvocationLogs,
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// Step 4a: Recover stuck tasks (best-effort) before deciding whether to (re)start a runner.
|
|
221
|
+
// This is what unblocks orphaned/infinite-hang scenarios without manual intervention.
|
|
222
|
+
let recoveredActions = 0;
|
|
223
|
+
let skippedRecoveryDueToSafetyLimit = false;
|
|
224
|
+
try {
|
|
225
|
+
const { db: projectDb, close: closeProjectDb } = openDatabase(project.path);
|
|
226
|
+
try {
|
|
227
|
+
const config = loadConfig(project.path);
|
|
228
|
+
const recovery = await recoverStuckTasks({
|
|
229
|
+
projectPath: project.path,
|
|
230
|
+
projectDb,
|
|
231
|
+
globalDb: global.db,
|
|
232
|
+
config,
|
|
233
|
+
dryRun,
|
|
234
|
+
});
|
|
235
|
+
recoveredActions = recovery.actions.length;
|
|
236
|
+
skippedRecoveryDueToSafetyLimit = recovery.skippedDueToSafetyLimit;
|
|
237
|
+
if (recoveredActions > 0 && !quiet) {
|
|
238
|
+
log(`Recovered ${recoveredActions} stuck item(s) in ${project.path}`);
|
|
239
|
+
}
|
|
240
|
+
if (skippedRecoveryDueToSafetyLimit && !quiet) {
|
|
241
|
+
log(`Skipping auto-recovery in ${project.path}: safety limit hit (maxIncidentsPerHour)`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
closeProjectDb();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// If recovery can't run (DB missing/corrupt), we still proceed with runner checks.
|
|
250
|
+
}
|
|
251
|
+
// Skip if project already has an active runner (after recovery, which may have killed/removed it).
|
|
252
|
+
if (hasActiveRunnerForProject(project.path)) {
|
|
253
|
+
log(`Skipping ${project.path}: runner already active`);
|
|
254
|
+
results.push({
|
|
255
|
+
action: 'none',
|
|
256
|
+
reason: recoveredActions > 0
|
|
257
|
+
? `Runner already active (recovered ${recoveredActions} stuck item(s))`
|
|
258
|
+
: 'Runner already active',
|
|
259
|
+
projectPath: project.path,
|
|
260
|
+
recoveredActions,
|
|
261
|
+
skippedRecoveryDueToSafetyLimit,
|
|
262
|
+
deletedInvocationLogs,
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Start runner for this project
|
|
267
|
+
log(`Starting runner for: ${project.path}`);
|
|
268
|
+
if (dryRun) {
|
|
269
|
+
results.push({
|
|
270
|
+
action: 'would_start',
|
|
271
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); would start runner (dry-run)` : `Would start runner (dry-run)`,
|
|
272
|
+
projectPath: project.path,
|
|
273
|
+
recoveredActions,
|
|
274
|
+
skippedRecoveryDueToSafetyLimit,
|
|
275
|
+
deletedInvocationLogs,
|
|
276
|
+
});
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const startResult = startRunner(project.path);
|
|
280
|
+
if (startResult) {
|
|
281
|
+
results.push({
|
|
282
|
+
action: 'started',
|
|
283
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); started runner` : `Started runner`,
|
|
284
|
+
pid: startResult.pid,
|
|
285
|
+
projectPath: project.path,
|
|
286
|
+
recoveredActions,
|
|
287
|
+
skippedRecoveryDueToSafetyLimit,
|
|
288
|
+
deletedInvocationLogs,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
results.push({
|
|
293
|
+
action: 'none',
|
|
294
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); failed to start runner` : 'Failed to start runner',
|
|
295
|
+
projectPath: project.path,
|
|
296
|
+
recoveredActions,
|
|
297
|
+
skippedRecoveryDueToSafetyLimit,
|
|
298
|
+
deletedInvocationLogs,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// If no specific results, add a summary
|
|
303
|
+
if (results.length === 0) {
|
|
304
|
+
results.push({
|
|
305
|
+
action: 'none',
|
|
306
|
+
reason: 'No action needed',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return results;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
global.close();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Check if wake-up is needed without taking action
|
|
317
|
+
*/
|
|
318
|
+
export async function checkWakeupNeeded() {
|
|
319
|
+
const lockStatus = checkLockStatus();
|
|
320
|
+
if (lockStatus.locked && lockStatus.pid) {
|
|
321
|
+
const { db, close } = openGlobalDatabase();
|
|
322
|
+
try {
|
|
323
|
+
const staleRunners = findStaleRunners(db);
|
|
324
|
+
if (staleRunners.length > 0) {
|
|
325
|
+
return {
|
|
326
|
+
needed: true,
|
|
327
|
+
reason: `${staleRunners.length} stale runner(s) need cleanup`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { needed: false, reason: 'Runner is healthy' };
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
close();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (lockStatus.isZombie) {
|
|
337
|
+
return { needed: true, reason: 'Zombie lock needs cleanup' };
|
|
338
|
+
}
|
|
339
|
+
// Check registered projects
|
|
340
|
+
const registeredProjects = getRegisteredProjects(false);
|
|
341
|
+
let projectsWithWork = 0;
|
|
342
|
+
for (const project of registeredProjects) {
|
|
343
|
+
if (existsSync(project.path) && (await projectHasPendingWork(project.path))) {
|
|
344
|
+
projectsWithWork++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (projectsWithWork > 0) {
|
|
348
|
+
return {
|
|
349
|
+
needed: true,
|
|
350
|
+
reason: `${projectsWithWork} project(s) have pending tasks`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return { needed: false, reason: 'No pending tasks' };
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=wakeup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeup.js","sourceRoot":"","sources":["../../../../src/runners/wakeup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utilities for API security
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate that a path is safe and points to a valid Steroids project
|
|
6
|
+
*
|
|
7
|
+
* @param path - Path to validate
|
|
8
|
+
* @returns True if path is valid and safe
|
|
9
|
+
*/
|
|
10
|
+
export declare function isValidProjectPath(path: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Validate request body for path-based operations
|
|
13
|
+
*
|
|
14
|
+
* @param body - Request body
|
|
15
|
+
* @returns Validation result with error message if invalid
|
|
16
|
+
*/
|
|
17
|
+
export declare function validatePathRequest(body: unknown): {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAoBxD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBpG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utilities for API security
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* Validate that a path is safe and points to a valid Steroids project
|
|
8
|
+
*
|
|
9
|
+
* @param path - Path to validate
|
|
10
|
+
* @returns True if path is valid and safe
|
|
11
|
+
*/
|
|
12
|
+
export function isValidProjectPath(path) {
|
|
13
|
+
try {
|
|
14
|
+
const realPath = realpathSync(path);
|
|
15
|
+
// Must contain .steroids directory with database
|
|
16
|
+
const steroidsDb = join(realPath, '.steroids', 'steroids.db');
|
|
17
|
+
if (!existsSync(steroidsDb)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Must not be system directories
|
|
21
|
+
const forbidden = ['/etc', '/var', '/usr', '/bin', '/sbin', '/System'];
|
|
22
|
+
if (forbidden.some((f) => realPath.startsWith(f))) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate request body for path-based operations
|
|
33
|
+
*
|
|
34
|
+
* @param body - Request body
|
|
35
|
+
* @returns Validation result with error message if invalid
|
|
36
|
+
*/
|
|
37
|
+
export function validatePathRequest(body) {
|
|
38
|
+
if (!body || typeof body !== 'object') {
|
|
39
|
+
return { valid: false, error: 'Request body must be an object' };
|
|
40
|
+
}
|
|
41
|
+
const { path } = body;
|
|
42
|
+
if (!path || typeof path !== 'string') {
|
|
43
|
+
return { valid: false, error: 'Request body must contain a "path" string field' };
|
|
44
|
+
}
|
|
45
|
+
if (path.trim().length === 0) {
|
|
46
|
+
return { valid: false, error: 'Path cannot be empty' };
|
|
47
|
+
}
|
|
48
|
+
return { valid: true, path };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAEpC,iDAAiD;QACjD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;QAC9D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,iCAAiC;QACjC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;QACvE,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;IACnE,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,IAA0B,CAAC;IAE5C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACzD,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
export type SqliteOpenOptions = {
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Open a connection suitable for reading a WAL-enabled database that may be
|
|
7
|
+
* concurrently written by another process.
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT:
|
|
10
|
+
* - In WAL mode, SQLite readers participate in coordination via the `-shm` file.
|
|
11
|
+
* - Opening the main database as SQLITE_OPEN_READONLY can break that coordination
|
|
12
|
+
* and lead to transient IO errors (e.g. SHORT_READ) if the writer checkpoints/truncates.
|
|
13
|
+
*
|
|
14
|
+
* So we open read-write, then enforce read-only at the SQL layer via `PRAGMA query_only=ON`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function openSqliteForRead(dbPath: string, opts?: SqliteOpenOptions): Database.Database;
|
|
17
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/utils/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,QAAQ,CAAC,QAAQ,CAiBjG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Open a connection suitable for reading a WAL-enabled database that may be
|
|
4
|
+
* concurrently written by another process.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT:
|
|
7
|
+
* - In WAL mode, SQLite readers participate in coordination via the `-shm` file.
|
|
8
|
+
* - Opening the main database as SQLITE_OPEN_READONLY can break that coordination
|
|
9
|
+
* and lead to transient IO errors (e.g. SHORT_READ) if the writer checkpoints/truncates.
|
|
10
|
+
*
|
|
11
|
+
* So we open read-write, then enforce read-only at the SQL layer via `PRAGMA query_only=ON`.
|
|
12
|
+
*/
|
|
13
|
+
export function openSqliteForRead(dbPath, opts = {}) {
|
|
14
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
15
|
+
const db = new Database(dbPath, {
|
|
16
|
+
// Must exist; callers use existence checks only for UX, not correctness.
|
|
17
|
+
fileMustExist: true,
|
|
18
|
+
timeout: timeoutMs,
|
|
19
|
+
});
|
|
20
|
+
// Ensure the connection will wait for locks instead of failing immediately.
|
|
21
|
+
// (The `timeout` option also sets this, but being explicit makes intent clear.)
|
|
22
|
+
db.pragma(`busy_timeout = ${timeoutMs}`);
|
|
23
|
+
// Enforce read-only at the SQL layer while still allowing WAL shared-memory coordination.
|
|
24
|
+
db.pragma('query_only = ON');
|
|
25
|
+
return db;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/utils/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAMtC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,OAA0B,EAAE;IAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IAEzC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE;QAC9B,yEAAyE;QACzE,aAAa,EAAE,IAAI;QACnB,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC;IAEH,4EAA4E;IAC5E,gFAAgF;IAChF,EAAE,CAAC,MAAM,CAAC,kBAAkB,SAAS,EAAE,CAAC,CAAC;IAEzC,0FAA0F;IAC1F,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAE7B,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type StorageBreakdown } from '../../../dist/cleanup/directory-size.js';
|
|
2
|
+
export interface ListStorageInfo {
|
|
3
|
+
storage_bytes: number;
|
|
4
|
+
storage_human: string;
|
|
5
|
+
storage_warning: 'orange' | 'red' | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Validate that a query path is a registered project.
|
|
9
|
+
* Uses fs.promises.realpath() to resolve symlinks, then checks the global DB.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateProjectPath(path: string | undefined): Promise<{
|
|
12
|
+
valid: true;
|
|
13
|
+
realPath: string;
|
|
14
|
+
} | {
|
|
15
|
+
valid: false;
|
|
16
|
+
error: string;
|
|
17
|
+
status: number;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Get detailed storage breakdown with 60s cache.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCachedStorageBreakdown(projectPath: string, retentionDays?: number, backupRetentionDays?: number): Promise<StorageBreakdown>;
|
|
23
|
+
/**
|
|
24
|
+
* Get lightweight storage info for project list with 5-minute cache.
|
|
25
|
+
* Returns null if not yet computed (triggers background computation).
|
|
26
|
+
*/
|
|
27
|
+
export declare function getCachedListStorage(projectPath: string): ListStorageInfo | null;
|
|
28
|
+
/**
|
|
29
|
+
* Bust all caches for a given project path.
|
|
30
|
+
* Called after POST /clear-logs to ensure fresh data.
|
|
31
|
+
*/
|
|
32
|
+
export declare function bustStorageCache(projectPath: string): void;
|
|
33
|
+
//# sourceMappingURL=storage-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-cache.d.ts","sourceRoot":"","sources":["../../src/utils/storage-cache.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,yCAAyC,CAAC;AAcjD,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAe9F;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAU,EACzB,mBAAmB,GAAE,MAAW,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAOhF;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAG1D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache for project storage computations.
|
|
3
|
+
* Keyed by project path with configurable TTL.
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { getStorageBreakdown, } from '../../../dist/cleanup/directory-size.js';
|
|
8
|
+
import { getRegisteredProject } from '../../../dist/runners/projects.js';
|
|
9
|
+
const detailCache = new Map();
|
|
10
|
+
const listCache = new Map();
|
|
11
|
+
const DETAIL_TTL_MS = 60_000; // 60 seconds
|
|
12
|
+
const LIST_TTL_MS = 300_000; // 5 minutes
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a query path is a registered project.
|
|
15
|
+
* Uses fs.promises.realpath() to resolve symlinks, then checks the global DB.
|
|
16
|
+
*/
|
|
17
|
+
export async function validateProjectPath(path) {
|
|
18
|
+
if (!path || typeof path !== 'string' || path.trim().length === 0) {
|
|
19
|
+
return { valid: false, error: 'Query parameter "path" is required', status: 400 };
|
|
20
|
+
}
|
|
21
|
+
let realPath;
|
|
22
|
+
try {
|
|
23
|
+
realPath = await fs.realpath(path);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { valid: false, error: 'Path does not exist or is inaccessible', status: 404 };
|
|
27
|
+
}
|
|
28
|
+
const project = getRegisteredProject(realPath);
|
|
29
|
+
if (!project) {
|
|
30
|
+
return { valid: false, error: 'Path is not a registered project', status: 403 };
|
|
31
|
+
}
|
|
32
|
+
return { valid: true, realPath };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get detailed storage breakdown with 60s cache.
|
|
36
|
+
*/
|
|
37
|
+
export async function getCachedStorageBreakdown(projectPath, retentionDays = 7, backupRetentionDays = 30) {
|
|
38
|
+
const entry = detailCache.get(projectPath);
|
|
39
|
+
if (entry && Date.now() < entry.expiresAt)
|
|
40
|
+
return entry.data;
|
|
41
|
+
const steroidsDir = join(projectPath, '.steroids');
|
|
42
|
+
const data = await getStorageBreakdown(steroidsDir, retentionDays, backupRetentionDays);
|
|
43
|
+
detailCache.set(projectPath, { data, expiresAt: Date.now() + DETAIL_TTL_MS });
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get lightweight storage info for project list with 5-minute cache.
|
|
48
|
+
* Returns null if not yet computed (triggers background computation).
|
|
49
|
+
*/
|
|
50
|
+
export function getCachedListStorage(projectPath) {
|
|
51
|
+
const entry = listCache.get(projectPath);
|
|
52
|
+
if (entry && Date.now() < entry.expiresAt)
|
|
53
|
+
return entry.data;
|
|
54
|
+
// Trigger background computation (don't block the list response)
|
|
55
|
+
computeListStorageInBackground(projectPath);
|
|
56
|
+
return entry?.data ?? null; // Return stale data if available, null otherwise
|
|
57
|
+
}
|
|
58
|
+
function computeListStorageInBackground(projectPath) {
|
|
59
|
+
const steroidsDir = join(projectPath, '.steroids');
|
|
60
|
+
getStorageBreakdown(steroidsDir)
|
|
61
|
+
.then((breakdown) => {
|
|
62
|
+
listCache.set(projectPath, {
|
|
63
|
+
data: {
|
|
64
|
+
storage_bytes: breakdown.total_bytes,
|
|
65
|
+
storage_human: breakdown.total_human,
|
|
66
|
+
storage_warning: breakdown.threshold_warning,
|
|
67
|
+
},
|
|
68
|
+
expiresAt: Date.now() + LIST_TTL_MS,
|
|
69
|
+
});
|
|
70
|
+
})
|
|
71
|
+
.catch(() => { });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Bust all caches for a given project path.
|
|
75
|
+
* Called after POST /clear-logs to ensure fresh data.
|
|
76
|
+
*/
|
|
77
|
+
export function bustStorageCache(projectPath) {
|
|
78
|
+
detailCache.delete(projectPath);
|
|
79
|
+
listCache.delete(projectPath);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=storage-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-cache.js","sourceRoot":"","sources":["../../src/utils/storage-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utilities for API security
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate that a path is safe and points to a valid Steroids project
|
|
6
|
+
*
|
|
7
|
+
* @param path - Path to validate
|
|
8
|
+
* @returns True if path is valid and safe
|
|
9
|
+
*/
|
|
10
|
+
export declare function isValidProjectPath(path: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Validate request body for path-based operations
|
|
13
|
+
*
|
|
14
|
+
* @param body - Request body
|
|
15
|
+
* @returns Validation result with error message if invalid
|
|
16
|
+
*/
|
|
17
|
+
export declare function validatePathRequest(body: unknown): {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuBxD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBpG"}
|