project-compass 2.3.1 โ 2.4.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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/cli.js +28 -8
- package/src/projectDetection.js +43 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Project Compass (v2.
|
|
1
|
+
# Project Compass (v2.4.0)
|
|
2
2
|
|
|
3
3
|
Project Compass is a futuristic CLI navigator built with [Ink](https://github.com/vadimdemedes/ink) that scans your current folder tree for familiar code projects and gives you one-keystroke access to build, test, or run them.
|
|
4
4
|
|
|
@@ -9,6 +9,7 @@ Project Compass is a futuristic CLI navigator built with [Ink](https://github.co
|
|
|
9
9
|
- ๐ **New Keyboard-Centric UX**: Shortcuts now use **Shift** instead of Ctrl to avoid terminal interference.
|
|
10
10
|
- ๐ก **Refined Output**: Improved stdin buffer with proper spacing and reliable scrolling (Shift+โ/โ).
|
|
11
11
|
- ๐ง **Smart Detection**: Support for 20+ frameworks including **Spring Boot** (Maven/Gradle), **ASP.NET Core**, **Rocket/Actix** (Rust), **Laravel** (PHP), **Vite**, **Prisma**, and more.
|
|
12
|
+
- โ ๏ธ **Runtime Health**: Automatically checks if the required language/runtime (e.g., `node`, `python`, `cargo`) is installed and warns you if it's missing.
|
|
12
13
|
- ๐ **Extensible**: Add custom commands with **Shift+C** and frameworks via `plugins.json`.
|
|
13
14
|
|
|
14
15
|
## Installation
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -363,17 +363,23 @@ const projectRows = [];
|
|
|
363
363
|
projects.forEach((project, index) => {
|
|
364
364
|
const isSelected = index === selectedIndex;
|
|
365
365
|
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
366
|
+
const hasMissingRuntime = project.missingBinaries && project.missingBinaries.length > 0;
|
|
366
367
|
projectRows.push(
|
|
367
368
|
create(
|
|
368
369
|
Box,
|
|
369
370
|
{key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1},
|
|
370
371
|
create(
|
|
371
|
-
|
|
372
|
-
{
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
372
|
+
Box,
|
|
373
|
+
{flexDirection: 'row'},
|
|
374
|
+
create(
|
|
375
|
+
Text,
|
|
376
|
+
{
|
|
377
|
+
color: isSelected ? 'cyan' : 'white',
|
|
378
|
+
bold: isSelected
|
|
379
|
+
},
|
|
380
|
+
`${project.icon} ${project.name}`
|
|
381
|
+
),
|
|
382
|
+
hasMissingRuntime && create(Text, {color: 'red', bold: true}, ' โ ๏ธ Runtime missing')
|
|
377
383
|
),
|
|
378
384
|
create(Text, {dimColor: true}, ` ${project.type} ยท ${path.relative(rootPath, project.path) || '.'}`),
|
|
379
385
|
frameworkBadges && create(Text, {dimColor: true}, ` ${frameworkBadges}`)
|
|
@@ -385,7 +391,12 @@ const projectRows = [];
|
|
|
385
391
|
const detailContent = [];
|
|
386
392
|
if (viewMode === 'detail' && selectedProject) {
|
|
387
393
|
detailContent.push(
|
|
388
|
-
create(
|
|
394
|
+
create(
|
|
395
|
+
Box,
|
|
396
|
+
{flexDirection: 'row'},
|
|
397
|
+
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
398
|
+
selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' โ ๏ธ MISSING RUNTIME')
|
|
399
|
+
),
|
|
389
400
|
create(Text, {dimColor: true}, `${selectedProject.type} ยท ${selectedProject.manifest || 'detected manifest'}`),
|
|
390
401
|
create(Text, {dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
391
402
|
);
|
|
@@ -399,7 +410,16 @@ const projectRows = [];
|
|
|
399
410
|
if (selectedProject.extra?.scripts && selectedProject.extra.scripts.length) {
|
|
400
411
|
detailContent.push(create(Text, {dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
401
412
|
}
|
|
402
|
-
|
|
413
|
+
|
|
414
|
+
if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
|
|
415
|
+
detailContent.push(
|
|
416
|
+
create(Text, {color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
|
|
417
|
+
create(Text, {color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`),
|
|
418
|
+
create(Text, {dimColor: true}, 'Project commands may fail until these are in your PATH.')
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
detailContent.push(create(Text, {dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
403
423
|
detailContent.push(create(Text, {dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
404
424
|
detailContent.push(create(Text, {bold: true, marginTop: 1}, 'Commands'));
|
|
405
425
|
detailedIndexed.forEach((command) => {
|
package/src/projectDetection.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import {execSync} from 'child_process';
|
|
3
4
|
import fastGlob from 'fast-glob';
|
|
4
5
|
import {ensureConfigDir, PLUGIN_FILE} from './configPaths.js';
|
|
5
6
|
|
|
@@ -7,6 +8,16 @@ const IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/b
|
|
|
7
8
|
|
|
8
9
|
const PYTHON_ENTRY_FILES = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
|
|
9
10
|
|
|
11
|
+
function checkBinary(name) {
|
|
12
|
+
try {
|
|
13
|
+
const cmd = process.platform === 'win32' ? `where ${name}` : `which ${name}`;
|
|
14
|
+
execSync(cmd, {stdio: 'ignore'});
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
function findPythonEntry(projectPath) {
|
|
11
22
|
return PYTHON_ENTRY_FILES.find((file) => hasProjectFile(projectPath, file)) || null;
|
|
12
23
|
}
|
|
@@ -538,7 +549,9 @@ class SchemaRegistry {
|
|
|
538
549
|
icon: '๐ข',
|
|
539
550
|
priority: 100,
|
|
540
551
|
files: ['package.json'],
|
|
552
|
+
binaries: ['node', 'npm'],
|
|
541
553
|
async build(projectPath, manifest) {
|
|
554
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
542
555
|
const pkgPath = path.join(projectPath, 'package.json');
|
|
543
556
|
if (!fs.existsSync(pkgPath)) {
|
|
544
557
|
return null;
|
|
@@ -587,6 +600,7 @@ class SchemaRegistry {
|
|
|
587
600
|
metadata,
|
|
588
601
|
manifest: path.basename(manifest),
|
|
589
602
|
description: pkg.description || '',
|
|
603
|
+
missingBinaries,
|
|
590
604
|
extra: {
|
|
591
605
|
scripts: Object.keys(scripts),
|
|
592
606
|
setupHints
|
|
@@ -600,7 +614,9 @@ class SchemaRegistry {
|
|
|
600
614
|
icon: '๐',
|
|
601
615
|
priority: 95,
|
|
602
616
|
files: ['pyproject.toml', 'requirements.txt', 'setup.py', 'Pipfile'],
|
|
617
|
+
binaries: [process.platform === 'win32' ? 'python' : 'python3', 'pip'],
|
|
603
618
|
async build(projectPath, manifest) {
|
|
619
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
604
620
|
const commands = {};
|
|
605
621
|
if (hasProjectFile(projectPath, 'pyproject.toml')) {
|
|
606
622
|
commands.test = {label: 'Pytest', command: ['pytest']};
|
|
@@ -636,6 +652,7 @@ class SchemaRegistry {
|
|
|
636
652
|
metadata,
|
|
637
653
|
manifest: path.basename(manifest),
|
|
638
654
|
description: '',
|
|
655
|
+
missingBinaries,
|
|
639
656
|
extra: {
|
|
640
657
|
entry,
|
|
641
658
|
setupHints
|
|
@@ -649,7 +666,9 @@ class SchemaRegistry {
|
|
|
649
666
|
icon: '๐ฆ',
|
|
650
667
|
priority: 90,
|
|
651
668
|
files: ['Cargo.toml'],
|
|
669
|
+
binaries: ['cargo', 'rustc'],
|
|
652
670
|
async build(projectPath, manifest) {
|
|
671
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
653
672
|
return {
|
|
654
673
|
id: `${projectPath}::rust`,
|
|
655
674
|
path: projectPath,
|
|
@@ -665,6 +684,7 @@ class SchemaRegistry {
|
|
|
665
684
|
metadata: {},
|
|
666
685
|
manifest: path.basename(manifest),
|
|
667
686
|
description: '',
|
|
687
|
+
missingBinaries,
|
|
668
688
|
extra: {
|
|
669
689
|
setupHints: ['cargo fetch', 'Run cargo build before releasing']
|
|
670
690
|
}
|
|
@@ -677,7 +697,9 @@ class SchemaRegistry {
|
|
|
677
697
|
icon: '๐น',
|
|
678
698
|
priority: 85,
|
|
679
699
|
files: ['go.mod'],
|
|
700
|
+
binaries: ['go'],
|
|
680
701
|
async build(projectPath, manifest) {
|
|
702
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
681
703
|
return {
|
|
682
704
|
id: `${projectPath}::go`,
|
|
683
705
|
path: projectPath,
|
|
@@ -693,6 +715,7 @@ class SchemaRegistry {
|
|
|
693
715
|
metadata: {},
|
|
694
716
|
manifest: path.basename(manifest),
|
|
695
717
|
description: '',
|
|
718
|
+
missingBinaries,
|
|
696
719
|
extra: {
|
|
697
720
|
setupHints: ['go mod tidy', 'Ensure Go toolchain is installed']
|
|
698
721
|
}
|
|
@@ -705,7 +728,9 @@ class SchemaRegistry {
|
|
|
705
728
|
icon: 'โ๏ธ',
|
|
706
729
|
priority: 80,
|
|
707
730
|
files: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
|
|
731
|
+
binaries: ['java', 'javac'],
|
|
708
732
|
async build(projectPath, manifest) {
|
|
733
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
709
734
|
const hasMvnw = hasProjectFile(projectPath, 'mvnw');
|
|
710
735
|
const hasGradlew = hasProjectFile(projectPath, 'gradlew');
|
|
711
736
|
const commands = {};
|
|
@@ -731,6 +756,7 @@ class SchemaRegistry {
|
|
|
731
756
|
metadata: {},
|
|
732
757
|
manifest: path.basename(manifest),
|
|
733
758
|
description: '',
|
|
759
|
+
missingBinaries,
|
|
734
760
|
extra: {
|
|
735
761
|
setupHints: ['Install JDK 17+ and run ./mvnw install or ./gradlew build']
|
|
736
762
|
}
|
|
@@ -743,7 +769,9 @@ class SchemaRegistry {
|
|
|
743
769
|
icon: '๐ต',
|
|
744
770
|
priority: 70,
|
|
745
771
|
files: ['build.sbt'],
|
|
772
|
+
binaries: ['sbt', 'scala'],
|
|
746
773
|
async build(projectPath, manifest) {
|
|
774
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
747
775
|
return {
|
|
748
776
|
id: `${projectPath}::scala`,
|
|
749
777
|
path: projectPath,
|
|
@@ -759,6 +787,7 @@ class SchemaRegistry {
|
|
|
759
787
|
metadata: {},
|
|
760
788
|
manifest: path.basename(manifest),
|
|
761
789
|
description: '',
|
|
790
|
+
missingBinaries,
|
|
762
791
|
extra: {
|
|
763
792
|
setupHints: ['Ensure sbt is installed', 'Run sbt compile before running your app']
|
|
764
793
|
}
|
|
@@ -771,7 +800,9 @@ class SchemaRegistry {
|
|
|
771
800
|
icon: '๐',
|
|
772
801
|
priority: 65,
|
|
773
802
|
files: ['composer.json'],
|
|
803
|
+
binaries: ['php', 'composer'],
|
|
774
804
|
async build(projectPath, manifest) {
|
|
805
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
775
806
|
return {
|
|
776
807
|
id: `${projectPath}::php`,
|
|
777
808
|
path: projectPath,
|
|
@@ -785,6 +816,7 @@ class SchemaRegistry {
|
|
|
785
816
|
metadata: {},
|
|
786
817
|
manifest: path.basename(manifest),
|
|
787
818
|
description: '',
|
|
819
|
+
missingBinaries,
|
|
788
820
|
extra: {
|
|
789
821
|
setupHints: ['composer install to install dependencies']
|
|
790
822
|
}
|
|
@@ -797,7 +829,9 @@ class SchemaRegistry {
|
|
|
797
829
|
icon: '๐',
|
|
798
830
|
priority: 65,
|
|
799
831
|
files: ['Gemfile'],
|
|
832
|
+
binaries: ['ruby', 'bundle'],
|
|
800
833
|
async build(projectPath, manifest) {
|
|
834
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
801
835
|
return {
|
|
802
836
|
id: `${projectPath}::ruby`,
|
|
803
837
|
path: projectPath,
|
|
@@ -812,6 +846,7 @@ class SchemaRegistry {
|
|
|
812
846
|
metadata: {},
|
|
813
847
|
manifest: path.basename(manifest),
|
|
814
848
|
description: '',
|
|
849
|
+
missingBinaries,
|
|
815
850
|
extra: {
|
|
816
851
|
setupHints: ['bundle install to ensure gems are present']
|
|
817
852
|
}
|
|
@@ -824,7 +859,9 @@ class SchemaRegistry {
|
|
|
824
859
|
icon: '๐ท',
|
|
825
860
|
priority: 65,
|
|
826
861
|
files: ['*.csproj'],
|
|
862
|
+
binaries: ['dotnet'],
|
|
827
863
|
async build(projectPath, manifest) {
|
|
864
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
828
865
|
return {
|
|
829
866
|
id: `${projectPath}::dotnet`,
|
|
830
867
|
path: projectPath,
|
|
@@ -840,6 +877,7 @@ class SchemaRegistry {
|
|
|
840
877
|
metadata: {},
|
|
841
878
|
manifest: path.basename(manifest),
|
|
842
879
|
description: '',
|
|
880
|
+
missingBinaries,
|
|
843
881
|
extra: {
|
|
844
882
|
setupHints: ['Install .NET SDK 8+', 'dotnet restore before running']
|
|
845
883
|
}
|
|
@@ -852,7 +890,9 @@ class SchemaRegistry {
|
|
|
852
890
|
icon: '๐',
|
|
853
891
|
priority: 50,
|
|
854
892
|
files: ['Makefile', 'build.sh'],
|
|
893
|
+
binaries: ['make', 'sh'],
|
|
855
894
|
async build(projectPath, manifest) {
|
|
895
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
856
896
|
return {
|
|
857
897
|
id: `${projectPath}::shell`,
|
|
858
898
|
path: projectPath,
|
|
@@ -867,6 +907,7 @@ class SchemaRegistry {
|
|
|
867
907
|
metadata: {},
|
|
868
908
|
manifest: path.basename(manifest),
|
|
869
909
|
description: '',
|
|
910
|
+
missingBinaries,
|
|
870
911
|
extra: {
|
|
871
912
|
setupHints: ['Run make install if available', 'Ensure shell scripts are executable']
|
|
872
913
|
}
|
|
@@ -879,6 +920,7 @@ class SchemaRegistry {
|
|
|
879
920
|
icon: '๐งฐ',
|
|
880
921
|
priority: 10,
|
|
881
922
|
files: ['README.md'],
|
|
923
|
+
binaries: [],
|
|
882
924
|
async build(projectPath, manifest) {
|
|
883
925
|
return {
|
|
884
926
|
id: `${projectPath}::generic`,
|
|
@@ -891,6 +933,7 @@ class SchemaRegistry {
|
|
|
891
933
|
metadata: {},
|
|
892
934
|
manifest: path.basename(manifest),
|
|
893
935
|
description: 'Detected via README or Makefile layout.',
|
|
936
|
+
missingBinaries: [],
|
|
894
937
|
extra: {
|
|
895
938
|
setupHints: ['Read the README for custom build instructions']
|
|
896
939
|
}
|