project-compass 2.0.0 → 2.0.1
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 +4 -2
- package/package.json +1 -1
- package/project-compass-2.0.0.tgz +0 -0
- package/project-compass-2.0.1.tgz +0 -0
- package/src/projectDetection.js +311 -21
package/README.md
CHANGED
|
@@ -81,10 +81,12 @@ Projects and details now occupy the same row, while the output panel takes its o
|
|
|
81
81
|
|
|
82
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.
|
|
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
|
package/src/projectDetection.js
CHANGED
|
@@ -5,6 +5,21 @@ import {ensureConfigDir, PLUGIN_FILE} from './configPaths.js';
|
|
|
5
5
|
|
|
6
6
|
const IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/target/**'];
|
|
7
7
|
|
|
8
|
+
function hasProjectFile(projectPath, file) {
|
|
9
|
+
return fs.existsSync(path.join(projectPath, file));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveScriptCommand(project, scriptName, fallback = null) {
|
|
13
|
+
const scripts = project.metadata?.scripts || {};
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
15
|
+
return ['npm', 'run', scriptName];
|
|
16
|
+
}
|
|
17
|
+
if (typeof fallback === 'function') {
|
|
18
|
+
return fallback();
|
|
19
|
+
}
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
function gatherNodeDependencies(pkg) {
|
|
9
24
|
const deps = new Set();
|
|
10
25
|
['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].forEach((key) => {
|
|
@@ -71,6 +86,286 @@ function parseCommandTokens(value) {
|
|
|
71
86
|
return [];
|
|
72
87
|
}
|
|
73
88
|
|
|
89
|
+
const builtInFrameworks = [
|
|
90
|
+
{
|
|
91
|
+
id: 'next',
|
|
92
|
+
name: 'Next.js',
|
|
93
|
+
icon: '🧭',
|
|
94
|
+
description: 'React + Next.js (SSR/SSG) apps',
|
|
95
|
+
languages: ['Node.js'],
|
|
96
|
+
priority: 115,
|
|
97
|
+
match(project) {
|
|
98
|
+
const hasNextConfig = hasProjectFile(project.path, 'next.config.js');
|
|
99
|
+
return dependencyMatches(project, 'next') || hasNextConfig;
|
|
100
|
+
},
|
|
101
|
+
commands(project) {
|
|
102
|
+
const commands = {};
|
|
103
|
+
const add = (key, label, fallback) => {
|
|
104
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
105
|
+
if (tokens) {
|
|
106
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const buildFallback = () => ['npx', 'next', 'build'];
|
|
110
|
+
const startFallback = () => ['npx', 'next', 'start'];
|
|
111
|
+
const devFallback = () => ['npx', 'next', 'dev'];
|
|
112
|
+
add('run', 'Next dev', devFallback);
|
|
113
|
+
add('build', 'Next build', buildFallback);
|
|
114
|
+
add('test', 'Next test', () => ['npm', 'run', 'test']);
|
|
115
|
+
add('start', 'Next start', startFallback);
|
|
116
|
+
return commands;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'react',
|
|
121
|
+
name: 'React',
|
|
122
|
+
icon: '⚛️',
|
|
123
|
+
description: 'React apps (CRA, Vite React)',
|
|
124
|
+
languages: ['Node.js'],
|
|
125
|
+
priority: 112,
|
|
126
|
+
match(project) {
|
|
127
|
+
return dependencyMatches(project, 'react') && (dependencyMatches(project, 'react-scripts') || dependencyMatches(project, 'vite') || hasProjectFile(project.path, 'vite.config.js'));
|
|
128
|
+
},
|
|
129
|
+
commands(project) {
|
|
130
|
+
const commands = {};
|
|
131
|
+
const add = (key, label, fallback) => {
|
|
132
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
133
|
+
if (tokens) {
|
|
134
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
add('run', 'React dev', () => ['npm', 'run', 'dev']);
|
|
138
|
+
add('build', 'React build', () => ['npm', 'run', 'build']);
|
|
139
|
+
add('test', 'React test', () => ['npm', 'run', 'test']);
|
|
140
|
+
return commands;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'vue',
|
|
145
|
+
name: 'Vue.js',
|
|
146
|
+
icon: '🟩',
|
|
147
|
+
description: 'Vue CLI or Vite + Vue apps',
|
|
148
|
+
languages: ['Node.js'],
|
|
149
|
+
priority: 111,
|
|
150
|
+
match(project) {
|
|
151
|
+
return dependencyMatches(project, 'vue') && (hasProjectFile(project.path, 'vue.config.js') || dependencyMatches(project, '@vue/cli-service') || dependencyMatches(project, 'vite'));
|
|
152
|
+
},
|
|
153
|
+
commands(project) {
|
|
154
|
+
const commands = {};
|
|
155
|
+
const add = (key, label, fallback) => {
|
|
156
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
157
|
+
if (tokens) {
|
|
158
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
add('run', 'Vue dev', () => ['npm', 'run', 'dev']);
|
|
162
|
+
add('build', 'Vue build', () => ['npm', 'run', 'build']);
|
|
163
|
+
add('test', 'Vue test', () => ['npm', 'run', 'test']);
|
|
164
|
+
return commands;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'nest',
|
|
169
|
+
name: 'NestJS',
|
|
170
|
+
icon: '🛡️',
|
|
171
|
+
description: 'NestJS backend',
|
|
172
|
+
languages: ['Node.js'],
|
|
173
|
+
priority: 110,
|
|
174
|
+
match(project) {
|
|
175
|
+
return dependencyMatches(project, '@nestjs/cli') || dependencyMatches(project, '@nestjs/core');
|
|
176
|
+
},
|
|
177
|
+
commands(project) {
|
|
178
|
+
const commands = {};
|
|
179
|
+
const add = (key, label, fallback) => {
|
|
180
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
181
|
+
if (tokens) {
|
|
182
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
add('run', 'Nest dev', () => ['npm', 'run', 'start:dev']);
|
|
186
|
+
add('build', 'Nest build', () => ['npm', 'run', 'build']);
|
|
187
|
+
add('test', 'Nest test', () => ['npm', 'run', 'test']);
|
|
188
|
+
return commands;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'angular',
|
|
193
|
+
name: 'Angular',
|
|
194
|
+
icon: '🅰️',
|
|
195
|
+
description: 'Angular CLI projects',
|
|
196
|
+
languages: ['Node.js'],
|
|
197
|
+
priority: 109,
|
|
198
|
+
match(project) {
|
|
199
|
+
return hasProjectFile(project.path, 'angular.json') || dependencyMatches(project, '@angular/cli');
|
|
200
|
+
},
|
|
201
|
+
commands(project) {
|
|
202
|
+
const commands = {};
|
|
203
|
+
const add = (key, label, fallback) => {
|
|
204
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
205
|
+
if (tokens) {
|
|
206
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
add('run', 'Angular serve', () => ['npm', 'run', 'start']);
|
|
210
|
+
add('build', 'Angular build', () => ['npm', 'run', 'build']);
|
|
211
|
+
add('test', 'Angular test', () => ['npm', 'run', 'test']);
|
|
212
|
+
return commands;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'sveltekit',
|
|
217
|
+
name: 'SvelteKit',
|
|
218
|
+
icon: '🌀',
|
|
219
|
+
description: 'SvelteKit apps',
|
|
220
|
+
languages: ['Node.js'],
|
|
221
|
+
priority: 108,
|
|
222
|
+
match(project) {
|
|
223
|
+
return hasProjectFile(project.path, 'svelte.config.js') || dependencyMatches(project, '@sveltejs/kit');
|
|
224
|
+
},
|
|
225
|
+
commands(project) {
|
|
226
|
+
const commands = {};
|
|
227
|
+
const add = (key, label, fallback) => {
|
|
228
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
229
|
+
if (tokens) {
|
|
230
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
add('run', 'SvelteKit dev', () => ['npm', 'run', 'dev']);
|
|
234
|
+
add('build', 'SvelteKit build', () => ['npm', 'run', 'build']);
|
|
235
|
+
add('test', 'SvelteKit test', () => ['npm', 'run', 'test']);
|
|
236
|
+
add('preview', 'SvelteKit preview', () => ['npm', 'run', 'preview']);
|
|
237
|
+
return commands;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'nuxt',
|
|
242
|
+
name: 'Nuxt',
|
|
243
|
+
icon: '🪄',
|
|
244
|
+
description: 'Nuxt.js / Vue SSR',
|
|
245
|
+
languages: ['Node.js'],
|
|
246
|
+
priority: 107,
|
|
247
|
+
match(project) {
|
|
248
|
+
return hasProjectFile(project.path, 'nuxt.config.js') || dependencyMatches(project, 'nuxt');
|
|
249
|
+
},
|
|
250
|
+
commands(project) {
|
|
251
|
+
const commands = {};
|
|
252
|
+
const add = (key, label, fallback) => {
|
|
253
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
254
|
+
if (tokens) {
|
|
255
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
add('run', 'Nuxt dev', () => ['npm', 'run', 'dev']);
|
|
259
|
+
add('build', 'Nuxt build', () => ['npm', 'run', 'build']);
|
|
260
|
+
add('start', 'Nuxt start', () => ['npm', 'run', 'start']);
|
|
261
|
+
return commands;
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'astro',
|
|
266
|
+
name: 'Astro',
|
|
267
|
+
icon: '✨',
|
|
268
|
+
description: 'Astro static sites',
|
|
269
|
+
languages: ['Node.js'],
|
|
270
|
+
priority: 106,
|
|
271
|
+
match(project) {
|
|
272
|
+
const matches = ['astro.config.mjs', 'astro.config.ts'].some((file) => hasProjectFile(project.path, file));
|
|
273
|
+
return matches || dependencyMatches(project, 'astro');
|
|
274
|
+
},
|
|
275
|
+
commands(project) {
|
|
276
|
+
const commands = {};
|
|
277
|
+
const add = (key, label, fallback) => {
|
|
278
|
+
const tokens = resolveScriptCommand(project, key, fallback);
|
|
279
|
+
if (tokens) {
|
|
280
|
+
commands[key] = {label, command: tokens, source: 'framework'};
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
add('run', 'Astro dev', () => ['npm', 'run', 'dev']);
|
|
284
|
+
add('build', 'Astro build', () => ['npm', 'run', 'build']);
|
|
285
|
+
add('preview', 'Astro preview', () => ['npm', 'run', 'preview']);
|
|
286
|
+
return commands;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: 'django',
|
|
291
|
+
name: 'Django',
|
|
292
|
+
icon: '🌿',
|
|
293
|
+
description: 'Django web application',
|
|
294
|
+
languages: ['Python'],
|
|
295
|
+
priority: 110,
|
|
296
|
+
match(project) {
|
|
297
|
+
return dependencyMatches(project, 'django') || hasProjectFile(project.path, 'manage.py');
|
|
298
|
+
},
|
|
299
|
+
commands(project) {
|
|
300
|
+
const managePath = path.join(project.path, 'manage.py');
|
|
301
|
+
if (!fs.existsSync(managePath)) {
|
|
302
|
+
return {};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
run: {label: 'Django runserver', command: ['python', 'manage.py', 'runserver'], source: 'framework'},
|
|
306
|
+
test: {label: 'Django test', command: ['python', 'manage.py', 'test'], source: 'framework'},
|
|
307
|
+
migrate: {label: 'Django migrate', command: ['python', 'manage.py', 'migrate'], source: 'framework'}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'flask',
|
|
313
|
+
name: 'Flask',
|
|
314
|
+
icon: '🍶',
|
|
315
|
+
description: 'Flask microservices',
|
|
316
|
+
languages: ['Python'],
|
|
317
|
+
priority: 105,
|
|
318
|
+
match(project) {
|
|
319
|
+
return dependencyMatches(project, 'flask') || hasProjectFile(project.path, 'app.py');
|
|
320
|
+
},
|
|
321
|
+
commands(project) {
|
|
322
|
+
const entry = hasProjectFile(project.path, 'app.py') ? 'app.py' : 'main.py';
|
|
323
|
+
return {
|
|
324
|
+
run: {label: 'Flask app', command: ['python', entry], source: 'framework'},
|
|
325
|
+
test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: 'fastapi',
|
|
331
|
+
name: 'FastAPI',
|
|
332
|
+
icon: '⚡',
|
|
333
|
+
description: 'FastAPI + Uvicorn',
|
|
334
|
+
languages: ['Python'],
|
|
335
|
+
priority: 105,
|
|
336
|
+
match(project) {
|
|
337
|
+
return dependencyMatches(project, 'fastapi');
|
|
338
|
+
},
|
|
339
|
+
commands(project) {
|
|
340
|
+
const entry = hasProjectFile(project.path, 'main.py') ? 'main.py' : 'app.py';
|
|
341
|
+
return {
|
|
342
|
+
run: {label: 'Uvicorn reload', command: ['uvicorn', `${entry.split('.')[0]}:app`, '--reload'], source: 'framework'},
|
|
343
|
+
test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 'spring',
|
|
349
|
+
name: 'Spring Boot',
|
|
350
|
+
icon: '🌱',
|
|
351
|
+
description: 'Spring Boot apps',
|
|
352
|
+
languages: ['Java'],
|
|
353
|
+
priority: 105,
|
|
354
|
+
match(project) {
|
|
355
|
+
return dependencyMatches(project, 'spring-boot-starter') || hasProjectFile(project.path, 'src/main/java');
|
|
356
|
+
},
|
|
357
|
+
commands(project) {
|
|
358
|
+
const hasMvnw = hasProjectFile(project.path, 'mvnw');
|
|
359
|
+
const base = hasMvnw ? './mvnw' : 'mvn';
|
|
360
|
+
return {
|
|
361
|
+
run: {label: 'Spring Boot run', command: [base, 'spring-boot:run'], source: 'framework'},
|
|
362
|
+
build: {label: 'Maven package', command: [base, 'package'], source: 'framework'},
|
|
363
|
+
test: {label: 'Maven test', command: [base, 'test'], source: 'framework'}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
];
|
|
368
|
+
|
|
74
369
|
class SchemaRegistry {
|
|
75
370
|
constructor() {
|
|
76
371
|
this.cache = null;
|
|
@@ -102,10 +397,10 @@ class SchemaRegistry {
|
|
|
102
397
|
const pkg = JSON.parse(content);
|
|
103
398
|
const scripts = pkg.scripts || {};
|
|
104
399
|
const commands = {};
|
|
105
|
-
const preferScript = (targetKey, names,
|
|
400
|
+
const preferScript = (targetKey, names, labelText) => {
|
|
106
401
|
for (const name of names) {
|
|
107
402
|
if (Object.prototype.hasOwnProperty.call(scripts, name)) {
|
|
108
|
-
commands[targetKey] = {label, command: ['npm', 'run', name]};
|
|
403
|
+
commands[targetKey] = {label: labelText, command: ['npm', 'run', name]};
|
|
109
404
|
break;
|
|
110
405
|
}
|
|
111
406
|
}
|
|
@@ -126,7 +421,7 @@ class SchemaRegistry {
|
|
|
126
421
|
const setupHints = [];
|
|
127
422
|
if (metadata.dependencies.length) {
|
|
128
423
|
setupHints.push('Run npm install to fetch dependencies.');
|
|
129
|
-
if (
|
|
424
|
+
if (hasProjectFile(projectPath, 'yarn.lock')) {
|
|
130
425
|
setupHints.push('Or run yarn install if you prefer Yarn.');
|
|
131
426
|
}
|
|
132
427
|
}
|
|
@@ -157,7 +452,7 @@ class SchemaRegistry {
|
|
|
157
452
|
files: ['pyproject.toml', 'requirements.txt', 'setup.py', 'Pipfile'],
|
|
158
453
|
async build(projectPath, manifest) {
|
|
159
454
|
const commands = {};
|
|
160
|
-
if (
|
|
455
|
+
if (hasProjectFile(projectPath, 'pyproject.toml')) {
|
|
161
456
|
commands.test = {label: 'Pytest', command: ['pytest']};
|
|
162
457
|
} else {
|
|
163
458
|
commands.test = {label: 'Unittest', command: ['python', '-m', 'unittest', 'discover']};
|
|
@@ -173,12 +468,11 @@ class SchemaRegistry {
|
|
|
173
468
|
};
|
|
174
469
|
|
|
175
470
|
const setupHints = [];
|
|
176
|
-
|
|
177
|
-
if (fs.existsSync(reqPath)) {
|
|
471
|
+
if (hasProjectFile(projectPath, 'requirements.txt')) {
|
|
178
472
|
setupHints.push('pip install -r requirements.txt');
|
|
179
473
|
}
|
|
180
|
-
if (
|
|
181
|
-
setupHints.push('pipenv install --dev or poetry install');
|
|
474
|
+
if (hasProjectFile(projectPath, 'Pipfile')) {
|
|
475
|
+
setupHints.push('Use pipenv install --dev or poetry install');
|
|
182
476
|
}
|
|
183
477
|
|
|
184
478
|
return {
|
|
@@ -201,8 +495,7 @@ class SchemaRegistry {
|
|
|
201
495
|
findPythonEntry(projectPath) {
|
|
202
496
|
const candidates = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
|
|
203
497
|
for (const candidate of candidates) {
|
|
204
|
-
|
|
205
|
-
if (fs.existsSync(candidatePath)) {
|
|
498
|
+
if (hasProjectFile(projectPath, candidate)) {
|
|
206
499
|
return candidate;
|
|
207
500
|
}
|
|
208
501
|
}
|
|
@@ -272,8 +565,8 @@ class SchemaRegistry {
|
|
|
272
565
|
priority: 80,
|
|
273
566
|
files: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
|
|
274
567
|
async build(projectPath, manifest) {
|
|
275
|
-
const hasMvnw =
|
|
276
|
-
const hasGradlew =
|
|
568
|
+
const hasMvnw = hasProjectFile(projectPath, 'mvnw');
|
|
569
|
+
const hasGradlew = hasProjectFile(projectPath, 'gradlew');
|
|
277
570
|
const commands = {};
|
|
278
571
|
if (hasGradlew) {
|
|
279
572
|
commands.build = {label: 'Gradle build', command: ['./gradlew', 'build']};
|
|
@@ -285,6 +578,7 @@ class SchemaRegistry {
|
|
|
285
578
|
commands.build = {label: 'Maven package', command: ['mvn', 'package']};
|
|
286
579
|
commands.test = {label: 'Maven test', command: ['mvn', 'test']};
|
|
287
580
|
}
|
|
581
|
+
|
|
288
582
|
return {
|
|
289
583
|
id: `${projectPath}::java`,
|
|
290
584
|
path: projectPath,
|
|
@@ -297,7 +591,7 @@ class SchemaRegistry {
|
|
|
297
591
|
manifest: path.basename(manifest),
|
|
298
592
|
description: '',
|
|
299
593
|
extra: {
|
|
300
|
-
setupHints: ['Install JDK 17+ and run ./mvnw install
|
|
594
|
+
setupHints: ['Install JDK 17+ and run ./mvnw install or ./gradlew build']
|
|
301
595
|
}
|
|
302
596
|
};
|
|
303
597
|
}
|
|
@@ -345,7 +639,7 @@ class SchemaRegistry {
|
|
|
345
639
|
icon: '🐘',
|
|
346
640
|
priority: this.priority,
|
|
347
641
|
commands: {
|
|
348
|
-
test: {label: '
|
|
642
|
+
test: {label: 'PHP -v', command: ['php', '-v']}
|
|
349
643
|
},
|
|
350
644
|
metadata: {},
|
|
351
645
|
manifest: path.basename(manifest),
|
|
@@ -469,9 +763,6 @@ class SchemaRegistry {
|
|
|
469
763
|
|
|
470
764
|
const schemaRegistry = new SchemaRegistry();
|
|
471
765
|
|
|
472
|
-
const builtInFrameworks = [];
|
|
473
|
-
|
|
474
|
-
|
|
475
766
|
function loadUserFrameworks() {
|
|
476
767
|
ensureConfigDir();
|
|
477
768
|
if (!fs.existsSync(PLUGIN_FILE)) {
|
|
@@ -484,7 +775,7 @@ function loadUserFrameworks() {
|
|
|
484
775
|
const normalizedId = entry.id || (entry.name ? entry.name.toLowerCase().replace(/\s+/g, '-') : `plugin-${Math.random().toString(36).slice(2, 8)}`);
|
|
485
776
|
const commands = {};
|
|
486
777
|
Object.entries(entry.commands || {}).forEach(([key, value]) => {
|
|
487
|
-
const tokens = parseCommandTokens(value);
|
|
778
|
+
const tokens = parseCommandTokens(typeof value === 'object' ? value.command : value);
|
|
488
779
|
if (!tokens.length) {
|
|
489
780
|
return;
|
|
490
781
|
}
|
|
@@ -507,8 +798,7 @@ function loadUserFrameworks() {
|
|
|
507
798
|
commands,
|
|
508
799
|
match: entry.match
|
|
509
800
|
};
|
|
510
|
-
})
|
|
511
|
-
.filter((plugin) => plugin.name && plugin.commands && Object.keys(plugin.commands).length);
|
|
801
|
+
}).filter((plugin) => plugin.name && plugin.commands && Object.keys(plugin.commands).length);
|
|
512
802
|
} catch (error) {
|
|
513
803
|
console.error(`Failed to parse plugins.json: ${error.message}`);
|
|
514
804
|
return [];
|
|
@@ -530,7 +820,7 @@ function matchesPlugin(project, plugin) {
|
|
|
530
820
|
return false;
|
|
531
821
|
}
|
|
532
822
|
if (plugin.files && plugin.files.length > 0) {
|
|
533
|
-
const hit = plugin.files.some((file) =>
|
|
823
|
+
const hit = plugin.files.some((file) => hasProjectFile(project.path, file));
|
|
534
824
|
if (!hit) {
|
|
535
825
|
return false;
|
|
536
826
|
}
|