gitship-core 0.0.1 → 0.0.3
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/db.d.ts +31 -0
- package/dist/db.js +325 -0
- package/dist/db.js.map +1 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +373 -0
- package/dist/engine.js.map +1 -0
- package/dist/github.d.ts +25 -0
- package/dist/github.js +98 -0
- package/dist/github.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +33 -0
- package/dist/paths.js.map +1 -0
- package/dist/queue.d.ts +10 -0
- package/dist/queue.js +102 -0
- package/dist/queue.js.map +1 -0
- package/package.json +4 -1
- package/src/db.ts +0 -425
- package/src/engine.ts +0 -477
- package/src/github.ts +0 -124
- package/src/paths.ts +0 -35
- package/src/queue.ts +0 -130
- package/tsconfig.json +0 -8
- /package/{src/index.ts → dist/index.d.ts} +0 -0
package/dist/queue.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import { createDeployment, getProject, getQueuedDeployments, getRunningDeployment, updateDeploymentStatus, getDeployment, appendDeploymentLog, } from "./db.js";
|
|
3
|
+
import { runDeploymentPipeline } from "./engine.js";
|
|
4
|
+
// Global map to track active execa processes for cancelling running tasks
|
|
5
|
+
export const activeProcesses = new Map();
|
|
6
|
+
export async function enqueueDeployment(projectId, branch, commitSha, commitMessage, author, rollbackOfId = null) {
|
|
7
|
+
const deploymentId = `dep_${nanoid(10)}`;
|
|
8
|
+
const deployment = createDeployment({
|
|
9
|
+
id: deploymentId,
|
|
10
|
+
project_id: projectId,
|
|
11
|
+
branch,
|
|
12
|
+
commit_sha: commitSha,
|
|
13
|
+
commit_message: commitMessage,
|
|
14
|
+
author,
|
|
15
|
+
status: "QUEUED",
|
|
16
|
+
started_at: null,
|
|
17
|
+
finished_at: null,
|
|
18
|
+
total_duration_ms: null,
|
|
19
|
+
rollback_of_id: rollbackOfId,
|
|
20
|
+
});
|
|
21
|
+
// Trigger queue processing asynchronously
|
|
22
|
+
processQueue(projectId).catch(err => {
|
|
23
|
+
console.error(`Error processing queue for project ${projectId}:`, err);
|
|
24
|
+
});
|
|
25
|
+
return deployment;
|
|
26
|
+
}
|
|
27
|
+
export async function processQueue(projectId) {
|
|
28
|
+
// 1. Check if there is already a running deployment for this project
|
|
29
|
+
const running = getRunningDeployment(projectId);
|
|
30
|
+
if (running) {
|
|
31
|
+
// A deployment is already running, wait for it to finish.
|
|
32
|
+
// It will trigger processQueue again upon completion.
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// 2. Get the next queued deployment
|
|
36
|
+
const queued = getQueuedDeployments(projectId);
|
|
37
|
+
if (queued.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const nextDeployment = queued[0];
|
|
41
|
+
const project = getProject(projectId);
|
|
42
|
+
if (!project) {
|
|
43
|
+
console.error(`Project ${projectId} not found for deployment ${nextDeployment.id}`);
|
|
44
|
+
updateDeploymentStatus(nextDeployment.id, "FAILED", {
|
|
45
|
+
finished_at: Date.now(),
|
|
46
|
+
total_duration_ms: 0,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
// Execute the deployment pipeline
|
|
52
|
+
await runDeploymentPipeline(project, nextDeployment);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(`Pipeline exception for deployment ${nextDeployment.id}:`, err);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
// Process the next item in the queue
|
|
59
|
+
processQueue(projectId).catch(err => {
|
|
60
|
+
console.error(`Error in queue recursion for project ${projectId}:`, err);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function cancelDeployment(id) {
|
|
65
|
+
const dep = getDeployment(id);
|
|
66
|
+
if (!dep) {
|
|
67
|
+
return { success: false, message: "Deployment not found" };
|
|
68
|
+
}
|
|
69
|
+
if (dep.status === "SUCCESS" || dep.status === "FAILED" || dep.status === "CANCELLED") {
|
|
70
|
+
return { success: false, message: `Deployment already finished with status: ${dep.status}` };
|
|
71
|
+
}
|
|
72
|
+
if (dep.status === "QUEUED") {
|
|
73
|
+
updateDeploymentStatus(id, "CANCELLED", {
|
|
74
|
+
finished_at: Date.now(),
|
|
75
|
+
total_duration_ms: 0,
|
|
76
|
+
});
|
|
77
|
+
appendDeploymentLog(id, "\n=== Deployment CANCELLED while in queue ===\n");
|
|
78
|
+
return { success: true, message: "Queued deployment cancelled successfully" };
|
|
79
|
+
}
|
|
80
|
+
if (dep.status === "RUNNING") {
|
|
81
|
+
// Set status to CANCELLED in DB first
|
|
82
|
+
updateDeploymentStatus(id, "CANCELLED", {
|
|
83
|
+
finished_at: Date.now(),
|
|
84
|
+
});
|
|
85
|
+
appendDeploymentLog(id, "\n=== Deployment CANCELLATION REQUESTED ===\n");
|
|
86
|
+
// Check if we have an active process to kill
|
|
87
|
+
const proc = activeProcesses.get(id);
|
|
88
|
+
if (proc) {
|
|
89
|
+
try {
|
|
90
|
+
proc.kill();
|
|
91
|
+
activeProcesses.delete(id);
|
|
92
|
+
return { success: true, message: "Running deployment cancelled and terminated" };
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
return { success: true, message: `Running deployment status set to CANCELLED, but failed to kill process: ${err.message}` };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { success: true, message: "Running deployment marked as CANCELLED" };
|
|
99
|
+
}
|
|
100
|
+
return { success: false, message: "Unknown status" };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/db.ts
DELETED
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
import Database from "better-sqlite3";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import { DB_PATH, ensureDirsExist } from "./paths.js";
|
|
4
|
-
import {
|
|
5
|
-
Project,
|
|
6
|
-
Webhook,
|
|
7
|
-
Deployment,
|
|
8
|
-
DeploymentStep,
|
|
9
|
-
DeploymentLog,
|
|
10
|
-
DeploymentStatus,
|
|
11
|
-
StepStatus,
|
|
12
|
-
} from "gitship-shared";
|
|
13
|
-
|
|
14
|
-
let dbInstance: Database.Database | null = null;
|
|
15
|
-
|
|
16
|
-
export function getDb(): Database.Database {
|
|
17
|
-
if (dbInstance) return dbInstance;
|
|
18
|
-
ensureDirsExist();
|
|
19
|
-
dbInstance = new Database(DB_PATH);
|
|
20
|
-
try {
|
|
21
|
-
fs.chmodSync(DB_PATH, 0o600);
|
|
22
|
-
} catch {}
|
|
23
|
-
dbInstance.pragma("journal_mode = WAL");
|
|
24
|
-
dbInstance.pragma("foreign_keys = ON");
|
|
25
|
-
initDb(dbInstance);
|
|
26
|
-
return dbInstance;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function initDb(db: Database.Database) {
|
|
30
|
-
db.exec(`
|
|
31
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
32
|
-
id TEXT PRIMARY KEY,
|
|
33
|
-
name TEXT UNIQUE NOT NULL,
|
|
34
|
-
owner TEXT NOT NULL,
|
|
35
|
-
repo TEXT NOT NULL,
|
|
36
|
-
branch TEXT NOT NULL,
|
|
37
|
-
target_type TEXT NOT NULL,
|
|
38
|
-
target_host TEXT,
|
|
39
|
-
target_path TEXT NOT NULL,
|
|
40
|
-
install_cmd TEXT,
|
|
41
|
-
build_cmd TEXT,
|
|
42
|
-
restart_cmd TEXT,
|
|
43
|
-
healthcheck_path TEXT,
|
|
44
|
-
healthcheck_port INTEGER,
|
|
45
|
-
healthcheck_retries INTEGER,
|
|
46
|
-
healthcheck_interval_ms INTEGER,
|
|
47
|
-
healthcheck_timeout_ms INTEGER,
|
|
48
|
-
webhook_secret TEXT NOT NULL,
|
|
49
|
-
created_at INTEGER NOT NULL,
|
|
50
|
-
updated_at INTEGER NOT NULL
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
CREATE TABLE IF NOT EXISTS webhooks (
|
|
54
|
-
id TEXT PRIMARY KEY,
|
|
55
|
-
project_id TEXT NOT NULL,
|
|
56
|
-
github_webhook_id INTEGER,
|
|
57
|
-
url TEXT NOT NULL,
|
|
58
|
-
secret TEXT NOT NULL,
|
|
59
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
60
|
-
created_at INTEGER NOT NULL,
|
|
61
|
-
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
CREATE TABLE IF NOT EXISTS deployments (
|
|
65
|
-
id TEXT PRIMARY KEY,
|
|
66
|
-
project_id TEXT NOT NULL,
|
|
67
|
-
branch TEXT NOT NULL,
|
|
68
|
-
commit_sha TEXT,
|
|
69
|
-
commit_message TEXT,
|
|
70
|
-
author TEXT,
|
|
71
|
-
status TEXT NOT NULL,
|
|
72
|
-
started_at INTEGER,
|
|
73
|
-
finished_at INTEGER,
|
|
74
|
-
total_duration_ms INTEGER,
|
|
75
|
-
rollback_of_id TEXT,
|
|
76
|
-
created_at INTEGER NOT NULL,
|
|
77
|
-
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
CREATE TABLE IF NOT EXISTS deployment_steps (
|
|
81
|
-
id TEXT PRIMARY KEY,
|
|
82
|
-
deployment_id TEXT NOT NULL,
|
|
83
|
-
step_name TEXT NOT NULL,
|
|
84
|
-
status TEXT NOT NULL,
|
|
85
|
-
started_at INTEGER,
|
|
86
|
-
finished_at INTEGER,
|
|
87
|
-
duration_ms INTEGER,
|
|
88
|
-
FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
CREATE TABLE IF NOT EXISTS deployment_logs (
|
|
92
|
-
deployment_id TEXT PRIMARY KEY,
|
|
93
|
-
log_data TEXT NOT NULL,
|
|
94
|
-
FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
98
|
-
id TEXT PRIMARY KEY,
|
|
99
|
-
created_at INTEGER NOT NULL
|
|
100
|
-
);
|
|
101
|
-
`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Project Repositories
|
|
105
|
-
export function addProject(project: Omit<Project, "created_at" | "updated_at">): Project {
|
|
106
|
-
const db = getDb();
|
|
107
|
-
const now = Date.now();
|
|
108
|
-
const fullProject = { ...project, created_at: now, updated_at: now };
|
|
109
|
-
const stmt = db.prepare(`
|
|
110
|
-
INSERT INTO projects (id, name, owner, repo, branch, target_type, target_host, target_path, install_cmd, build_cmd, restart_cmd, healthcheck_path, healthcheck_port, healthcheck_retries, healthcheck_interval_ms, healthcheck_timeout_ms, webhook_secret, created_at, updated_at)
|
|
111
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
112
|
-
ON CONFLICT(name) DO UPDATE SET
|
|
113
|
-
owner = excluded.owner,
|
|
114
|
-
repo = excluded.repo,
|
|
115
|
-
branch = excluded.branch,
|
|
116
|
-
target_type = excluded.target_type,
|
|
117
|
-
target_host = excluded.target_host,
|
|
118
|
-
target_path = excluded.target_path,
|
|
119
|
-
install_cmd = excluded.install_cmd,
|
|
120
|
-
build_cmd = excluded.build_cmd,
|
|
121
|
-
restart_cmd = excluded.restart_cmd,
|
|
122
|
-
healthcheck_path = excluded.healthcheck_path,
|
|
123
|
-
healthcheck_port = excluded.healthcheck_port,
|
|
124
|
-
healthcheck_retries = excluded.healthcheck_retries,
|
|
125
|
-
healthcheck_interval_ms = excluded.healthcheck_interval_ms,
|
|
126
|
-
healthcheck_timeout_ms = excluded.healthcheck_timeout_ms,
|
|
127
|
-
webhook_secret = excluded.webhook_secret,
|
|
128
|
-
updated_at = excluded.updated_at
|
|
129
|
-
`);
|
|
130
|
-
stmt.run(
|
|
131
|
-
fullProject.id,
|
|
132
|
-
fullProject.name,
|
|
133
|
-
fullProject.owner,
|
|
134
|
-
fullProject.repo,
|
|
135
|
-
fullProject.branch,
|
|
136
|
-
fullProject.target_type,
|
|
137
|
-
fullProject.target_host || null,
|
|
138
|
-
fullProject.target_path,
|
|
139
|
-
fullProject.install_cmd || null,
|
|
140
|
-
fullProject.build_cmd || null,
|
|
141
|
-
fullProject.restart_cmd || null,
|
|
142
|
-
fullProject.healthcheck_path || null,
|
|
143
|
-
fullProject.healthcheck_port || null,
|
|
144
|
-
fullProject.healthcheck_retries || null,
|
|
145
|
-
fullProject.healthcheck_interval_ms || null,
|
|
146
|
-
fullProject.healthcheck_timeout_ms || null,
|
|
147
|
-
fullProject.webhook_secret,
|
|
148
|
-
fullProject.created_at,
|
|
149
|
-
fullProject.updated_at
|
|
150
|
-
);
|
|
151
|
-
return fullProject;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function getProject(idOrName: string): Project | null {
|
|
155
|
-
const db = getDb();
|
|
156
|
-
const stmt = db.prepare("SELECT * FROM projects WHERE id = ? OR name = ?");
|
|
157
|
-
const res = stmt.get(idOrName, idOrName) as Project | undefined;
|
|
158
|
-
return res || null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function getProjects(): Project[] {
|
|
162
|
-
const db = getDb();
|
|
163
|
-
const stmt = db.prepare("SELECT * FROM projects ORDER BY name ASC");
|
|
164
|
-
return stmt.all() as Project[];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function removeProject(id: string): void {
|
|
168
|
-
const db = getDb();
|
|
169
|
-
const stmt = db.prepare("DELETE FROM projects WHERE id = ?");
|
|
170
|
-
stmt.run(id);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Webhook Repositories
|
|
174
|
-
export function saveWebhook(webhook: Webhook): void {
|
|
175
|
-
const db = getDb();
|
|
176
|
-
const stmt = db.prepare(`
|
|
177
|
-
INSERT INTO webhooks (id, project_id, github_webhook_id, url, secret, active, created_at)
|
|
178
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
179
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
180
|
-
github_webhook_id = excluded.github_webhook_id,
|
|
181
|
-
url = excluded.url,
|
|
182
|
-
secret = excluded.secret,
|
|
183
|
-
active = excluded.active
|
|
184
|
-
`);
|
|
185
|
-
stmt.run(
|
|
186
|
-
webhook.id,
|
|
187
|
-
webhook.project_id,
|
|
188
|
-
webhook.github_webhook_id,
|
|
189
|
-
webhook.url,
|
|
190
|
-
webhook.secret,
|
|
191
|
-
webhook.active ? 1 : 0,
|
|
192
|
-
webhook.created_at
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function getWebhookByProjectId(projectId: string): Webhook | null {
|
|
197
|
-
const db = getDb();
|
|
198
|
-
const stmt = db.prepare("SELECT * FROM webhooks WHERE project_id = ?");
|
|
199
|
-
const res = stmt.get(projectId) as any;
|
|
200
|
-
if (!res) return null;
|
|
201
|
-
return {
|
|
202
|
-
...res,
|
|
203
|
-
active: res.active === 1,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Deployment Repositories
|
|
208
|
-
export function createDeployment(deployment: Omit<Deployment, "created_at">): Deployment {
|
|
209
|
-
const db = getDb();
|
|
210
|
-
const now = Date.now();
|
|
211
|
-
const fullDeployment = { ...deployment, created_at: now };
|
|
212
|
-
const stmt = db.prepare(`
|
|
213
|
-
INSERT INTO deployments (id, project_id, branch, commit_sha, commit_message, author, status, started_at, finished_at, total_duration_ms, rollback_of_id, created_at)
|
|
214
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
215
|
-
`);
|
|
216
|
-
stmt.run(
|
|
217
|
-
fullDeployment.id,
|
|
218
|
-
fullDeployment.project_id,
|
|
219
|
-
fullDeployment.branch,
|
|
220
|
-
fullDeployment.commit_sha || null,
|
|
221
|
-
fullDeployment.commit_message || null,
|
|
222
|
-
fullDeployment.author || null,
|
|
223
|
-
fullDeployment.status,
|
|
224
|
-
fullDeployment.started_at || null,
|
|
225
|
-
fullDeployment.finished_at || null,
|
|
226
|
-
fullDeployment.total_duration_ms || null,
|
|
227
|
-
fullDeployment.rollback_of_id || null,
|
|
228
|
-
fullDeployment.created_at
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// Initialize empty logs for this deployment
|
|
232
|
-
const logStmt = db.prepare("INSERT INTO deployment_logs (deployment_id, log_data) VALUES (?, ?)");
|
|
233
|
-
logStmt.run(fullDeployment.id, "");
|
|
234
|
-
|
|
235
|
-
return fullDeployment;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function updateDeploymentStatus(
|
|
239
|
-
id: string,
|
|
240
|
-
status: DeploymentStatus,
|
|
241
|
-
fields: Partial<Deployment> = {}
|
|
242
|
-
): void {
|
|
243
|
-
const db = getDb();
|
|
244
|
-
const updates: string[] = ["status = ?"];
|
|
245
|
-
const params: any[] = [status];
|
|
246
|
-
|
|
247
|
-
for (const [key, val] of Object.entries(fields)) {
|
|
248
|
-
if (key !== "id" && key !== "status") {
|
|
249
|
-
updates.push(`${key} = ?`);
|
|
250
|
-
params.push(val);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
params.push(id);
|
|
255
|
-
const stmt = db.prepare(`UPDATE deployments SET ${updates.join(", ")} WHERE id = ?`);
|
|
256
|
-
stmt.run(...params);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function getDeployment(id: string): Deployment | null {
|
|
260
|
-
const db = getDb();
|
|
261
|
-
const stmt = db.prepare("SELECT * FROM deployments WHERE id = ?");
|
|
262
|
-
const res = stmt.get(id) as Deployment | undefined;
|
|
263
|
-
return res || null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function getDeployments(projectId?: string, limit?: number): Deployment[] {
|
|
267
|
-
const db = getDb();
|
|
268
|
-
let query = "SELECT * FROM deployments";
|
|
269
|
-
const params: any[] = [];
|
|
270
|
-
|
|
271
|
-
if (projectId) {
|
|
272
|
-
query += " WHERE project_id = ?";
|
|
273
|
-
params.push(projectId);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
query += " ORDER BY created_at DESC";
|
|
277
|
-
|
|
278
|
-
if (limit) {
|
|
279
|
-
query += " LIMIT ?";
|
|
280
|
-
params.push(limit);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const stmt = db.prepare(query);
|
|
284
|
-
return stmt.all(...params) as Deployment[];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function getQueuedDeployments(projectId: string): Deployment[] {
|
|
288
|
-
const db = getDb();
|
|
289
|
-
const stmt = db.prepare("SELECT * FROM deployments WHERE project_id = ? AND status = 'QUEUED' ORDER BY created_at ASC");
|
|
290
|
-
return stmt.all(projectId) as Deployment[];
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function getRunningDeployment(projectId: string): Deployment | null {
|
|
294
|
-
const db = getDb();
|
|
295
|
-
const stmt = db.prepare("SELECT * FROM deployments WHERE project_id = ? AND status = 'RUNNING'");
|
|
296
|
-
const res = stmt.get(projectId) as Deployment | undefined;
|
|
297
|
-
return res || null;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Steps Repositories
|
|
301
|
-
export function createDeploymentStep(step: DeploymentStep): void {
|
|
302
|
-
const db = getDb();
|
|
303
|
-
const stmt = db.prepare(`
|
|
304
|
-
INSERT INTO deployment_steps (id, deployment_id, step_name, status, started_at, finished_at, duration_ms)
|
|
305
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
306
|
-
`);
|
|
307
|
-
stmt.run(step.id, step.deployment_id, step.step_name, step.status, step.started_at, step.finished_at, step.duration_ms);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
export function updateDeploymentStep(id: string, fields: Partial<DeploymentStep>): void {
|
|
311
|
-
const db = getDb();
|
|
312
|
-
const updates: string[] = [];
|
|
313
|
-
const params: any[] = [];
|
|
314
|
-
|
|
315
|
-
for (const [key, val] of Object.entries(fields)) {
|
|
316
|
-
if (key !== "id") {
|
|
317
|
-
updates.push(`${key} = ?`);
|
|
318
|
-
params.push(val);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
params.push(id);
|
|
323
|
-
const stmt = db.prepare(`UPDATE deployment_steps SET ${updates.join(", ")} WHERE id = ?`);
|
|
324
|
-
stmt.run(...params);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export function getDeploymentSteps(deploymentId: string): DeploymentStep[] {
|
|
328
|
-
const db = getDb();
|
|
329
|
-
const stmt = db.prepare("SELECT * FROM deployment_steps WHERE deployment_id = ? ORDER BY started_at ASC");
|
|
330
|
-
return stmt.all(deploymentId) as DeploymentStep[];
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Log Repositories
|
|
334
|
-
export function appendDeploymentLog(deploymentId: string, text: string): void {
|
|
335
|
-
const db = getDb();
|
|
336
|
-
const stmt = db.prepare(`
|
|
337
|
-
UPDATE deployment_logs
|
|
338
|
-
SET log_data = log_data || ?
|
|
339
|
-
WHERE deployment_id = ?
|
|
340
|
-
`);
|
|
341
|
-
stmt.run(text, deploymentId);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export function getDeploymentLog(deploymentId: string): string | null {
|
|
345
|
-
const db = getDb();
|
|
346
|
-
const stmt = db.prepare("SELECT log_data FROM deployment_logs WHERE deployment_id = ?");
|
|
347
|
-
const res = stmt.get(deploymentId) as { log_data: string } | undefined;
|
|
348
|
-
return res ? res.log_data : null;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Stats Repository
|
|
352
|
-
export interface ProjectStats {
|
|
353
|
-
totalDeployments: number;
|
|
354
|
-
successRate: number;
|
|
355
|
-
avgDeployTimeMs: number;
|
|
356
|
-
avgBuildTimeMs: number;
|
|
357
|
-
slowestDeployMs: number;
|
|
358
|
-
fastestDeployMs: number;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export function getStats(projectId?: string): ProjectStats {
|
|
362
|
-
const db = getDb();
|
|
363
|
-
const filter = projectId ? "WHERE project_id = ?" : "WHERE 1=1";
|
|
364
|
-
const params = projectId ? [projectId] : [];
|
|
365
|
-
|
|
366
|
-
const totalRow = db.prepare(`SELECT count(*) as count FROM deployments ${filter}`).get(...params) as { count: number };
|
|
367
|
-
const total = totalRow.count;
|
|
368
|
-
|
|
369
|
-
if (total === 0) {
|
|
370
|
-
return {
|
|
371
|
-
totalDeployments: 0,
|
|
372
|
-
successRate: 0,
|
|
373
|
-
avgDeployTimeMs: 0,
|
|
374
|
-
avgBuildTimeMs: 0,
|
|
375
|
-
slowestDeployMs: 0,
|
|
376
|
-
fastestDeployMs: 0,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const successRow = db.prepare(`SELECT count(*) as count FROM deployments ${filter} AND status = 'SUCCESS'`).get(...params) as { count: number };
|
|
381
|
-
const successRate = total > 0 ? (successRow.count / total) * 100 : 0;
|
|
382
|
-
|
|
383
|
-
const durationRow = db.prepare(`
|
|
384
|
-
SELECT
|
|
385
|
-
avg(total_duration_ms) as avg_duration,
|
|
386
|
-
max(total_duration_ms) as max_duration,
|
|
387
|
-
min(total_duration_ms) as min_duration
|
|
388
|
-
FROM deployments
|
|
389
|
-
${filter} AND status = 'SUCCESS'
|
|
390
|
-
`).get(...params) as { avg_duration: number | null; max_duration: number | null; min_duration: number | null };
|
|
391
|
-
|
|
392
|
-
const buildDurationRow = db.prepare(`
|
|
393
|
-
SELECT avg(ds.duration_ms) as avg_build
|
|
394
|
-
FROM deployment_steps ds
|
|
395
|
-
JOIN deployments d ON ds.deployment_id = d.id
|
|
396
|
-
${projectId ? "WHERE d.project_id = ?" : "WHERE 1=1"} AND ds.step_name = 'build' AND ds.status = 'SUCCESS'
|
|
397
|
-
`).get(projectId ? [projectId] : []) as { avg_build: number | null };
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
totalDeployments: total,
|
|
401
|
-
successRate: parseFloat(successRate.toFixed(1)),
|
|
402
|
-
avgDeployTimeMs: Math.round(durationRow.avg_duration || 0),
|
|
403
|
-
avgBuildTimeMs: Math.round(buildDurationRow.avg_build || 0),
|
|
404
|
-
slowestDeployMs: durationRow.max_duration || 0,
|
|
405
|
-
fastestDeployMs: durationRow.min_duration || 0,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export function isWebhookDeliveryProcessed(id: string): boolean {
|
|
410
|
-
const db = getDb();
|
|
411
|
-
const stmt = db.prepare("SELECT count(*) as count FROM webhook_deliveries WHERE id = ?");
|
|
412
|
-
const res = stmt.get(id) as { count: number };
|
|
413
|
-
return res.count > 0;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export function recordWebhookDelivery(id: string): void {
|
|
417
|
-
const db = getDb();
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
const insertStmt = db.prepare("INSERT OR IGNORE INTO webhook_deliveries (id, created_at) VALUES (?, ?)");
|
|
420
|
-
insertStmt.run(id, now);
|
|
421
|
-
|
|
422
|
-
// Prune older than 24 hours (86,400,000 ms)
|
|
423
|
-
const pruneStmt = db.prepare("DELETE FROM webhook_deliveries WHERE created_at < ?");
|
|
424
|
-
pruneStmt.run(now - 86400000);
|
|
425
|
-
}
|