zigrix 0.1.0-alpha.4 → 0.1.0-alpha.5

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.
@@ -0,0 +1,35 @@
1
+ /** Default port for zigrix dashboard */
2
+ export declare const DASHBOARD_DEFAULT_PORT = 3838;
3
+ /**
4
+ * Resolve the dashboard directory from a given dist index.js path.
5
+ * Convention: dist/index.js → ../../dashboard (i.e. <package-root>/dashboard)
6
+ *
7
+ * The path "../../dashboard" is interpreted relative to the FILE dist/index.js:
8
+ * - First ".." removes the filename component → <pkg>/dist/
9
+ * - Second ".." removes the dist directory → <pkg>/ (package root)
10
+ * - "dashboard" → <pkg>/dashboard
11
+ *
12
+ * Using path.resolve(distIndexPath, '..', '..', 'dashboard') achieves exactly
13
+ * this because path.resolve treats each ".." as stripping the last segment of
14
+ * the current accumulated path (including the filename on the first step).
15
+ */
16
+ export declare function resolveDashboardDir(distIndexPath: string): string;
17
+ /**
18
+ * Check whether a TCP port is already in use.
19
+ * Returns true if the port is busy (another process is listening).
20
+ */
21
+ export declare function isPortBusy(port: number): Promise<boolean>;
22
+ export interface RunDashboardOptions {
23
+ port?: number;
24
+ }
25
+ /**
26
+ * Main entry point for `zigrix dashboard`.
27
+ *
28
+ * Steps:
29
+ * 1. Resolve dashboard directory relative to this module's dist path.
30
+ * 2. Check port availability; fail clearly on conflict.
31
+ * 3. Auto-install if node_modules is missing.
32
+ * 4. Auto-build if .next directory is missing.
33
+ * 5. Spawn `next start -p <port>` in foreground and wait.
34
+ */
35
+ export declare function runDashboard(options?: RunDashboardOptions): Promise<void>;
@@ -0,0 +1,113 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as net from 'node:net';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ /** Default port for zigrix dashboard */
7
+ export const DASHBOARD_DEFAULT_PORT = 3838;
8
+ /**
9
+ * Resolve the dashboard directory from a given dist index.js path.
10
+ * Convention: dist/index.js → ../../dashboard (i.e. <package-root>/dashboard)
11
+ *
12
+ * The path "../../dashboard" is interpreted relative to the FILE dist/index.js:
13
+ * - First ".." removes the filename component → <pkg>/dist/
14
+ * - Second ".." removes the dist directory → <pkg>/ (package root)
15
+ * - "dashboard" → <pkg>/dashboard
16
+ *
17
+ * Using path.resolve(distIndexPath, '..', '..', 'dashboard') achieves exactly
18
+ * this because path.resolve treats each ".." as stripping the last segment of
19
+ * the current accumulated path (including the filename on the first step).
20
+ */
21
+ export function resolveDashboardDir(distIndexPath) {
22
+ return path.resolve(distIndexPath, '..', '..', 'dashboard');
23
+ }
24
+ /**
25
+ * Check whether a TCP port is already in use.
26
+ * Returns true if the port is busy (another process is listening).
27
+ */
28
+ export function isPortBusy(port) {
29
+ return new Promise((resolve) => {
30
+ const server = net.createServer();
31
+ server.once('error', (err) => {
32
+ if (err.code === 'EADDRINUSE') {
33
+ resolve(true);
34
+ }
35
+ else {
36
+ // Unexpected error — treat as busy to be safe
37
+ resolve(true);
38
+ }
39
+ });
40
+ server.once('listening', () => {
41
+ server.close(() => resolve(false));
42
+ });
43
+ server.listen(port, '127.0.0.1');
44
+ });
45
+ }
46
+ /**
47
+ * Main entry point for `zigrix dashboard`.
48
+ *
49
+ * Steps:
50
+ * 1. Resolve dashboard directory relative to this module's dist path.
51
+ * 2. Check port availability; fail clearly on conflict.
52
+ * 3. Auto-install if node_modules is missing.
53
+ * 4. Auto-build if .next directory is missing.
54
+ * 5. Spawn `next start -p <port>` in foreground and wait.
55
+ */
56
+ export async function runDashboard(options = {}) {
57
+ const port = options.port ?? DASHBOARD_DEFAULT_PORT;
58
+ // Resolve dashboard directory.
59
+ // When running from compiled dist/index.js: import.meta.url resolves to
60
+ // file:///…/dist/index.js, so dirname is …/dist.
61
+ // ../../dashboard therefore points to <package-root>/dashboard.
62
+ const selfPath = fileURLToPath(import.meta.url);
63
+ const dashboardDir = resolveDashboardDir(selfPath);
64
+ if (!fs.existsSync(dashboardDir)) {
65
+ throw new Error(`Dashboard directory not found: ${dashboardDir}`);
66
+ }
67
+ // --- Port conflict check ---
68
+ const busy = await isPortBusy(port);
69
+ if (busy) {
70
+ throw new Error(`Port ${port} is already in use. ` +
71
+ `Stop the conflicting process or specify a different port with --port.`);
72
+ }
73
+ // --- Auto-install dependencies ---
74
+ const nodeModulesDir = path.join(dashboardDir, 'node_modules');
75
+ if (!fs.existsSync(nodeModulesDir)) {
76
+ console.log('📦 dashboard/node_modules not found — running npm install…');
77
+ execSync('npm install', { cwd: dashboardDir, stdio: 'inherit' });
78
+ }
79
+ // --- Auto-build ---
80
+ const nextBuildDir = path.join(dashboardDir, '.next');
81
+ if (!fs.existsSync(nextBuildDir)) {
82
+ console.log('🔨 dashboard/.next not found — running npm run build…');
83
+ execSync('npm run build', { cwd: dashboardDir, stdio: 'inherit' });
84
+ }
85
+ // --- Launch Next.js in foreground ---
86
+ console.log(`🚀 Starting zigrix dashboard on http://localhost:${port}`);
87
+ const nextBin = path.join(dashboardDir, 'node_modules', '.bin', 'next');
88
+ const child = spawn(nextBin, ['start', '-p', String(port)], {
89
+ cwd: dashboardDir,
90
+ stdio: 'inherit',
91
+ env: { ...process.env },
92
+ });
93
+ await new Promise((resolve, reject) => {
94
+ child.on('exit', (code, signal) => {
95
+ if (code === 0 || signal === 'SIGINT' || signal === 'SIGTERM') {
96
+ resolve();
97
+ }
98
+ else {
99
+ reject(new Error(`next start exited with code ${code ?? signal}`));
100
+ }
101
+ });
102
+ child.on('error', (err) => {
103
+ reject(new Error(`Failed to start next: ${err.message}`));
104
+ });
105
+ // Forward SIGINT/SIGTERM to child for clean shutdown
106
+ const forward = (sig) => {
107
+ if (!child.killed)
108
+ child.kill(sig);
109
+ };
110
+ process.once('SIGINT', () => forward('SIGINT'));
111
+ process.once('SIGTERM', () => forward('SIGTERM'));
112
+ });
113
+ }
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { loadRunRecord } from './runner/store.js';
21
21
  import { ensureBaseState, resolvePaths } from './state/paths.js';
