buddy-workbench 0.1.19 → 0.1.21
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/bin/devbuddy.js +195 -0
- package/package.json +5 -3
- package/server/config.js +1 -0
- package/server/repositories/branch-sync.js +56 -0
- package/server/routes/branch-sync.js +464 -0
- package/server/routes/file-organizer.js +3 -2
- package/server/services/file-organizer.js +67 -1
- package/server.js +2 -0
- package/ui/dist/assets/index-kLUg9ehS.js +530 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-jQB3dI9Z.js +0 -524
package/bin/devbuddy.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync, spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const root = dirname(__dirname);
|
|
11
|
+
|
|
12
|
+
const dataDir = join(root, 'data');
|
|
13
|
+
const pidFile = join(dataDir, 'devbuddy.pid');
|
|
14
|
+
const logFile = join(dataDir, 'devbuddy.log');
|
|
15
|
+
const serverJs = join(root, 'server.js');
|
|
16
|
+
|
|
17
|
+
function isProcessRunning(pid) {
|
|
18
|
+
try {
|
|
19
|
+
process.kill(pid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
return err.code === 'EPERM';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getRunningPid() {
|
|
27
|
+
if (!existsSync(pidFile)) return null;
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(pidFile, 'utf8').trim();
|
|
30
|
+
const pid = parseInt(content, 10);
|
|
31
|
+
if (!isNaN(pid) && isProcessRunning(pid)) {
|
|
32
|
+
return pid;
|
|
33
|
+
}
|
|
34
|
+
// Stale PID file
|
|
35
|
+
try { unlinkSync(pidFile); } catch {}
|
|
36
|
+
return null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sendSignal(pid, signal) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, signal);
|
|
45
|
+
return true;
|
|
46
|
+
} catch (err1) {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(-pid, signal);
|
|
49
|
+
return true;
|
|
50
|
+
} catch (err2) {
|
|
51
|
+
try {
|
|
52
|
+
execSync(`kill -${signal === 'SIGTERM' ? '15' : '9'} ${pid}`);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function start() {
|
|
62
|
+
const existingPid = getRunningPid();
|
|
63
|
+
if (existingPid) {
|
|
64
|
+
console.log(`DevBuddy is already running (PID ${existingPid}).`);
|
|
65
|
+
console.log(`URL: http://localhost:${process.env.PORT || 3100}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
mkdirSync(dataDir, { recursive: true });
|
|
70
|
+
|
|
71
|
+
const out = openSync(logFile, 'a');
|
|
72
|
+
const err = openSync(logFile, 'a');
|
|
73
|
+
|
|
74
|
+
const child = spawn(process.execPath, [serverJs], {
|
|
75
|
+
detached: true,
|
|
76
|
+
stdio: ['ignore', out, err],
|
|
77
|
+
cwd: root,
|
|
78
|
+
env: { ...process.env }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!child.pid) {
|
|
82
|
+
console.error('Error: Failed to spawn DevBuddy process.');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeFileSync(pidFile, String(child.pid));
|
|
87
|
+
child.unref();
|
|
88
|
+
|
|
89
|
+
// Wait briefly to confirm it didn't immediately crash
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
91
|
+
|
|
92
|
+
if (isProcessRunning(child.pid)) {
|
|
93
|
+
const port = process.env.PORT || 3100;
|
|
94
|
+
console.log(`DevBuddy started successfully (PID ${child.pid}).`);
|
|
95
|
+
console.log(`URL: http://localhost:${port}`);
|
|
96
|
+
console.log(`Logs: ${logFile}`);
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`DevBuddy failed to start. Check logs at: ${logFile}`);
|
|
99
|
+
try { unlinkSync(pidFile); } catch {}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function stop() {
|
|
105
|
+
const pid = getRunningPid();
|
|
106
|
+
if (!pid) {
|
|
107
|
+
console.log('DevBuddy is not running.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`Stopping DevBuddy (PID ${pid})…`);
|
|
112
|
+
sendSignal(pid, 'SIGTERM');
|
|
113
|
+
|
|
114
|
+
// Poll for process termination up to 5 seconds
|
|
115
|
+
const maxWait = 5000;
|
|
116
|
+
const interval = 100;
|
|
117
|
+
let elapsed = 0;
|
|
118
|
+
|
|
119
|
+
while (elapsed < maxWait) {
|
|
120
|
+
if (!isProcessRunning(pid)) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
124
|
+
elapsed += interval;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (isProcessRunning(pid)) {
|
|
128
|
+
console.log(`Process ${pid} did not exit in time. Force killing…`);
|
|
129
|
+
sendSignal(pid, 'SIGKILL');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try { unlinkSync(pidFile); } catch {}
|
|
133
|
+
console.log('DevBuddy stopped.');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function status() {
|
|
137
|
+
const pid = getRunningPid();
|
|
138
|
+
if (pid) {
|
|
139
|
+
const port = process.env.PORT || 3100;
|
|
140
|
+
console.log(`DevBuddy is running (PID ${pid}).`);
|
|
141
|
+
console.log(`URL: http://localhost:${port}`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log('DevBuddy is stopped.');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function help() {
|
|
148
|
+
console.log(`
|
|
149
|
+
DevBuddy CLI
|
|
150
|
+
|
|
151
|
+
Usage:
|
|
152
|
+
devbuddy <command>
|
|
153
|
+
|
|
154
|
+
Commands:
|
|
155
|
+
start Start DevBuddy in the background
|
|
156
|
+
stop Stop the running DevBuddy instance
|
|
157
|
+
status Check the status of DevBuddy
|
|
158
|
+
restart Restart DevBuddy
|
|
159
|
+
help Display this help message
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function main() {
|
|
164
|
+
const command = process.argv[2] || 'help';
|
|
165
|
+
|
|
166
|
+
switch (command.toLowerCase()) {
|
|
167
|
+
case 'start':
|
|
168
|
+
await start();
|
|
169
|
+
break;
|
|
170
|
+
case 'stop':
|
|
171
|
+
await stop();
|
|
172
|
+
break;
|
|
173
|
+
case 'status':
|
|
174
|
+
status();
|
|
175
|
+
break;
|
|
176
|
+
case 'restart':
|
|
177
|
+
await stop();
|
|
178
|
+
await start();
|
|
179
|
+
break;
|
|
180
|
+
case 'help':
|
|
181
|
+
case '-h':
|
|
182
|
+
case '--help':
|
|
183
|
+
help();
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
console.log(`Unknown command: ${command}`);
|
|
187
|
+
help();
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main().catch((err) => {
|
|
193
|
+
console.error('Unexpected error:', err);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buddy-workbench",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"devbuddy": "./bin/devbuddy.js",
|
|
8
|
+
"buddy-workbench": "./bin/devbuddy.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"server.js",
|
|
11
12
|
"server/",
|
|
12
13
|
"ui/dist/",
|
|
13
14
|
"plugins/",
|
|
14
|
-
"pages/"
|
|
15
|
+
"pages/",
|
|
16
|
+
"bin/"
|
|
15
17
|
],
|
|
16
18
|
"scripts": {
|
|
17
19
|
"start": "exec node server.js",
|
package/server/config.js
CHANGED
|
@@ -12,6 +12,7 @@ export const paths = {
|
|
|
12
12
|
staticPages: join(root, 'data', 'static-pages.json'),
|
|
13
13
|
errors: join(root, 'data', 'errors.json'),
|
|
14
14
|
postman: join(root, 'data', 'postman.json'),
|
|
15
|
+
branchSync: join(root, 'data', 'branch-sync.json'),
|
|
15
16
|
shutdownLog: join(root, 'data', 'shutdown.log'),
|
|
16
17
|
clipboardDir: join(root, 'data', 'clipboard'),
|
|
17
18
|
plugins: join(root, 'plugins'),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { paths } from '../config.js';
|
|
4
|
+
|
|
5
|
+
export function listBranchSyncApps() {
|
|
6
|
+
try {
|
|
7
|
+
return existsSync(paths.branchSync) ? JSON.parse(readFileSync(paths.branchSync, 'utf8')) : [];
|
|
8
|
+
} catch {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function saveBranchSyncApps(apps) {
|
|
14
|
+
mkdirSync(dirname(paths.branchSync), { recursive: true });
|
|
15
|
+
writeFileSync(paths.branchSync, JSON.stringify(apps, null, 2));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createBranchSyncApp(data) {
|
|
19
|
+
const apps = listBranchSyncApps();
|
|
20
|
+
const newApp = {
|
|
21
|
+
id: `app-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
22
|
+
name: data.name || '',
|
|
23
|
+
repo: data.repo || '',
|
|
24
|
+
activeBranch: data.activeBranch || '',
|
|
25
|
+
lastReleaseBranch: data.lastReleaseBranch || '',
|
|
26
|
+
subApps: Array.isArray(data.subApps) ? data.subApps : [],
|
|
27
|
+
createdAt: new Date().toISOString()
|
|
28
|
+
};
|
|
29
|
+
apps.push(newApp);
|
|
30
|
+
saveBranchSyncApps(apps);
|
|
31
|
+
return newApp;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function updateBranchSyncApp(id, data) {
|
|
35
|
+
const apps = listBranchSyncApps();
|
|
36
|
+
const index = apps.findIndex((a) => a.id === id);
|
|
37
|
+
if (index === -1) return null;
|
|
38
|
+
apps[index] = {
|
|
39
|
+
...apps[index],
|
|
40
|
+
name: data.name !== undefined ? data.name : apps[index].name,
|
|
41
|
+
repo: data.repo !== undefined ? data.repo : apps[index].repo,
|
|
42
|
+
activeBranch: data.activeBranch !== undefined ? data.activeBranch : apps[index].activeBranch,
|
|
43
|
+
lastReleaseBranch: data.lastReleaseBranch !== undefined ? data.lastReleaseBranch : apps[index].lastReleaseBranch,
|
|
44
|
+
subApps: Array.isArray(data.subApps) ? data.subApps : apps[index].subApps,
|
|
45
|
+
updatedAt: new Date().toISOString()
|
|
46
|
+
};
|
|
47
|
+
saveBranchSyncApps(apps);
|
|
48
|
+
return apps[index];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function removeBranchSyncApp(id) {
|
|
52
|
+
const apps = listBranchSyncApps();
|
|
53
|
+
const filtered = apps.filter((a) => a.id !== id);
|
|
54
|
+
saveBranchSyncApps(filtered);
|
|
55
|
+
return true;
|
|
56
|
+
}
|