@profoundlogic/coderflow-cli 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +322 -0
- package/README.md +102 -0
- package/coder.js +202 -0
- package/lib/commands/apply.js +238 -0
- package/lib/commands/attach.js +143 -0
- package/lib/commands/config.js +226 -0
- package/lib/commands/containers.js +213 -0
- package/lib/commands/discard.js +167 -0
- package/lib/commands/interactive.js +292 -0
- package/lib/commands/jira.js +464 -0
- package/lib/commands/license.js +172 -0
- package/lib/commands/list.js +104 -0
- package/lib/commands/login.js +329 -0
- package/lib/commands/logs.js +66 -0
- package/lib/commands/profile.js +539 -0
- package/lib/commands/reject.js +53 -0
- package/lib/commands/results.js +89 -0
- package/lib/commands/run.js +237 -0
- package/lib/commands/server.js +537 -0
- package/lib/commands/status.js +39 -0
- package/lib/commands/test.js +335 -0
- package/lib/config.js +378 -0
- package/lib/help.js +444 -0
- package/lib/http-client.js +180 -0
- package/lib/oidc.js +126 -0
- package/lib/profile.js +296 -0
- package/lib/state-capture.js +336 -0
- package/lib/task-grouping.js +210 -0
- package/lib/terminal-client.js +162 -0
- package/package.json +35 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server management commands
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This command requires the server package to be installed locally.
|
|
5
|
+
* It manages a local CoderFlow server instance on the same machine.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { createRequire } from 'module';
|
|
14
|
+
import {
|
|
15
|
+
saveCoderSetupPath,
|
|
16
|
+
getCoderSetupPath,
|
|
17
|
+
saveServerPort,
|
|
18
|
+
getServerPort,
|
|
19
|
+
getServerUrl
|
|
20
|
+
} from '../config.js';
|
|
21
|
+
|
|
22
|
+
const PID_FILE = path.join(os.homedir(), '.coder', 'server.pid');
|
|
23
|
+
const LOG_FILE = path.join(os.homedir(), '.coder', 'server.log');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the server package directory and entry script
|
|
27
|
+
* Returns { packagePath, scriptPath } or null if server package is not installed
|
|
28
|
+
*/
|
|
29
|
+
async function getServerPackageInfo() {
|
|
30
|
+
// In development, prefer source (start.js) over dist for easier iteration
|
|
31
|
+
// Use CODER_USE_DIST=1 to force using dist in development
|
|
32
|
+
const useDistInDev = process.env.CODER_USE_DIST === '1';
|
|
33
|
+
|
|
34
|
+
// First, try to find server package installed via npm (or via workspaces)
|
|
35
|
+
try {
|
|
36
|
+
const require = createRequire(import.meta.url);
|
|
37
|
+
const serverPkgPath = require.resolve('@profoundlogic/coderflow-server/package.json');
|
|
38
|
+
const packagePath = path.dirname(serverPkgPath);
|
|
39
|
+
const srcScript = path.join(packagePath, 'start.js');
|
|
40
|
+
const distScript = path.join(packagePath, 'dist', 'start.js');
|
|
41
|
+
|
|
42
|
+
// Check if source exists (indicates development/workspace setup)
|
|
43
|
+
let sourceExists = false;
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(srcScript);
|
|
46
|
+
sourceExists = true;
|
|
47
|
+
} catch {
|
|
48
|
+
// Source doesn't exist - this is a truly installed package
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Prefer source in development unless CODER_USE_DIST=1
|
|
52
|
+
if (sourceExists && !useDistInDev) {
|
|
53
|
+
return { packagePath, scriptPath: srcScript };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Use dist (for installed packages or when CODER_USE_DIST=1)
|
|
57
|
+
try {
|
|
58
|
+
await fs.access(distScript);
|
|
59
|
+
return { packagePath, scriptPath: distScript };
|
|
60
|
+
} catch {
|
|
61
|
+
// dist/start.js not found
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fall back to source if dist doesn't exist
|
|
65
|
+
if (sourceExists) {
|
|
66
|
+
return { packagePath, scriptPath: srcScript };
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Not installed as npm package, try monorepo development path
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try relative path for monorepo development (fallback if require.resolve fails)
|
|
73
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
74
|
+
const cliDir = path.dirname(path.dirname(path.dirname(currentFile)));
|
|
75
|
+
const monorepoServerPath = path.join(cliDir, '..', 'server');
|
|
76
|
+
|
|
77
|
+
const srcScript = path.join(monorepoServerPath, 'start.js');
|
|
78
|
+
const distScript = path.join(monorepoServerPath, 'dist', 'start.js');
|
|
79
|
+
|
|
80
|
+
if (!useDistInDev) {
|
|
81
|
+
// Prefer source for development
|
|
82
|
+
try {
|
|
83
|
+
await fs.access(srcScript);
|
|
84
|
+
return { packagePath: monorepoServerPath, scriptPath: srcScript };
|
|
85
|
+
} catch {
|
|
86
|
+
// source doesn't exist, try dist
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await fs.access(distScript);
|
|
92
|
+
return { packagePath: monorepoServerPath, scriptPath: distScript };
|
|
93
|
+
} catch {
|
|
94
|
+
// dist doesn't exist either
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If useDistInDev was set but dist doesn't exist, fall back to source
|
|
98
|
+
if (useDistInDev) {
|
|
99
|
+
try {
|
|
100
|
+
await fs.access(srcScript);
|
|
101
|
+
return { packagePath: monorepoServerPath, scriptPath: srcScript };
|
|
102
|
+
} catch {
|
|
103
|
+
// Server package not found
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if server package is available and show error if not
|
|
112
|
+
* Returns { packagePath, scriptPath }
|
|
113
|
+
*/
|
|
114
|
+
async function requireServerPackage() {
|
|
115
|
+
const serverInfo = await getServerPackageInfo();
|
|
116
|
+
if (!serverInfo) {
|
|
117
|
+
console.error('Error: Server package is not installed locally.');
|
|
118
|
+
console.error('');
|
|
119
|
+
console.error('The `coder server` command manages a local CoderFlow server.');
|
|
120
|
+
console.error('To use this command, install the server package:');
|
|
121
|
+
console.error('');
|
|
122
|
+
console.error(' npm install -g @profoundlogic/coderflow-server');
|
|
123
|
+
console.error('');
|
|
124
|
+
console.error('If you want to connect to a remote server instead, use:');
|
|
125
|
+
console.error(' coder config set serverUrl http://your-server:3000');
|
|
126
|
+
console.error(' coder login');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
return serverInfo;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if server is running
|
|
134
|
+
*/
|
|
135
|
+
async function isServerRunning() {
|
|
136
|
+
try {
|
|
137
|
+
const pidData = await fs.readFile(PID_FILE, 'utf-8');
|
|
138
|
+
const pid = parseInt(pidData.trim(), 10);
|
|
139
|
+
|
|
140
|
+
// Check if process exists
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
143
|
+
return { running: true, pid };
|
|
144
|
+
} catch {
|
|
145
|
+
// Process doesn't exist, clean up PID file
|
|
146
|
+
await fs.unlink(PID_FILE).catch(() => {});
|
|
147
|
+
return { running: false, pid: null };
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
return { running: false, pid: null };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get server process info
|
|
156
|
+
*/
|
|
157
|
+
async function getServerInfo() {
|
|
158
|
+
const { running, pid } = await isServerRunning();
|
|
159
|
+
const setupPath = await getCoderSetupPath();
|
|
160
|
+
const port = await getServerPort();
|
|
161
|
+
const serverUrl = await getServerUrl();
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
running,
|
|
165
|
+
pid,
|
|
166
|
+
setupPath,
|
|
167
|
+
port,
|
|
168
|
+
serverUrl
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Start the server
|
|
174
|
+
*/
|
|
175
|
+
async function startServer(args) {
|
|
176
|
+
const { running, pid } = await isServerRunning();
|
|
177
|
+
|
|
178
|
+
if (running) {
|
|
179
|
+
console.log(`Server is already running (PID: ${pid})`);
|
|
180
|
+
console.log(`URL: ${await getServerUrl()}`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parse arguments
|
|
185
|
+
let setupPath = null;
|
|
186
|
+
let port = null;
|
|
187
|
+
let daemon = false;
|
|
188
|
+
|
|
189
|
+
for (const arg of args) {
|
|
190
|
+
if (arg.startsWith('--setup-path=')) {
|
|
191
|
+
setupPath = arg.substring('--setup-path='.length);
|
|
192
|
+
} else if (arg.startsWith('--port=')) {
|
|
193
|
+
port = parseInt(arg.substring('--port='.length), 10);
|
|
194
|
+
} else if (arg === '--daemon' || arg === '-d') {
|
|
195
|
+
daemon = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Get setup path from args, config, or env
|
|
200
|
+
if (!setupPath) {
|
|
201
|
+
setupPath = await getCoderSetupPath();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!setupPath) {
|
|
205
|
+
console.error('Error: No coder-setup path configured');
|
|
206
|
+
console.error('');
|
|
207
|
+
console.error('Please provide a setup path:');
|
|
208
|
+
console.error(' coder server start --setup-path=/path/to/coder-setup');
|
|
209
|
+
console.error('');
|
|
210
|
+
console.error('Or set environment variable:');
|
|
211
|
+
console.error(' export CODER_SETUP_PATH=/path/to/coder-setup');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Resolve to absolute path
|
|
216
|
+
setupPath = path.resolve(setupPath);
|
|
217
|
+
|
|
218
|
+
// Verify setup path exists
|
|
219
|
+
try {
|
|
220
|
+
const stat = await fs.stat(setupPath);
|
|
221
|
+
if (!stat.isDirectory()) {
|
|
222
|
+
console.error(`Error: Setup path is not a directory: ${setupPath}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(`Error: Setup path does not exist: ${setupPath}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Save setup path to config for future use
|
|
231
|
+
await saveCoderSetupPath(setupPath);
|
|
232
|
+
console.log(`✓ Saved setup path to config: ${setupPath}`);
|
|
233
|
+
|
|
234
|
+
// Get port from args, config, or default
|
|
235
|
+
if (!port) {
|
|
236
|
+
port = await getServerPort();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Save port to config
|
|
240
|
+
if (port !== 3000) {
|
|
241
|
+
await saveServerPort(port);
|
|
242
|
+
console.log(`✓ Saved server port to config: ${port}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Ensure server package is installed (scriptPath already verified to exist)
|
|
246
|
+
const { scriptPath: serverScript } = await requireServerPackage();
|
|
247
|
+
|
|
248
|
+
console.log('');
|
|
249
|
+
console.log('Starting CoderFlow Server...');
|
|
250
|
+
console.log(` Setup Path: ${setupPath}`);
|
|
251
|
+
console.log(` Port: ${port}`);
|
|
252
|
+
console.log(` Mode: ${daemon ? 'daemon' : 'foreground'}`);
|
|
253
|
+
console.log('');
|
|
254
|
+
|
|
255
|
+
const env = {
|
|
256
|
+
...process.env,
|
|
257
|
+
CODER_SETUP_PATH: setupPath,
|
|
258
|
+
PORT: port.toString()
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const spawnArgs = [serverScript];
|
|
262
|
+
|
|
263
|
+
// Allow the coder server itself to be debugged.
|
|
264
|
+
if (typeof process.env.CODER_SERVER_INSPECT === "string" && process.env.CODER_SERVER_INSPECT.length > 0) {
|
|
265
|
+
spawnArgs.unshift('--inspect');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (daemon) {
|
|
269
|
+
// Start server as daemon
|
|
270
|
+
const logStream = await fs.open(LOG_FILE, 'a');
|
|
271
|
+
|
|
272
|
+
const serverProcess = spawn('node', spawnArgs, {
|
|
273
|
+
env,
|
|
274
|
+
detached: true,
|
|
275
|
+
stdio: ['ignore', logStream.fd, logStream.fd]
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
serverProcess.unref();
|
|
279
|
+
|
|
280
|
+
// Save PID
|
|
281
|
+
const pidDir = path.dirname(PID_FILE);
|
|
282
|
+
await fs.mkdir(pidDir, { recursive: true });
|
|
283
|
+
await fs.writeFile(PID_FILE, serverProcess.pid.toString(), 'utf-8');
|
|
284
|
+
|
|
285
|
+
console.log(`✓ Server started in daemon mode (PID: ${serverProcess.pid})`);
|
|
286
|
+
console.log(` URL: http://localhost:${port}`);
|
|
287
|
+
console.log(` Logs: ${LOG_FILE}`);
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log('Use "coder server status" to check server status');
|
|
290
|
+
console.log('Use "coder server stop" to stop the server');
|
|
291
|
+
console.log('Use "coder server logs" to view logs');
|
|
292
|
+
} else {
|
|
293
|
+
// Start server in foreground
|
|
294
|
+
const serverProcess = spawn('node', spawnArgs, {
|
|
295
|
+
env,
|
|
296
|
+
stdio: 'inherit'
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Save PID
|
|
300
|
+
const pidDir = path.dirname(PID_FILE);
|
|
301
|
+
await fs.mkdir(pidDir, { recursive: true });
|
|
302
|
+
await fs.writeFile(PID_FILE, serverProcess.pid.toString(), 'utf-8');
|
|
303
|
+
|
|
304
|
+
// Handle termination
|
|
305
|
+
const cleanup = async () => {
|
|
306
|
+
await fs.unlink(PID_FILE).catch(() => {});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
serverProcess.on('exit', cleanup);
|
|
310
|
+
process.on('SIGINT', () => {
|
|
311
|
+
cleanup().then(() => process.exit(0));
|
|
312
|
+
});
|
|
313
|
+
process.on('SIGTERM', () => {
|
|
314
|
+
cleanup().then(() => process.exit(0));
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Stop the server
|
|
321
|
+
*/
|
|
322
|
+
async function stopServer() {
|
|
323
|
+
const { running, pid } = await isServerRunning();
|
|
324
|
+
|
|
325
|
+
if (!running) {
|
|
326
|
+
console.log('Server is not running');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`Stopping server (PID: ${pid})...`);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
process.kill(pid, 'SIGTERM');
|
|
334
|
+
|
|
335
|
+
// Wait for process to exit (max 5 seconds)
|
|
336
|
+
for (let i = 0; i < 50; i++) {
|
|
337
|
+
try {
|
|
338
|
+
process.kill(pid, 0);
|
|
339
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
340
|
+
} catch {
|
|
341
|
+
// Process exited
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Check if still running, force kill if needed
|
|
347
|
+
try {
|
|
348
|
+
process.kill(pid, 0);
|
|
349
|
+
console.log('Server did not stop gracefully, forcing...');
|
|
350
|
+
process.kill(pid, 'SIGKILL');
|
|
351
|
+
} catch {
|
|
352
|
+
// Already stopped
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Clean up PID file
|
|
356
|
+
await fs.unlink(PID_FILE).catch(() => {});
|
|
357
|
+
|
|
358
|
+
console.log('✓ Server stopped');
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error(`Error stopping server: ${error.message}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Show server status
|
|
367
|
+
*/
|
|
368
|
+
async function showStatus() {
|
|
369
|
+
const info = await getServerInfo();
|
|
370
|
+
|
|
371
|
+
console.log('CoderFlow Server Status:');
|
|
372
|
+
console.log('');
|
|
373
|
+
console.log(` Status: ${info.running ? '✓ Running' : '✗ Not running'}`);
|
|
374
|
+
|
|
375
|
+
if (info.running) {
|
|
376
|
+
console.log(` PID: ${info.pid}`);
|
|
377
|
+
console.log(` URL: ${info.serverUrl}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (info.setupPath) {
|
|
381
|
+
console.log(` Setup Path: ${info.setupPath}`);
|
|
382
|
+
} else {
|
|
383
|
+
console.log(` Setup Path: (not configured)`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log(` Port: ${info.port}`);
|
|
387
|
+
console.log('');
|
|
388
|
+
|
|
389
|
+
if (!info.running && info.setupPath) {
|
|
390
|
+
console.log('Start the server with:');
|
|
391
|
+
console.log(' coder server start');
|
|
392
|
+
} else if (!info.running) {
|
|
393
|
+
console.log('Configure and start the server with:');
|
|
394
|
+
console.log(' coder server start --setup-path=/path/to/coder-setup');
|
|
395
|
+
} else {
|
|
396
|
+
console.log('Stop the server with:');
|
|
397
|
+
console.log(' coder server stop');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Show server logs
|
|
403
|
+
*/
|
|
404
|
+
async function showLogs(args) {
|
|
405
|
+
const { running } = await isServerRunning();
|
|
406
|
+
|
|
407
|
+
if (!running) {
|
|
408
|
+
console.log('Server is not running');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Parse arguments
|
|
413
|
+
let follow = false;
|
|
414
|
+
let lines = null;
|
|
415
|
+
|
|
416
|
+
for (const arg of args) {
|
|
417
|
+
if (arg === '--follow' || arg === '-f') {
|
|
418
|
+
follow = true;
|
|
419
|
+
} else if (arg.startsWith('--tail=')) {
|
|
420
|
+
lines = parseInt(arg.substring('--tail='.length), 10);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const logContent = await fs.readFile(LOG_FILE, 'utf-8');
|
|
426
|
+
const logLines = logContent.split('\n');
|
|
427
|
+
|
|
428
|
+
if (lines) {
|
|
429
|
+
console.log(logLines.slice(-lines).join('\n'));
|
|
430
|
+
} else {
|
|
431
|
+
console.log(logContent);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (follow) {
|
|
435
|
+
console.log('\n--- Following logs (Ctrl+C to stop) ---\n');
|
|
436
|
+
|
|
437
|
+
// Simple tail -f implementation
|
|
438
|
+
let lastSize = (await fs.stat(LOG_FILE)).size;
|
|
439
|
+
|
|
440
|
+
const interval = setInterval(async () => {
|
|
441
|
+
try {
|
|
442
|
+
const currentSize = (await fs.stat(LOG_FILE)).size;
|
|
443
|
+
if (currentSize > lastSize) {
|
|
444
|
+
const stream = await fs.open(LOG_FILE, 'r');
|
|
445
|
+
const buffer = Buffer.alloc(currentSize - lastSize);
|
|
446
|
+
await stream.read(buffer, 0, buffer.length, lastSize);
|
|
447
|
+
await stream.close();
|
|
448
|
+
process.stdout.write(buffer.toString('utf-8'));
|
|
449
|
+
lastSize = currentSize;
|
|
450
|
+
}
|
|
451
|
+
} catch (error) {
|
|
452
|
+
clearInterval(interval);
|
|
453
|
+
}
|
|
454
|
+
}, 500);
|
|
455
|
+
|
|
456
|
+
process.on('SIGINT', () => {
|
|
457
|
+
clearInterval(interval);
|
|
458
|
+
process.exit(0);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (error.code === 'ENOENT') {
|
|
463
|
+
console.log('No log file found');
|
|
464
|
+
console.log('Server may have been started in foreground mode');
|
|
465
|
+
} else {
|
|
466
|
+
console.error(`Error reading logs: ${error.message}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Restart the server
|
|
473
|
+
*/
|
|
474
|
+
async function restartServer(args) {
|
|
475
|
+
const { running } = await isServerRunning();
|
|
476
|
+
|
|
477
|
+
if (running) {
|
|
478
|
+
console.log('Stopping server...');
|
|
479
|
+
await stopServer();
|
|
480
|
+
console.log('');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log('Starting server...');
|
|
484
|
+
await startServer(args);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Handle server commands
|
|
489
|
+
*/
|
|
490
|
+
export async function handleServer(args) {
|
|
491
|
+
const subcommand = args[0];
|
|
492
|
+
|
|
493
|
+
// Check for server package availability for commands that need it
|
|
494
|
+
// (start, restart require the package; stop/status/logs work with running server)
|
|
495
|
+
if (subcommand === 'start' || subcommand === 'restart') {
|
|
496
|
+
await requireServerPackage();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
switch (subcommand) {
|
|
500
|
+
case 'start':
|
|
501
|
+
await startServer(args.slice(1));
|
|
502
|
+
break;
|
|
503
|
+
|
|
504
|
+
case 'stop':
|
|
505
|
+
await stopServer();
|
|
506
|
+
break;
|
|
507
|
+
|
|
508
|
+
case 'status':
|
|
509
|
+
await showStatus();
|
|
510
|
+
break;
|
|
511
|
+
|
|
512
|
+
case 'logs':
|
|
513
|
+
await showLogs(args.slice(1));
|
|
514
|
+
break;
|
|
515
|
+
|
|
516
|
+
case 'restart':
|
|
517
|
+
await restartServer(args.slice(1));
|
|
518
|
+
break;
|
|
519
|
+
|
|
520
|
+
case undefined:
|
|
521
|
+
console.error('Error: No subcommand provided');
|
|
522
|
+
console.error('');
|
|
523
|
+
console.error('Available subcommands:');
|
|
524
|
+
console.error(' coder server start [--setup-path=PATH] [--port=PORT] [--daemon]');
|
|
525
|
+
console.error(' coder server stop');
|
|
526
|
+
console.error(' coder server status');
|
|
527
|
+
console.error(' coder server logs [--follow] [--tail=N]');
|
|
528
|
+
console.error(' coder server restart [--setup-path=PATH] [--port=PORT] [--daemon]');
|
|
529
|
+
process.exit(1);
|
|
530
|
+
break;
|
|
531
|
+
|
|
532
|
+
default:
|
|
533
|
+
console.error(`Error: Unknown subcommand "${subcommand}"`);
|
|
534
|
+
console.error('Run "coder server" for available subcommands');
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command: coder status - Check status of a task
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { request } from '../http-client.js';
|
|
6
|
+
|
|
7
|
+
export async function getStatus(taskId) {
|
|
8
|
+
if (!taskId) {
|
|
9
|
+
console.error('Error: Task ID required');
|
|
10
|
+
console.error('Usage: coder status <task-id>');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(`Fetching status for task ${taskId}...`);
|
|
15
|
+
|
|
16
|
+
const data = await request(`/tasks/${taskId}`);
|
|
17
|
+
|
|
18
|
+
console.log(`\nTask Status:`);
|
|
19
|
+
console.log(` Task ID: ${data.taskId}`);
|
|
20
|
+
console.log(` Status: ${data.status}`);
|
|
21
|
+
console.log(` Created: ${data.createdAt}`);
|
|
22
|
+
|
|
23
|
+
if (data.finishedAt) {
|
|
24
|
+
console.log(` Finished: ${data.finishedAt}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (data.exitCode !== undefined) {
|
|
28
|
+
console.log(` Exit Code: ${data.exitCode}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (data.status === 'completed') {
|
|
32
|
+
console.log(`\n✓ Task completed successfully`);
|
|
33
|
+
console.log(`Use "coder results ${taskId}" to get full results`);
|
|
34
|
+
} else if (data.status === 'failed') {
|
|
35
|
+
console.log(`\n✗ Task failed`);
|
|
36
|
+
} else if (data.status === 'running') {
|
|
37
|
+
console.log(`\n⋯ Task is still running`);
|
|
38
|
+
}
|
|
39
|
+
}
|