project-scan 1.0.1 → 1.0.3
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/lib/test-runner.js +53 -23
- package/package.json +1 -1
package/lib/test-runner.js
CHANGED
|
@@ -9,6 +9,8 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
12
|
+
const IS_WIN = process.platform === 'win32';
|
|
13
|
+
|
|
12
14
|
// ─── Per-language runner configs ─────────────────────────────────────────────
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -100,9 +102,9 @@ const RUNNERS = [
|
|
|
100
102
|
hasSourceExt(root, '.py');
|
|
101
103
|
},
|
|
102
104
|
run(root) {
|
|
103
|
-
const cmd = commandExists('pytest')
|
|
104
|
-
: commandExists('python3')
|
|
105
|
-
: commandExists('python')
|
|
105
|
+
const cmd = commandExists('pytest') ? 'pytest -v --tb=short'
|
|
106
|
+
: commandExists('python3') ? 'python3 -m pytest -v --tb=short'
|
|
107
|
+
: commandExists('python') ? 'python -m pytest -v --tb=short'
|
|
106
108
|
: null;
|
|
107
109
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'pytest', parseOutput: parsePytestOutput })];
|
|
108
110
|
}
|
|
@@ -115,7 +117,7 @@ const RUNNERS = [
|
|
|
115
117
|
run(root) {
|
|
116
118
|
return [runCommand({
|
|
117
119
|
dir: root, label: '(root)',
|
|
118
|
-
cmd: commandExists('go') ? 'go test ./... -v
|
|
120
|
+
cmd: commandExists('go') ? 'go test ./... -v' : null,
|
|
119
121
|
suite: 'Go test',
|
|
120
122
|
parseOutput: parseGoTestOutput,
|
|
121
123
|
})];
|
|
@@ -129,7 +131,7 @@ const RUNNERS = [
|
|
|
129
131
|
run(root) {
|
|
130
132
|
return [runCommand({
|
|
131
133
|
dir: root, label: '(root)',
|
|
132
|
-
cmd: commandExists('cargo') ? 'cargo test
|
|
134
|
+
cmd: commandExists('cargo') ? 'cargo test' : null,
|
|
133
135
|
suite: 'Cargo test',
|
|
134
136
|
parseOutput: parseCargoOutput,
|
|
135
137
|
})];
|
|
@@ -141,7 +143,7 @@ const RUNNERS = [
|
|
|
141
143
|
name: 'Maven',
|
|
142
144
|
detect(root) { return fs.existsSync(path.join(root, 'pom.xml')); },
|
|
143
145
|
run(root) {
|
|
144
|
-
const cmd = commandExists('mvn') ? 'mvn test -B
|
|
146
|
+
const cmd = commandExists('mvn') ? 'mvn test -B' : null;
|
|
145
147
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'Maven', parseOutput: parseMavenOutput })];
|
|
146
148
|
}
|
|
147
149
|
},
|
|
@@ -154,8 +156,13 @@ const RUNNERS = [
|
|
|
154
156
|
fs.existsSync(path.join(root, 'build.gradle.kts'));
|
|
155
157
|
},
|
|
156
158
|
run(root) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
// Windows uses gradlew.bat; Unix uses ./gradlew
|
|
160
|
+
const wrapperWin = path.join(root, 'gradlew.bat');
|
|
161
|
+
const wrapperUnix = path.join(root, 'gradlew');
|
|
162
|
+
const gradleCmd = fs.existsSync(IS_WIN ? wrapperWin : wrapperUnix)
|
|
163
|
+
? (IS_WIN ? 'gradlew.bat' : './gradlew')
|
|
164
|
+
: (commandExists('gradle') ? 'gradle' : null);
|
|
165
|
+
return [runCommand({ dir: root, label: '(root)', cmd: gradleCmd ? `${gradleCmd} test` : null, suite: 'Gradle', parseOutput: parseGradleOutput })];
|
|
159
166
|
}
|
|
160
167
|
},
|
|
161
168
|
|
|
@@ -163,14 +170,16 @@ const RUNNERS = [
|
|
|
163
170
|
{
|
|
164
171
|
name: 'PHPUnit',
|
|
165
172
|
detect(root) {
|
|
166
|
-
return fs.existsSync(path.join(root, 'phpunit.xml'))
|
|
167
|
-
fs.existsSync(path.join(root, 'phpunit.xml.dist'))
|
|
168
|
-
fs.existsSync(path.join(root, 'vendor
|
|
173
|
+
return fs.existsSync(path.join(root, 'phpunit.xml')) ||
|
|
174
|
+
fs.existsSync(path.join(root, 'phpunit.xml.dist')) ||
|
|
175
|
+
fs.existsSync(path.join(root, 'vendor', 'bin', 'phpunit')) ||
|
|
169
176
|
hasSourceExt(root, '.php');
|
|
170
177
|
},
|
|
171
178
|
run(root) {
|
|
172
|
-
const
|
|
173
|
-
|
|
179
|
+
const vendorBin = path.join(root, 'vendor', 'bin', 'phpunit');
|
|
180
|
+
const cmd = fs.existsSync(vendorBin)
|
|
181
|
+
? `"${vendorBin}" --testdox`
|
|
182
|
+
: commandExists('phpunit') ? 'phpunit --testdox' : null;
|
|
174
183
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'PHPUnit', parseOutput: parsePHPUnitOutput })];
|
|
175
184
|
}
|
|
176
185
|
},
|
|
@@ -184,7 +193,7 @@ const RUNNERS = [
|
|
|
184
193
|
fs.existsSync(path.join(root, 'spec'));
|
|
185
194
|
},
|
|
186
195
|
run(root) {
|
|
187
|
-
const cmd = commandExists('rspec') ? 'rspec --format progress
|
|
196
|
+
const cmd = commandExists('rspec') ? 'rspec --format progress' : (commandExists('bundle') ? 'bundle exec rspec --format progress' : null);
|
|
188
197
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'RSpec', parseOutput: parseRSpecOutput })];
|
|
189
198
|
}
|
|
190
199
|
},
|
|
@@ -194,7 +203,7 @@ const RUNNERS = [
|
|
|
194
203
|
name: 'Flutter/Dart test',
|
|
195
204
|
detect(root) { return fs.existsSync(path.join(root, 'pubspec.yaml')); },
|
|
196
205
|
run(root) {
|
|
197
|
-
const cmd = commandExists('flutter') ? 'flutter test
|
|
206
|
+
const cmd = commandExists('flutter') ? 'flutter test' : (commandExists('dart') ? 'dart test' : null);
|
|
198
207
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'Flutter/Dart', parseOutput: parseDartOutput })];
|
|
199
208
|
}
|
|
200
209
|
},
|
|
@@ -208,7 +217,7 @@ const RUNNERS = [
|
|
|
208
217
|
} catch { return false; }
|
|
209
218
|
},
|
|
210
219
|
run(root) {
|
|
211
|
-
const cmd = commandExists('dotnet') ? 'dotnet test --logger "console;verbosity=normal"
|
|
220
|
+
const cmd = commandExists('dotnet') ? 'dotnet test --logger "console;verbosity=normal"' : null;
|
|
212
221
|
return [runCommand({ dir: root, label: '(root)', cmd, suite: 'dotnet test', parseOutput: parseDotnetOutput })];
|
|
213
222
|
}
|
|
214
223
|
},
|
|
@@ -252,17 +261,18 @@ function runAllTests(rootDir) {
|
|
|
252
261
|
|
|
253
262
|
function runJest(dir, label) {
|
|
254
263
|
const start = Date.now();
|
|
255
|
-
if (!fs.existsSync(path.join(dir, 'node_modules', '.bin', 'jest'))
|
|
264
|
+
if (!fs.existsSync(path.join(dir, 'node_modules', '.bin', 'jest')) &&
|
|
265
|
+
!fs.existsSync(path.join(dir, 'node_modules', '.bin', 'jest.cmd')) &&
|
|
256
266
|
!fs.existsSync(path.join(dir, 'node_modules', 'jest'))) {
|
|
257
|
-
// Try npm test fallback
|
|
258
267
|
const pkg = tryReadJSON(path.join(dir, 'package.json'));
|
|
259
268
|
if (!pkg?.scripts?.test) {
|
|
260
269
|
return { suite: `Jest (${label})`, status: 'skipped', passed: 0, failed: 0, skipped: 0, output: 'Jest not installed. Run npm install first.', duration: 0 };
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
272
|
|
|
273
|
+
// shell:true is required on Windows so npx.cmd is resolved correctly
|
|
264
274
|
const result = spawnSync('npx', ['jest', '--no-coverage', '--json', '--forceExit'], {
|
|
265
|
-
cwd: dir, encoding: 'utf8', timeout: 120000,
|
|
275
|
+
cwd: dir, encoding: 'utf8', timeout: 120000, shell: true,
|
|
266
276
|
env: { ...process.env, FORCE_COLOR: '0' }
|
|
267
277
|
});
|
|
268
278
|
|
|
@@ -329,7 +339,11 @@ function runCommand({ dir, label, cmd, suite, parseOutput }) {
|
|
|
329
339
|
}
|
|
330
340
|
const start = Date.now();
|
|
331
341
|
try {
|
|
332
|
-
|
|
342
|
+
// shell:true is required on Windows; harmless on Unix
|
|
343
|
+
const output = execSync(cmd, {
|
|
344
|
+
cwd: dir, encoding: 'utf8', timeout: 120000, shell: true,
|
|
345
|
+
env: { ...process.env, FORCE_COLOR: '0' }, stdio: 'pipe'
|
|
346
|
+
});
|
|
333
347
|
const duration = Date.now() - start;
|
|
334
348
|
return { suite: `${suite} (${label})`, duration, ...parseOutput(output, true) };
|
|
335
349
|
} catch (err) {
|
|
@@ -432,12 +446,28 @@ function parseDotnetOutput(output, exitOk) {
|
|
|
432
446
|
return { status: exitOk ? 'passed' : 'failed', passed: p ? parseInt(p[1]) : 0, failed: f ? parseInt(f[1]) : 0, skipped: 0, output: output.slice(-3000) };
|
|
433
447
|
}
|
|
434
448
|
|
|
435
|
-
// ─── Utility helpers
|
|
449
|
+
// ─── Utility helpers ─────────────────────────────────────────────────────────
|
|
436
450
|
|
|
437
451
|
function tryReadJSON(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } }
|
|
438
452
|
function allDeps(pkg) { return [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {}), ...Object.keys(pkg.peerDependencies || {})]; }
|
|
439
|
-
|
|
440
|
-
|
|
453
|
+
|
|
454
|
+
/** Works on Windows (where and Get-Command) and Unix (which / command -v). */
|
|
455
|
+
function commandExists(cmd) {
|
|
456
|
+
try {
|
|
457
|
+
if (IS_WIN) {
|
|
458
|
+
execSync(`where ${cmd}`, { encoding: 'utf8', stdio: 'pipe' });
|
|
459
|
+
} else {
|
|
460
|
+
execSync(`command -v ${cmd}`, { encoding: 'utf8', stdio: 'pipe', shell: '/bin/sh' });
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
} catch { return false; }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function hasBin(dir, bin) {
|
|
467
|
+
// On Windows the bin is a .cmd wrapper
|
|
468
|
+
return fs.existsSync(path.join(dir, 'node_modules', '.bin', bin)) ||
|
|
469
|
+
fs.existsSync(path.join(dir, 'node_modules', '.bin', bin + '.cmd'));
|
|
470
|
+
}
|
|
441
471
|
function hasSourceExt(root, ext) {
|
|
442
472
|
try { return fs.readdirSync(root).some(f => f.endsWith(ext)); } catch { return false; }
|
|
443
473
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-scan",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Universal project summarizer & test runner — scans any project for languages, frameworks, databases, cloud services, CI/CD pipelines and runs tests. Works with JavaScript, TypeScript, Python, Go, Rust, Java, Kotlin, PHP, Ruby, Dart, C#, and more.",
|
|
5
5
|
"main": "lib/scanner.js",
|
|
6
6
|
"bin": {
|