pnpm-dash 0.1.2 → 0.1.4
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/cli.js +1 -1
- package/dist/runner.js +26 -24
- package/dist/ui/dashboard.js +38 -8
- package/dist/ui/logview.js +2 -5
- package/dist/ui/statusbar.js +0 -1
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ export function parseCLI() {
|
|
|
4
4
|
program
|
|
5
5
|
.name('pnpm-dash')
|
|
6
6
|
.description('A TUI dashboard for pnpm workspaces - run scripts across packages with a split-pane interface')
|
|
7
|
-
.version('0.1.
|
|
7
|
+
.version('0.1.3')
|
|
8
8
|
.argument('<script>', 'Script name to run across workspace packages (e.g., dev, start)')
|
|
9
9
|
.option('-F, --filter <pattern...>', 'Filter packages by name pattern, supports * for wildcard and ! for exclusions')
|
|
10
10
|
.parse();
|
package/dist/runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { RingBuffer } from './ringbuf.js';
|
|
4
4
|
import { MAX_LOG_LINES } from './constants.js';
|
|
@@ -21,7 +21,6 @@ export class Runner extends EventEmitter {
|
|
|
21
21
|
let state = this.states.get(pkg.name);
|
|
22
22
|
if (state) {
|
|
23
23
|
if (state.subprocess && state.subprocess.exitCode == null) {
|
|
24
|
-
console.error(`${pkg.name} subprocess is still running`, state.subprocess.pid);
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
state.status = 'running';
|
|
@@ -35,21 +34,19 @@ export class Runner extends EventEmitter {
|
|
|
35
34
|
};
|
|
36
35
|
this.states.set(pkg.name, state);
|
|
37
36
|
}
|
|
38
|
-
const
|
|
37
|
+
const cmd = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
38
|
+
const subprocess = spawn(cmd, ['run', this.scriptName], {
|
|
39
39
|
cwd: pkg.path,
|
|
40
40
|
env: {
|
|
41
41
|
...process.env,
|
|
42
42
|
FORCE_COLOR: '1',
|
|
43
43
|
},
|
|
44
|
-
|
|
45
|
-
buffer: false,
|
|
46
|
-
reject: false,
|
|
47
|
-
cleanup: false,
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
45
|
detached: true,
|
|
49
46
|
});
|
|
50
47
|
state.subprocess = subprocess;
|
|
51
48
|
this.emit('start', pkg.name);
|
|
52
|
-
|
|
49
|
+
const handleData = (data) => {
|
|
53
50
|
const lines = data.toString().split('\n');
|
|
54
51
|
for (const line of lines) {
|
|
55
52
|
if (line) {
|
|
@@ -57,15 +54,20 @@ export class Runner extends EventEmitter {
|
|
|
57
54
|
this.emit('log', pkg.name, line);
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
|
-
}
|
|
61
|
-
subprocess.
|
|
62
|
-
|
|
57
|
+
};
|
|
58
|
+
subprocess.stdout?.on('data', handleData);
|
|
59
|
+
subprocess.stderr?.on('data', handleData);
|
|
60
|
+
subprocess.on('close', (code) => {
|
|
61
|
+
state.status = code === 0 ? 'success' : 'error';
|
|
63
62
|
state.subprocess = null;
|
|
64
|
-
this.emit('exit', pkg.name,
|
|
65
|
-
})
|
|
63
|
+
this.emit('exit', pkg.name, code);
|
|
64
|
+
});
|
|
65
|
+
subprocess.on('error', (error) => {
|
|
66
66
|
state.status = 'error';
|
|
67
|
-
state.logs.push(`Error: ${error.message}`);
|
|
68
67
|
state.subprocess = null;
|
|
68
|
+
const logLine = `Error: ${error.message}`;
|
|
69
|
+
state.logs.push(logLine);
|
|
70
|
+
this.emit('log', pkg.name, logLine);
|
|
69
71
|
this.emit('error', pkg.name, error);
|
|
70
72
|
});
|
|
71
73
|
}
|
|
@@ -103,16 +105,16 @@ export class Runner extends EventEmitter {
|
|
|
103
105
|
if (!subprocess?.pid)
|
|
104
106
|
return;
|
|
105
107
|
const pid = subprocess.pid;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const forceKillTimeout = setTimeout(() => {
|
|
110
|
+
this.killProcessGroup(pid, true);
|
|
111
|
+
}, 2000);
|
|
112
|
+
subprocess.once('close', () => {
|
|
113
|
+
clearTimeout(forceKillTimeout);
|
|
114
|
+
resolve();
|
|
115
|
+
});
|
|
116
|
+
this.killProcessGroup(pid);
|
|
117
|
+
});
|
|
116
118
|
}
|
|
117
119
|
async stopAll() {
|
|
118
120
|
await Promise.all(Array.from(this.states.keys()).map((name) => this.stopPackage(name)));
|
package/dist/ui/dashboard.js
CHANGED
|
@@ -2,6 +2,7 @@ import blessed from 'reblessed';
|
|
|
2
2
|
import { createSidebar, updateSidebarItems } from './sidebar.js';
|
|
3
3
|
import { createLogView, updateLogView, appendLog, toggleLogAutoScroll, expandLogView, shrinkLogView } from './logview.js';
|
|
4
4
|
import { createStatusBar, updateStatusBar } from './statusbar.js';
|
|
5
|
+
const RENDER_INTERVAL = 33;
|
|
5
6
|
export class Dashboard {
|
|
6
7
|
screen;
|
|
7
8
|
sidebar;
|
|
@@ -10,6 +11,9 @@ export class Dashboard {
|
|
|
10
11
|
runner;
|
|
11
12
|
state;
|
|
12
13
|
packageNames = [];
|
|
14
|
+
renderTimer = null;
|
|
15
|
+
needsRender = false;
|
|
16
|
+
pendingLogs = [];
|
|
13
17
|
constructor(runner, packages) {
|
|
14
18
|
this.runner = runner;
|
|
15
19
|
this.packageNames = packages.map((p) => p.name);
|
|
@@ -62,20 +66,33 @@ export class Dashboard {
|
|
|
62
66
|
setupRunnerEvents() {
|
|
63
67
|
this.runner.on('start', (packageName) => {
|
|
64
68
|
this.refreshSidebar();
|
|
65
|
-
this.
|
|
69
|
+
this.needsRender = true;
|
|
66
70
|
});
|
|
67
71
|
this.runner.on('log', (packageName, line) => {
|
|
68
|
-
|
|
72
|
+
if (packageName === this.getSelectedPackageName()) {
|
|
73
|
+
this.pendingLogs.push(line);
|
|
74
|
+
this.needsRender = true;
|
|
75
|
+
}
|
|
69
76
|
});
|
|
70
77
|
this.runner.on('exit', (packageName, code) => {
|
|
71
78
|
this.refreshSidebar();
|
|
72
|
-
this.
|
|
79
|
+
this.needsRender = true;
|
|
73
80
|
});
|
|
74
81
|
this.runner.on('error', (packageName, error) => {
|
|
75
82
|
this.refreshSidebar();
|
|
76
|
-
this.
|
|
83
|
+
this.needsRender = true;
|
|
77
84
|
});
|
|
78
85
|
}
|
|
86
|
+
flushRender() {
|
|
87
|
+
if (this.pendingLogs.length > 0) {
|
|
88
|
+
appendLog(this.logView, this.pendingLogs);
|
|
89
|
+
this.pendingLogs = [];
|
|
90
|
+
}
|
|
91
|
+
if (this.needsRender) {
|
|
92
|
+
this.screen.render();
|
|
93
|
+
this.needsRender = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
79
96
|
getSelectedPackageName() {
|
|
80
97
|
return this.packageNames[this.state.selectedIndex];
|
|
81
98
|
}
|
|
@@ -92,6 +109,8 @@ export class Dashboard {
|
|
|
92
109
|
}
|
|
93
110
|
this.refreshSidebar();
|
|
94
111
|
this.refreshLogView();
|
|
112
|
+
this.pendingLogs = [];
|
|
113
|
+
this.needsRender = true;
|
|
95
114
|
}
|
|
96
115
|
selectPrev() {
|
|
97
116
|
if (this.state.selectedIndex > 0) {
|
|
@@ -102,12 +121,15 @@ export class Dashboard {
|
|
|
102
121
|
}
|
|
103
122
|
this.refreshSidebar();
|
|
104
123
|
this.refreshLogView();
|
|
124
|
+
this.pendingLogs = [];
|
|
125
|
+
this.needsRender = true;
|
|
105
126
|
}
|
|
106
127
|
clearSelected() {
|
|
107
128
|
const state = this.getSelectedState();
|
|
108
129
|
if (state) {
|
|
109
130
|
state.logs.clear();
|
|
110
131
|
this.refreshLogView();
|
|
132
|
+
this.needsRender = true;
|
|
111
133
|
}
|
|
112
134
|
}
|
|
113
135
|
stopSelected() {
|
|
@@ -128,8 +150,8 @@ export class Dashboard {
|
|
|
128
150
|
toggleAutoScroll() {
|
|
129
151
|
this.state.autoScroll = !this.state.autoScroll;
|
|
130
152
|
toggleLogAutoScroll(this.logView, this.state.autoScroll);
|
|
131
|
-
|
|
132
|
-
this.
|
|
153
|
+
this.refreshStatusBar();
|
|
154
|
+
this.needsRender = true;
|
|
133
155
|
}
|
|
134
156
|
toggleSidebar() {
|
|
135
157
|
this.state.sidebarHidden = !this.state.sidebarHidden;
|
|
@@ -141,8 +163,7 @@ export class Dashboard {
|
|
|
141
163
|
this.sidebar.show();
|
|
142
164
|
shrinkLogView(this.logView);
|
|
143
165
|
}
|
|
144
|
-
|
|
145
|
-
this.screen.render();
|
|
166
|
+
this.needsRender = true;
|
|
146
167
|
}
|
|
147
168
|
refreshSidebar() {
|
|
148
169
|
updateSidebarItems(this.sidebar, this.state.packages, this.state.selectedIndex);
|
|
@@ -150,7 +171,14 @@ export class Dashboard {
|
|
|
150
171
|
refreshLogView() {
|
|
151
172
|
updateLogView(this.logView, this.getSelectedState());
|
|
152
173
|
}
|
|
174
|
+
refreshStatusBar() {
|
|
175
|
+
updateStatusBar(this.statusBar, this.state.autoScroll);
|
|
176
|
+
}
|
|
153
177
|
async quit() {
|
|
178
|
+
if (this.renderTimer) {
|
|
179
|
+
clearInterval(this.renderTimer);
|
|
180
|
+
this.renderTimer = null;
|
|
181
|
+
}
|
|
154
182
|
await this.runner.stopAll();
|
|
155
183
|
this.screen.destroy();
|
|
156
184
|
process.exit(0);
|
|
@@ -158,7 +186,9 @@ export class Dashboard {
|
|
|
158
186
|
start() {
|
|
159
187
|
this.refreshSidebar();
|
|
160
188
|
this.refreshLogView();
|
|
189
|
+
this.refreshStatusBar();
|
|
161
190
|
this.logView.focus();
|
|
162
191
|
this.screen.render();
|
|
192
|
+
this.renderTimer = setInterval(() => this.flushRender(), RENDER_INTERVAL);
|
|
163
193
|
}
|
|
164
194
|
}
|
package/dist/ui/logview.js
CHANGED
|
@@ -39,11 +39,8 @@ export function updateLogView(logView, state) {
|
|
|
39
39
|
logView.setContent(state.logs.toArray().join('\n'));
|
|
40
40
|
logView.setScroll(0);
|
|
41
41
|
}
|
|
42
|
-
export function appendLog(logView,
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
logView.add(line);
|
|
42
|
+
export function appendLog(logView, lines) {
|
|
43
|
+
logView.add(lines.join("\n"));
|
|
47
44
|
}
|
|
48
45
|
export function toggleLogAutoScroll(logView, autoScroll) {
|
|
49
46
|
logView.scrollOnInput = autoScroll;
|
package/dist/ui/statusbar.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-dash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A TUI dashboard for pnpm workspaces - run scripts across packages with a split-pane interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,11 +25,14 @@
|
|
|
25
25
|
],
|
|
26
26
|
"author": "artygus",
|
|
27
27
|
"license": "Unlicense",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/artygus/pnpm-dash.git"
|
|
31
|
+
},
|
|
28
32
|
"dependencies": {
|
|
29
33
|
"@pnpm/find-workspace-dir": "^7.0.2",
|
|
30
34
|
"@pnpm/workspace.find-packages": "^4.0.5",
|
|
31
35
|
"commander": "^13.1.0",
|
|
32
|
-
"execa": "^9.6.1",
|
|
33
36
|
"reblessed": "^0.2.1"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|