project-compass 3.6.6 → 3.8.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/AGENTS.md +36 -0
- package/ARCHITECTURE.md +21 -0
- package/CONTRIBUTING.md +23 -0
- package/LICENSE +0 -0
- package/README.md +15 -3
- 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/commands.md +12 -2
- package/eslint.config.cjs +0 -0
- package/memory/2026-03-02.md +28 -0
- package/memory/2026-03-03.md +22 -0
- package/package.json +1 -1
- package/src/cli.js +20 -38
- package/src/components/Footer.js +25 -0
- package/src/components/Header.js +21 -0
- package/src/components/Navigator.js +56 -0
- package/src/components/PackageRegistry.js +44 -20
- package/src/components/ProjectArchitect.js +4 -1
- package/src/components/Studio.js +0 -0
- package/src/components/TaskManager.js +0 -0
- package/src/configPaths.js +0 -0
- package/src/detectors/frameworks.js +226 -0
- package/src/detectors/generic.js +28 -0
- package/src/detectors/go.js +35 -0
- package/src/detectors/java.js +46 -0
- package/src/detectors/node.js +82 -0
- package/src/detectors/python.js +97 -0
- package/src/detectors/rust.js +35 -0
- package/src/detectors/utils.js +60 -0
- package/src/projectDetection.js +53 -1002
- package/src/store/useProjectStore.js +32 -0
- package/src/cli.js.bak +0 -679
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { dependencyMatches, hasProjectFile, resolveScriptCommand } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export const builtInFrameworks = [
|
|
4
|
+
{
|
|
5
|
+
id: 'next',
|
|
6
|
+
name: 'Next.js',
|
|
7
|
+
icon: '🧭',
|
|
8
|
+
description: 'React + Next.js (SSR/SSG) apps',
|
|
9
|
+
languages: ['Node.js'],
|
|
10
|
+
priority: 115,
|
|
11
|
+
match(project) {
|
|
12
|
+
const hasNextConfig = ['next.config.js', 'next.config.mjs', 'next.config.ts'].some(f => hasProjectFile(project.path, f));
|
|
13
|
+
return dependencyMatches(project, 'next') || hasNextConfig;
|
|
14
|
+
},
|
|
15
|
+
commands(project) {
|
|
16
|
+
const commands = {};
|
|
17
|
+
const add = (key, label, fallback) => {
|
|
18
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
19
|
+
if (tokens) {
|
|
20
|
+
commands[key] = { label, command: tokens, source: 'framework' };
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
add('install', 'Next install', (pm) => [pm, 'install']);
|
|
24
|
+
add('run', 'Next dev', (pm) => pm === 'npm' ? ['npx', 'next', 'dev'] : [pm, 'next', 'dev']);
|
|
25
|
+
add('build', 'Next build', (pm) => pm === 'npm' ? ['npx', 'next', 'build'] : [pm, 'next', 'build']);
|
|
26
|
+
add('test', 'Next test', (pm) => [pm, 'run', 'test']);
|
|
27
|
+
add('start', 'Next start', (pm) => pm === 'npm' ? ['npx', 'next', 'start'] : [pm, 'next', 'start']);
|
|
28
|
+
return commands;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'react',
|
|
33
|
+
name: 'React',
|
|
34
|
+
icon: '⚛️',
|
|
35
|
+
description: 'React apps (CRA, Vite React)',
|
|
36
|
+
languages: ['Node.js'],
|
|
37
|
+
priority: 112,
|
|
38
|
+
match(project) {
|
|
39
|
+
return dependencyMatches(project, 'react') && (dependencyMatches(project, 'react-scripts') || dependencyMatches(project, 'vite') || hasProjectFile(project.path, 'vite.config.js') || hasProjectFile(project.path, 'vite.config.ts'));
|
|
40
|
+
},
|
|
41
|
+
commands(project) {
|
|
42
|
+
const commands = {};
|
|
43
|
+
const add = (key, label, fallback) => {
|
|
44
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
45
|
+
if (tokens) {
|
|
46
|
+
commands[key] = { label, command: tokens, source: 'framework' };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
add('install', 'React install', (pm) => [pm, 'install']);
|
|
50
|
+
add('run', 'React dev', (pm) => [pm, 'run', 'dev']);
|
|
51
|
+
add('build', 'React build', (pm) => [pm, 'run', 'build']);
|
|
52
|
+
add('test', 'React test', (pm) => [pm, 'run', 'test']);
|
|
53
|
+
return commands;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'vue',
|
|
58
|
+
name: 'Vue.js',
|
|
59
|
+
icon: '🟩',
|
|
60
|
+
description: 'Vue CLI or Vite + Vue apps',
|
|
61
|
+
languages: ['Node.js'],
|
|
62
|
+
priority: 111,
|
|
63
|
+
match(project) {
|
|
64
|
+
return dependencyMatches(project, 'vue') && (hasProjectFile(project.path, 'vue.config.js') || hasProjectFile(project.path, 'vue.config.ts') || dependencyMatches(project, '@vue/cli-service') || dependencyMatches(project, 'vite'));
|
|
65
|
+
},
|
|
66
|
+
commands(project) {
|
|
67
|
+
const commands = {};
|
|
68
|
+
const add = (key, label, fallback) => {
|
|
69
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
70
|
+
if (tokens) {
|
|
71
|
+
commands[key] = { label, command: tokens, source: 'framework' };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
add('install', 'Vue install', (pm) => [pm, 'install']);
|
|
75
|
+
add('run', 'Vue dev', (pm) => [pm, 'run', 'dev']);
|
|
76
|
+
add('build', 'Vue build', (pm) => [pm, 'run', 'build']);
|
|
77
|
+
add('test', 'Vue test', (pm) => [pm, 'run', 'test']);
|
|
78
|
+
return commands;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'nest',
|
|
83
|
+
name: 'NestJS',
|
|
84
|
+
icon: '🛡️',
|
|
85
|
+
description: 'NestJS backend',
|
|
86
|
+
languages: ['Node.js'],
|
|
87
|
+
priority: 110,
|
|
88
|
+
match(project) {
|
|
89
|
+
return dependencyMatches(project, '@nestjs/cli') || dependencyMatches(project, '@nestjs/core') || hasProjectFile(project.path, 'nest-cli.json');
|
|
90
|
+
},
|
|
91
|
+
commands(project) {
|
|
92
|
+
const commands = {};
|
|
93
|
+
const add = (key, label, fallback) => {
|
|
94
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
95
|
+
if (tokens) {
|
|
96
|
+
commands[key] = { label, command: tokens, source: 'framework' };
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
add('install', 'Nest install', (pm) => [pm, 'install']);
|
|
100
|
+
add('run', 'Nest dev', (pm) => [pm, 'run', 'start:dev']);
|
|
101
|
+
add('build', 'Nest build', (pm) => [pm, 'run', 'build']);
|
|
102
|
+
add('test', 'Nest test', (pm) => [pm, 'run', 'test']);
|
|
103
|
+
return commands;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'django',
|
|
108
|
+
name: 'Django',
|
|
109
|
+
icon: '🌿',
|
|
110
|
+
description: 'Django web application',
|
|
111
|
+
languages: ['Python'],
|
|
112
|
+
priority: 110,
|
|
113
|
+
match(project) {
|
|
114
|
+
return dependencyMatches(project, 'django') || hasProjectFile(project.path, 'manage.py');
|
|
115
|
+
},
|
|
116
|
+
commands(project) {
|
|
117
|
+
const commands = {};
|
|
118
|
+
if (hasProjectFile(project.path, 'requirements.txt')) {
|
|
119
|
+
commands.install = { label: 'Pip install', command: ['pip', 'install', '-r', 'requirements.txt'], source: 'framework' };
|
|
120
|
+
}
|
|
121
|
+
if (hasProjectFile(project.path, 'manage.py')) {
|
|
122
|
+
commands.run = { label: 'Django runserver', command: ['python', 'manage.py', 'runserver'], source: 'framework' };
|
|
123
|
+
commands.test = { label: 'Django test', command: ['python', 'manage.py', 'test'], source: 'framework' };
|
|
124
|
+
commands.migrate = { label: 'Django migrate', command: ['python', 'manage.py', 'migrate'], source: 'framework' };
|
|
125
|
+
}
|
|
126
|
+
return commands;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'vite',
|
|
131
|
+
name: 'Vite',
|
|
132
|
+
icon: '⚡',
|
|
133
|
+
description: 'Vite-powered frontend',
|
|
134
|
+
languages: ['Node.js'],
|
|
135
|
+
priority: 100,
|
|
136
|
+
match(project) {
|
|
137
|
+
return hasProjectFile(project.path, 'vite.config.js') || hasProjectFile(project.path, 'vite.config.ts') || dependencyMatches(project, 'vite');
|
|
138
|
+
},
|
|
139
|
+
commands(project) {
|
|
140
|
+
const commands = {};
|
|
141
|
+
const add = (key, label, fallback) => {
|
|
142
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
143
|
+
if (tokens) {
|
|
144
|
+
commands[key] = { label, command: tokens, source: 'framework' };
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
add('install', 'Vite install', (pm) => [pm, 'install']);
|
|
148
|
+
add('run', 'Vite dev', (pm) => pm === 'npm' ? ['npx', 'vite'] : [pm, 'vite']);
|
|
149
|
+
add('build', 'Vite build', (pm) => pm === 'npm' ? ['npx', 'vite', 'build'] : [pm, 'vite', 'build']);
|
|
150
|
+
add('preview', 'Vite preview', (pm) => pm === 'npm' ? ['npx', 'vite', 'preview'] : [pm, 'vite', 'preview']);
|
|
151
|
+
return commands;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'tailwind',
|
|
156
|
+
name: 'Tailwind CSS',
|
|
157
|
+
icon: '🎨',
|
|
158
|
+
description: 'Tailwind utility-first CSS',
|
|
159
|
+
languages: ['Node.js'],
|
|
160
|
+
priority: 50,
|
|
161
|
+
match(project) {
|
|
162
|
+
return hasProjectFile(project.path, 'tailwind.config.js') || hasProjectFile(project.path, 'tailwind.config.ts') || dependencyMatches(project, 'tailwindcss');
|
|
163
|
+
},
|
|
164
|
+
commands(project) {
|
|
165
|
+
const pm = project.metadata?.packageManager || 'npm';
|
|
166
|
+
return { install: { label: 'Tailwind install', command: [pm, 'install', '-D', 'tailwindcss'], source: 'framework' } };
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'prisma',
|
|
171
|
+
name: 'Prisma',
|
|
172
|
+
icon: '◮',
|
|
173
|
+
description: 'Prisma ORM',
|
|
174
|
+
languages: ['Node.js'],
|
|
175
|
+
priority: 50,
|
|
176
|
+
match(project) {
|
|
177
|
+
return hasProjectFile(project.path, 'prisma/schema.prisma') || dependencyMatches(project, '@prisma/client');
|
|
178
|
+
},
|
|
179
|
+
commands(project) {
|
|
180
|
+
const pm = project.metadata?.packageManager || 'npm';
|
|
181
|
+
const npxLike = pm === 'npm' ? 'npx' : pm;
|
|
182
|
+
return {
|
|
183
|
+
install: { label: 'Prisma install', command: [pm, 'install', '@prisma/client'], source: 'framework' },
|
|
184
|
+
generate: { label: 'Prisma generate', command: [npxLike, 'prisma', 'generate'], source: 'framework' },
|
|
185
|
+
studio: { label: 'Prisma studio', command: [npxLike, 'prisma', 'studio'], source: 'framework' }
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'rocket',
|
|
191
|
+
name: 'Rocket',
|
|
192
|
+
icon: '🚀',
|
|
193
|
+
description: 'Rocket Rust Web',
|
|
194
|
+
languages: ['Rust'],
|
|
195
|
+
priority: 105,
|
|
196
|
+
match(project) {
|
|
197
|
+
return dependencyMatches(project, 'rocket');
|
|
198
|
+
},
|
|
199
|
+
commands() {
|
|
200
|
+
return {
|
|
201
|
+
install: { label: 'Cargo Fetch', command: ['cargo', 'fetch'], source: 'framework' },
|
|
202
|
+
run: { label: 'Rocket Run', command: ['cargo', 'run'], source: 'framework' },
|
|
203
|
+
test: { label: 'Rocket Test', command: ['cargo', 'test'], source: 'framework' }
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'laravel',
|
|
209
|
+
name: 'Laravel',
|
|
210
|
+
icon: '🧡',
|
|
211
|
+
description: 'Laravel PHP Framework',
|
|
212
|
+
languages: ['PHP'],
|
|
213
|
+
priority: 105,
|
|
214
|
+
match(project) {
|
|
215
|
+
return hasProjectFile(project.path, 'artisan') || dependencyMatches(project, 'laravel/framework');
|
|
216
|
+
},
|
|
217
|
+
commands() {
|
|
218
|
+
return {
|
|
219
|
+
install: { label: 'Composer install', command: ['composer', 'install'], source: 'framework' },
|
|
220
|
+
run: { label: 'Artisan Serve', command: ['php', 'artisan', 'serve'], source: 'framework' },
|
|
221
|
+
test: { label: 'Artisan Test', command: ['php', 'artisan', 'test'], source: 'framework' },
|
|
222
|
+
migrate: { label: 'Artisan Migrate', command: ['php', 'artisan', 'migrate'], source: 'framework' }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
type: 'generic',
|
|
5
|
+
label: 'Custom project',
|
|
6
|
+
icon: '🧰',
|
|
7
|
+
priority: 10,
|
|
8
|
+
files: ['README.md', 'Makefile', 'build.sh'],
|
|
9
|
+
binaries: [],
|
|
10
|
+
async build(projectPath, manifest) {
|
|
11
|
+
return {
|
|
12
|
+
id: `${projectPath}::generic`,
|
|
13
|
+
path: projectPath,
|
|
14
|
+
name: path.basename(projectPath),
|
|
15
|
+
type: 'Custom',
|
|
16
|
+
icon: '🧰',
|
|
17
|
+
priority: this.priority,
|
|
18
|
+
commands: {},
|
|
19
|
+
metadata: {},
|
|
20
|
+
manifest: path.basename(manifest),
|
|
21
|
+
description: 'Detected via README or Makefile layout.',
|
|
22
|
+
missingBinaries: [],
|
|
23
|
+
extra: {
|
|
24
|
+
setupHints: ['Read the README for custom build instructions']
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { checkBinary } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
type: 'go',
|
|
6
|
+
label: 'Go',
|
|
7
|
+
icon: '🐹',
|
|
8
|
+
priority: 85,
|
|
9
|
+
files: ['go.mod'],
|
|
10
|
+
binaries: ['go'],
|
|
11
|
+
async build(projectPath, manifest) {
|
|
12
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
13
|
+
return {
|
|
14
|
+
id: `${projectPath}::go`,
|
|
15
|
+
path: projectPath,
|
|
16
|
+
name: path.basename(projectPath),
|
|
17
|
+
type: 'Go',
|
|
18
|
+
icon: '🐹',
|
|
19
|
+
priority: this.priority,
|
|
20
|
+
commands: {
|
|
21
|
+
install: { label: 'Go tidy', command: ['go', 'mod', 'tidy'] },
|
|
22
|
+
build: { label: 'Go build', command: ['go', 'build', './...'] },
|
|
23
|
+
test: { label: 'Go test', command: ['go', 'test', './...'] },
|
|
24
|
+
run: { label: 'Go run', command: ['go', 'run', '.'] }
|
|
25
|
+
},
|
|
26
|
+
metadata: {},
|
|
27
|
+
manifest: path.basename(manifest),
|
|
28
|
+
description: '',
|
|
29
|
+
missingBinaries,
|
|
30
|
+
extra: {
|
|
31
|
+
setupHints: ['go mod tidy', 'Ensure Go toolchain is installed']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { checkBinary, hasProjectFile } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
type: 'java',
|
|
6
|
+
label: 'Java',
|
|
7
|
+
icon: '☕️',
|
|
8
|
+
priority: 80,
|
|
9
|
+
files: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
|
|
10
|
+
binaries: ['java', 'javac'],
|
|
11
|
+
async build(projectPath, manifest) {
|
|
12
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
13
|
+
const hasMvnw = hasProjectFile(projectPath, 'mvnw');
|
|
14
|
+
const hasGradlew = hasProjectFile(projectPath, 'gradlew');
|
|
15
|
+
const commands = {};
|
|
16
|
+
if (hasGradlew) {
|
|
17
|
+
commands.install = { label: 'Gradle resolve', command: ['./gradlew', 'dependencies'] };
|
|
18
|
+
commands.build = { label: 'Gradle build', command: ['./gradlew', 'build'] };
|
|
19
|
+
commands.test = { label: 'Gradle test', command: ['./gradlew', 'test'] };
|
|
20
|
+
} else if (hasMvnw) {
|
|
21
|
+
commands.install = { label: 'Maven install', command: ['./mvnw', 'install'] };
|
|
22
|
+
commands.build = { label: 'Maven package', command: ['./mvnw', 'package'] };
|
|
23
|
+
commands.test = { label: 'Maven test', command: ['./mvnw', 'test'] };
|
|
24
|
+
} else {
|
|
25
|
+
commands.build = { label: 'Maven package', command: ['mvn', 'package'] };
|
|
26
|
+
commands.test = { label: 'Maven test', command: ['mvn', 'test'] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
id: `${projectPath}::java`,
|
|
31
|
+
path: projectPath,
|
|
32
|
+
name: path.basename(projectPath),
|
|
33
|
+
type: 'Java',
|
|
34
|
+
icon: '☕️',
|
|
35
|
+
priority: this.priority,
|
|
36
|
+
commands,
|
|
37
|
+
metadata: {},
|
|
38
|
+
manifest: path.basename(manifest),
|
|
39
|
+
description: '',
|
|
40
|
+
missingBinaries,
|
|
41
|
+
extra: {
|
|
42
|
+
setupHints: ['Install JDK 17+ and run ./mvnw install or ./gradlew build']
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getPackageManager, checkBinary, hasProjectFile } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function gatherNodeDependencies(pkg) {
|
|
6
|
+
const deps = new Set();
|
|
7
|
+
['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].forEach((key) => {
|
|
8
|
+
if (pkg[key]) {
|
|
9
|
+
Object.keys(pkg[key]).forEach((name) => deps.add(name));
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return Array.from(deps);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
type: 'node',
|
|
17
|
+
label: 'Node.js',
|
|
18
|
+
icon: '🟢',
|
|
19
|
+
priority: 100,
|
|
20
|
+
files: ['package.json'],
|
|
21
|
+
binaries: ['node', 'npm'],
|
|
22
|
+
async build(projectPath, manifest) {
|
|
23
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
24
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
25
|
+
if (!fs.existsSync(pkgPath)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const content = await fs.promises.readFile(pkgPath, 'utf-8');
|
|
29
|
+
const pkg = JSON.parse(content);
|
|
30
|
+
const pm = getPackageManager(projectPath);
|
|
31
|
+
const scripts = pkg.scripts || {};
|
|
32
|
+
const commands = {};
|
|
33
|
+
const preferScript = (targetKey, names, labelText) => {
|
|
34
|
+
for (const name of names) {
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(scripts, name)) {
|
|
36
|
+
commands[targetKey] = { label: labelText, command: [pm, 'run', name] };
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
commands.install = { label: 'Install', command: [pm, 'install'] };
|
|
42
|
+
preferScript('build', ['build', 'compile', 'dist'], 'Build');
|
|
43
|
+
preferScript('test', ['test', 'check', 'spec'], 'Test');
|
|
44
|
+
preferScript('run', ['start', 'dev', 'serve', 'run'], 'Start');
|
|
45
|
+
if (Object.prototype.hasOwnProperty.call(scripts, 'lint')) {
|
|
46
|
+
commands.lint = { label: 'Lint', command: [pm, 'run', 'lint'] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const metadata = {
|
|
50
|
+
dependencies: gatherNodeDependencies(pkg),
|
|
51
|
+
scripts,
|
|
52
|
+
packageJson: pkg,
|
|
53
|
+
packageManager: getPackageManager(projectPath)
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const setupHints = [];
|
|
57
|
+
if (metadata.dependencies.length) {
|
|
58
|
+
setupHints.push('Run npm install to fetch dependencies.');
|
|
59
|
+
if (hasProjectFile(projectPath, 'yarn.lock')) {
|
|
60
|
+
setupHints.push('Or run yarn install if you prefer Yarn.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
id: `${projectPath}::node`,
|
|
66
|
+
path: projectPath,
|
|
67
|
+
name: pkg.name || path.basename(projectPath),
|
|
68
|
+
type: 'Node.js',
|
|
69
|
+
icon: '🟢',
|
|
70
|
+
priority: this.priority,
|
|
71
|
+
commands,
|
|
72
|
+
metadata,
|
|
73
|
+
manifest: path.basename(manifest),
|
|
74
|
+
description: pkg.description || '',
|
|
75
|
+
missingBinaries,
|
|
76
|
+
extra: {
|
|
77
|
+
scripts: Object.keys(scripts),
|
|
78
|
+
setupHints
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { checkBinary, hasProjectFile } from './utils.js';
|
|
4
|
+
|
|
5
|
+
const PYTHON_ENTRY_FILES = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
|
|
6
|
+
|
|
7
|
+
function findPythonEntry(projectPath) {
|
|
8
|
+
return PYTHON_ENTRY_FILES.find((file) => hasProjectFile(projectPath, file)) || null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function gatherPythonDependencies(projectPath) {
|
|
12
|
+
const set = new Set();
|
|
13
|
+
const addFromFile = (filePath) => {
|
|
14
|
+
if (!fs.existsSync(filePath)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
18
|
+
raw.split(/\r?\n/).forEach((line) => {
|
|
19
|
+
const clean = line.trim().split('#')[0].trim();
|
|
20
|
+
if (clean) {
|
|
21
|
+
const token = clean.split(/[>=<=~!]/)[0].trim().toLowerCase();
|
|
22
|
+
if (token) {
|
|
23
|
+
set.add(token);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
addFromFile(path.join(projectPath, 'requirements.txt'));
|
|
29
|
+
addFromFile(path.join(projectPath, 'Pipfile'));
|
|
30
|
+
const pyproject = path.join(projectPath, 'pyproject.toml');
|
|
31
|
+
if (fs.existsSync(pyproject)) {
|
|
32
|
+
const content = fs.readFileSync(pyproject, 'utf-8').toLowerCase();
|
|
33
|
+
const matches = content.match(/\b[a-z0-9-_/.]+\b/g);
|
|
34
|
+
(matches || []).forEach((match) => {
|
|
35
|
+
if (match) {
|
|
36
|
+
set.add(match);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return Array.from(set);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
type: 'python',
|
|
45
|
+
label: 'Python',
|
|
46
|
+
icon: '🐍',
|
|
47
|
+
priority: 95,
|
|
48
|
+
files: ['pyproject.toml', 'requirements.txt', 'setup.py', 'Pipfile'],
|
|
49
|
+
binaries: [process.platform === 'win32' ? 'python' : 'python3', 'pip'],
|
|
50
|
+
async build(projectPath, manifest) {
|
|
51
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
52
|
+
const commands = {};
|
|
53
|
+
if (hasProjectFile(projectPath, 'requirements.txt')) {
|
|
54
|
+
commands.install = { label: 'Pip Install', command: ['pip', 'install', '-r', 'requirements.txt'] };
|
|
55
|
+
}
|
|
56
|
+
if (hasProjectFile(projectPath, 'pyproject.toml')) {
|
|
57
|
+
commands.test = { label: 'Pytest', command: ['pytest'] };
|
|
58
|
+
} else {
|
|
59
|
+
commands.test = { label: 'Unittest', command: ['python', '-m', 'unittest', 'discover'] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const entry = findPythonEntry(projectPath);
|
|
63
|
+
if (entry) {
|
|
64
|
+
commands.run = { label: 'Run', command: ['python', entry] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const metadata = {
|
|
68
|
+
dependencies: gatherPythonDependencies(projectPath)
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const setupHints = [];
|
|
72
|
+
if (hasProjectFile(projectPath, 'requirements.txt')) {
|
|
73
|
+
setupHints.push('pip install -r requirements.txt');
|
|
74
|
+
}
|
|
75
|
+
if (hasProjectFile(projectPath, 'Pipfile')) {
|
|
76
|
+
setupHints.push('Use pipenv install --dev or poetry install');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
id: `${projectPath}::python`,
|
|
81
|
+
path: projectPath,
|
|
82
|
+
name: path.basename(projectPath),
|
|
83
|
+
type: 'Python',
|
|
84
|
+
icon: '🐍',
|
|
85
|
+
priority: this.priority,
|
|
86
|
+
commands,
|
|
87
|
+
metadata,
|
|
88
|
+
manifest: path.basename(manifest),
|
|
89
|
+
description: '',
|
|
90
|
+
missingBinaries,
|
|
91
|
+
extra: {
|
|
92
|
+
entry,
|
|
93
|
+
setupHints
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { checkBinary } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
type: 'rust',
|
|
6
|
+
label: 'Rust',
|
|
7
|
+
icon: '🦀',
|
|
8
|
+
priority: 90,
|
|
9
|
+
files: ['Cargo.toml'],
|
|
10
|
+
binaries: ['cargo', 'rustc'],
|
|
11
|
+
async build(projectPath, manifest) {
|
|
12
|
+
const missingBinaries = this.binaries.filter(b => !checkBinary(b));
|
|
13
|
+
return {
|
|
14
|
+
id: `${projectPath}::rust`,
|
|
15
|
+
path: projectPath,
|
|
16
|
+
name: path.basename(projectPath),
|
|
17
|
+
type: 'Rust',
|
|
18
|
+
icon: '🦀',
|
|
19
|
+
priority: this.priority,
|
|
20
|
+
commands: {
|
|
21
|
+
install: { label: 'Cargo fetch', command: ['cargo', 'fetch'] },
|
|
22
|
+
build: { label: 'Cargo build', command: ['cargo', 'build'] },
|
|
23
|
+
test: { label: 'Cargo test', command: ['cargo', 'test'] },
|
|
24
|
+
run: { label: 'Cargo run', command: ['cargo', 'run'] }
|
|
25
|
+
},
|
|
26
|
+
metadata: {},
|
|
27
|
+
manifest: path.basename(manifest),
|
|
28
|
+
description: '',
|
|
29
|
+
missingBinaries,
|
|
30
|
+
extra: {
|
|
31
|
+
setupHints: ['cargo fetch', 'Run cargo build before releasing']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
export function checkBinary(name) {
|
|
6
|
+
try {
|
|
7
|
+
const cmd = process.platform === 'win32' ? `where ${name}` : `which ${name}`;
|
|
8
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function hasProjectFile(projectPath, file) {
|
|
16
|
+
return fs.existsSync(path.join(projectPath, file));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getPackageManager(projectPath) {
|
|
20
|
+
if (hasProjectFile(projectPath, 'bun.lockb') || hasProjectFile(projectPath, 'bun.lock')) return 'bun';
|
|
21
|
+
if (hasProjectFile(projectPath, 'pnpm-lock.yaml')) return 'pnpm';
|
|
22
|
+
if (hasProjectFile(projectPath, 'yarn.lock')) return 'yarn';
|
|
23
|
+
return 'npm';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveScriptCommand(project, scriptName, fallback = null) {
|
|
27
|
+
const scripts = project.metadata?.scripts || {};
|
|
28
|
+
const pm = project.metadata?.packageManager || 'npm';
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
30
|
+
return [pm, 'run', scriptName];
|
|
31
|
+
}
|
|
32
|
+
if (typeof fallback === 'function') {
|
|
33
|
+
return fallback(pm);
|
|
34
|
+
}
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function dependencyMatches(project, needle) {
|
|
39
|
+
const dependencies = (project.metadata?.dependencies || []).map((dep) => dep.toLowerCase());
|
|
40
|
+
const target = needle.toLowerCase();
|
|
41
|
+
return dependencies.some((value) => value === target || value.startsWith(`${target}@`) || value.includes(`/${target}`));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseCommandTokens(value) {
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (value && typeof value === 'object') {
|
|
49
|
+
if (Array.isArray(value.command)) {
|
|
50
|
+
return value.command;
|
|
51
|
+
}
|
|
52
|
+
if (typeof value.command === 'string') {
|
|
53
|
+
return value.command.trim().split(/\s+/).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === 'string') {
|
|
57
|
+
return value.trim().split(/\s+/).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|