@adriandmitroca/relay 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/cli.ts +93 -10
- package/src/config.ts +0 -28
- package/src/daemon.ts +31 -90
- package/src/db.ts +0 -54
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Turn your backlog into pull requests — automatically.**
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
Relay connects to your existing project management tools, picks up tasks, and uses Claude Code to implement them in isolated git worktrees. You review the diff, click Accept, and a PR appears. That's it.
|
|
6
8
|
|
|
7
9
|
No API keys to manage. No per-token billing. No workflow files to write. Relay runs locally on your machine using your Claude Code subscription (Pro, Max, or Team) and plugs directly into the tools you already use.
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Daemon } from "./daemon.ts";
|
|
4
4
|
import { setLogLevel } from "./utils/logger.ts";
|
|
5
|
+
import { IssueDB, ConfigDB } from "./db.ts";
|
|
5
6
|
import { join, dirname } from "node:path";
|
|
6
7
|
import { unlinkSync } from "node:fs";
|
|
7
8
|
import pkg from "../package.json" with { type: "json" };
|
|
@@ -21,8 +22,8 @@ function getFlag(name: string): string | undefined {
|
|
|
21
22
|
return undefined;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
-
return
|
|
25
|
+
function dataDir(): string {
|
|
26
|
+
return join(process.env.HOME ?? "~", ".relay");
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function getCommand(): string {
|
|
@@ -52,8 +53,10 @@ async function checkForUpdates() {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
async function cmdStart() {
|
|
55
|
-
const
|
|
56
|
-
|
|
56
|
+
const dir = dataDir();
|
|
57
|
+
await Bun.$`mkdir -p ${dir}`.quiet();
|
|
58
|
+
const pidFile = join(dir, "relay.pid");
|
|
59
|
+
const dbPath = join(dir, "sqlite.db");
|
|
57
60
|
const logLevel = getFlag("log-level");
|
|
58
61
|
if (logLevel) setLogLevel(logLevel as "debug" | "info" | "warn" | "error");
|
|
59
62
|
|
|
@@ -74,7 +77,7 @@ async function cmdStart() {
|
|
|
74
77
|
// Silent update check in background
|
|
75
78
|
checkForUpdates().catch(() => {});
|
|
76
79
|
|
|
77
|
-
const daemon = new Daemon(
|
|
80
|
+
const daemon = new Daemon(dbPath);
|
|
78
81
|
|
|
79
82
|
const cleanup = () => {
|
|
80
83
|
try { unlinkSync(pidFile); } catch {}
|
|
@@ -91,7 +94,7 @@ async function cmdStart() {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
async function cmdStop() {
|
|
94
|
-
const pidFile = join(
|
|
97
|
+
const pidFile = join(dataDir(), "relay.pid");
|
|
95
98
|
const pid = Bun.file(pidFile);
|
|
96
99
|
if (!(await pid.exists())) {
|
|
97
100
|
console.log(`${YELLOW}No running daemon found.${RESET}`);
|
|
@@ -139,6 +142,83 @@ async function cmdUpdate() {
|
|
|
139
142
|
}
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
async function cmdDoctor() {
|
|
146
|
+
let failures = 0;
|
|
147
|
+
|
|
148
|
+
function ok(msg: string) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
|
|
149
|
+
function fail(msg: string, hint?: string) {
|
|
150
|
+
console.log(` ${RED}✗${RESET} ${msg}`);
|
|
151
|
+
if (hint) console.log(` ${DIM}${hint}${RESET}`);
|
|
152
|
+
failures++;
|
|
153
|
+
}
|
|
154
|
+
function warn(msg: string, hint?: string) {
|
|
155
|
+
console.log(` ${YELLOW}⚠${RESET} ${msg}`);
|
|
156
|
+
if (hint) console.log(` ${DIM}${hint}${RESET}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`\n${BOLD}relay doctor${RESET} — checking your setup\n`);
|
|
160
|
+
|
|
161
|
+
// 1. Bun
|
|
162
|
+
const bun = await Bun.$`bun --version`.quiet().nothrow();
|
|
163
|
+
if (bun.exitCode === 0) ok(`Bun ${bun.stdout.toString().trim()}`);
|
|
164
|
+
else fail("Bun not found", "Install at https://bun.sh");
|
|
165
|
+
|
|
166
|
+
// 2. Claude CLI
|
|
167
|
+
const claude = await Bun.$`claude --version`.quiet().nothrow();
|
|
168
|
+
if (claude.exitCode === 0) ok(`claude ${claude.stdout.toString().trim()}`);
|
|
169
|
+
else fail("claude CLI not found", "Install Claude Code: https://claude.ai/code");
|
|
170
|
+
|
|
171
|
+
// 3. GitHub CLI
|
|
172
|
+
const gh = await Bun.$`gh auth status`.quiet().nothrow();
|
|
173
|
+
if (gh.exitCode === 0) {
|
|
174
|
+
const line = gh.stderr.toString().split("\n").find(l => l.includes("Logged in"));
|
|
175
|
+
ok(`gh — ${line?.trim() ?? "authenticated"}`);
|
|
176
|
+
} else {
|
|
177
|
+
fail("gh not authenticated", "Run: gh auth login");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 4. Data directory
|
|
181
|
+
const dir = dataDir();
|
|
182
|
+
const dirStat = await Bun.$`test -d ${dir}`.quiet().nothrow();
|
|
183
|
+
if (dirStat.exitCode === 0) ok(`~/.relay/ exists`);
|
|
184
|
+
else warn("~/.relay/ not found", "Will be created automatically on first start");
|
|
185
|
+
|
|
186
|
+
// 5. Database + config
|
|
187
|
+
const dbPath = join(dir, "sqlite.db");
|
|
188
|
+
const dbExists = await Bun.file(dbPath).exists();
|
|
189
|
+
if (dbExists) {
|
|
190
|
+
try {
|
|
191
|
+
const db = new IssueDB(dbPath);
|
|
192
|
+
const configDB = new ConfigDB(db.getDatabase());
|
|
193
|
+
if (configDB.hasConfig()) {
|
|
194
|
+
const workspaces = configDB.getWorkspaces();
|
|
195
|
+
const totalProjects = workspaces.reduce((n, ws) => n + configDB.getProjects(ws.id).length, 0);
|
|
196
|
+
ok(`database — ${workspaces.length} workspace${workspaces.length !== 1 ? "s" : ""}, ${totalProjects} project${totalProjects !== 1 ? "s" : ""}`);
|
|
197
|
+
|
|
198
|
+
// 6. Check repo paths for each project
|
|
199
|
+
for (const ws of workspaces) {
|
|
200
|
+
for (const proj of configDB.getProjects(ws.id)) {
|
|
201
|
+
const repoStat = await Bun.$`test -d ${join(proj.repo_path, ".git")}`.quiet().nothrow();
|
|
202
|
+
if (repoStat.exitCode === 0) ok(`${proj.key} — repo path valid`);
|
|
203
|
+
else fail(`${proj.key} — repo path not found: ${proj.repo_path}`, "Update the path in Settings");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
warn("database exists but no config", "Open http://localhost:7842/setup to configure");
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
fail("database exists but could not be read", "Try deleting ~/.relay/sqlite.db and reconfiguring");
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
warn("no database yet", "Run 'relay start' to create it");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log();
|
|
217
|
+
if (failures === 0) console.log(`${GREEN}All checks passed.${RESET}\n`);
|
|
218
|
+
else console.log(`${RED}${failures} check${failures !== 1 ? "s" : ""} failed.${RESET}\n`);
|
|
219
|
+
if (failures > 0) process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
142
222
|
function showHelp() {
|
|
143
223
|
console.log(`
|
|
144
224
|
${BOLD}Relay${RESET} v${VERSION} — Autonomous SWE agent
|
|
@@ -146,18 +226,18 @@ ${BOLD}Relay${RESET} v${VERSION} — Autonomous SWE agent
|
|
|
146
226
|
${BOLD}Usage:${RESET}
|
|
147
227
|
relay start Start the daemon (opens dashboard at localhost:7842)
|
|
148
228
|
relay stop Stop the daemon
|
|
229
|
+
relay doctor Check your setup for problems
|
|
149
230
|
relay update Update to the latest version
|
|
150
231
|
relay version Show version
|
|
151
232
|
relay help Show this help
|
|
152
233
|
|
|
153
234
|
${BOLD}Options:${RESET}
|
|
154
|
-
--config <path> Path to config.json (default: ./config.json)
|
|
155
235
|
--log-level <level> Override log level (debug/info/warn/error)
|
|
156
236
|
|
|
157
237
|
${BOLD}Getting started:${RESET}
|
|
158
|
-
bunx
|
|
159
|
-
bun install -g
|
|
160
|
-
relay start
|
|
238
|
+
bunx @adriandmitroca/relay Try without installing
|
|
239
|
+
bun install -g @adriandmitroca/relay Install globally
|
|
240
|
+
relay start Start — configure via the setup wizard at localhost:7842
|
|
161
241
|
`);
|
|
162
242
|
}
|
|
163
243
|
|
|
@@ -171,6 +251,9 @@ switch (cmd) {
|
|
|
171
251
|
case "stop":
|
|
172
252
|
await cmdStop();
|
|
173
253
|
break;
|
|
254
|
+
case "doctor":
|
|
255
|
+
await cmdDoctor();
|
|
256
|
+
break;
|
|
174
257
|
case "update":
|
|
175
258
|
await cmdUpdate();
|
|
176
259
|
break;
|
package/src/config.ts
CHANGED
|
@@ -78,34 +78,6 @@ const DEFAULTS = {
|
|
|
78
78
|
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
export async function loadConfig(configPath: string): Promise<Config> {
|
|
82
|
-
const file = Bun.file(configPath);
|
|
83
|
-
if (!(await file.exists())) {
|
|
84
|
-
throw new Error(`Config not found at ${configPath}. Run 'relay init' to create one.`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let raw: Record<string, unknown>;
|
|
88
|
-
try {
|
|
89
|
-
raw = await file.json();
|
|
90
|
-
} catch {
|
|
91
|
-
throw new Error(`Config at ${configPath} is not valid JSON. Check for syntax errors.`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const config: Config = {
|
|
95
|
-
workspaces: (raw.workspaces as WorkspaceConfig[]) ?? [],
|
|
96
|
-
pollIntervalSeconds: (raw.pollIntervalSeconds as number) ?? DEFAULTS.pollIntervalSeconds,
|
|
97
|
-
maxConcurrency: (raw.maxConcurrency as number) ?? DEFAULTS.maxConcurrency,
|
|
98
|
-
claudeTimeout: (raw.claudeTimeout as number) ?? DEFAULTS.claudeTimeout,
|
|
99
|
-
triageTimeout: (raw.triageTimeout as number) ?? DEFAULTS.triageTimeout,
|
|
100
|
-
triage: (raw.triage as boolean) ?? DEFAULTS.triage,
|
|
101
|
-
logLevel: (raw.logLevel as Config["logLevel"]) ?? DEFAULTS.logLevel,
|
|
102
|
-
allowedTools: (raw.allowedTools as string[]) ?? DEFAULTS.allowedTools,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
validate(config);
|
|
106
|
-
return config;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
81
|
export function findProjectConfig(config: Config, projectKey: string): { workspace: WorkspaceConfig; project: ProjectConfig } | null {
|
|
110
82
|
for (const ws of config.workspaces) {
|
|
111
83
|
const project = ws.projects.find((p) => p.key === projectKey);
|
package/src/daemon.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { allProjects, findProjectConfig, type Config, type ProjectConfig, type WorkspaceConfig } from "./config.ts";
|
|
2
2
|
import { IssueDB, ConfigDB, type IssueRow, type IssueStatus } from "./db.ts";
|
|
3
3
|
import { PriorityQueue } from "./queue.ts";
|
|
4
4
|
import { SentryAdapter } from "./sources/sentry.ts";
|
|
@@ -44,33 +44,19 @@ export class Daemon {
|
|
|
44
44
|
private queue!: PriorityQueue<PipelineItem>;
|
|
45
45
|
private workspaces: Map<string, WorkspaceRuntime> = new Map();
|
|
46
46
|
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
47
|
-
private configWatcher: ReturnType<typeof import("node:fs").watch> | null = null;
|
|
48
47
|
private stopping = false;
|
|
49
|
-
private
|
|
48
|
+
private dbPath: string;
|
|
50
49
|
private dashboard!: DashboardServer;
|
|
51
|
-
private baseDir: string;
|
|
52
50
|
|
|
53
|
-
constructor(
|
|
54
|
-
this.
|
|
55
|
-
this.baseDir = dirname(configPath);
|
|
51
|
+
constructor(dbPath: string) {
|
|
52
|
+
this.dbPath = dbPath;
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
async start() {
|
|
59
56
|
// Init DB
|
|
60
|
-
|
|
61
|
-
this.db = new IssueDB(dbPath);
|
|
57
|
+
this.db = new IssueDB(this.dbPath);
|
|
62
58
|
this.configDB = new ConfigDB(this.db.getDatabase());
|
|
63
59
|
|
|
64
|
-
// Auto-import config.json if DB has no config
|
|
65
|
-
if (!this.configDB.hasConfig()) {
|
|
66
|
-
const configFile = Bun.file(this.configPath);
|
|
67
|
-
if (await configFile.exists()) {
|
|
68
|
-
const jsonConfig = await loadConfig(this.configPath);
|
|
69
|
-
this.configDB.importFromJson(jsonConfig);
|
|
70
|
-
logger.info("Auto-imported config.json into database");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
// Start dashboard (always — even without config for setup wizard)
|
|
75
61
|
const isDev = process.env.DEV === "1";
|
|
76
62
|
// Resolve dist/ relative to this source file so it works both in-repo and when
|
|
@@ -129,9 +115,6 @@ export class Daemon {
|
|
|
129
115
|
// Start poll interval
|
|
130
116
|
this.pollTimer = setInterval(() => this.poll(), this.config.pollIntervalSeconds * 1000);
|
|
131
117
|
|
|
132
|
-
// Watch config.json for hot reload (legacy support)
|
|
133
|
-
this.watchConfig();
|
|
134
|
-
|
|
135
118
|
// Setup shutdown handlers
|
|
136
119
|
this.setupShutdownHandlers();
|
|
137
120
|
|
|
@@ -223,11 +206,6 @@ export class Daemon {
|
|
|
223
206
|
this.stopping = true;
|
|
224
207
|
logger.info("Shutting down...");
|
|
225
208
|
|
|
226
|
-
if (this.configWatcher) {
|
|
227
|
-
this.configWatcher.close();
|
|
228
|
-
this.configWatcher = null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
209
|
if (this.pollTimer) {
|
|
232
210
|
clearInterval(this.pollTimer);
|
|
233
211
|
this.pollTimer = null;
|
|
@@ -330,58 +308,6 @@ export class Daemon {
|
|
|
330
308
|
}
|
|
331
309
|
}
|
|
332
310
|
|
|
333
|
-
private watchConfig() {
|
|
334
|
-
const { watch } = require("node:fs") as typeof import("node:fs");
|
|
335
|
-
let debounce: ReturnType<typeof setTimeout> | null = null;
|
|
336
|
-
|
|
337
|
-
this.configWatcher = watch(this.configPath, () => {
|
|
338
|
-
if (debounce) clearTimeout(debounce);
|
|
339
|
-
debounce = setTimeout(() => this.reloadConfig(), 500);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
logger.debug("Watching config for changes", { path: this.configPath });
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private async reloadConfig() {
|
|
346
|
-
try {
|
|
347
|
-
const newConfig = await loadConfig(this.configPath);
|
|
348
|
-
|
|
349
|
-
// Restart poll timer if interval changed
|
|
350
|
-
if (newConfig.pollIntervalSeconds !== this.config.pollIntervalSeconds) {
|
|
351
|
-
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
352
|
-
this.pollTimer = setInterval(() => this.poll(), newConfig.pollIntervalSeconds * 1000);
|
|
353
|
-
logger.info("Poll interval updated", { seconds: newConfig.pollIntervalSeconds });
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (newConfig.logLevel !== this.config.logLevel) {
|
|
357
|
-
setLogLevel(newConfig.logLevel);
|
|
358
|
-
logger.info("Log level updated", { level: newConfig.logLevel });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Reinit workspaces if workspace config changed
|
|
362
|
-
const oldWsJson = JSON.stringify(this.config.workspaces);
|
|
363
|
-
const newWsJson = JSON.stringify(newConfig.workspaces);
|
|
364
|
-
if (oldWsJson !== newWsJson) {
|
|
365
|
-
for (const ws of this.workspaces.values()) {
|
|
366
|
-
ws.telegram?.stopPolling();
|
|
367
|
-
}
|
|
368
|
-
this.workspaces.clear();
|
|
369
|
-
this.config = newConfig;
|
|
370
|
-
this.initWorkspaces();
|
|
371
|
-
logger.info("Workspaces reinitialized");
|
|
372
|
-
} else {
|
|
373
|
-
this.config = newConfig;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
logger.info("Config reloaded");
|
|
377
|
-
|
|
378
|
-
// Trigger immediate poll after reload
|
|
379
|
-
await this.poll();
|
|
380
|
-
} catch (err) {
|
|
381
|
-
logger.error("Config reload failed, keeping current config", { error: String(err) });
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
311
|
private async poll() {
|
|
386
312
|
if (this.stopping) return;
|
|
387
313
|
|
|
@@ -1066,31 +992,46 @@ export class Daemon {
|
|
|
1066
992
|
function buildPRBody(row: IssueRow): string {
|
|
1067
993
|
const lines: string[] = [];
|
|
1068
994
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
995
|
+
// Summary
|
|
996
|
+
lines.push("## Summary");
|
|
997
|
+
if (row.fixSummary) {
|
|
998
|
+
lines.push(row.fixSummary);
|
|
999
|
+
} else {
|
|
1000
|
+
lines.push(`Automated fix for: ${row.title}`);
|
|
1001
|
+
}
|
|
1002
|
+
lines.push("");
|
|
1003
|
+
|
|
1004
|
+
// Context — triage plan if available
|
|
1005
|
+
if (row.triagePlan) {
|
|
1006
|
+
lines.push("## Context");
|
|
1007
|
+
lines.push(row.triagePlan);
|
|
1072
1008
|
lines.push("");
|
|
1073
1009
|
}
|
|
1074
1010
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1011
|
+
// Source link
|
|
1012
|
+
if (row.externalUrl) {
|
|
1013
|
+
const sourceLabel = row.source.charAt(0).toUpperCase() + row.source.slice(1);
|
|
1014
|
+
lines.push(`**${sourceLabel}:** ${row.externalUrl}`);
|
|
1015
|
+
lines.push("");
|
|
1016
|
+
}
|
|
1078
1017
|
|
|
1018
|
+
// Test plan
|
|
1079
1019
|
lines.push("## Test plan");
|
|
1080
1020
|
lines.push("- [ ] Verify the changes resolve the issue");
|
|
1081
|
-
lines.push("- [ ]
|
|
1082
|
-
lines.push("- [ ] Manual QA");
|
|
1021
|
+
lines.push("- [ ] Existing tests pass");
|
|
1022
|
+
lines.push("- [ ] Manual QA if applicable");
|
|
1083
1023
|
lines.push("");
|
|
1084
1024
|
|
|
1025
|
+
// Signature
|
|
1085
1026
|
lines.push("---");
|
|
1086
|
-
lines.push("🤖 Generated with [
|
|
1027
|
+
lines.push("🤖 Generated with [Relay](https://github.com/adriandmitroca/relay) using [Claude Code](https://claude.com/claude-code)");
|
|
1087
1028
|
|
|
1088
1029
|
return lines.join("\n");
|
|
1089
1030
|
}
|
|
1090
1031
|
|
|
1091
1032
|
// When run directly
|
|
1092
1033
|
if (import.meta.main) {
|
|
1093
|
-
const
|
|
1094
|
-
const daemon = new Daemon(
|
|
1034
|
+
const dbPath = process.argv[2] || join(process.env.HOME ?? "~", ".relay", "sqlite.db");
|
|
1035
|
+
const daemon = new Daemon(dbPath);
|
|
1095
1036
|
await daemon.start();
|
|
1096
1037
|
}
|
package/src/db.ts
CHANGED
|
@@ -615,60 +615,6 @@ export class ConfigDB {
|
|
|
615
615
|
return row.count > 0;
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
-
importFromJson(config: Config): void {
|
|
619
|
-
this.db.exec("BEGIN TRANSACTION");
|
|
620
|
-
try {
|
|
621
|
-
// Import settings
|
|
622
|
-
const settingsMap: Record<string, string> = {
|
|
623
|
-
pollIntervalSeconds: String(config.pollIntervalSeconds),
|
|
624
|
-
maxConcurrency: String(config.maxConcurrency),
|
|
625
|
-
claudeTimeout: String(config.claudeTimeout),
|
|
626
|
-
triageTimeout: String(config.triageTimeout),
|
|
627
|
-
triage: String(config.triage),
|
|
628
|
-
logLevel: config.logLevel,
|
|
629
|
-
allowedTools: JSON.stringify(config.allowedTools),
|
|
630
|
-
};
|
|
631
|
-
this.updateSettings(settingsMap);
|
|
632
|
-
|
|
633
|
-
// Import workspaces
|
|
634
|
-
for (const ws of config.workspaces) {
|
|
635
|
-
const wsRow = this.createWorkspace(ws.key);
|
|
636
|
-
|
|
637
|
-
if (ws.telegram) {
|
|
638
|
-
this.upsertTelegramConfig(wsRow.id, ws.telegram.botToken, ws.telegram.chatId);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
for (const proj of ws.projects) {
|
|
642
|
-
const projRow = this.createProject(wsRow.id, {
|
|
643
|
-
key: proj.key,
|
|
644
|
-
repoPath: proj.repoPath,
|
|
645
|
-
baseBranch: proj.baseBranch,
|
|
646
|
-
testCommand: proj.testCommand,
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
if (proj.sources.sentry) {
|
|
650
|
-
this.upsertSourceConfig(projRow.id, "sentry", proj.sources.sentry as unknown as Record<string, unknown>);
|
|
651
|
-
}
|
|
652
|
-
if (proj.sources.asana) {
|
|
653
|
-
this.upsertSourceConfig(projRow.id, "asana", proj.sources.asana as unknown as Record<string, unknown>);
|
|
654
|
-
}
|
|
655
|
-
if (proj.sources.linear) {
|
|
656
|
-
this.upsertSourceConfig(projRow.id, "linear", proj.sources.linear as unknown as Record<string, unknown>);
|
|
657
|
-
}
|
|
658
|
-
if (proj.sources.jira) {
|
|
659
|
-
this.upsertSourceConfig(projRow.id, "jira", proj.sources.jira as unknown as Record<string, unknown>);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
this.db.exec("COMMIT");
|
|
665
|
-
logger.info("Imported config from JSON to DB", { workspaces: config.workspaces.length });
|
|
666
|
-
} catch (err) {
|
|
667
|
-
this.db.exec("ROLLBACK");
|
|
668
|
-
throw err;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
618
|
toConfig(): Config {
|
|
673
619
|
const settings = this.getSettings();
|
|
674
620
|
const workspaces: WorkspaceConfig[] = [];
|