-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdetect-code-changes.mjs
More file actions
195 lines (170 loc) · 6.21 KB
/
detect-code-changes.mjs
File metadata and controls
195 lines (170 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env node
/**
* Detect code changes for CI/CD pipeline
*
* This script detects what types of files have changed between two commits
* and outputs the results for use in GitHub Actions workflow conditions.
*
* Key behavior:
* - For PRs: compares PR head against base branch
* - For pushes: compares HEAD against HEAD^
* - Excludes certain folders and file types from "code changes" detection
*
* Excluded from code changes (don't require changesets):
* - Markdown files (*.md) in any folder
* - .changeset/ folder (changeset metadata)
* - docs/ folder (documentation)
* - experiments/ folder (experimental scripts)
* - examples/ folder (example scripts)
*
* Usage:
* node scripts/detect-code-changes.mjs
* bun scripts/detect-code-changes.mjs
*
* Environment variables (set by GitHub Actions):
* - GITHUB_EVENT_NAME: 'pull_request' or 'push'
* - GITHUB_BASE_SHA: Base commit SHA for PR
* - GITHUB_HEAD_SHA: Head commit SHA for PR
*
* Outputs (written to GITHUB_OUTPUT):
* - java-changed: 'true' if any .java files changed
* - pom-changed: 'true' if pom.xml changed
* - mjs-changed: 'true' if any .mjs files changed
* - docs-changed: 'true' if any .md files changed
* - workflow-changed: 'true' if any .github/workflows/ files changed
* - any-code-changed: 'true' if any code files changed (excludes docs, changesets, experiments, examples)
*/
import { execSync } from 'child_process';
import { appendFileSync } from 'fs';
/**
* Execute a shell command and return trimmed output
* @param {string} command - The command to execute
* @returns {string} - The trimmed command output
*/
function exec(command) {
try {
return execSync(command, { encoding: 'utf-8' }).trim();
} catch (error) {
console.error(`Error executing command: ${command}`);
console.error(error.message);
return '';
}
}
/**
* Write output to GitHub Actions output file
* @param {string} name - Output name
* @param {string} value - Output value
*/
function setOutput(name, value) {
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
appendFileSync(outputFile, `${name}=${value}\n`);
}
console.log(`${name}=${value}`);
}
/**
* Get the list of changed files between two commits
* @returns {string[]} Array of changed file paths
*/
function getChangedFiles() {
const eventName = process.env.GITHUB_EVENT_NAME || 'local';
if (eventName === 'pull_request') {
const baseSha = process.env.GITHUB_BASE_SHA;
const headSha = process.env.GITHUB_HEAD_SHA;
if (baseSha && headSha) {
console.log(`Comparing PR: ${baseSha}...${headSha}`);
try {
// Ensure we have the base commit
try {
execSync(`git cat-file -e ${baseSha}`, { stdio: 'ignore' });
} catch {
console.log('Base commit not available locally, attempting fetch...');
execSync(`git fetch origin ${baseSha}`, { stdio: 'inherit' });
}
const output = exec(`git diff --name-only ${baseSha} ${headSha}`);
return output ? output.split('\n').filter(Boolean) : [];
} catch (error) {
console.error(`Git diff failed: ${error.message}`);
}
}
}
// For push events or fallback
console.log('Comparing HEAD^ to HEAD');
try {
const output = exec('git diff --name-only HEAD^ HEAD');
return output ? output.split('\n').filter(Boolean) : [];
} catch {
// If HEAD^ doesn't exist (first commit), list all files in HEAD
console.log('HEAD^ not available, listing all files in HEAD');
const output = exec('git ls-tree --name-only -r HEAD');
return output ? output.split('\n').filter(Boolean) : [];
}
}
/**
* Check if a file should be excluded from code changes detection
* @param {string} filePath - The file path to check
* @returns {boolean} True if the file should be excluded
*/
function isExcludedFromCodeChanges(filePath) {
// Exclude markdown files in any folder
if (filePath.endsWith('.md')) {
return true;
}
// Exclude specific folders from code changes
const excludedFolders = ['.changeset/', 'docs/', 'experiments/', 'examples/'];
for (const folder of excludedFolders) {
if (filePath.startsWith(folder)) {
return true;
}
}
return false;
}
/**
* Main function to detect changes
*/
function detectChanges() {
console.log('Detecting file changes for CI/CD...\n');
const changedFiles = getChangedFiles();
console.log('Changed files:');
if (changedFiles.length === 0) {
console.log(' (none)');
} else {
changedFiles.forEach((file) => console.log(` ${file}`));
}
console.log('');
// Detect .java file changes
const javaChanged = changedFiles.some((file) => file.endsWith('.java'));
setOutput('java-changed', javaChanged ? 'true' : 'false');
// Detect pom.xml changes
const pomChanged = changedFiles.some((file) => file === 'pom.xml');
setOutput('pom-changed', pomChanged ? 'true' : 'false');
// Detect .mjs file changes (scripts)
const mjsChanged = changedFiles.some((file) => file.endsWith('.mjs'));
setOutput('mjs-changed', mjsChanged ? 'true' : 'false');
// Detect documentation changes (any .md file)
const docsChanged = changedFiles.some((file) => file.endsWith('.md'));
setOutput('docs-changed', docsChanged ? 'true' : 'false');
// Detect workflow changes
const workflowChanged = changedFiles.some((file) =>
file.startsWith('.github/workflows/')
);
setOutput('workflow-changed', workflowChanged ? 'true' : 'false');
// Detect code changes (excluding docs, changesets, experiments, examples folders, and markdown files)
const codeChangedFiles = changedFiles.filter(
(file) => !isExcludedFromCodeChanges(file)
);
console.log('\nFiles considered as code changes:');
if (codeChangedFiles.length === 0) {
console.log(' (none)');
} else {
codeChangedFiles.forEach((file) => console.log(` ${file}`));
}
console.log('');
// Check if any code files changed (.java, .mjs, .xml, .yml, .yaml, or workflow files)
const codePattern = /\.(java|mjs|xml|yml|yaml|properties)$|\.github\/workflows\//;
const codeChanged = codeChangedFiles.some((file) => codePattern.test(file));
setOutput('any-code-changed', codeChanged ? 'true' : 'false');
console.log('\nChange detection completed.');
}
// Run the detection
detectChanges();