22
22
  import { applyStalePolicy, createTask, findStaleTasks, listTaskEvents, listTasks, loadTask, rebuildIndex, recordTaskProgress, updateTaskStatus, } from './state/tasks.js';
23
23
  import { verifyState } from './state/verify.js';
24
+ import { runDashboard, DASHBOARD_DEFAULT_PORT } from './dashboard.js';
24
25
  const STATUS_MAP = {
25
26
  start: 'IN_PROGRESS',
26
27
  report: 'REPORTED',
@@ -786,6 +787,20 @@ program
786
787
  const loaded = loadConfig({ baseDir: options.baseDir, configPath: options.config });
787
788
  printValue(loadRunRecord(loaded.config, runIdOrPath), true);
788
789
  });
790
+ // ─── dashboard ──────────────────────────────────────────────────────────────
791
+ program
792
+ .command('dashboard')
793
+ .description('Start the zigrix web dashboard (Next.js) in the foreground')
794
+ .option('--port <n>', `TCP port to listen on (default: ${DASHBOARD_DEFAULT_PORT})`, String(DASHBOARD_DEFAULT_PORT))
795
+ .action(async (options) => {
796
+ const port = parseInt(options.port, 10);
797
+ if (isNaN(port) || port < 1 || port > 65535) {
798
+ console.error(`Invalid port: ${options.port}`);
799
+ process.exitCode = 1;
800
+ return;
801
+ }
802
+ await runDashboard({ port });
803
+ });
789
804
  program.parseAsync(process.argv).catch((error) => {
790
805
  console.error(error instanceof Error ? error.message : String(error));
791
806
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigrix",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.5",
4
4
  "type": "module",
5
5
  "description": "Multi-project parallel task orchestration CLI for agent-assisted development",
6
6
  "license": "Apache-2.0",