project-compass 2.3.1 → 2.5.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 +10 -2
- package/package.json +1 -1
- package/src/cli.js +114 -19
- package/src/projectDetection.js +44 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Project Compass (v2.
|
|
1
|
+
# Project Compass (v2.5.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,8 @@ 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.
|
|
13
|
+
- 💎 **Omni-Studio**: A new interactive environment intelligence mode to see all installed runtimes and versions.
|
|
12
14
|
- 🔌 **Extensible**: Add custom commands with **Shift+C** and frameworks via `plugins.json`.
|
|
13
15
|
|
|
14
16
|
## Installation
|
|
@@ -20,7 +22,7 @@ npm install -g project-compass
|
|
|
20
22
|
## Usage
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
|
-
project-compass [--dir /path/to/workspace]
|
|
25
|
+
project-compass [--dir /path/to/workspace] [--studio]
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
### Keyboard Guide
|
|
@@ -30,7 +32,9 @@ project-compass [--dir /path/to/workspace]
|
|
|
30
32
|
| ↑ / ↓ | Move focus, **Enter**: toggle details |
|
|
31
33
|
| B / T / R | Build / Test / Run |
|
|
32
34
|
| 1‑9 | Execute numbered detail commands |
|
|
35
|
+
| **Shift+A** | Open **Omni-Studio** (Environment View) |
|
|
33
36
|
| **Shift+C** | Add a custom command (`label|cmd`) |
|
|
37
|
+
| **Shift+X** | **Clear output logs** |
|
|
34
38
|
| **Shift ↑ / ↓** | Scroll output buffer |
|
|
35
39
|
| **Shift+L** | Rerun last command |
|
|
36
40
|
| **Shift+H** | Toggle help cards |
|
|
@@ -39,6 +43,10 @@ project-compass [--dir /path/to/workspace]
|
|
|
39
43
|
| ? | Toggle help overlay |
|
|
40
44
|
| Ctrl+C | Interrupt running command |
|
|
41
45
|
|
|
46
|
+
## Omni-Studio
|
|
47
|
+
|
|
48
|
+
Launch with `project-compass --studio` or press **Shift+A** inside the app. Omni-Studio provides real-time intelligence on your installed development environments, checking versions for Node, Python, Rust, Go, Java, and more.
|
|
49
|
+
|
|
42
50
|
## Layout & UX
|
|
43
51
|
|
|
44
52
|
Project Compass features a split layout where Projects and Details stay paired while Output takes a full-width band. The stdin buffer (at the bottom) now has a clear distinction between the label and your input for better readability. The help cards (Shift+H) have been refactored for a cleaner, more readable look.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import kleur from 'kleur';
|
|
7
7
|
import {execa} from 'execa';
|
|
8
|
-
import {discoverProjects, SCHEMA_GUIDE} from './projectDetection.js';
|
|
8
|
+
import {discoverProjects, SCHEMA_GUIDE, checkBinary} from './projectDetection.js';
|
|
9
9
|
import {CONFIG_PATH, PLUGIN_FILE, ensureConfigDir} from './configPaths.js';
|
|
10
10
|
|
|
11
11
|
const create = React.createElement;
|
|
@@ -95,11 +95,69 @@ function buildDetailCommands(project, config) {
|
|
|
95
95
|
return [...builtins, ...custom];
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function
|
|
98
|
+
function Studio() {
|
|
99
|
+
const [runtimes, setRuntimes] = useState([]);
|
|
100
|
+
const [loading, setLoading] = useState(true);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const checks = [
|
|
104
|
+
{name: 'Node.js', binary: 'node', versionCmd: ['-v']},
|
|
105
|
+
{name: 'npm', binary: 'npm', versionCmd: ['-v']},
|
|
106
|
+
{name: 'Python', binary: process.platform === 'win32' ? 'python' : 'python3', versionCmd: ['--version']},
|
|
107
|
+
{name: 'Rust (Cargo)', binary: 'cargo', versionCmd: ['--version']},
|
|
108
|
+
{name: 'Go', binary: 'go', versionCmd: ['version']},
|
|
109
|
+
{name: 'Java', binary: 'java', versionCmd: ['-version']},
|
|
110
|
+
{name: 'PHP', binary: 'php', versionCmd: ['-v']},
|
|
111
|
+
{name: 'Ruby', binary: 'ruby', versionCmd: ['-v']},
|
|
112
|
+
{name: '.NET', binary: 'dotnet', versionCmd: ['--version']}
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
(async () => {
|
|
116
|
+
const results = await Promise.all(checks.map(async (lang) => {
|
|
117
|
+
if (!checkBinary(lang.binary)) {
|
|
118
|
+
return {...lang, status: 'missing', version: 'not installed'};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const {stdout, stderr} = await execa(lang.binary, lang.versionCmd);
|
|
122
|
+
const version = (stdout || stderr || '').split('\n')[0].trim();
|
|
123
|
+
return {...lang, status: 'ok', version};
|
|
124
|
+
} catch {
|
|
125
|
+
return {...lang, status: 'error', version: 'failed to check'};
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
setRuntimes(results);
|
|
129
|
+
setLoading(false);
|
|
130
|
+
})();
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return create(
|
|
134
|
+
Box,
|
|
135
|
+
{flexDirection: 'column', borderStyle: 'double', borderColor: 'blue', padding: 1},
|
|
136
|
+
create(Text, {bold: true, color: 'blue'}, '💎 Omni-Studio | Environment Intelligence'),
|
|
137
|
+
create(Text, {dimColor: true, marginBottom: 1}, 'Overview of installed languages and build tools.'),
|
|
138
|
+
loading
|
|
139
|
+
? create(Text, {dimColor: true}, 'Gathering intelligence...')
|
|
140
|
+
: create(
|
|
141
|
+
Box,
|
|
142
|
+
{flexDirection: 'column'},
|
|
143
|
+
...runtimes.map(r => create(
|
|
144
|
+
Box,
|
|
145
|
+
{key: r.name, marginBottom: 1},
|
|
146
|
+
create(Text, {width: 15, color: r.status === 'ok' ? 'green' : 'red'}, `${r.status === 'ok' ? '✓' : '✗'} ${r.name}`),
|
|
147
|
+
create(Text, {dimColor: r.status !== 'ok'}, r.version)
|
|
148
|
+
)),
|
|
149
|
+
create(Text, {marginTop: 1, color: 'yellow'}, '🛠️ Interactive Project Creator coming soon in v3.0'),
|
|
150
|
+
create(Text, {dimColor: true}, 'Press Shift+A to return to Navigator.')
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Compass({rootPath, initialView = 'navigator'}) {
|
|
99
156
|
const {exit} = useApp();
|
|
100
157
|
const {projects, loading, error} = useScanner(rootPath);
|
|
101
158
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
102
159
|
const [viewMode, setViewMode] = useState('list');
|
|
160
|
+
const [mainView, setMainView] = useState(initialView);
|
|
103
161
|
const [logLines, setLogLines] = useState([]);
|
|
104
162
|
const [logOffset, setLogOffset] = useState(0);
|
|
105
163
|
const [running, setRunning] = useState(false);
|
|
@@ -120,7 +178,7 @@ function Compass({rootPath}) {
|
|
|
120
178
|
setLogLines((prev) => {
|
|
121
179
|
const normalized = typeof line === 'string' ? line : JSON.stringify(line);
|
|
122
180
|
const appended = [...prev, normalized];
|
|
123
|
-
const next = appended.length >
|
|
181
|
+
const next = appended.length > 500 ? appended.slice(appended.length - 500) : appended;
|
|
124
182
|
return next;
|
|
125
183
|
});
|
|
126
184
|
}, []);
|
|
@@ -256,6 +314,7 @@ function Compass({rootPath}) {
|
|
|
256
314
|
}
|
|
257
315
|
|
|
258
316
|
const normalizedInput = input?.toLowerCase();
|
|
317
|
+
const ctrlCombo = (char) => key.ctrl && normalizedInput === char;
|
|
259
318
|
const shiftCombo = (char) => key.shift && normalizedInput === char;
|
|
260
319
|
const toggleShortcut = (char) => shiftCombo(char);
|
|
261
320
|
if (toggleShortcut('h')) {
|
|
@@ -266,6 +325,15 @@ function Compass({rootPath}) {
|
|
|
266
325
|
setShowStructureGuide((prev) => !prev);
|
|
267
326
|
return;
|
|
268
327
|
}
|
|
328
|
+
if (toggleShortcut('a')) {
|
|
329
|
+
setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator'));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (toggleShortcut('x')) {
|
|
333
|
+
setLogLines([]);
|
|
334
|
+
setLogOffset(0);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
269
337
|
|
|
270
338
|
const scrollLogs = (delta) => {
|
|
271
339
|
setLogOffset((prev) => {
|
|
@@ -349,6 +417,11 @@ function Compass({rootPath}) {
|
|
|
349
417
|
runProjectCommand(detailShortcutMap.get(normalizedInput), selectedProject);
|
|
350
418
|
}
|
|
351
419
|
});
|
|
420
|
+
|
|
421
|
+
if (mainView === 'studio') {
|
|
422
|
+
return create(Studio);
|
|
423
|
+
}
|
|
424
|
+
|
|
352
425
|
const projectRows = [];
|
|
353
426
|
if (loading) {
|
|
354
427
|
projectRows.push(create(Text, {dimColor: true}, 'Scanning projects…'));
|
|
@@ -363,17 +436,23 @@ const projectRows = [];
|
|
|
363
436
|
projects.forEach((project, index) => {
|
|
364
437
|
const isSelected = index === selectedIndex;
|
|
365
438
|
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
439
|
+
const hasMissingRuntime = project.missingBinaries && project.missingBinaries.length > 0;
|
|
366
440
|
projectRows.push(
|
|
367
441
|
create(
|
|
368
442
|
Box,
|
|
369
443
|
{key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1},
|
|
370
444
|
create(
|
|
371
|
-
|
|
372
|
-
{
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
445
|
+
Box,
|
|
446
|
+
{flexDirection: 'row'},
|
|
447
|
+
create(
|
|
448
|
+
Text,
|
|
449
|
+
{
|
|
450
|
+
color: isSelected ? 'cyan' : 'white',
|
|
451
|
+
bold: isSelected
|
|
452
|
+
},
|
|
453
|
+
`${project.icon} ${project.name}`
|
|
454
|
+
),
|
|
455
|
+
hasMissingRuntime && create(Text, {color: 'red', bold: true}, ' ⚠️ Runtime missing')
|
|
377
456
|
),
|
|
378
457
|
create(Text, {dimColor: true}, ` ${project.type} · ${path.relative(rootPath, project.path) || '.'}`),
|
|
379
458
|
frameworkBadges && create(Text, {dimColor: true}, ` ${frameworkBadges}`)
|
|
@@ -385,7 +464,12 @@ const projectRows = [];
|
|
|
385
464
|
const detailContent = [];
|
|
386
465
|
if (viewMode === 'detail' && selectedProject) {
|
|
387
466
|
detailContent.push(
|
|
388
|
-
create(
|
|
467
|
+
create(
|
|
468
|
+
Box,
|
|
469
|
+
{flexDirection: 'row'},
|
|
470
|
+
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
471
|
+
selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' ⚠️ MISSING RUNTIME')
|
|
472
|
+
),
|
|
389
473
|
create(Text, {dimColor: true}, `${selectedProject.type} · ${selectedProject.manifest || 'detected manifest'}`),
|
|
390
474
|
create(Text, {dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
391
475
|
);
|
|
@@ -399,7 +483,16 @@ const projectRows = [];
|
|
|
399
483
|
if (selectedProject.extra?.scripts && selectedProject.extra.scripts.length) {
|
|
400
484
|
detailContent.push(create(Text, {dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
401
485
|
}
|
|
402
|
-
|
|
486
|
+
|
|
487
|
+
if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
|
|
488
|
+
detailContent.push(
|
|
489
|
+
create(Text, {color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
|
|
490
|
+
create(Text, {color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`),
|
|
491
|
+
create(Text, {dimColor: true}, 'Project commands may fail until these are in your PATH.')
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
detailContent.push(create(Text, {dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
403
496
|
detailContent.push(create(Text, {dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
404
497
|
detailContent.push(create(Text, {bold: true, marginTop: 1}, 'Commands'));
|
|
405
498
|
detailedIndexed.forEach((command) => {
|
|
@@ -529,14 +622,14 @@ const projectRows = [];
|
|
|
529
622
|
'B / T / R build/test/run',
|
|
530
623
|
'1-9 run detail commands',
|
|
531
624
|
'Shift+L rerun last command',
|
|
532
|
-
'
|
|
625
|
+
'Shift+X clear output logs'
|
|
533
626
|
]
|
|
534
627
|
},
|
|
535
628
|
{
|
|
536
|
-
label: '
|
|
629
|
+
label: 'System & Studio',
|
|
537
630
|
color: 'yellow',
|
|
538
631
|
body: [
|
|
539
|
-
|
|
632
|
+
'Shift+A open Omni-Studio',
|
|
540
633
|
'Shift+S toggle structure guide',
|
|
541
634
|
'Shift+C save custom action',
|
|
542
635
|
'Shift+Q quit application'
|
|
@@ -600,11 +693,11 @@ const projectRows = [];
|
|
|
600
693
|
padding: 1
|
|
601
694
|
},
|
|
602
695
|
create(Text, {color: 'cyan', bold: true}, 'Help overlay · press ? to hide'),
|
|
603
|
-
create(Text, null, 'Shift+↑/↓ scrolls the log buffer
|
|
696
|
+
create(Text, null, 'Shift+↑/↓ scrolls the log buffer; Shift+X clears logs; Shift+A opens Omni-Studio.'),
|
|
604
697
|
create(Text, null, 'B/T/R run build/test/run; 1-9 executes detail commands; Shift+L reruns the previous command.'),
|
|
605
|
-
create(Text, null, 'Shift+H toggles
|
|
698
|
+
create(Text, null, 'Shift+H toggles help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
|
|
606
699
|
create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
|
|
607
|
-
create(Text, null, 'Structure guide lists the manifests that trigger each language detection
|
|
700
|
+
create(Text, null, 'Structure guide lists the manifests that trigger each language detection.')
|
|
608
701
|
)
|
|
609
702
|
: null;
|
|
610
703
|
|
|
@@ -731,6 +824,8 @@ function parseArgs() {
|
|
|
731
824
|
i += 1;
|
|
732
825
|
} else if (token === '--help' || token === '-h') {
|
|
733
826
|
args.help = true;
|
|
827
|
+
} else if (token === '--studio') {
|
|
828
|
+
args.view = 'studio';
|
|
734
829
|
}
|
|
735
830
|
}
|
|
736
831
|
return args;
|
|
@@ -740,7 +835,7 @@ async function main() {
|
|
|
740
835
|
const args = parseArgs();
|
|
741
836
|
if (args.help) {
|
|
742
837
|
console.log('Project Compass · Ink project runner');
|
|
743
|
-
console.log('Usage: project-compass [--dir <path>] [--mode test]');
|
|
838
|
+
console.log('Usage: project-compass [--dir <path>] [--mode test] [--studio]');
|
|
744
839
|
return;
|
|
745
840
|
}
|
|
746
841
|
const rootPath = args.root ? path.resolve(args.root) : process.cwd();
|
|
@@ -753,7 +848,7 @@ async function main() {
|
|
|
753
848
|
return;
|
|
754
849
|
}
|
|
755
850
|
|
|
756
|
-
render(create(Compass, {rootPath}));
|
|
851
|
+
render(create(Compass, {rootPath, initialView: args.view || 'navigator'}));
|
|
757
852
|
}
|
|
758
853
|
|
|
759
854
|
main().catch((error) => {
|
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
|
}
|
|
@@ -1055,4 +1098,4 @@ const SCHEMA_GUIDE = schemaRegistry.getSchemas().map((schema) => ({
|
|
|
1055
1098
|
files: schema.files
|
|
1056
1099
|
}));
|
|
1057
1100
|
|
|
1058
|
-
export {discoverProjects, SCHEMA_GUIDE};
|
|
1101
|
+
export {discoverProjects, SCHEMA_GUIDE, checkBinary};
|