fix: recognize flutter-ci-setup composite action in change detection
Add .github/actions/flutter-ci-setup/ path recognition: - isCiPath() now recognizes flutter-ci-setup as CI-critical - isNativeBuildPath() now recognizes flutter-ci-setup as requiring release builds - Only Android-specific composite actions (flutter-ci-setup) trigger builds - Non-Android actions (website-deploy, etc.) are not recognized as CI-critical Add comprehensive test coverage: - Test composite action path classification (7 new tests added) - Verify workflow YAML for output mapping and release condition - Ensure non-Android actions are correctly skipped - Test workflow condition evaluation for all scenarios Test results: 65/65 passing (all new tests verify the composite action fix) Flutter-ci-setup is a composite action used by Android workflow jobs (analysis, test, and debug APK), so changes to it must trigger Android verification and release builds. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
32059f9b8a
commit
a573b2d275
|
|
@ -44,10 +44,12 @@ function isNativeBuildPath(filePath) {
|
||||||
if (!p) return false;
|
if (!p) return false;
|
||||||
// Native build requires: Android code, CI-critical files, or build config.
|
// Native build requires: Android code, CI-critical files, or build config.
|
||||||
// Only CI files that affect build behavior trigger release builds; doc/comment changes do not.
|
// Only CI files that affect build behavior trigger release builds; doc/comment changes do not.
|
||||||
|
// Composite actions: only Android-specific actions (flutter-ci-setup) trigger builds.
|
||||||
return (
|
return (
|
||||||
p.startsWith('android/') ||
|
p.startsWith('android/') ||
|
||||||
p === '.github/workflows/android-build.yml' ||
|
p === '.github/workflows/android-build.yml' ||
|
||||||
p.startsWith('.github/scripts/') ||
|
p.startsWith('.github/scripts/') ||
|
||||||
|
p.startsWith('.github/actions/flutter-ci-setup/') ||
|
||||||
p === 'pubspec.yaml' ||
|
p === 'pubspec.yaml' ||
|
||||||
p === 'pubspec.lock' ||
|
p === 'pubspec.lock' ||
|
||||||
p === 'analysis_options.yaml' ||
|
p === 'analysis_options.yaml' ||
|
||||||
|
|
@ -58,9 +60,11 @@ function isNativeBuildPath(filePath) {
|
||||||
function isCiPath(filePath) {
|
function isCiPath(filePath) {
|
||||||
const p = normalizePath(filePath);
|
const p = normalizePath(filePath);
|
||||||
if (!p) return false;
|
if (!p) return false;
|
||||||
|
// CI paths: workflows, scripts, and Android-specific composite actions.
|
||||||
return (
|
return (
|
||||||
p === '.github/workflows/android-build.yml' ||
|
p === '.github/workflows/android-build.yml' ||
|
||||||
p.startsWith('.github/scripts/')
|
p.startsWith('.github/scripts/') ||
|
||||||
|
p.startsWith('.github/actions/flutter-ci-setup/')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +291,7 @@ module.exports = {
|
||||||
isZeroSha,
|
isZeroSha,
|
||||||
isAppPath,
|
isAppPath,
|
||||||
isCiPath,
|
isCiPath,
|
||||||
|
isNativeBuildPath,
|
||||||
classifyFiles,
|
classifyFiles,
|
||||||
getChangedFiles,
|
getChangedFiles,
|
||||||
detectAndroidChanges
|
detectAndroidChanges
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const {
|
||||||
isZeroSha,
|
isZeroSha,
|
||||||
isAppPath,
|
isAppPath,
|
||||||
isCiPath,
|
isCiPath,
|
||||||
|
isNativeBuildPath,
|
||||||
classifyFiles,
|
classifyFiles,
|
||||||
getChangedFiles,
|
getChangedFiles,
|
||||||
detectAndroidChanges
|
detectAndroidChanges
|
||||||
|
|
@ -71,14 +72,37 @@ describe('File Path Classification', () => {
|
||||||
assert.strictEqual(isCiPath('.github/workflows/android-build.yml'), true);
|
assert.strictEqual(isCiPath('.github/workflows/android-build.yml'), true);
|
||||||
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.cjs'), true);
|
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.cjs'), true);
|
||||||
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.test.cjs'), true);
|
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.test.cjs'), true);
|
||||||
|
assert.strictEqual(isCiPath('.github/actions/flutter-ci-setup/action.yml'), true);
|
||||||
|
assert.strictEqual(isCiPath('.github/actions/flutter-ci-setup/index.js'), true);
|
||||||
|
|
||||||
// Negative matches for Android CI paths
|
// Negative matches for Android CI paths
|
||||||
assert.strictEqual(isCiPath('.github/workflows/website-ci.yml'), false);
|
assert.strictEqual(isCiPath('.github/workflows/website-ci.yml'), false);
|
||||||
assert.strictEqual(isCiPath('.github/workflows/backend-ci.yml'), false);
|
assert.strictEqual(isCiPath('.github/workflows/backend-ci.yml'), false);
|
||||||
assert.strictEqual(isCiPath('.github/workflows/waze-relay-ci.yml'), false);
|
assert.strictEqual(isCiPath('.github/workflows/waze-relay-ci.yml'), false);
|
||||||
assert.strictEqual(isCiPath('.github/workflows/firebase-emulator-tests.yml'), false);
|
assert.strictEqual(isCiPath('.github/workflows/firebase-emulator-tests.yml'), false);
|
||||||
|
assert.strictEqual(isCiPath('.github/actions/website-deploy/action.yml'), false);
|
||||||
|
assert.strictEqual(isCiPath('.github/actions/other-action/index.js'), false);
|
||||||
assert.strictEqual(isCiPath('.github/pull_request_template.md'), false);
|
assert.strictEqual(isCiPath('.github/pull_request_template.md'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('identifies native build paths correctly', () => {
|
||||||
|
assert.strictEqual(isNativeBuildPath('android/app/build.gradle'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/workflows/android-build.yml'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/scripts/detect-android-changes.cjs'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/actions/flutter-ci-setup/action.yml'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/actions/flutter-ci-setup/index.js'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('pubspec.yaml'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('pubspec.lock'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('analysis_options.yaml'), true);
|
||||||
|
assert.strictEqual(isNativeBuildPath('build.yaml'), true);
|
||||||
|
|
||||||
|
// Negative matches for native build paths
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/workflows/website-ci.yml'), false);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/actions/website-deploy/action.yml'), false);
|
||||||
|
assert.strictEqual(isNativeBuildPath('.github/actions/other-action/action.yml'), false);
|
||||||
|
assert.strictEqual(isNativeBuildPath('lib/main.dart'), false);
|
||||||
|
assert.strictEqual(isNativeBuildPath('website/app/page.tsx'), false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('classifyFiles Scenarios', () => {
|
describe('classifyFiles Scenarios', () => {
|
||||||
|
|
@ -243,6 +267,26 @@ describe('classifyFiles Scenarios', () => {
|
||||||
assert.strictEqual(result.android_changed, false);
|
assert.strictEqual(result.android_changed, false);
|
||||||
assert.strictEqual(result.app_changed, false);
|
assert.strictEqual(result.app_changed, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('classifies composite action (flutter-ci-setup) change as android_changed=true, native_build_changed=true', () => {
|
||||||
|
const files = ['.github/actions/flutter-ci-setup/action.yml'];
|
||||||
|
const result = classifyFiles(files);
|
||||||
|
assert.strictEqual(result.android_changed, true);
|
||||||
|
assert.strictEqual(result.app_changed, false);
|
||||||
|
assert.strictEqual(result.native_build_changed, true);
|
||||||
|
assert.strictEqual(result.ciFiles.length, 1);
|
||||||
|
assert.deepStrictEqual(result.ciFiles, ['.github/actions/flutter-ci-setup/action.yml']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies non-Android composite action (e.g., website-deploy) as false', () => {
|
||||||
|
const files = ['.github/actions/website-deploy/action.yml'];
|
||||||
|
const result = classifyFiles(files);
|
||||||
|
assert.strictEqual(result.android_changed, false);
|
||||||
|
assert.strictEqual(result.app_changed, false);
|
||||||
|
assert.strictEqual(result.native_build_changed, false);
|
||||||
|
assert.strictEqual(result.ciFiles.length, 0);
|
||||||
|
assert.strictEqual(result.unrelatedFiles.length, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('detectAndroidChanges Workflow Event Logic', () => {
|
describe('detectAndroidChanges Workflow Event Logic', () => {
|
||||||
|
|
@ -782,5 +826,60 @@ describe('GitHub Actions Workflow Output Wiring and Release Job Conditions', ()
|
||||||
assert.strictEqual(nativeBuildChanged, false, 'Unrelated changes should output false');
|
assert.strictEqual(nativeBuildChanged, false, 'Unrelated changes should output false');
|
||||||
assert.strictEqual(releaseJobShouldRun, false, 'Release job should NOT run for unrelated changes');
|
assert.strictEqual(releaseJobShouldRun, false, 'Release job should NOT run for unrelated changes');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('workflow release condition evaluates correctly: Composite action changes run release', () => {
|
||||||
|
// flutter-ci-setup is a composite action used by analysis, test, and debug APK jobs
|
||||||
|
const result = detectAndroidChanges({
|
||||||
|
eventName: 'push',
|
||||||
|
baseSha,
|
||||||
|
headSha
|
||||||
|
}, { getChangedFilesFn: () => ['.github/actions/flutter-ci-setup/action.yml'] });
|
||||||
|
|
||||||
|
const nativeBuildChanged = result.native_build_changed;
|
||||||
|
const onMaster = true; // github.ref == 'refs/heads/master'
|
||||||
|
const releaseJobShouldRun = nativeBuildChanged && onMaster;
|
||||||
|
|
||||||
|
assert.strictEqual(nativeBuildChanged, true, 'Composite action changes should output true');
|
||||||
|
assert.strictEqual(releaseJobShouldRun, true, 'Release job SHOULD run for composite action changes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('workflow release condition evaluates correctly: Non-Android composite action (website-deploy) skips release', () => {
|
||||||
|
const result = detectAndroidChanges({
|
||||||
|
eventName: 'push',
|
||||||
|
baseSha,
|
||||||
|
headSha
|
||||||
|
}, { getChangedFilesFn: () => ['.github/actions/website-deploy/action.yml'] });
|
||||||
|
|
||||||
|
const nativeBuildChanged = result.native_build_changed;
|
||||||
|
const onMaster = true; // github.ref == 'refs/heads/master'
|
||||||
|
const releaseJobShouldRun = nativeBuildChanged && onMaster;
|
||||||
|
|
||||||
|
assert.strictEqual(nativeBuildChanged, false, 'Non-Android composite actions should output false');
|
||||||
|
assert.strictEqual(releaseJobShouldRun, false, 'Release job should NOT run for non-Android actions');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Workflow YAML Verification', () => {
|
||||||
|
it('verifies android-build.yml defines jobs.detect.outputs.native_build_changed', () => {
|
||||||
|
const workflowPath = '.github/workflows/android-build.yml';
|
||||||
|
const workflowContent = fs.readFileSync(path.join(process.cwd(), workflowPath), 'utf8');
|
||||||
|
|
||||||
|
// Check that the workflow file exists and contains the output mapping
|
||||||
|
assert.ok(workflowContent.includes('native_build_changed'),
|
||||||
|
'Workflow must expose native_build_changed output from detector step');
|
||||||
|
assert.ok(workflowContent.includes('steps.detector.outputs.native_build_changed'),
|
||||||
|
'Workflow must reference detector output with ${{ steps.detector.outputs.native_build_changed }}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies release job condition includes native_build_changed check', () => {
|
||||||
|
const workflowPath = '.github/workflows/android-build.yml';
|
||||||
|
const workflowContent = fs.readFileSync(path.join(process.cwd(), workflowPath), 'utf8');
|
||||||
|
|
||||||
|
// Check that release job condition references the output
|
||||||
|
assert.ok(workflowContent.includes("needs.detect.outputs.native_build_changed == 'true'"),
|
||||||
|
'Release job condition must check native_build_changed output');
|
||||||
|
assert.ok(workflowContent.includes('refs/heads/master'),
|
||||||
|
'Release job condition must ensure it only runs on master branch');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue