From 11a970e11e5fb9a03254a013b4138c619eb9a1ed Mon Sep 17 00:00:00 2001 From: Beksultan Pirmatov Date: Fri, 9 Oct 2026 11:16:44 +0600 Subject: [PATCH] fix: detect Dart/Flutter projects in project-detect.js LANGUAGE_RULES had no entry for Dart, and FRAMEWORK_RULES had no way to detect Flutter, so any Flutter app (pubspec.yaml, lib/*.dart, android/, ios/) was reported as primary: "unknown" by the SessionStart project-type banner, despite ECC shipping a dart-flutter-patterns skill and a dart-build-resolver agent for this exact stack. Adds a 'dart' language rule (pubspec.yaml/pubspec.lock markers, .dart extension) and a 'flutter' framework rule that checks pubspec.yaml for the standard `flutter: / sdk: flutter` dependency shape, via a new customCheck hook on FRAMEWORK_RULES entries. This distinguishes Flutter apps from plain Dart packages, which don't depend on the Flutter SDK. Closes #3469 --- scripts/lib/project-detect.js | 35 ++++++++++++++++++++-- tests/lib/project-detect.test.js | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/scripts/lib/project-detect.js b/scripts/lib/project-detect.js index 98308fa0a..a7f625847 100644 --- a/scripts/lib/project-detect.js +++ b/scripts/lib/project-detect.js @@ -84,6 +84,11 @@ const LANGUAGE_RULES = [ type: 'php', markers: ['composer.json', 'composer.lock'], extensions: ['.php'] + }, + { + type: 'dart', + markers: ['pubspec.yaml', 'pubspec.lock'], + extensions: ['.dart'] } ]; @@ -129,7 +134,10 @@ const FRAMEWORK_RULES = [ { framework: 'symfony', language: 'php', markers: ['symfony.lock'], packageKeys: ['symfony/framework-bundle'] }, // Elixir frameworks - { framework: 'phoenix', language: 'elixir', markers: [], packageKeys: ['phoenix'] } + { framework: 'phoenix', language: 'elixir', markers: [], packageKeys: ['phoenix'] }, + + // Dart frameworks + { framework: 'flutter', language: 'dart', markers: [], packageKeys: [], customCheck: hasFlutterSdkDependency } ]; /** @@ -330,6 +338,27 @@ function getElixirDeps(projectDir) { } } +/** + * Check whether pubspec.yaml declares the Flutter SDK as a dependency, + * which distinguishes a Flutter app from a plain Dart package. + * @param {string} projectDir - Project root directory + * @returns {boolean} + */ +function hasFlutterSdkDependency(projectDir) { + try { + const pubspecPath = path.join(projectDir, 'pubspec.yaml'); + if (!fs.existsSync(pubspecPath)) return false; + const content = fs.readFileSync(pubspecPath, 'utf8'); + // Matches the standard Flutter pubspec shape: + // dependencies: + // flutter: + // sdk: flutter + return /^\s*flutter:\s*$/m.test(content) && /sdk:\s*flutter/.test(content); + } catch { + return false; + } +} + /** * Detect project languages and frameworks * @param {string} [projectDir] - Project directory (defaults to cwd) @@ -409,7 +438,9 @@ function detectProjectType(projectDir) { }); } - if (hasMarker || hasDep) { + const hasCustom = typeof rule.customCheck === 'function' ? rule.customCheck(projectDir) : false; + + if (hasMarker || hasDep || hasCustom) { frameworks.push(rule.framework); } } diff --git a/tests/lib/project-detect.test.js b/tests/lib/project-detect.test.js index 30727f360..100e1c6c3 100644 --- a/tests/lib/project-detect.test.js +++ b/tests/lib/project-detect.test.js @@ -493,6 +493,57 @@ function runTests() { } })) passed++; else failed++; + // Dart/Flutter detection + console.log('\nDart/Flutter Detection:'); + + if (test('detects dart from pubspec.yaml and .dart files', () => { + const dir = createTempDir(); + try { + writeTestFile(dir, 'pubspec.yaml', 'name: test_pkg\nversion: 1.0.0\n'); + writeTestFile(dir, 'main.dart', 'void main() {}\n'); + const result = detectProjectType(dir); + assert.ok(result.languages.includes('dart')); + } finally { + cleanupDir(dir); + } + })) passed++; else failed++; + + if (test('detects flutter framework when pubspec.yaml depends on the Flutter SDK', () => { + const dir = createTempDir(); + try { + writeTestFile(dir, 'pubspec.yaml', [ + 'name: halal_cosmetics_app', + 'dependencies:', + ' flutter:', + ' sdk: flutter', + ' cupertino_icons: ^1.0.2' + ].join('\n')); + const result = detectProjectType(dir); + assert.ok(result.languages.includes('dart')); + assert.ok(result.frameworks.includes('flutter'), `flutter not detected: ${JSON.stringify(result.frameworks)}`); + assert.strictEqual(result.primary, 'flutter'); + } finally { + cleanupDir(dir); + } + })) passed++; else failed++; + + if (test('does not misclassify a plain Dart package as flutter', () => { + const dir = createTempDir(); + try { + writeTestFile(dir, 'pubspec.yaml', [ + 'name: plain_dart_pkg', + 'dependencies:', + ' http: ^1.0.0' + ].join('\n')); + const result = detectProjectType(dir); + assert.ok(result.languages.includes('dart')); + assert.ok(!result.frameworks.includes('flutter'), `plain Dart package wrongly detected as flutter: ${JSON.stringify(result.frameworks)}`); + assert.strictEqual(result.primary, 'dart'); + } finally { + cleanupDir(dir); + } + })) passed++; else failed++; + // Edge cases console.log('\nEdge Cases:');