@techninja/clearstack 0.4.5 → 0.4.6
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/lib/spec-config.js +6 -0
- package/lib/watch-ui.js +12 -15
- package/lib/watch-widgets.js +5 -1
- package/lib/watch.js +14 -0
- package/package.json +1 -1
package/lib/spec-config.js
CHANGED
|
@@ -96,6 +96,12 @@ export function buildCmds(projectDir, opts) {
|
|
|
96
96
|
prettier: `${runner} prettier --config .configs/.prettierrc --check src scripts`,
|
|
97
97
|
mdlint: `${runner} markdownlint-cli2 --config .configs/.markdownlint.jsonc "docs/**/*.md" "*.md"`,
|
|
98
98
|
audit,
|
|
99
|
+
fix: {
|
|
100
|
+
es: `${runner} eslint --config .configs/eslint.config.js . --fix`,
|
|
101
|
+
css: `${runner} stylelint --config .configs/.stylelintrc.json "src/**/*.css" --fix`,
|
|
102
|
+
prettier: `${runner} prettier --config .configs/.prettierrc --write src scripts`,
|
|
103
|
+
md: `${runner} markdownlint-cli2 --config .configs/.markdownlint.jsonc --fix "docs/**/*.md" "*.md"`,
|
|
104
|
+
},
|
|
99
105
|
};
|
|
100
106
|
return {
|
|
101
107
|
lint: `${runner} eslint --config .configs/eslint.config.js . --fix`,
|
package/lib/watch-ui.js
CHANGED
|
@@ -64,7 +64,11 @@ export function extractViolations(row, projectDir) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
if (row.result?.errors?.length) {
|
|
67
|
-
|
|
67
|
+
const errors = row.result.errors;
|
|
68
|
+
// Prettier: [warn] src/file.js — expand each into its own entry
|
|
69
|
+
const prettierFiles = errors.map((l) => l.match(/^\[warn\]\s+(.+\.\w+)$/)?.[1]).filter(Boolean);
|
|
70
|
+
if (prettierFiles.length) return prettierFiles.map((f) => [f, 'formatting — run npm run format']);
|
|
71
|
+
return [[row.label ?? row.name ?? row.key, errors.slice(0, 5).join('\n')]];
|
|
68
72
|
}
|
|
69
73
|
return [];
|
|
70
74
|
}
|
|
@@ -84,6 +88,9 @@ function rowIcon(r) {
|
|
|
84
88
|
return '{red-fg}!{/} ';
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/** @param {object} r @param {number} w */
|
|
92
|
+
const rowLine = (r, w) => { const n = (r.label ?? r.name ?? r.key).padEnd(w); return ` ${rowIcon(r)} ${r.pass === false ? `{red-fg}${n}{/}` : n} ${r.detail ? `{grey-fg}${r.detail}{/}` : ''}`; };
|
|
93
|
+
|
|
87
94
|
/**
|
|
88
95
|
* Render the dashboard.
|
|
89
96
|
* @param {object[]} rows
|
|
@@ -92,13 +99,8 @@ function rowIcon(r) {
|
|
|
92
99
|
* @param {string[][]} violations
|
|
93
100
|
*/
|
|
94
101
|
export function render(rows, lastCheck, watchDirs, violations) {
|
|
95
|
-
const
|
|
96
|
-
const statusLines = rows.map((r) =>
|
|
97
|
-
const name = (r.label ?? r.name ?? r.key).padEnd(labelWidth);
|
|
98
|
-
const styled = r.pass === false ? `{red-fg}${name}{/}` : name;
|
|
99
|
-
const detail = r.detail ? `{grey-fg}${r.detail}{/}` : '';
|
|
100
|
-
return ` ${rowIcon(r)} ${styled} ${detail}`;
|
|
101
|
-
});
|
|
102
|
+
const w = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
|
|
103
|
+
const statusLines = rows.map((r) => rowLine(r, w));
|
|
102
104
|
statusLines.push('');
|
|
103
105
|
statusLines.push(`{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`);
|
|
104
106
|
statusBox.setContent(statusLines.join('\n'));
|
|
@@ -137,12 +139,7 @@ export function destroyScreen() {
|
|
|
137
139
|
|
|
138
140
|
/** Refresh just the status box — used for transient notes like copy confirmation. */
|
|
139
141
|
export function renderNote(rows, lastCheck, watchDirs) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
...rows.map((r) => { const n = (r.label ?? r.name ?? r.key).padEnd(labelWidth); return ` ${rowIcon(r)} ${r.pass === false ? `{red-fg}${n}{/}` : n} ${r.detail ? `{grey-fg}${r.detail}{/}` : ''}`; }),
|
|
143
|
-
'',
|
|
144
|
-
`{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`,
|
|
145
|
-
];
|
|
146
|
-
statusBox.setContent(lines.join('\n'));
|
|
142
|
+
const w = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
|
|
143
|
+
statusBox.setContent([...rows.map((r) => rowLine(r, w)), '', `{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`].join('\n'));
|
|
147
144
|
screen.render();
|
|
148
145
|
}
|
package/lib/watch-widgets.js
CHANGED
|
@@ -15,7 +15,7 @@ export const screen = blessed.screen({ smartCSR: true, title: 'Clearstack Spec W
|
|
|
15
15
|
export const outer = blessed.box({
|
|
16
16
|
top: 0, left: 0, width: '100%', height: '100%',
|
|
17
17
|
border: { type: 'line' },
|
|
18
|
-
label: { text: ' {blue-fg}♥{/blue-fg} Clearstack Spec Watch q quit ↑↓ scroll c copy {/}', side: 'left' },
|
|
18
|
+
label: { text: ' {blue-fg}♥{/blue-fg} Clearstack Spec Watch q quit ↑↓ scroll c copy f fix {/}', side: 'left' },
|
|
19
19
|
tags: true,
|
|
20
20
|
style: { border: { fg: 'cyan' }, label: { fg: 'grey', bold: false } },
|
|
21
21
|
});
|
|
@@ -58,9 +58,13 @@ export const getCopyNote = () => _copyNote;
|
|
|
58
58
|
let _onCopyNote = () => {};
|
|
59
59
|
export const setOnCopyNote = (fn) => { _onCopyNote = fn; };
|
|
60
60
|
|
|
61
|
+
let _onFix = () => {};
|
|
62
|
+
export const setOnFix = (fn) => { _onFix = fn; };
|
|
63
|
+
|
|
61
64
|
screen.key(['C-c', 'q'], () => _quit());
|
|
62
65
|
screen.key(['pageup', 'up'], () => { logBox.scroll(-1); screen.render(); });
|
|
63
66
|
screen.key(['pagedown', 'down'], () => { logBox.scroll(1); screen.render(); });
|
|
67
|
+
screen.key(['f'], () => _onFix());
|
|
64
68
|
screen.key(['c'], () => {
|
|
65
69
|
const raw = logBox.getContent()
|
|
66
70
|
.replace(/\{[^}]+\}/g, '') // blessed tags
|
package/lib/watch.js
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { resolve } from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
10
11
|
import { loadConfig, buildCmds, makeExtMap, FAST_CHECKS } from './spec-config.js';
|
|
11
12
|
import { buildChecks } from './check.js';
|
|
12
13
|
import { render, extractViolations, renderNote } from './watch-ui.js';
|
|
13
14
|
import { spawnServer, detectServerCmd } from './server-proc.js';
|
|
14
15
|
import { setupLifecycle } from './watch-lifecycle.js';
|
|
16
|
+
import { setOnFix } from './watch-widgets.js';
|
|
15
17
|
|
|
16
18
|
/** Adapt a Check from buildChecks into a watch row (adds pass/detail/result state). */
|
|
17
19
|
function toRow(check) {
|
|
@@ -123,6 +125,18 @@ export async function startWatch(projectDir) {
|
|
|
123
125
|
|
|
124
126
|
runKeys(new Set(specRows.map((r) => r.key)));
|
|
125
127
|
|
|
128
|
+
// f key — run fix commands for all currently-failing fixable checks
|
|
129
|
+
setOnFix(() => {
|
|
130
|
+
const fixable = specRows.filter((r) => !r.pass && cmds.fix?.[r.key]);
|
|
131
|
+
if (!fixable.length) return;
|
|
132
|
+
for (const row of fixable) {
|
|
133
|
+
try { execSync(cmds.fix[row.key], { cwd: projectDir, stdio: 'pipe' }); } catch { /* linter may exit 1 even after fixing */ }
|
|
134
|
+
slowPending.add(row.key);
|
|
135
|
+
}
|
|
136
|
+
clearTimeout(slowTimer);
|
|
137
|
+
slowTimer = setTimeout(runSlow, 100);
|
|
138
|
+
});
|
|
139
|
+
|
|
126
140
|
setupLifecycle({
|
|
127
141
|
serverRow, specRows, projectDir, watchDirs,
|
|
128
142
|
schedule,
|