pnpm-dash 0.1.0 → 0.1.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # pnpm-dash
2
2
 
3
- pnpm-dash is a terminal-based user interface (TUI) for pnpm, designed to provide a quick and interactive way to manage your pnpm workspace and packages.
3
+ pnpm-dash is a terminal-based user interface (TUI) for pnpm run command
4
4
 
5
5
  ## Usage
6
6
 
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.0')
7
+ .version('0.1.2')
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();
@@ -0,0 +1 @@
1
+ export const MAX_LOG_LINES = 10000;
@@ -0,0 +1,34 @@
1
+ export class RingBuffer {
2
+ buffer;
3
+ head = 0;
4
+ size = 0;
5
+ cap;
6
+ constructor(capacity) {
7
+ this.cap = capacity;
8
+ this.buffer = new Array(capacity);
9
+ }
10
+ push(item) {
11
+ const index = (this.head + this.size) % this.cap;
12
+ if (this.size < this.cap) {
13
+ this.size++;
14
+ }
15
+ else {
16
+ this.head = (this.head + 1) % this.cap;
17
+ }
18
+ this.buffer[index] = item;
19
+ }
20
+ clear() {
21
+ this.head = 0;
22
+ this.size = 0;
23
+ }
24
+ toArray() {
25
+ const result = new Array(this.size);
26
+ for (let i = 0; i < this.size; i++) {
27
+ result[i] = this.buffer[(this.head + i) % this.cap];
28
+ }
29
+ return result;
30
+ }
31
+ get length() {
32
+ return this.size;
33
+ }
34
+ }
package/dist/runner.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { execa } from 'execa';
2
2
  import { EventEmitter } from 'node:events';
3
+ import { RingBuffer } from './ringbuf.js';
4
+ import { MAX_LOG_LINES } from './constants.js';
3
5
  export class Runner extends EventEmitter {
4
6
  states = new Map();
5
7
  scriptName;
@@ -29,7 +31,7 @@ export class Runner extends EventEmitter {
29
31
  package: pkg,
30
32
  status: 'running',
31
33
  subprocess: null,
32
- logs: [],
34
+ logs: new RingBuffer(MAX_LOG_LINES),
33
35
  };
34
36
  this.states.set(pkg.name, state);
35
37
  }
@@ -1,6 +1,6 @@
1
1
  import blessed from 'reblessed';
2
2
  import { createSidebar, updateSidebarItems } from './sidebar.js';
3
- import { createLogView, updateLogView, appendLog, toggleLogAutoScroll } from './logview.js';
3
+ import { createLogView, updateLogView, appendLog, toggleLogAutoScroll, expandLogView, shrinkLogView } from './logview.js';
4
4
  import { createStatusBar, updateStatusBar } from './statusbar.js';
