project-compass 3.2.1 → 3.3.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +31 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Ink-based project explorer that detects local repos and lets you build/test/run them without memorizing commands.",
5
5
  "main": "src/cli.js",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -152,7 +152,7 @@ const TaskManager = memo(({tasks, activeTaskId, renameMode, renameInput, renameC
152
152
  Box,
153
153
  {flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
154
154
  create(Text, {bold: true, color: 'yellow'}, '🛰️ Task Manager | Background Processes'),
155
- create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Kill/Delete, Shift+R: Rename'),
155
+ create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Force Kill, Shift+R: Rename'),
156
156
  ...tasks.map(t => create(
157
157
  Box,
158
158
  {key: t.id, marginBottom: 0, flexDirection: 'column'},
@@ -225,22 +225,27 @@ function Compass({rootPath, initialView = 'navigator'}) {
225
225
  const [stdinBuffer, setStdinBuffer] = useState('');
226
226
  const [stdinCursor, setStdinCursor] = useState(0);
227
227
  const [showHelp, setShowHelp] = useState(false);
228
- const selectedProject = projects[selectedIndex] || null;
229
228
  const runningProcessMap = useRef(new Map());
230
229
  const lastCommandRef = useRef(null);
231
230
 
232
231
  const activeTask = useMemo(() => tasks.find(t => t.id === activeTaskId), [tasks, activeTaskId]);
233
232
  const running = activeTask?.status === 'running';
234
233
  const hasRunningTasks = useMemo(() => tasks.some(t => t.status === 'running'), [tasks]);
234
+ const selectedProject = useMemo(() => projects[selectedIndex] || null, [projects, selectedIndex]);
235
235
 
236
236
  const addLogToTask = useCallback((taskId, line) => {
237
- setTasks(prev => prev.map(t => {
238
- if (t.id !== taskId) return t;
237
+ setTasks(prev => {
238
+ const idx = prev.findIndex(t => t.id === taskId);
239
+ if (idx === -1) return prev;
240
+ const t = prev[idx];
239
241
  const normalized = typeof line === 'string' ? line : JSON.stringify(line);
240
- const lines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
241
- const nextLogs = [...t.logs, ...lines];
242
- return { ...t, logs: nextLogs.length > 500 ? nextLogs.slice(-500) : nextLogs };
243
- }));
242
+ const newLines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
243
+ const nextLogs = [...t.logs, ...newLines];
244
+ const updatedTask = { ...t, logs: nextLogs.length > 500 ? nextLogs.slice(-500) : nextLogs };
245
+ const nextTasks = [...prev];
246
+ nextTasks[idx] = updatedTask;
247
+ return nextTasks;
248
+ });
244
249
  }, []);
245
250
 
246
251
  const detailedIndexed = useMemo(() => buildDetailCommands(selectedProject, config).map((command, index) => ({
@@ -256,7 +261,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
256
261
 
257
262
  const killAllTasks = useCallback(() => {
258
263
  runningProcessMap.current.forEach((proc) => {
259
- try { proc.kill('SIGINT'); } catch { /* ignore */ }
264
+ try { proc.kill('SIGKILL'); } catch { /* ignore */ }
260
265
  });
261
266
  runningProcessMap.current.clear();
262
267
  }, []);
@@ -284,7 +289,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
284
289
  const subprocess = execa(commandMeta.command[0], commandMeta.command.slice(1), {
285
290
  cwd: project.path,
286
291
  env: process.env,
287
- stdin: 'pipe'
292
+ stdin: 'pipe',
293
+ cleanup: true
288
294
  });
289
295
  runningProcessMap.current.set(taskId, subprocess);
290
296
 
@@ -295,9 +301,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
295
301
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'finished'} : t));
296
302
  addLogToTask(taskId, kleur.green(`✓ ${commandLabel} finished`));
297
303
  } catch (error) {
298
- if (error.isCanceled || error.killed) {
304
+ if (error.isCanceled || error.killed || error.signal === 'SIGKILL' || error.signal === 'SIGINT') {
299
305
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'killed'} : t));
300
- addLogToTask(taskId, kleur.yellow(`! Task killed by user`));
306
+ addLogToTask(taskId, kleur.yellow(`! Task killed forcefully`));
301
307
  } else {
302
308
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
303
309
  addLogToTask(taskId, kleur.red(`✗ ${commandLabel} failed: ${error.shortMessage || error.message}`));
@@ -310,7 +316,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
310
316
  const handleKillTask = useCallback((taskId) => {
311
317
  const proc = runningProcessMap.current.get(taskId);
312
318
  if (proc) {
313
- proc.kill('SIGINT');
319
+ proc.kill('SIGKILL');
314
320
  } else {
315
321
  setTasks(prev => prev.filter(t => t.id !== taskId));
316
322
  if (activeTaskId === taskId) setActiveTaskId(null);
@@ -318,15 +324,16 @@ function Compass({rootPath, initialView = 'navigator'}) {
318
324
  }, [activeTaskId]);
319
325
 
320
326
  const exportLogs = useCallback(() => {
321
- if (!activeTask || !activeTask.logs.length) return;
327
+ const taskToExport = tasks.find(t => t.id === activeTaskId);
328
+ if (!taskToExport || !taskToExport.logs.length) return;
322
329
  try {
323
- const exportPath = path.resolve(process.cwd(), `compass-${activeTask.id}.txt`);
324
- fs.writeFileSync(exportPath, activeTask.logs.join('\n'));
330
+ const exportPath = path.resolve(process.cwd(), `compass-${taskToExport.id}.txt`);
331
+ fs.writeFileSync(exportPath, taskToExport.logs.join('\n'));
325
332
  addLogToTask(activeTaskId, kleur.green(`✓ Logs exported to ${exportPath}`));
326
333
  } catch {
327
334
  addLogToTask(activeTaskId, kleur.red('✗ Export failed'));
328
335
  }
329
- }, [activeTask, activeTaskId, addLogToTask]);
336
+ }, [tasks, activeTaskId, addLogToTask]);
330
337
 
331
338
  useInput((input, key) => {
332
339
  if (quitConfirm) {
@@ -338,13 +345,14 @@ function Compass({rootPath, initialView = 'navigator'}) {
338
345
  if (customMode) {
339
346
  if (key.return) {
340
347
  const raw = customInput.trim();
341
- if (selectedProject && raw) {
348
+ const selProj = selectedProject;
349
+ if (selProj && raw) {
342
350
  const [labelPart, commandPart] = raw.split('|');
343
351
  const commandTokens = (commandPart || labelPart).trim().split(/\s+/).filter(Boolean);
344
352
  if (commandTokens.length) {
345
- const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
353
+ const label = commandPart ? labelPart.trim() : `Custom ${selProj.name}`;
346
354
  setConfig((prev) => {
347
- const projectKey = selectedProject.path;
355
+ const projectKey = selProj.path;
348
356
  const existing = prev.customCommands?.[projectKey] || [];
349
357
  const nextConfig = { ...prev, customCommands: { ...prev.customCommands, [projectKey]: [...existing, {label, command: commandTokens}] } };
350
358
  saveConfig(nextConfig);
@@ -448,11 +456,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
448
456
  if (tasks.length > 0) {
449
457
  if (key.upArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) - 1 + tasks.length) % tasks.length]?.id); return; }
450
458
  if (key.downArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) + 1) % tasks.length]?.id); return; }
451
- if (shiftCombo('k') && activeTaskId) {
452
- handleKillTask(activeTaskId);
453
- return;
454
- }
459
+ if (shiftCombo('k') && activeTaskId) { handleKillTask(activeTaskId); return; }
455
460
  if (shiftCombo('r') && activeTaskId) { setRenameMode(true); setRenameInput(activeTask.name); setRenameCursor(activeTask.name.length); return; }
461
+ if (key.ctrl && input === 'c') { handleKillTask(activeTaskId); return; }
456
462
  }
457
463
  if (key.return) { setMainView('navigator'); return; }
458
464
  return;
@@ -460,7 +466,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
460
466
 
461
467
  if (running && activeTaskId && runningProcessMap.current.has(activeTaskId)) {
462
468
  const proc = runningProcessMap.current.get(activeTaskId);
463
- if (key.ctrl && input === 'c') { proc.kill('SIGINT'); setStdinBuffer(''); setStdinCursor(0); return; }
469
+ if (key.ctrl && input === 'c') { proc.kill('SIGKILL'); setStdinBuffer(''); setStdinCursor(0); return; }
464
470
  if (key.return) { proc.stdin?.write(stdinBuffer + '\n'); setStdinBuffer(''); setStdinCursor(0); return; }
465
471
  if (key.backspace || key.delete) {
466
472
  if (stdinCursor > 0) {