project-compass 2.0.0 → 2.0.2
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 +5 -3
- package/package.json +1 -1
- package/project-compass-2.0.0.tgz +0 -0
- package/project-compass-2.0.1.tgz +0 -0
- package/project-compass-2.0.2.tgz +0 -0
- package/src/cli.js +16 -17
- package/src/projectDetection.js +326 -30
package/README.md
CHANGED
|
@@ -79,12 +79,14 @@ Projects and details now occupy the same row, while the output panel takes its o
|
|
|
79
79
|
|
|
80
80
|
## Structure guide
|
|
81
81
|
|
|
82
|
-
Press `S` to reveal the structure guide that lists which manifest files trigger each language detection (Node.js looks for `package.json`, Python looks for `pyproject.toml` or `requirements.txt`, Rust needs `Cargo.toml`, etc.). Use `H` to hide the help cards if you need every pixel for your output, then bring them back when you want a refresher.
|
|
82
|
+
Press `Ctrl+S` to reveal the structure guide that lists which manifest files trigger each language detection (Node.js looks for `package.json`, Python looks for `pyproject.toml` or `requirements.txt`, Rust needs `Cargo.toml`, etc.). Use `Ctrl+H` to hide the help cards if you need every pixel for your output, then bring them back when you want a refresher.
|
|
83
83
|
|
|
84
|
+
## Detection & setup hints
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
Project Compass now has a modular detection engine (`src/projectDetection.js`) that looks at manifests, frameworks, and optional plugins to identify what kind of project you are standing in. Every schema fills `extra.setupHints` (npm install, pip install, go mod tidy, etc.) and those hints show up under the detail view whenever a project needs bootstrapping.
|
|
87
|
+
|
|
88
|
+
Try the sample Python project at `/mnt/ramdisk/daily_builds/test_python_proj` (includes `pyproject.toml`, `requirements.txt`, and `app.py`) so you can run it via Project Compass and confirm stdin/input behavior.
|
|
86
89
|
|
|
87
|
-
Project Compass now points at a dedicated detection module (`src/projectDetection.js`). It runs a set of SCHEMAS that look for Node.js, Python, Rust, Go, Java, Scala, PHP, Ruby, .NET, and shell/Makefile projects (plus a generic fallback), and it discovers any plugins you drop under `~/.project-compass/plugins.json`. Each detected project carries `setupHints` (e.g., `npm install`, `pip install -r requirements.txt`, `cargo fetch`) so you know what to do before running the commands listed in the detail view.
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
## Developer notes
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/cli.js
CHANGED
|
@@ -524,9 +524,8 @@ const projectRows = [];
|
|
|
524
524
|
label: 'Navigation',
|
|
525
525
|
color: 'magenta',
|
|
526
526
|
body: [
|
|
527
|
-
'↑ / ↓ move the project focus',
|
|
528
|
-
'
|
|
529
|
-
'Shift + ↑ / ↓ scrolls only the log buffer',
|
|
527
|
+
'↑ / ↓ move the project focus, Enter toggles details',
|
|
528
|
+
'Shift+↑ / Shift+↓ scroll only the output buffer',
|
|
530
529
|
'Ctrl+H toggles cards, ? opens the overlay'
|
|
531
530
|
]
|
|
532
531
|
},
|
|
@@ -534,10 +533,10 @@ const projectRows = [];
|
|
|
534
533
|
label: 'Command flow',
|
|
535
534
|
color: 'cyan',
|
|
536
535
|
body: [
|
|
537
|
-
'B / T / R trigger build/test/run',
|
|
536
|
+
'B / T / R trigger build/test/run shortcuts',
|
|
538
537
|
'1-9 execute detail commands in order',
|
|
539
538
|
'Ctrl+L reruns the last command you launched',
|
|
540
|
-
'Ctrl+C aborts
|
|
539
|
+
'Ctrl+C aborts; typing feeds stdin (buffer mirrors it)'
|
|
541
540
|
]
|
|
542
541
|
},
|
|
543
542
|
{
|
|
@@ -545,8 +544,8 @@ const projectRows = [];
|
|
|
545
544
|
color: 'yellow',
|
|
546
545
|
body: [
|
|
547
546
|
recentRuns.length ? `${recentRuns.length} runs recorded` : 'No runs yet · start with B/T/R',
|
|
548
|
-
'Ctrl+S
|
|
549
|
-
'
|
|
547
|
+
'Ctrl+S toggles the structure guide when unsure',
|
|
548
|
+
'C → label|cmd saves a custom action'
|
|
550
549
|
]
|
|
551
550
|
}
|
|
552
551
|
];
|
|
@@ -575,7 +574,7 @@ const projectRows = [];
|
|
|
575
574
|
)
|
|
576
575
|
)
|
|
577
576
|
)
|
|
578
|
-
: create(Text, {dimColor: true, marginTop: 1}, 'Help cards hidden · press H to show navigation, command flow, and recent runs.');
|
|
577
|
+
: create(Text, {dimColor: true, marginTop: 1}, 'Help cards hidden · press Ctrl+H to show navigation, command flow, and recent runs.');
|
|
579
578
|
|
|
580
579
|
const structureGuide = showStructureGuide
|
|
581
580
|
? create(
|
|
@@ -587,7 +586,7 @@ const projectRows = [];
|
|
|
587
586
|
marginTop: 1,
|
|
588
587
|
padding: 1
|
|
589
588
|
},
|
|
590
|
-
create(Text, {color: 'cyan', bold: true}, 'Project structure guide · press S to hide'),
|
|
589
|
+
create(Text, {color: 'cyan', bold: true}, 'Project structure guide · press Ctrl+S to hide'),
|
|
591
590
|
...SCHEMA_GUIDE.map((entry) =>
|
|
592
591
|
create(Text, {key: entry.type, dimColor: true}, `• ${entry.icon} ${entry.label}: ${entry.files.join(', ')}`)
|
|
593
592
|
),
|
|
@@ -606,18 +605,18 @@ const projectRows = [];
|
|
|
606
605
|
padding: 1
|
|
607
606
|
},
|
|
608
607
|
create(Text, {color: 'cyan', bold: true}, 'Help overlay · press ? to hide'),
|
|
609
|
-
create(Text, null, 'Shift
|
|
610
|
-
create(Text, null, '
|
|
611
|
-
create(Text, null, 'Ctrl+H toggles
|
|
612
|
-
create(Text, null, 'Projects + Details stay
|
|
613
|
-
create(Text, null, 'Structure guide lists the manifests that trigger each language detection (
|
|
608
|
+
create(Text, null, 'Shift+↑/↓ scrolls the log buffer while commands stream; type to feed stdin (Enter submits, Ctrl+C aborts).'),
|
|
609
|
+
create(Text, null, 'B/T/R run build/test/run; 1-9 executes detail commands; Ctrl+L reruns the previous command.'),
|
|
610
|
+
create(Text, null, 'Ctrl+H toggles these help cards, Ctrl+S toggles the structure guide, ? toggles this overlay, Ctrl+Q quits.'),
|
|
611
|
+
create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
|
|
612
|
+
create(Text, null, 'Structure guide lists the manifests that trigger each language detection (Ctrl+S to toggle).')
|
|
614
613
|
)
|
|
615
614
|
: null;
|
|
616
615
|
|
|
617
616
|
const toggleHint = showHelpCards ? 'Ctrl+H hides the help cards' : 'Ctrl+H shows the help cards';
|
|
618
617
|
const headerHint = viewMode === 'detail'
|
|
619
|
-
? `Detail mode · 1-${Math.max(detailedIndexed.length, 1)} to execute, C: add custom commands, Enter: back to list, Ctrl+Q: quit · ${toggleHint}, Ctrl+S
|
|
620
|
-
: `Quick run · B/T/R to build/test/run, Enter: view details, Ctrl+Q: quit · ${toggleHint}, Ctrl+S
|
|
618
|
+
? `Detail mode · 1-${Math.max(detailedIndexed.length, 1)} to execute, C: add custom commands, Enter: back to list, Ctrl+Q: quit · ${toggleHint}, Ctrl+S toggles structure guide`
|
|
619
|
+
: `Quick run · B/T/R to build/test/run, Enter: view details, Ctrl+Q: quit · ${toggleHint}, Ctrl+S toggles structure guide`;
|
|
621
620
|
|
|
622
621
|
return create(
|
|
623
622
|
Box,
|
|
@@ -702,7 +701,7 @@ const projectRows = [];
|
|
|
702
701
|
Box,
|
|
703
702
|
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
|
|
704
703
|
create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter submits, Ctrl+C aborts.' : 'Run a command or press ? for extra help.'),
|
|
705
|
-
create(Text, {dimColor: true}, `${toggleHint}, S toggles the structure guide`)
|
|
704
|
+
create(Text, {dimColor: true}, `${toggleHint}, Ctrl+S toggles the structure guide`)
|
|
706
705
|
),
|
|
707
706
|
create(
|
|
708
707
|
Box,
|
package/src/projectDetection.js
CHANGED
|
@@ -5,6 +5,27 @@ import {ensureConfigDir, PLUGIN_FILE} from './configPaths.js';
|
|
|
5
5
|
|
|
6
6
|
const IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/target/**'];
|
|
7
7
|
|
|
8
|
+
const PYTHON_ENTRY_FILES = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
|
|
9
|
+
|
|
10
|
+
function findPythonEntry(projectPath) {
|
|
11
|
+
return PYTHON_ENTRY_FILES.find((file) => hasProjectFile(projectPath, file)) || null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hasProjectFile(projectPath, file) {
|
|
15
|
+
return fs.existsSync(path.join(projectPath, file));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveScriptCommand(project, scriptName, fallback = null) {
|
|
19
|
+
const scripts = project.metadata?.scripts || {};
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
21
|
+
return ['npm', 'run', scriptName];
|
|
22
|
+
}
|
|
23
|
+
if (typeof fallback === 'function') {
|
|
24
|
+
return fallback();
|
|
25
|
+
}
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
8
29
|
function gatherNodeDependencies(pkg) {
|
|
9
30
|
const deps = new Set();
|
|
10
31
|
['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].forEach((key) => {
|
|
@@ -71,6 +92,295 @@ function parseCommandTokens(value) {
|
|
|
71
92
|
return [];
|
|
72
93
|
}
|
|
73
94
|
|
|
95
|
+
const builtInFrameworks = [
|
|
96
|
+
{
|
|
97
|
+
id: 'next',
|
|
98
|
+
name: 'Next.js',
|
|
99
|
+
icon: '🧭',
|
|
100
|
+
description: 'React + Next.js (SSR/SSG) apps',
|
|
101
|
+
languages: ['Node.js'],
|
|
102
|
+
priority: 115,
|
|
103
|
+
match(project) {
|
|
104
|
+
const hasNextConfig = hasProjectFile(project.path, 'next.config.js');
|
|
105
|
+
return dependencyMatches(project, 'next') || hasNextConfig;
|
|
106
|
+
},
|
|
107
|
+
commands(project) {
|
|
108
|
+
const commands = {};
|
|
109
|
+
const add = (key, label, fallback) => {
|
|
110
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
111
|
+
if (tokens) {
|
|
112
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const buildFallback = () => ['npx', 'next', 'build'];
|
|
116
|
+
const startFallback = () => ['npx', 'next', 'start'];
|
|
117
|
+
const devFallback = () => ['npx', 'next', 'dev'];
|
|
118
|
+
add('run', 'Next dev', devFallback);
|
|
119
|
+
add('build', 'Next build', buildFallback);
|
|
120
|
+
add('test', 'Next test', () => ['npm', 'run', 'test']);
|
|
121
|
+
add('start', 'Next start', startFallback);
|
|
122
|
+
return commands;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'react',
|
|
127
|
+
name: 'React',
|
|
128
|
+
icon: '⚛️',
|
|
129
|
+
description: 'React apps (CRA, Vite React)',
|
|
130
|
+
languages: ['Node.js'],
|
|
131
|
+
priority: 112,
|
|
132
|
+
match(project) {
|
|
133
|
+
return dependencyMatches(project, 'react') && (dependencyMatches(project, 'react-scripts') || dependencyMatches(project, 'vite') || hasProjectFile(project.path, 'vite.config.js'));
|
|
134
|
+
},
|
|
135
|
+
commands(project) {
|
|
136
|
+
const commands = {};
|
|
137
|
+
const add = (key, label, fallback) => {
|
|
138
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
139
|
+
if (tokens) {
|
|
140
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
add('run', 'React dev', () => ['npm', 'run', 'dev']);
|
|
144
|
+
add('build', 'React build', () => ['npm', 'run', 'build']);
|
|
145
|
+
add('test', 'React test', () => ['npm', 'run', 'test']);
|
|
146
|
+
return commands;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'vue',
|
|
151
|
+
name: 'Vue.js',
|
|
152
|
+
icon: '🟩',
|
|
153
|
+
description: 'Vue CLI or Vite + Vue apps',
|
|
154
|
+
languages: ['Node.js'],
|
|
155
|
+
priority: 111,
|
|
156
|
+
match(project) {
|
|
157
|
+
return dependencyMatches(project, 'vue') && (hasProjectFile(project.path, 'vue.config.js') || dependencyMatches(project, '@vue/cli-service') || dependencyMatches(project, 'vite'));
|
|
158
|
+
},
|
|
159
|
+
commands(project) {
|
|
160
|
+
const commands = {};
|
|
161
|
+
const add = (key, label, fallback) => {
|
|
162
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
163
|
+
if (tokens) {
|
|
164
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
add('run', 'Vue dev', () => ['npm', 'run', 'dev']);
|
|
168
|
+
add('build', 'Vue build', () => ['npm', 'run', 'build']);
|
|
169
|
+
add('test', 'Vue test', () => ['npm', 'run', 'test']);
|
|
170
|
+
return commands;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'nest',
|
|
175
|
+
name: 'NestJS',
|
|
176
|
+
icon: '🛡️',
|
|
177
|
+
description: 'NestJS backend',
|
|
178
|
+
languages: ['Node.js'],
|
|
179
|
+
priority: 110,
|
|
180
|
+
match(project) {
|
|
181
|
+
return dependencyMatches(project, '@nestjs/cli') || dependencyMatches(project, '@nestjs/core');
|
|
182
|
+
},
|
|
183
|
+
commands(project) {
|
|
184
|
+
const commands = {};
|
|
185
|
+
const add = (key, label, fallback) => {
|
|
186
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
187
|
+
if (tokens) {
|
|
188
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
add('run', 'Nest dev', () => ['npm', 'run', 'start:dev']);
|
|
192
|
+
add('build', 'Nest build', () => ['npm', 'run', 'build']);
|
|
193
|
+
add('test', 'Nest test', () => ['npm', 'run', 'test']);
|
|
194
|
+
return commands;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'angular',
|
|
199
|
+
name: 'Angular',
|
|
200
|
+
icon: '🅰️',
|
|
201
|
+
description: 'Angular CLI projects',
|
|
202
|
+
languages: ['Node.js'],
|
|
203
|
+
priority: 109,
|
|
204
|
+
match(project) {
|
|
205
|
+
return hasProjectFile(project.path, 'angular.json') || dependencyMatches(project, '@angular/cli');
|
|
206
|
+
},
|
|
207
|
+
commands(project) {
|
|
208
|
+
const commands = {};
|
|
209
|
+
const add = (key, label, fallback) => {
|
|
210
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
211
|
+
if (tokens) {
|
|
212
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
add('run', 'Angular serve', () => ['npm', 'run', 'start']);
|
|
216
|
+
add('build', 'Angular build', () => ['npm', 'run', 'build']);
|
|
217
|
+
add('test', 'Angular test', () => ['npm', 'run', 'test']);
|
|
218
|
+
return commands;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'sveltekit',
|
|
223
|
+
name: 'SvelteKit',
|
|
224
|
+
icon: '🌀',
|
|
225
|
+
description: 'SvelteKit apps',
|
|
226
|
+
languages: ['Node.js'],
|
|
227
|
+
priority: 108,
|
|
228
|
+
match(project) {
|
|
229
|
+
return hasProjectFile(project.path, 'svelte.config.js') || dependencyMatches(project, '@sveltejs/kit');
|
|
230
|
+
},
|
|
231
|
+
commands(project) {
|
|
232
|
+
const commands = {};
|
|
233
|
+
const add = (key, label, fallback) => {
|
|
234
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
235
|
+
if (tokens) {
|
|
236
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
add('run', 'SvelteKit dev', () => ['npm', 'run', 'dev']);
|
|
240
|
+
add('build', 'SvelteKit build', () => ['npm', 'run', 'build']);
|
|
241
|
+
add('test', 'SvelteKit test', () => ['npm', 'run', 'test']);
|
|
242
|
+
add('preview', 'SvelteKit preview', () => ['npm', 'run', 'preview']);
|
|
243
|
+
return commands;
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'nuxt',
|
|
248
|
+
name: 'Nuxt',
|
|
249
|
+
icon: '🪄',
|
|
250
|
+
description: 'Nuxt.js / Vue SSR',
|
|
251
|
+
languages: ['Node.js'],
|
|
252
|
+
priority: 107,
|
|
253
|
+
match(project) {
|
|
254
|
+
return hasProjectFile(project.path, 'nuxt.config.js') || dependencyMatches(project, 'nuxt');
|
|
255
|
+
},
|
|
256
|
+
commands(project) {
|
|
257
|
+
const commands = {};
|
|
258
|
+
const add = (key, label, fallback) => {
|
|
259
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
260
|
+
if (tokens) {
|
|
261
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
add('run', 'Nuxt dev', () => ['npm', 'run', 'dev']);
|
|
265
|
+
add('build', 'Nuxt build', () => ['npm', 'run', 'build']);
|
|
266
|
+
add('start', 'Nuxt start', () => ['npm', 'run', 'start']);
|
|
267
|
+
return commands;
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'astro',
|
|
272
|
+
name: 'Astro',
|
|
273
|
+
icon: '✨',
|
|
274
|
+
description: 'Astro static sites',
|
|
275
|
+
languages: ['Node.js'],
|
|
276
|
+
priority: 106,
|
|
277
|
+
match(project) {
|
|
278
|
+
const matches = ['astro.config.mjs', 'astro.config.ts'].some((file) => hasProjectFile(project.path, file));
|
|
279
|
+
return matches || dependencyMatches(project, 'astro');
|
|
280
|
+
},
|
|
281
|
+
commands(project) {
|
|
282
|
+
const commands = {};
|
|
283
|
+
const add = (key, label, fallback) => {
|
|
284
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
285
|
+
if (tokens) {
|
|
286
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
add('run', 'Astro dev', () => ['npm', 'run', 'dev']);
|
|
290
|
+
add('build', 'Astro build', () => ['npm', 'run', 'build']);
|
|
291
|
+
add('preview', 'Astro preview', () => ['npm', 'run', 'preview']);
|
|
292
|
+
return commands;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: 'django',
|
|
297
|
+
name: 'Django',
|
|
298
|
+
icon: '🌿',
|
|
299
|
+
description: 'Django web application',
|
|
300
|
+
languages: ['Python'],
|
|
301
|
+
priority: 110,
|
|
302
|
+
match(project) {
|
|
303
|
+
return dependencyMatches(project, 'django') || hasProjectFile(project.path, 'manage.py');
|
|
304
|
+
},
|
|
305
|
+
commands(project) {
|
|
306
|
+
const managePath = path.join(project.path, 'manage.py');
|
|
307
|
+
if (!fs.existsSync(managePath)) {
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
run: {label: 'Django runserver', command: ['python', 'manage.py', 'runserver'], source: 'framework'},
|
|
312
|
+
test: {label: 'Django test', command: ['python', 'manage.py', 'test'], source: 'framework'},
|
|
313
|
+
migrate: {label: 'Django migrate', command: ['python', 'manage.py', 'migrate'], source: 'framework'}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: 'flask',
|
|
319
|
+
name: 'Flask',
|
|
320
|
+
icon: '🍶',
|
|
321
|
+
description: 'Flask microservices',
|
|
322
|
+
languages: ['Python'],
|
|
323
|
+
priority: 105,
|
|
324
|
+
match(project) {
|
|
325
|
+
const entry = findPythonEntry(project.path);
|
|
326
|
+
return Boolean(entry && dependencyMatches(project, 'flask'));
|
|
327
|
+
},
|
|
328
|
+
commands(project) {
|
|
329
|
+
const entry = findPythonEntry(project.path);
|
|
330
|
+
if (!entry) {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
run: {label: 'Flask app', command: ['python', entry], source: 'framework'},
|
|
335
|
+
test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: 'fastapi',
|
|
341
|
+
name: 'FastAPI',
|
|
342
|
+
icon: '⚡',
|
|
343
|
+
description: 'FastAPI + Uvicorn',
|
|
344
|
+
languages: ['Python'],
|
|
345
|
+
priority: 105,
|
|
346
|
+
match(project) {
|
|
347
|
+
const entry = findPythonEntry(project.path);
|
|
348
|
+
return Boolean(entry && dependencyMatches(project, 'fastapi'));
|
|
349
|
+
},
|
|
350
|
+
commands(project) {
|
|
351
|
+
const entry = findPythonEntry(project.path);
|
|
352
|
+
if (!entry) {
|
|
353
|
+
return {};
|
|
354
|
+
}
|
|
355
|
+
const moduleName = entry.split('.').slice(0, -1).join('.') || entry;
|
|
356
|
+
return {
|
|
357
|
+
run: {label: 'Uvicorn reload', command: ['uvicorn', `${moduleName}:app`, '--reload'], source: 'framework'},
|
|
358
|
+
test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 'spring',
|
|
364
|
+
name: 'Spring Boot',
|
|
365
|
+
icon: '🌱',
|
|
366
|
+
description: 'Spring Boot apps',
|
|
367
|
+
languages: ['Java'],
|
|
368
|
+
priority: 105,
|
|
369
|
+
match(project) {
|
|
370
|
+
return dependencyMatches(project, 'spring-boot-starter') || hasProjectFile(project.path, 'src/main/java');
|
|
371
|
+
},
|
|
372
|
+
commands(project) {
|
|
373
|
+
const hasMvnw = hasProjectFile(project.path, 'mvnw');
|
|
374
|
+
const base = hasMvnw ? './mvnw' : 'mvn';
|
|
375
|
+
return {
|
|
376
|
+
run: {label: 'Spring Boot run', command: [base, 'spring-boot:run'], source: 'framework'},
|
|
377
|
+
build: {label: 'Maven package', command: [base, 'package'], source: 'framework'},
|
|
378
|
+
test: {label: 'Maven test', command: [base, 'test'], source: 'framework'}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
];
|
|
383
|
+
|
|
74
384
|
class SchemaRegistry {
|
|
75
385
|
constructor() {
|
|
76
386
|
this.cache = null;
|
|
@@ -102,10 +412,10 @@ class SchemaRegistry {
|
|
|
102
412
|
const pkg = JSON.parse(content);
|
|
103
413
|
const scripts = pkg.scripts || {};
|
|
104
414
|
const commands = {};
|
|
105
|
-
const preferScript = (targetKey, names,
|
|
415
|
+
const preferScript = (targetKey, names, labelText) => {
|
|
106
416
|
for (const name of names) {
|
|
107
417
|
if (Object.prototype.hasOwnProperty.call(scripts, name)) {
|
|
108
|
-
commands[targetKey] = {label, command: ['npm', 'run', name]};
|
|
418
|
+
commands[targetKey] = {label: labelText, command: ['npm', 'run', name]};
|
|
109
419
|
break;
|
|
110
420
|
}
|
|
111
421
|
}
|
|
@@ -126,7 +436,7 @@ class SchemaRegistry {
|
|
|
126
436
|
const setupHints = [];
|
|
127
437
|
if (metadata.dependencies.length) {
|
|
128
438
|
setupHints.push('Run npm install to fetch dependencies.');
|
|
129
|
-
if (
|
|
439
|
+
if (hasProjectFile(projectPath, 'yarn.lock')) {
|
|
130
440
|
setupHints.push('Or run yarn install if you prefer Yarn.');
|
|
131
441
|
}
|
|
132
442
|
}
|
|
@@ -157,13 +467,13 @@ class SchemaRegistry {
|
|
|
157
467
|
files: ['pyproject.toml', 'requirements.txt', 'setup.py', 'Pipfile'],
|
|
158
468
|
async build(projectPath, manifest) {
|
|
159
469
|
const commands = {};
|
|
160
|
-
if (
|
|
470
|
+
if (hasProjectFile(projectPath, 'pyproject.toml')) {
|
|
161
471
|
commands.test = {label: 'Pytest', command: ['pytest']};
|
|
162
472
|
} else {
|
|
163
473
|
commands.test = {label: 'Unittest', command: ['python', '-m', 'unittest', 'discover']};
|
|
164
474
|
}
|
|
165
475
|
|
|
166
|
-
const entry =
|
|
476
|
+
const entry = findPythonEntry(projectPath);
|
|
167
477
|
if (entry) {
|
|
168
478
|
commands.run = {label: 'Run', command: ['python', entry]};
|
|
169
479
|
}
|
|
@@ -173,12 +483,11 @@ class SchemaRegistry {
|
|
|
173
483
|
};
|
|
174
484
|
|
|
175
485
|
const setupHints = [];
|
|
176
|
-
|
|
177
|
-
if (fs.existsSync(reqPath)) {
|
|
486
|
+
if (hasProjectFile(projectPath, 'requirements.txt')) {
|
|
178
487
|
setupHints.push('pip install -r requirements.txt');
|
|
179
488
|
}
|
|
180
|
-
if (
|
|
181
|
-
setupHints.push('pipenv install --dev or poetry install');
|
|
489
|
+
if (hasProjectFile(projectPath, 'Pipfile')) {
|
|
490
|
+
setupHints.push('Use pipenv install --dev or poetry install');
|
|
182
491
|
}
|
|
183
492
|
|
|
184
493
|
return {
|
|
@@ -198,16 +507,6 @@ class SchemaRegistry {
|
|
|
198
507
|
}
|
|
199
508
|
};
|
|
200
509
|
},
|
|
201
|
-
findPythonEntry(projectPath) {
|
|
202
|
-
const candidates = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
|
|
203
|
-
for (const candidate of candidates) {
|
|
204
|
-
const candidatePath = path.join(projectPath, candidate);
|
|
205
|
-
if (fs.existsSync(candidatePath)) {
|
|
206
|
-
return candidate;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
510
|
},
|
|
212
511
|
{
|
|
213
512
|
type: 'rust',
|
|
@@ -272,8 +571,8 @@ class SchemaRegistry {
|
|
|
272
571
|
priority: 80,
|
|
273
572
|
files: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
|
|
274
573
|
async build(projectPath, manifest) {
|
|
275
|
-
const hasMvnw =
|
|
276
|
-
const hasGradlew =
|
|
574
|
+
const hasMvnw = hasProjectFile(projectPath, 'mvnw');
|
|
575
|
+
const hasGradlew = hasProjectFile(projectPath, 'gradlew');
|
|
277
576
|
const commands = {};
|
|
278
577
|
if (hasGradlew) {
|
|
279
578
|
commands.build = {label: 'Gradle build', command: ['./gradlew', 'build']};
|
|
@@ -285,6 +584,7 @@ class SchemaRegistry {
|
|
|
285
584
|
commands.build = {label: 'Maven package', command: ['mvn', 'package']};
|
|
286
585
|
commands.test = {label: 'Maven test', command: ['mvn', 'test']};
|
|
287
586
|
}
|
|
587
|
+
|
|
288
588
|
return {
|
|
289
589
|
id: `${projectPath}::java`,
|
|
290
590
|
path: projectPath,
|
|
@@ -297,7 +597,7 @@ class SchemaRegistry {
|
|
|
297
597
|
manifest: path.basename(manifest),
|
|
298
598
|
description: '',
|
|
299
599
|
extra: {
|
|
300
|
-
setupHints: ['Install JDK 17+ and run ./mvnw install
|
|
600
|
+
setupHints: ['Install JDK 17+ and run ./mvnw install or ./gradlew build']
|
|
301
601
|
}
|
|
302
602
|
};
|
|
303
603
|
}
|
|
@@ -345,7 +645,7 @@ class SchemaRegistry {
|
|
|
345
645
|
icon: '🐘',
|
|
346
646
|
priority: this.priority,
|
|
347
647
|
commands: {
|
|
348
|
-
test: {label: '
|
|
648
|
+
test: {label: 'PHP -v', command: ['php', '-v']}
|
|
349
649
|
},
|
|
350
650
|
metadata: {},
|
|
351
651
|
manifest: path.basename(manifest),
|
|
@@ -469,9 +769,6 @@ class SchemaRegistry {
|
|
|
469
769
|
|
|
470
770
|
const schemaRegistry = new SchemaRegistry();
|
|
471
771
|
|
|
472
|
-
const builtInFrameworks = [];
|
|
473
|
-
|
|
474
|
-
|
|
475
772
|
function loadUserFrameworks() {
|
|
476
773
|
ensureConfigDir();
|
|
477
774
|
if (!fs.existsSync(PLUGIN_FILE)) {
|
|
@@ -484,7 +781,7 @@ function loadUserFrameworks() {
|
|
|
484
781
|
const normalizedId = entry.id || (entry.name ? entry.name.toLowerCase().replace(/\s+/g, '-') : `plugin-${Math.random().toString(36).slice(2, 8)}`);
|
|
485
782
|
const commands = {};
|
|
486
783
|
Object.entries(entry.commands || {}).forEach(([key, value]) => {
|
|
487
|
-
const tokens = parseCommandTokens(value);
|
|
784
|
+
const tokens = parseCommandTokens(typeof value === 'object' ? value.command : value);
|
|
488
785
|
if (!tokens.length) {
|
|
489
786
|
return;
|
|
490
787
|
}
|
|
@@ -507,8 +804,7 @@ function loadUserFrameworks() {
|
|
|
507
804
|
commands,
|
|
508
805
|
match: entry.match
|
|
509
806
|
};
|
|
510
|
-
})
|
|
511
|
-
.filter((plugin) => plugin.name && plugin.commands && Object.keys(plugin.commands).length);
|
|
807
|
+
}).filter((plugin) => plugin.name && plugin.commands && Object.keys(plugin.commands).length);
|
|
512
808
|
} catch (error) {
|
|
513
809
|
console.error(`Failed to parse plugins.json: ${error.message}`);
|
|
514
810
|
return [];
|
|
@@ -530,7 +826,7 @@ function matchesPlugin(project, plugin) {
|
|
|
530
826
|
return false;
|
|
531
827
|
}
|
|
532
828
|
if (plugin.files && plugin.files.length > 0) {
|
|
533
|
-
const hit = plugin.files.some((file) =>
|
|
829
|
+
const hit = plugin.files.some((file) => hasProjectFile(project.path, file));
|
|
534
830
|
if (!hit) {
|
|
535
831
|
return false;
|
|
536
832
|
}
|