5
5
  export class Dashboard {
6
6
  screen;
@@ -17,6 +17,7 @@ export class Dashboard {
17
17
  packages: runner.getStates(),
18
18
  selectedIndex: 0,
19
19
  autoScroll: true,
20
+ sidebarHidden: false,
20
21
  };
21
22
  this.screen = blessed.screen({
22
23
  smartCSR: true,
@@ -54,6 +55,9 @@ export class Dashboard {
54
55
  this.screen.key(['c'], () => {
55
56
  this.clearSelected();
56
57
  });
58
+ this.screen.key(['tab'], () => {
59
+ this.toggleSidebar();
60
+ });
57
61
  }
58
62
  setupRunnerEvents() {
59
63
  this.runner.on('start', (packageName) => {
@@ -102,7 +106,7 @@ export class Dashboard {
102
106
  clearSelected() {
103
107
  const state = this.getSelectedState();
104
108
  if (state) {
105
- state.logs = [];
109
+ state.logs.clear();
106
110
  this.refreshLogView();
107
111
  }
108
112
  }
@@ -127,6 +131,19 @@ export class Dashboard {
127
131
  updateStatusBar(this.statusBar, this.state.autoScroll);
128
132
  this.screen.render();
129
133
  }
134
+ toggleSidebar() {
135
+ this.state.sidebarHidden = !this.state.sidebarHidden;
136
+ if (this.state.sidebarHidden) {
137
+ this.sidebar.hide();
138
+ expandLogView(this.logView);
139
+ }
140
+ else {
141
+ this.sidebar.show();
142
+ shrinkLogView(this.logView);
143
+ }
144
+ updateStatusBar(this.statusBar, this.state.autoScroll);
145
+ this.screen.render();
146
+ }
130
147
  refreshSidebar() {
131
148
  updateSidebarItems(this.sidebar, this.state.packages, this.state.selectedIndex);
132
149
  }
@@ -141,7 +158,7 @@ export class Dashboard {
141
158
  start() {
142
159
  this.refreshSidebar();
143
160
  this.refreshLogView();
144
- this.sidebar.focus();
161
+ this.logView.focus();
145
162
  this.screen.render();
146
163
  }
147
164
  }
@@ -1,4 +1,5 @@
1
1
  import blessed from 'reblessed';
2
+ import { MAX_LOG_LINES } from '../constants.js';
2
3
  export function createLogView(screen, autoScroll) {
3
4
  const logView = blessed.log({
4
5
  parent: screen,
@@ -22,6 +23,7 @@ export function createLogView(screen, autoScroll) {
22
23
  scrollbar: {
23
24
  ch: '│',
24
25
  },
26
+ scrollback: MAX_LOG_LINES,
25
27
  scrollOnInput: autoScroll,
26
28
  });
27
29
  return logView;
@@ -34,7 +36,7 @@ export function updateLogView(logView, state) {
34
36
  return;
35
37
  }
36
38
  logView.setLabel(` Logs - ${state.package.name} `);
37
- logView.setContent(state.logs.join('\n'));
39
+ logView.setContent(state.logs.toArray().join('\n'));
38
40
  logView.setScroll(0);
39
41
  }
40
42
  export function appendLog(logView, currentPackage, packageName, line) {
@@ -49,3 +51,13 @@ export function toggleLogAutoScroll(logView, autoScroll) {
49
51
  logView.setScrollPerc(100);
50
52
  }
51
53
  }
54
+ export function expandLogView(logView) {
55
+ logView.left = 0;
56
+ logView.width = '100%';
57
+ logView.border = { type: 'line', top: true, left: false, right: false, bottom: false };
58
+ }
59
+ export function shrinkLogView(logView) {
60
+ logView.left = '25%';
61
+ logView.width = '75%';
62
+ logView.border = { type: 'line', top: true, left: true, right: true, bottom: true };
63
+ }
@@ -17,7 +17,8 @@ export function createStatusBar(screen) {
17
17
  }
18
18
  export function updateStatusBar(statusBar, autoScroll) {
19
19
  const scrollStatus = autoScroll ? 'ON' : 'OFF';
20
- statusBar.setContent(` {bold}Q{/bold}:exit {bold}q{/bold}:quit task {bold}r{/bold}:restart task ` +
20
+ statusBar.setContent(` {bold}Q{/bold}:exit {bold}tab{/bold}:toggle sidebar ` +
21
+ ` {bold}q{/bold}:quit task {bold}r{/bold}:restart task ` +
21
22
  ` {bold}R{/bold}:restart all {bold}j/k{/bold}:navigate {bold}c{/bold}:clear ` +
22
- ` {bold}s{/bold}:autoscroll [${scrollStatus}]`);
23
+ ` {bold}s{/bold}:autoscroll [${scrollStatus}] `);
23
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pnpm-dash",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",
@@ -24,7 +24,7 @@
24
24
  "terminal"
25
25
  ],
26
26
  "author": "artygus",
27
- "license": "UNLICENSED",
27
+ "license": "Unlicense",
28
28
  "dependencies": {
29
29
  "@pnpm/find-workspace-dir": "^7.0.2",
30
30
  "@pnpm/workspace.find-packages": "^4.0.5",