project-compass 4.3.6 → 4.3.8
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/AGENTS.md +1121 -22
- package/ARCHITECTURE.md +826 -10
- package/CONTRIBUTING.md +974 -10
- package/PROJECT_CONTEXT.md +404 -0
- package/README.md +637 -67
- package/assets/screenshots/ai_mode.png +0 -0
- package/assets/screenshots/art_bar.png +0 -0
- package/assets/screenshots/help_structure.png +0 -0
- package/assets/screenshots/home.png +0 -0
- package/assets/screenshots/languages_checker_omni_studio.png +0 -0
- package/assets/screenshots/task_manager.png +0 -0
- package/commands.md +841 -104
- package/package.json +5 -4
- package/src/cli.js +310 -169
- package/src/components/AIHorizon.js +138 -255
- package/src/components/Footer.js +8 -64
- package/src/components/Header.js +8 -47
- package/src/components/Navigator.js +16 -70
- package/src/components/PackageRegistry.js +4 -3
- package/src/components/ProjectArchitect.js +6 -1
- package/src/components/TaskManager.js +12 -70
- package/src/detectors/dotnet.js +3 -2
- package/src/detectors/frameworks.js +28 -28
- package/src/detectors/go.js +6 -6
- package/src/detectors/java.js +2 -1
- package/src/detectors/node.js +3 -2
- package/src/detectors/php.js +1 -1
- package/src/detectors/python.js +11 -4
- package/src/detectors/ruby.js +1 -1
- package/src/detectors/rust.js +2 -2
- package/src/detectors/utils.js +6 -7
- package/src/projectDetection.js +41 -5
- package/assets/screenshots/architect.jpg +0 -0
- package/assets/screenshots/artboard.jpg +0 -0
- package/assets/screenshots/exit-confirm.jpg +0 -0
- package/assets/screenshots/navigator.jpg +0 -0
- package/assets/screenshots/overlays.jpg +0 -0
- package/assets/screenshots/registry.jpg +0 -0
- package/assets/screenshots/studio.jpg +0 -0
- package/assets/screenshots/taskmanager.jpg +0 -0
- package/src/store/useProjectStore.js +0 -32
package/src/components/Header.js
CHANGED
|
@@ -1,60 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import path from 'path';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export default function Header({ projectCountLabel, rootPath, running, toggleHint, orbitHint }) {
|
|
8
|
-
const time = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
9
|
-
const spark = SPARKS[Math.floor(Date.now() / 1000) % SPARKS.length];
|
|
10
|
-
|
|
4
|
+
export default function Header({ projectCountLabel, rootPath, running, statusHint, toggleHint, orbitHint, artHint }) {
|
|
11
5
|
return React.createElement(
|
|
12
6
|
Box,
|
|
13
|
-
{
|
|
14
|
-
// Top bar with logo and status
|
|
15
|
-
React.createElement(
|
|
16
|
-
Box,
|
|
17
|
-
{ justifyContent: 'space-between', alignItems: 'center' },
|
|
18
|
-
React.createElement(
|
|
19
|
-
Box,
|
|
20
|
-
{ flexDirection: 'row', alignItems: 'center' },
|
|
21
|
-
React.createElement(Text, { color: 'magenta', bold: true }, '🧭 '),
|
|
22
|
-
React.createElement(Text, { color: 'magenta', bold: true }, 'PROJECT COMPASS'),
|
|
23
|
-
React.createElement(Text, { color: 'cyan' }, ` ${spark}`)
|
|
24
|
-
),
|
|
25
|
-
React.createElement(
|
|
26
|
-
Box,
|
|
27
|
-
{ flexDirection: 'row', alignItems: 'center' },
|
|
28
|
-
React.createElement(Text, {
|
|
29
|
-
color: running ? 'yellow' : 'green',
|
|
30
|
-
bold: true
|
|
31
|
-
}, running ? '⚡ ACTIVE' : '✓ IDLE'),
|
|
32
|
-
React.createElement(Text, { dimColor: true }, ` ${time}`)
|
|
33
|
-
)
|
|
34
|
-
),
|
|
35
|
-
// Info bar
|
|
7
|
+
{ justifyContent: 'space-between' },
|
|
36
8
|
React.createElement(
|
|
37
9
|
Box,
|
|
38
|
-
{
|
|
39
|
-
React.createElement(
|
|
40
|
-
|
|
41
|
-
{ flexDirection: 'row' },
|
|
42
|
-
React.createElement(Text, { dimColor: true }, `${spark} `),
|
|
43
|
-
React.createElement(Text, { color: 'cyan' }, projectCountLabel),
|
|
44
|
-
React.createElement(Text, { dimColor: true }, ' in '),
|
|
45
|
-
React.createElement(Text, { color: 'white' }, path.basename(rootPath))
|
|
46
|
-
),
|
|
47
|
-
React.createElement(
|
|
48
|
-
Box,
|
|
49
|
-
{ flexDirection: 'row' },
|
|
50
|
-
React.createElement(Text, { dimColor: true }, `${toggleHint} · ${orbitHint}`)
|
|
51
|
-
)
|
|
10
|
+
{ flexDirection: 'column' },
|
|
11
|
+
React.createElement(Text, { color: 'magenta', bold: true }, 'Project Compass'),
|
|
12
|
+
React.createElement(Text, { dimColor: true }, `${projectCountLabel} detected in ${rootPath}`)
|
|
52
13
|
),
|
|
53
|
-
// Separator
|
|
54
14
|
React.createElement(
|
|
55
15
|
Box,
|
|
56
|
-
{
|
|
57
|
-
React.createElement(Text, {
|
|
16
|
+
{ flexDirection: 'column', alignItems: 'flex-end' },
|
|
17
|
+
React.createElement(Text, { color: running ? 'yellow' : 'green' }, statusHint),
|
|
18
|
+
React.createElement(Text, { dimColor: true }, `${toggleHint} · ${orbitHint} · ${artHint} · Shift+Q: Quit`)
|
|
58
19
|
)
|
|
59
20
|
);
|
|
60
21
|
}
|
|
@@ -1,102 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
import React, { useMemo, useState, useEffect } from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
3
2
|
import { Box, Text } from 'ink';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
|
|
6
|
-
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
|
-
const SELECTION_BAR = '█';
|
|
8
|
-
|
|
9
5
|
export default function Navigator({
|
|
10
6
|
projects,
|
|
11
7
|
selectedIndex,
|
|
12
8
|
rootPath,
|
|
13
9
|
loading,
|
|
14
10
|
error,
|
|
15
|
-
maxVisibleProjects = 3
|
|
11
|
+
maxVisibleProjects = 3
|
|
16
12
|
}) {
|
|
17
|
-
const [tick, setTick] = useState(0);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!loading) return;
|
|
21
|
-
const timer = setInterval(() => setTick(t => t + 1), 100);
|
|
22
|
-
return () => clearInterval(timer);
|
|
23
|
-
}, [loading]);
|
|
24
|
-
|
|
25
13
|
const page = Math.floor(selectedIndex / maxVisibleProjects);
|
|
26
14
|
const start = page * maxVisibleProjects;
|
|
27
15
|
const end = start + maxVisibleProjects;
|
|
28
16
|
const visibleProjects = projects.slice(start, end);
|
|
29
|
-
|
|
17
|
+
|
|
30
18
|
const projectRows = useMemo(() => {
|
|
31
|
-
if (loading) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Box,
|
|
35
|
-
{ key: 'scanning', flexDirection: 'row', alignItems: 'center' },
|
|
36
|
-
React.createElement(Text, { color: 'cyan' }, ` ${spinner} `),
|
|
37
|
-
React.createElement(Text, { dimColor: true }, 'Scanning projects…'),
|
|
38
|
-
React.createElement(Text, { color: 'cyan' }, ' ●'.repeat(3 + (tick % 3)))
|
|
39
|
-
)];
|
|
40
|
-
}
|
|
41
|
-
if (error) return [React.createElement(
|
|
42
|
-
Box,
|
|
43
|
-
{ key: 'error', flexDirection: 'row', alignItems: 'center' },
|
|
44
|
-
React.createElement(Text, { color: 'red', bold: true }, ' ✗ '),
|
|
45
|
-
React.createElement(Text, { color: 'red' }, `Unable to scan: ${error}`)
|
|
46
|
-
)];
|
|
47
|
-
if (projects.length === 0) return [React.createElement(
|
|
48
|
-
Box,
|
|
49
|
-
{ key: 'empty', flexDirection: 'row', alignItems: 'center' },
|
|
50
|
-
React.createElement(Text, { color: 'yellow' }, ' ⚠ '),
|
|
51
|
-
React.createElement(Text, { dimColor: true }, 'No recognizable project manifests found.')
|
|
52
|
-
)];
|
|
19
|
+
if (loading) return [React.createElement(Text, { key: 'scanning', dimColor: true }, 'Scanning projects…')];
|
|
20
|
+
if (error) return [React.createElement(Text, { key: 'error', color: 'red' }, `Unable to scan: ${error}`)];
|
|
21
|
+
if (projects.length === 0) return [React.createElement(Text, { key: 'empty', dimColor: true }, 'No recognizable project manifests found.')];
|
|
53
22
|
|
|
54
23
|
return visibleProjects.map((project, index) => {
|
|
55
24
|
const absoluteIndex = start + index;
|
|
56
25
|
const isSelected = absoluteIndex === selectedIndex;
|
|
57
|
-
const frameworkBadges = (project.frameworks || []).map((frame) =>
|
|
58
|
-
React.createElement(Text, { key: frame.name, color: 'cyan', dimColor: !isSelected }, ` ${frame.icon}${frame.name}`)
|
|
59
|
-
);
|
|
26
|
+
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
60
27
|
const hasMissingRuntime = project.missingBinaries && project.missingBinaries.length > 0;
|
|
61
28
|
|
|
62
29
|
return React.createElement(
|
|
63
30
|
Box,
|
|
64
|
-
{
|
|
65
|
-
key: project.id,
|
|
66
|
-
flexDirection: 'column',
|
|
67
|
-
marginBottom: 0,
|
|
68
|
-
borderStyle: isSelected ? 'bold' : 'single',
|
|
69
|
-
borderColor: isSelected ? 'cyan' : 'gray',
|
|
70
|
-
paddingX: 1,
|
|
71
|
-
paddingY: 0
|
|
72
|
-
},
|
|
31
|
+
{ key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1 },
|
|
73
32
|
React.createElement(
|
|
74
33
|
Box,
|
|
75
|
-
{ flexDirection: 'row'
|
|
76
|
-
React.createElement(Text, {
|
|
77
|
-
|
|
78
|
-
bold: isSelected
|
|
79
|
-
}, `${isSelected ? SELECTION_BAR + ' ' : ' '}${project.icon} ${project.name}`),
|
|
80
|
-
hasMissingRuntime && React.createElement(Text, { color: 'red', bold: true }, ' ⚠ Runtime missing')
|
|
34
|
+
{ flexDirection: 'row' },
|
|
35
|
+
React.createElement(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected }, `${project.icon} ${project.name}`),
|
|
36
|
+
hasMissingRuntime && React.createElement(Text, { color: 'red', bold: true }, ' ⚠️ Runtime missing')
|
|
81
37
|
),
|
|
82
|
-
React.createElement(
|
|
83
|
-
|
|
84
|
-
{ flexDirection: 'row', alignItems: 'center' },
|
|
85
|
-
React.createElement(Text, { dimColor: true }, ` ${project.type}`),
|
|
86
|
-
React.createElement(Text, { dimColor: true }, ` · ${path.relative(rootPath, project.path) || '.'}`)
|
|
87
|
-
),
|
|
88
|
-
frameworkBadges.length > 0 && React.createElement(
|
|
89
|
-
Box,
|
|
90
|
-
{ marginTop: 0 },
|
|
91
|
-
React.createElement(Text, { dimColor: true }, ' '),
|
|
92
|
-
...frameworkBadges
|
|
93
|
-
)
|
|
38
|
+
React.createElement(Text, { dimColor: true }, ` ${project.type} · ${path.relative(rootPath, project.path) || '.'}`),
|
|
39
|
+
frameworkBadges && React.createElement(Text, { dimColor: true }, ` ${frameworkBadges}`)
|
|
94
40
|
);
|
|
95
41
|
});
|
|
96
|
-
}, [loading, error, projects.length, visibleProjects, selectedIndex, start, rootPath
|
|
42
|
+
}, [loading, error, projects.length, visibleProjects, selectedIndex, start, rootPath]);
|
|
97
43
|
|
|
98
44
|
const totalPages = Math.ceil(projects.length / maxVisibleProjects);
|
|
99
|
-
|
|
45
|
+
|
|
100
46
|
return React.createElement(
|
|
101
47
|
Box,
|
|
102
48
|
{ flexDirection: 'column' },
|
|
@@ -104,7 +50,7 @@ export default function Navigator({
|
|
|
104
50
|
projects.length > maxVisibleProjects && React.createElement(
|
|
105
51
|
Box,
|
|
106
52
|
{ marginTop: 1, justifyContent: 'center' },
|
|
107
|
-
React.createElement(Text, { dimColor: true },
|
|
53
|
+
React.createElement(Text, { dimColor: true }, `Page ${page + 1} of ${totalPages} (Total: ${projects.length})`)
|
|
108
54
|
)
|
|
109
55
|
);
|
|
110
56
|
}
|
|
@@ -88,11 +88,12 @@ const PackageRegistry = memo(({selectedProject, projects = [], onRunCommand, Cur
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (inputStr.toLowerCase() === 'a') { setMode('add'); setInput(''); setCursor(0); }
|
|
92
|
-
if (inputStr.toLowerCase() === 'r') { setMode('remove'); setInput(''); setCursor(0); }
|
|
93
|
-
if (inputStr.toLowerCase() === 's') { setView('select'); }
|
|
91
|
+
if (inputStr.toLowerCase() === 'a') { setMode('add'); setInput(''); setCursor(0); return; }
|
|
92
|
+
if (inputStr.toLowerCase() === 'r') { setMode('remove'); setInput(''); setCursor(0); return; }
|
|
93
|
+
if (inputStr.toLowerCase() === 's') { setView('select'); return; }
|
|
94
94
|
if (inputStr.toLowerCase() === 'v' && projectType === 'Python') {
|
|
95
95
|
onRunCommand({label: 'Create venv', command: ['python3', '-m', 'venv', '.venv']}, activeProject);
|
|
96
|
+
return;
|
|
96
97
|
}
|
|
97
98
|
});
|
|
98
99
|
|
|
@@ -20,7 +20,12 @@ const ProjectArchitect = memo(({rootPath, onRunCommand, CursorText, onReturn}) =
|
|
|
20
20
|
{name: 'Rust (Binary)', cmd: (p, n) => ['cargo', 'new', path.join(p, n)]},
|
|
21
21
|
{name: 'Django Project', cmd: (p, n) => ['django-admin', 'startproject', n, path.join(p, n)]},
|
|
22
22
|
{name: 'Python (Basic)', cmd: (p, n) => ['mkdir', '-p', path.join(p, n)]},
|
|
23
|
-
{name: 'Go Module', cmd: (p, n) =>
|
|
23
|
+
{name: 'Go Module', cmd: (p, n) => {
|
|
24
|
+
const dir = path.join(p, n);
|
|
25
|
+
return process.platform === 'win32'
|
|
26
|
+
? ['cmd', '/c', `mkdir "${dir}" && cd /d "${dir}" && go mod init ${n}`]
|
|
27
|
+
: ['sh', '-c', `mkdir -p "${dir}" && cd "${dir}" && go mod init ${n}`];
|
|
28
|
+
}}
|
|
24
29
|
];
|
|
25
30
|
|
|
26
31
|
useInput((inputStr, key) => {
|
|
@@ -2,80 +2,22 @@ import React, {memo} from 'react';
|
|
|
2
2
|
import {Box, Text} from 'ink';
|
|
3
3
|
|
|
4
4
|
const create = React.createElement;
|
|
5
|
-
const STATUC_COLORS = { running: 'green', finished: 'cyan', failed: 'red', killed: 'yellow' };
|
|
6
5
|
|
|
7
6
|
const TaskManager = memo(({tasks, activeTaskId, renameMode, renameInput, renameCursor, CursorText}) => {
|
|
8
|
-
const activeTask = tasks.find(t => t.id === activeTaskId);
|
|
9
|
-
const logLines = activeTask?.logs || [];
|
|
10
|
-
const visibleLogs = logLines.slice(-5);
|
|
11
|
-
|
|
12
7
|
return create(
|
|
13
8
|
Box,
|
|
14
|
-
{flexDirection: 'column', borderStyle: '
|
|
15
|
-
|
|
16
|
-
create(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
),
|
|
25
|
-
|
|
26
|
-
// Task List
|
|
27
|
-
create(Box, {flexDirection: 'column', flexGrow: 1},
|
|
28
|
-
...tasks.map((t, idx) => {
|
|
29
|
-
const isActive = t.id === activeTaskId;
|
|
30
|
-
const color = STATUC_COLORS[t.status] || 'white';
|
|
31
|
-
return create(
|
|
32
|
-
Box,
|
|
33
|
-
{key: t.id, marginBottom: 0,
|
|
34
|
-
borderStyle: isActive ? 'bold' : 'single',
|
|
35
|
-
borderColor: isActive ? color : 'gray',
|
|
36
|
-
paddingX: 1, paddingY: 0},
|
|
37
|
-
create(Box, {flexDirection: 'column'},
|
|
38
|
-
// Task header
|
|
39
|
-
isActive && renameMode
|
|
40
|
-
? create(Box, {flexDirection: 'row'},
|
|
41
|
-
create(Text, {color: 'cyan'}, '→ Rename: '),
|
|
42
|
-
create(CursorText, {value: renameInput, cursorIndex: renameCursor}))
|
|
43
|
-
: create(Box, {flexDirection: 'row', justifyContent: 'space-between'},
|
|
44
|
-
create(Box, {flexDirection: 'row', alignItems: 'center'},
|
|
45
|
-
create(Text, {color: isActive ? color : 'white', bold: isActive},
|
|
46
|
-
`${isActive ? '→ ' : ' '}[${t.status.toUpperCase()}] ${t.name}`),
|
|
47
|
-
t.status === 'running' && create(Text, {color: 'green'}, ' ⚡'),
|
|
48
|
-
t.status === 'failed' && create(Text, {color: 'red'}, ' ✗'),
|
|
49
|
-
t.status === 'killed' && create(Text, {color: 'yellow'}, ' ⚠')
|
|
50
|
-
),
|
|
51
|
-
create(Text, {dimColor: true}, `#${idx + 1}`)
|
|
52
|
-
),
|
|
53
|
-
// Mini log preview for active task
|
|
54
|
-
isActive && logLines.length > 0 && create(
|
|
55
|
-
Box, {marginTop: 0, paddingLeft: 2},
|
|
56
|
-
...visibleLogs.map((line, i) =>
|
|
57
|
-
create(Text, {key: i, dimColor: true},
|
|
58
|
-
`${i === visibleLogs.length - 1 ? '>' : '·'} ${line.slice(0, 60)}`)
|
|
59
|
-
)
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
);
|
|
63
|
-
}),
|
|
64
|
-
!tasks.length && create(
|
|
65
|
-
Box, {alignItems: 'center', justifyContent: 'center', flexGrow: 1},
|
|
66
|
-
create(Text, {dimColor: true, bold: true}, 'No active tasks'),
|
|
67
|
-
create(Text, {dimColor: true}, 'Run a command to see it here!')
|
|
68
|
-
)
|
|
69
|
-
),
|
|
70
|
-
|
|
71
|
-
// Footer
|
|
72
|
-
create(Box, {marginTop: 1, flexDirection: 'column'},
|
|
73
|
-
create(Box, {flexDirection: 'row', justifyContent: 'space-between'},
|
|
74
|
-
create(Text, {dimColor: true}, '↑/↓: Navigate | Enter: Select'),
|
|
75
|
-
create(Text, {dimColor: true}, 'Shift+K: Kill | Shift+R: Rename')
|
|
76
|
-
),
|
|
77
|
-
create(Text, {dimColor: true}, 'Press Enter or Shift+T to return to Navigator.')
|
|
78
|
-
)
|
|
9
|
+
{flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
|
|
10
|
+
create(Text, {bold: true, color: 'yellow'}, '🛰️ Orbit Task Manager | Background Processes'),
|
|
11
|
+
create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Force Kill, Shift+R: Rename'),
|
|
12
|
+
...tasks.map(t => create(
|
|
13
|
+
Box,
|
|
14
|
+
{key: t.id, marginBottom: 0, flexDirection: 'column'},
|
|
15
|
+
t.id === activeTaskId && renameMode
|
|
16
|
+
? create(Box, {flexDirection: 'row'}, create(Text, {color: 'cyan'}, '→ Rename to: '), create(CursorText, {value: renameInput, cursorIndex: renameCursor}))
|
|
17
|
+
: create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? '→' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
|
|
18
|
+
)),
|
|
19
|
+
!tasks.length && create(Text, {dimColor: true}, 'No active or background tasks.'),
|
|
20
|
+
create(Text, {marginTop: 1, dimColor: true}, 'Press Enter or Shift+T to return to Navigator.')
|
|
79
21
|
);
|
|
80
22
|
});
|
|
81
23
|
|
package/src/detectors/dotnet.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { checkBinary
|
|
3
|
+
import { checkBinary } from './utils.js';
|
|
4
4
|
|
|
5
5
|
function parseCsProj(content) {
|
|
6
6
|
const metadata = {
|
|
@@ -78,7 +78,8 @@ export default {
|
|
|
78
78
|
publish: { label: 'Dotnet publish', command: ['dotnet', 'publish'], source: 'builtin' }
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
const hasSln = fs.readdirSync(projectPath).some(f => f.endsWith('.sln'));
|
|
82
|
+
if (hasSln) {
|
|
82
83
|
commands['restore-sl'] = { label: 'Restore Solution', command: ['dotnet', 'restore'], source: 'builtin' };
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -73,7 +73,7 @@ export const builtInFrameworks = [
|
|
|
73
73
|
languages: ['Node.js'],
|
|
74
74
|
priority: 106,
|
|
75
75
|
match(project) {
|
|
76
|
-
return dependencyMatches(project, 'koa')
|
|
76
|
+
return dependencyMatches(project, 'koa');
|
|
77
77
|
},
|
|
78
78
|
commands(project) {
|
|
79
79
|
const pm = project.metadata?.packageManager || 'npm';
|
|
@@ -324,9 +324,9 @@ export const builtInFrameworks = [
|
|
|
324
324
|
description: 'Modern fast web framework for Python',
|
|
325
325
|
languages: ['Python'],
|
|
326
326
|
priority: 112,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
match(project) {
|
|
328
|
+
return dependencyMatches(project, 'fastapi');
|
|
329
|
+
},
|
|
330
330
|
commands(project) {
|
|
331
331
|
const pm = project.metadata?.packageManager || 'pip';
|
|
332
332
|
const isUV = pm === 'uv';
|
|
@@ -366,9 +366,9 @@ export const builtInFrameworks = [
|
|
|
366
366
|
description: 'Django web application',
|
|
367
367
|
languages: ['Python'],
|
|
368
368
|
priority: 110,
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
match(project) {
|
|
370
|
+
return dependencyMatches(project, 'django');
|
|
371
|
+
},
|
|
372
372
|
commands(project) {
|
|
373
373
|
const pm = project.metadata?.packageManager || 'pip';
|
|
374
374
|
const isUV = pm === 'uv';
|
|
@@ -636,9 +636,9 @@ export const builtInFrameworks = [
|
|
|
636
636
|
description: 'Spring Boot Java framework',
|
|
637
637
|
languages: ['Java'],
|
|
638
638
|
priority: 115,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
639
|
+
match(project) {
|
|
640
|
+
return dependencyMatches(project, 'spring-boot') || dependencyMatches(project, 'org.springframework.boot');
|
|
641
|
+
},
|
|
642
642
|
commands(project) {
|
|
643
643
|
if (hasProjectFile(project.path, 'pom.xml')) {
|
|
644
644
|
return {
|
|
@@ -665,9 +665,9 @@ export const builtInFrameworks = [
|
|
|
665
665
|
description: 'Kubernetes Native Java stack',
|
|
666
666
|
languages: ['Java'],
|
|
667
667
|
priority: 108,
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
668
|
+
match(project) {
|
|
669
|
+
return dependencyMatches(project, 'quarkus') || dependencyMatches(project, 'io.quarkus');
|
|
670
|
+
},
|
|
671
671
|
commands() {
|
|
672
672
|
return {
|
|
673
673
|
run: { label: 'Quarkus Dev', command: ['mvn', 'quarkus:dev'], source: 'framework' },
|
|
@@ -683,9 +683,9 @@ export const builtInFrameworks = [
|
|
|
683
683
|
description: 'Modern JVM-based framework',
|
|
684
684
|
languages: ['Java'],
|
|
685
685
|
priority: 106,
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
686
|
+
match(project) {
|
|
687
|
+
return dependencyMatches(project, 'micronaut') || dependencyMatches(project, 'io.micronaut');
|
|
688
|
+
},
|
|
689
689
|
commands() {
|
|
690
690
|
return {
|
|
691
691
|
run: { label: 'Micronaut Run', command: ['./mvnw', 'run'], source: 'framework' },
|
|
@@ -757,9 +757,9 @@ export const builtInFrameworks = [
|
|
|
757
757
|
description: 'Ruby on Rails framework',
|
|
758
758
|
languages: ['Ruby'],
|
|
759
759
|
priority: 110,
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
760
|
+
match(project) {
|
|
761
|
+
return dependencyMatches(project, 'rails') || hasProjectFile(project.path, 'bin/rails');
|
|
762
|
+
},
|
|
763
763
|
commands() {
|
|
764
764
|
return {
|
|
765
765
|
install: { label: 'Bundle install', command: ['bundle', 'install'], source: 'framework' },
|
|
@@ -776,9 +776,9 @@ export const builtInFrameworks = [
|
|
|
776
776
|
description: 'Sinatra DSL for quickly creating web applications',
|
|
777
777
|
languages: ['Ruby'],
|
|
778
778
|
priority: 105,
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
779
|
+
match(project) {
|
|
780
|
+
return dependencyMatches(project, 'sinatra');
|
|
781
|
+
},
|
|
782
782
|
commands() {
|
|
783
783
|
return {
|
|
784
784
|
install: { label: 'Bundle install', command: ['bundle', 'install'], source: 'framework' },
|
|
@@ -795,9 +795,9 @@ export const builtInFrameworks = [
|
|
|
795
795
|
description: 'ASP.NET Core web framework',
|
|
796
796
|
languages: ['.NET'],
|
|
797
797
|
priority: 110,
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
798
|
+
match(project) {
|
|
799
|
+
return dependencyMatches(project, 'Microsoft.AspNetCore') || dependencyMatches(project, 'Microsoft.AspNetCore.App');
|
|
800
|
+
},
|
|
801
801
|
commands() {
|
|
802
802
|
return {
|
|
803
803
|
install: { label: 'Dotnet restore', command: ['dotnet', 'restore'], source: 'framework' },
|
|
@@ -814,9 +814,9 @@ export const builtInFrameworks = [
|
|
|
814
814
|
description: 'Blazor interactive web UI with .NET',
|
|
815
815
|
languages: ['.NET'],
|
|
816
816
|
priority: 105,
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
817
|
+
match(project) {
|
|
818
|
+
return dependencyMatches(project, 'Microsoft.AspNetCore.Components.Web') || dependencyMatches(project, 'Blazor');
|
|
819
|
+
},
|
|
820
820
|
commands() {
|
|
821
821
|
return {
|
|
822
822
|
run: { label: 'Blazor Run', command: ['dotnet', 'run'], source: 'framework' },
|
package/src/detectors/go.js
CHANGED
|
@@ -46,16 +46,16 @@ function detectGoFrameworks(deps) {
|
|
|
46
46
|
const frameworks = [];
|
|
47
47
|
const depStr = deps.join(' ').toLowerCase();
|
|
48
48
|
|
|
49
|
-
if (depStr
|
|
50
|
-
if (depStr.includes('
|
|
51
|
-
if (depStr.includes('
|
|
52
|
-
if (depStr.includes('chi')) frameworks.push({ name: 'Chi', icon: '🤝' });
|
|
49
|
+
if (/\bgin\b/.test(depStr) || depStr.includes('gin-gonic')) frameworks.push({ name: 'Gin', icon: '🍸' });
|
|
50
|
+
if (depStr.includes('labstack/echo')) frameworks.push({ name: 'Echo', icon: '🔊' });
|
|
51
|
+
if (depStr.includes('gofiber')) frameworks.push({ name: 'Fiber', icon: '🔥' });
|
|
52
|
+
if (depStr.includes('go-chi/chi')) frameworks.push({ name: 'Chi', icon: '🤝' });
|
|
53
53
|
if (depStr.includes('gorilla')) frameworks.push({ name: 'Gorilla', icon: '🦍' });
|
|
54
|
-
if (depStr.includes('iris')) frameworks.push({ name: 'Iris', icon: '🌺' });
|
|
54
|
+
if (depStr.includes('iris-go')) frameworks.push({ name: 'Iris', icon: '🌺' });
|
|
55
55
|
if (depStr.includes('beego')) frameworks.push({ name: 'Beego', icon: '🐝' });
|
|
56
56
|
if (depStr.includes('revel')) frameworks.push({ name: 'Revel', icon: '🎉' });
|
|
57
57
|
if (depStr.includes('gqlgen')) frameworks.push({ name: 'GQLGen', icon: '◼️' });
|
|
58
|
-
if (depStr.includes('grpc')) frameworks.push({ name: 'gRPC', icon: '🔌' });
|
|
58
|
+
if (depStr.includes('/grpc')) frameworks.push({ name: 'gRPC', icon: '🔌' });
|
|
59
59
|
|
|
60
60
|
return frameworks;
|
|
61
61
|
}
|
package/src/detectors/java.js
CHANGED
|
@@ -122,7 +122,8 @@ export default {
|
|
|
122
122
|
commands.install = { label: 'Maven install', command: [...mvnCmd, 'install'], source: 'builtin' };
|
|
123
123
|
commands.build = { label: 'Maven package', command: [...mvnCmd, 'package'], source: 'builtin' };
|
|
124
124
|
commands.test = { label: 'Maven test', command: [...mvnCmd, 'test'], source: 'builtin' };
|
|
125
|
-
|
|
125
|
+
const isSpring = frameworks.some(f => f.name === 'Spring Boot');
|
|
126
|
+
commands.run = { label: isSpring ? 'Maven spring-boot:run' : 'Maven compile', command: isSpring ? [...mvnCmd, 'spring-boot:run'] : [...mvnCmd, 'compile'], source: 'builtin' };
|
|
126
127
|
commands.clean = { label: 'Maven clean', command: [...mvnCmd, 'clean'], source: 'builtin' };
|
|
127
128
|
}
|
|
128
129
|
|
package/src/detectors/node.js
CHANGED
|
@@ -29,7 +29,6 @@ function detectNodeProjectType(pkg) {
|
|
|
29
29
|
if (allDeps['nuxt']) return { type: 'Nuxt', icon: '🟢' };
|
|
30
30
|
if (allDeps['vite']) return { type: 'Vite', icon: '⚡' };
|
|
31
31
|
if (allDeps['electron']) return { type: 'Electron', icon: '⚛️' };
|
|
32
|
-
if (allDeps['typescript']) return { type: 'TypeScript', icon: '🔷' };
|
|
33
32
|
return { type: 'Node.js', icon: '🟢' };
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -117,6 +116,8 @@ export default {
|
|
|
117
116
|
setupHints.push('This is a monorepo with workspaces: ' + workspaces.join(', '));
|
|
118
117
|
}
|
|
119
118
|
|
|
119
|
+
const detectedFrameworks = projectType.type !== 'Node.js' ? [{ name: projectType.type, icon: projectType.icon }] : [];
|
|
120
|
+
|
|
120
121
|
return {
|
|
121
122
|
id: `${projectPath}::node`,
|
|
122
123
|
path: projectPath,
|
|
@@ -129,7 +130,7 @@ export default {
|
|
|
129
130
|
manifest: path.basename(manifest),
|
|
130
131
|
description: pkg.description || projectType.type,
|
|
131
132
|
missingBinaries,
|
|
132
|
-
frameworks:
|
|
133
|
+
frameworks: detectedFrameworks,
|
|
133
134
|
extra: {
|
|
134
135
|
scripts: Object.keys(scripts),
|
|
135
136
|
setupHints,
|
package/src/detectors/php.js
CHANGED
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
|
|
56
56
|
const commands = {
|
|
57
57
|
install: { label: 'Composer install', command: ['composer', 'install'], source: 'builtin' },
|
|
58
|
-
update: { label: 'Composer update', command: ['composer', 'update'], source: 'builtin' }
|
|
58
|
+
update: { label: 'Composer update all', command: ['composer', 'update'], source: 'builtin' }
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
if (hasProjectFile(projectPath, 'artisan')) {
|
package/src/detectors/python.js
CHANGED
|
@@ -123,10 +123,17 @@ export default {
|
|
|
123
123
|
icon: '🐍',
|
|
124
124
|
priority: 95,
|
|
125
125
|
files: ['pyproject.toml', 'requirements.txt', 'setup.py', 'Pipfile', 'manage.py'],
|
|
126
|
-
binaries: ['python3', 'python', 'uv']
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
binaries: ['python3', 'python', 'uv'],
|
|
127
|
+
async build(projectPath, manifest) {
|
|
128
|
+
const hasPython3 = checkBinary('python3');
|
|
129
|
+
const hasPython = checkBinary('python');
|
|
130
|
+
const hasUv = checkBinary('uv');
|
|
131
|
+
const hasRuntime = hasPython3 || hasPython || hasUv;
|
|
132
|
+
const missingBinaries = [];
|
|
133
|
+
if (!hasRuntime) {
|
|
134
|
+
missingBinaries.push('python');
|
|
135
|
+
}
|
|
136
|
+
const pkgManager = getPythonPackageManager(projectPath);
|
|
130
137
|
const isUV = pkgManager === 'uv';
|
|
131
138
|
const isPoetry = pkgManager === 'poetry';
|
|
132
139
|
const isPipenv = pkgManager === 'pipenv';
|
package/src/detectors/ruby.js
CHANGED
|
@@ -58,7 +58,7 @@ export default {
|
|
|
58
58
|
const commands = {
|
|
59
59
|
install: { label: 'Bundle install', command: ['bundle', 'install'], source: 'builtin' },
|
|
60
60
|
update: { label: 'Bundle update', command: ['bundle', 'update'], source: 'builtin' },
|
|
61
|
-
console: { label: 'Ruby console', command: ['
|
|
61
|
+
console: { label: 'Ruby console', command: ['irb'], source: 'builtin' }
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
if (hasProjectFile(projectPath, 'bin/rails')) {
|
package/src/detectors/rust.js
CHANGED
|
@@ -40,8 +40,8 @@ function parseCargoToml(content) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (inDependencies && trimmed && !trimmed.startsWith('#')) {
|
|
43
|
-
const depName = trimmed.split(
|
|
44
|
-
if (depName) metadata.dependencies.push(depName);
|
|
43
|
+
const depName = trimmed.split(/[={]/)[0]?.trim();
|
|
44
|
+
if (depName && !depName.startsWith('#')) metadata.dependencies.push(depName);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (inBin && trimmed.startsWith('name')) {
|
package/src/detectors/utils.js
CHANGED
|
@@ -98,13 +98,12 @@ export function dependencyMatches(project, needle) {
|
|
|
98
98
|
return '';
|
|
99
99
|
}).filter(Boolean);
|
|
100
100
|
const target = needle.toLowerCase();
|
|
101
|
-
return dependencies.some((value) =>
|
|
102
|
-
value === target
|
|
103
|
-
value.startsWith(`${target}@`) ||
|
|
104
|
-
value.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
);
|
|
101
|
+
return dependencies.some((value) => {
|
|
102
|
+
if (value === target) return true;
|
|
103
|
+
if (value.startsWith(`${target}@`) || value.startsWith(`${target}==`) || value.startsWith(`${target}>=`) || value.startsWith(`${target}~=`)) return true;
|
|
104
|
+
if (value.startsWith(`${target}/`) || value.endsWith(`/${target}`)) return true;
|
|
105
|
+
return false;
|
|
106
|
+
});
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
export function parseCommandTokens(value) {
|