freemium-ui 2.0.125 → 2.0.128
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/.github/workflows/automated-version-bump.yml +14 -0
- package/.github/workflows/enhanced-pr-labeler.yml +569 -0
- package/lib/index.cjs.js +3508 -3498
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3522 -3513
- package/lib/package.json +1 -1
- package/package.json +1 -1
|
@@ -187,6 +187,9 @@ jobs:
|
|
|
187
187
|
- name: Install dependencies and build
|
|
188
188
|
if: steps.check_version.outputs.is_version_bump == 'true'
|
|
189
189
|
run: |
|
|
190
|
+
# Authenticate for GitHub Packages
|
|
191
|
+
echo "@freshworks:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
192
|
+
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_PACKAGES_TOKEN }}" >> ~/.npmrc
|
|
190
193
|
npm ci
|
|
191
194
|
npm run build
|
|
192
195
|
|
|
@@ -278,3 +281,14 @@ jobs:
|
|
|
278
281
|
echo "GitHub release created!"
|
|
279
282
|
env:
|
|
280
283
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
284
|
+
|
|
285
|
+
- name: Notify Slack channel
|
|
286
|
+
if: steps.check_version.outputs.is_version_bump == 'true'
|
|
287
|
+
run: |
|
|
288
|
+
RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/${{ steps.check_version.outputs.version }}"
|
|
289
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
290
|
+
MESSAGE=":tada: *New Release Published!* \nPackage: *${PACKAGE_NAME}* \nVersion: *${{ steps.check_version.outputs.version }}* \nReleased by: @${{ github.actor }} \n<${RELEASE_URL}|View Release Notes>"
|
|
291
|
+
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$MESSAGE\"}" "${{ secrets.SLACK_WEBHOOK_URL }}"
|
|
292
|
+
echo "Slack notification sent!"
|
|
293
|
+
env:
|
|
294
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
name: Enhanced PR Auto Labeler
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened, edited]
|
|
6
|
+
pull_request_review:
|
|
7
|
+
types: [submitted]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
pr_number:
|
|
11
|
+
description: 'PR number to analyze (optional)'
|
|
12
|
+
required: false
|
|
13
|
+
type: number
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pull-requests: write
|
|
18
|
+
issues: write
|
|
19
|
+
|
|
20
|
+
env:
|
|
21
|
+
# Size thresholds (lines of code changed)
|
|
22
|
+
SIZE_TINY_THRESHOLD: 5
|
|
23
|
+
SIZE_XS_THRESHOLD: 10
|
|
24
|
+
SIZE_S_THRESHOLD: 50
|
|
25
|
+
SIZE_M_THRESHOLD: 200
|
|
26
|
+
SIZE_L_THRESHOLD: 500
|
|
27
|
+
SIZE_XL_THRESHOLD: 1000
|
|
28
|
+
|
|
29
|
+
# File thresholds
|
|
30
|
+
LARGE_FILE_THRESHOLD: 500
|
|
31
|
+
HUGE_FILE_THRESHOLD: 1000
|
|
32
|
+
MANY_FILES_THRESHOLD: 20
|
|
33
|
+
MASSIVE_FILES_THRESHOLD: 50
|
|
34
|
+
|
|
35
|
+
# Test coverage requirements
|
|
36
|
+
MIN_FILES_FOR_TEST_REQUIREMENT: 3
|
|
37
|
+
GOOD_TEST_COVERAGE_RATIO: 0.5
|
|
38
|
+
|
|
39
|
+
# Complexity scoring thresholds
|
|
40
|
+
COMPLEXITY_HIGH_THRESHOLD: 4
|
|
41
|
+
COMPLEXITY_MODERATE_THRESHOLD: 2
|
|
42
|
+
|
|
43
|
+
# Review risk thresholds
|
|
44
|
+
RISK_LARGE_CHANGESET: 1000
|
|
45
|
+
RISK_MANY_FILES: 30
|
|
46
|
+
RISK_MULTIPLE_LARGE_FILES: 3
|
|
47
|
+
RISK_NO_TESTS_MIN_FILES: 5
|
|
48
|
+
|
|
49
|
+
# Comment behavior
|
|
50
|
+
ENABLE_AUTO_COMMENTS: true
|
|
51
|
+
COMMENT_ON_OPENED_ONLY: false
|
|
52
|
+
MIN_LABELS_FOR_COMMENT: 2
|
|
53
|
+
|
|
54
|
+
# Feature toggles
|
|
55
|
+
ENABLE_SIZE_LABELS: true
|
|
56
|
+
ENABLE_FILE_TYPE_DETECTION: true
|
|
57
|
+
ENABLE_TEST_ANALYSIS: true
|
|
58
|
+
ENABLE_COMPLEXITY_ANALYSIS: true
|
|
59
|
+
ENABLE_PRIORITY_DETECTION: true
|
|
60
|
+
ENABLE_TYPE_DETECTION: true
|
|
61
|
+
ENABLE_ENVIRONMENT_DETECTION: false
|
|
62
|
+
ENABLE_REVIEW_STATUS: true
|
|
63
|
+
|
|
64
|
+
# Custom label prefixes (can be customized per team)
|
|
65
|
+
SIZE_LABEL_PREFIX: "size/"
|
|
66
|
+
TYPE_LABEL_PREFIX: "type/"
|
|
67
|
+
PRIORITY_LABEL_PREFIX: "priority/"
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
auto-label:
|
|
71
|
+
runs-on: ["self-hosted", "Linux"]
|
|
72
|
+
name: Enhanced Auto Label PR
|
|
73
|
+
|
|
74
|
+
steps:
|
|
75
|
+
- name: Checkout code
|
|
76
|
+
uses: actions/checkout@v4
|
|
77
|
+
with:
|
|
78
|
+
fetch-depth: 0
|
|
79
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
80
|
+
|
|
81
|
+
- name: Setup Node.js (for enhanced analysis)
|
|
82
|
+
uses: actions/setup-node@v4
|
|
83
|
+
with:
|
|
84
|
+
node-version: '18'
|
|
85
|
+
|
|
86
|
+
- name: Load configuration and analyze PR
|
|
87
|
+
id: pr-analysis
|
|
88
|
+
uses: actions/github-script@v7
|
|
89
|
+
env:
|
|
90
|
+
# Pass environment variables to the script
|
|
91
|
+
CONFIG_SIZE_TINY: ${{ env.SIZE_TINY_THRESHOLD }}
|
|
92
|
+
CONFIG_SIZE_XS: ${{ env.SIZE_XS_THRESHOLD }}
|
|
93
|
+
CONFIG_SIZE_S: ${{ env.SIZE_S_THRESHOLD }}
|
|
94
|
+
CONFIG_SIZE_M: ${{ env.SIZE_M_THRESHOLD }}
|
|
95
|
+
CONFIG_SIZE_L: ${{ env.SIZE_L_THRESHOLD }}
|
|
96
|
+
CONFIG_SIZE_XL: ${{ env.SIZE_XL_THRESHOLD }}
|
|
97
|
+
CONFIG_LARGE_FILE: ${{ env.LARGE_FILE_THRESHOLD }}
|
|
98
|
+
CONFIG_HUGE_FILE: ${{ env.HUGE_FILE_THRESHOLD }}
|
|
99
|
+
CONFIG_MANY_FILES: ${{ env.MANY_FILES_THRESHOLD }}
|
|
100
|
+
CONFIG_MASSIVE_FILES: ${{ env.MASSIVE_FILES_THRESHOLD }}
|
|
101
|
+
CONFIG_MIN_FILES_FOR_TESTS: ${{ env.MIN_FILES_FOR_TEST_REQUIREMENT }}
|
|
102
|
+
CONFIG_TEST_COVERAGE_RATIO: ${{ env.GOOD_TEST_COVERAGE_RATIO }}
|
|
103
|
+
CONFIG_ENABLE_SIZE: ${{ env.ENABLE_SIZE_LABELS }}
|
|
104
|
+
CONFIG_ENABLE_FILE_TYPE: ${{ env.ENABLE_FILE_TYPE_DETECTION }}
|
|
105
|
+
CONFIG_ENABLE_TESTS: ${{ env.ENABLE_TEST_ANALYSIS }}
|
|
106
|
+
CONFIG_ENABLE_COMPLEXITY: ${{ env.ENABLE_COMPLEXITY_ANALYSIS }}
|
|
107
|
+
CONFIG_ENABLE_PRIORITY: ${{ env.ENABLE_PRIORITY_DETECTION }}
|
|
108
|
+
CONFIG_ENABLE_TYPE: ${{ env.ENABLE_TYPE_DETECTION }}
|
|
109
|
+
CONFIG_ENABLE_ENV: ${{ env.ENABLE_ENVIRONMENT_DETECTION }}
|
|
110
|
+
CONFIG_SIZE_PREFIX: ${{ env.SIZE_LABEL_PREFIX }}
|
|
111
|
+
CONFIG_TYPE_PREFIX: ${{ env.TYPE_LABEL_PREFIX }}
|
|
112
|
+
CONFIG_PRIORITY_PREFIX: ${{ env.PRIORITY_LABEL_PREFIX }}
|
|
113
|
+
with:
|
|
114
|
+
script: |
|
|
115
|
+
try {
|
|
116
|
+
// Load configuration from environment variables
|
|
117
|
+
const config = {
|
|
118
|
+
thresholds: {
|
|
119
|
+
size: {
|
|
120
|
+
tiny: parseInt(process.env.CONFIG_SIZE_TINY) || 5,
|
|
121
|
+
xs: parseInt(process.env.CONFIG_SIZE_XS) || 10,
|
|
122
|
+
s: parseInt(process.env.CONFIG_SIZE_S) || 50,
|
|
123
|
+
m: parseInt(process.env.CONFIG_SIZE_M) || 200,
|
|
124
|
+
l: parseInt(process.env.CONFIG_SIZE_L) || 500,
|
|
125
|
+
xl: parseInt(process.env.CONFIG_SIZE_XL) || 1000
|
|
126
|
+
},
|
|
127
|
+
files: {
|
|
128
|
+
large: parseInt(process.env.CONFIG_LARGE_FILE) || 500,
|
|
129
|
+
huge: parseInt(process.env.CONFIG_HUGE_FILE) || 1000,
|
|
130
|
+
many: parseInt(process.env.CONFIG_MANY_FILES) || 20,
|
|
131
|
+
massive: parseInt(process.env.CONFIG_MASSIVE_FILES) || 50
|
|
132
|
+
},
|
|
133
|
+
tests: {
|
|
134
|
+
minFilesForRequirement: parseInt(process.env.CONFIG_MIN_FILES_FOR_TESTS) || 3,
|
|
135
|
+
goodCoverageRatio: parseFloat(process.env.CONFIG_TEST_COVERAGE_RATIO) || 0.5
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
features: {
|
|
139
|
+
enableSize: process.env.CONFIG_ENABLE_SIZE === 'true',
|
|
140
|
+
enableFileType: process.env.CONFIG_ENABLE_FILE_TYPE === 'true',
|
|
141
|
+
enableTests: process.env.CONFIG_ENABLE_TESTS === 'true',
|
|
142
|
+
enableComplexity: process.env.CONFIG_ENABLE_COMPLEXITY === 'true',
|
|
143
|
+
enablePriority: process.env.CONFIG_ENABLE_PRIORITY === 'true',
|
|
144
|
+
enableType: process.env.CONFIG_ENABLE_TYPE === 'true',
|
|
145
|
+
enableEnvironment: process.env.CONFIG_ENABLE_ENV === 'true'
|
|
146
|
+
},
|
|
147
|
+
labelPrefixes: {
|
|
148
|
+
size: process.env.CONFIG_SIZE_PREFIX || 'size/',
|
|
149
|
+
type: process.env.CONFIG_TYPE_PREFIX || 'type/',
|
|
150
|
+
priority: process.env.CONFIG_PRIORITY_PREFIX || 'priority/'
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
console.log('Configuration loaded:', JSON.stringify(config, null, 2));
|
|
155
|
+
|
|
156
|
+
// Determine PR number (from workflow dispatch or current PR)
|
|
157
|
+
const prNumber = context.payload.inputs?.pr_number || context.issue.number;
|
|
158
|
+
|
|
159
|
+
const { data: pr } = await github.rest.pulls.get({
|
|
160
|
+
owner: context.repo.owner,
|
|
161
|
+
repo: context.repo.repo,
|
|
162
|
+
pull_number: prNumber
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Get all files with pagination
|
|
166
|
+
let allFiles = [];
|
|
167
|
+
let page = 1;
|
|
168
|
+
let files;
|
|
169
|
+
|
|
170
|
+
do {
|
|
171
|
+
const { data: pageFiles } = await github.rest.pulls.listFiles({
|
|
172
|
+
owner: context.repo.owner,
|
|
173
|
+
repo: context.repo.repo,
|
|
174
|
+
pull_number: prNumber,
|
|
175
|
+
per_page: 100,
|
|
176
|
+
page: page
|
|
177
|
+
});
|
|
178
|
+
files = pageFiles;
|
|
179
|
+
allFiles = allFiles.concat(files);
|
|
180
|
+
page++;
|
|
181
|
+
} while (files.length === 100);
|
|
182
|
+
|
|
183
|
+
const labels = [];
|
|
184
|
+
const currentLabels = pr.labels.map(label => label.name);
|
|
185
|
+
|
|
186
|
+
// Calculate metrics
|
|
187
|
+
const totalChanges = pr.additions + pr.deletions;
|
|
188
|
+
const filesChanged = allFiles.length;
|
|
189
|
+
const additionsOnly = pr.additions;
|
|
190
|
+
const deletionsOnly = pr.deletions;
|
|
191
|
+
const largeFiles = allFiles.filter(file => file.changes > config.thresholds.files.large).length;
|
|
192
|
+
const hugeFiles = allFiles.filter(file => file.changes > config.thresholds.files.huge).length;
|
|
193
|
+
|
|
194
|
+
// Test file detection (configurable patterns)
|
|
195
|
+
const testFilePatterns = [
|
|
196
|
+
'test', 'spec', '.test.', '.spec.', '__tests__', 'test_', 'spec_',
|
|
197
|
+
'.test.js', '.test.ts', '.test.py', '_test.go', '.spec.js', '.spec.ts'
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const testFiles = config.features.enableTests ? allFiles.filter(file => {
|
|
201
|
+
const filename = file.filename.toLowerCase();
|
|
202
|
+
return testFilePatterns.some(pattern => filename.includes(pattern));
|
|
203
|
+
}).length : 0;
|
|
204
|
+
|
|
205
|
+
// Size classification (configurable thresholds)
|
|
206
|
+
if (config.features.enableSize) {
|
|
207
|
+
const sizePrefix = config.labelPrefixes.size;
|
|
208
|
+
if (totalChanges <= config.thresholds.size.tiny) {
|
|
209
|
+
labels.push(`${sizePrefix}tiny`);
|
|
210
|
+
} else if (totalChanges <= config.thresholds.size.xs) {
|
|
211
|
+
labels.push(`${sizePrefix}XS`);
|
|
212
|
+
} else if (totalChanges <= config.thresholds.size.s) {
|
|
213
|
+
labels.push(`${sizePrefix}S`);
|
|
214
|
+
} else if (totalChanges <= config.thresholds.size.m) {
|
|
215
|
+
labels.push(`${sizePrefix}M`);
|
|
216
|
+
} else if (totalChanges <= config.thresholds.size.l) {
|
|
217
|
+
labels.push(`${sizePrefix}L`);
|
|
218
|
+
} else if (totalChanges <= config.thresholds.size.xl) {
|
|
219
|
+
labels.push(`${sizePrefix}XL`);
|
|
220
|
+
} else {
|
|
221
|
+
labels.push(`${sizePrefix}XXL`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// File count analysis (configurable)
|
|
226
|
+
if (filesChanged > config.thresholds.files.massive) {
|
|
227
|
+
labels.push('massive-changes');
|
|
228
|
+
} else if (filesChanged > config.thresholds.files.many) {
|
|
229
|
+
labels.push('many-files');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Large file analysis (configurable)
|
|
233
|
+
if (hugeFiles > 0) {
|
|
234
|
+
labels.push('huge-files');
|
|
235
|
+
} else if (largeFiles > 0) {
|
|
236
|
+
labels.push('large-files');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Test coverage analysis (configurable)
|
|
240
|
+
if (config.features.enableTests) {
|
|
241
|
+
if (testFiles > 0) {
|
|
242
|
+
labels.push('has-tests');
|
|
243
|
+
if (testFiles >= filesChanged * config.thresholds.tests.goodCoverageRatio) {
|
|
244
|
+
labels.push('good-test-coverage');
|
|
245
|
+
}
|
|
246
|
+
} else if (filesChanged > config.thresholds.tests.minFilesForRequirement && testFiles === 0) {
|
|
247
|
+
labels.push('needs-tests');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// File type detection (configurable patterns)
|
|
252
|
+
const fileTypePatterns = config.features.enableFileType ? {
|
|
253
|
+
documentation: ['readme', '.md', 'doc', 'changelog', 'license'],
|
|
254
|
+
configuration: ['config', '.yaml', '.yml', '.json', '.toml', '.ini', 'settings', '.env'],
|
|
255
|
+
terraform: ['terraform', '.tf', '.tfvars', 'terragrunt'],
|
|
256
|
+
kubernetes: ['k8s', 'kubernetes', 'kustomiz'],
|
|
257
|
+
database: ['.sql', 'migration', 'schema', 'db/', 'database'],
|
|
258
|
+
docker: ['dockerfile', 'docker-compose', '.dockerignore', 'containerfile'],
|
|
259
|
+
cicd: ['.github/workflows', 'jenkinsfile', '.gitlab-ci', 'ci.yml', 'cd.yml', 'pipeline', 'build.sh'],
|
|
260
|
+
scripts: ['scripts/', '.sh', '.py', '.rb', '.pl', 'automation'],
|
|
261
|
+
security: ['security', 'auth', 'rbac', 'iam', 'policy', 'secret']
|
|
262
|
+
} : {};
|
|
263
|
+
|
|
264
|
+
// Apply file type labels
|
|
265
|
+
Object.entries(fileTypePatterns).forEach(([label, patterns]) => {
|
|
266
|
+
const hasMatch = allFiles.some(file => {
|
|
267
|
+
const filename = file.filename.toLowerCase();
|
|
268
|
+
return patterns.some(pattern => filename.includes(pattern)) ||
|
|
269
|
+
(label === 'kubernetes' && filename.includes('.yaml') &&
|
|
270
|
+
['deployment', 'service', 'ingress', 'configmap', 'secret'].some(k8sType => filename.includes(k8sType)));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (hasMatch) {
|
|
274
|
+
labels.push(label);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Priority and type detection (configurable)
|
|
279
|
+
const title = pr.title.toLowerCase();
|
|
280
|
+
const body = pr.body ? pr.body.toLowerCase() : '';
|
|
281
|
+
const combined = `${title} ${body}`;
|
|
282
|
+
|
|
283
|
+
// Configurable priority patterns
|
|
284
|
+
const priorityPatterns = config.features.enablePriority ? {
|
|
285
|
+
'critical': ['hotfix', 'urgent', 'critical', 'emergency', 'prod.*down', 'outage'],
|
|
286
|
+
'high': ['high.*priority', 'important', 'asap', 'rush'],
|
|
287
|
+
'low': ['low.*priority', 'nice.*to.*have', 'minor']
|
|
288
|
+
} : {};
|
|
289
|
+
|
|
290
|
+
Object.entries(priorityPatterns).forEach(([priority, patterns]) => {
|
|
291
|
+
const regex = new RegExp(`\\b(${patterns.join('|')})\\b`);
|
|
292
|
+
if (combined.match(regex)) {
|
|
293
|
+
labels.push(`${config.labelPrefixes.priority}${priority}`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Configurable type patterns
|
|
298
|
+
const typePatterns = config.features.enableType ? {
|
|
299
|
+
'feature': ['feat', 'feature', 'add', 'new', 'implement'],
|
|
300
|
+
'bugfix': ['fix', 'bug', 'issue', 'resolve', 'correct'],
|
|
301
|
+
'refactor': ['refactor', 'restructure', 'reorganize', 'clean.*up'],
|
|
302
|
+
'maintenance': ['chore', 'maintain', 'update', 'upgrade', 'deps', 'dependencies'],
|
|
303
|
+
'performance': ['perf', 'performance', 'optimize', 'speed', 'faster']
|
|
304
|
+
} : {};
|
|
305
|
+
|
|
306
|
+
Object.entries(typePatterns).forEach(([type, patterns]) => {
|
|
307
|
+
const regex = new RegExp(`\\b(${patterns.join('|')})\\b`);
|
|
308
|
+
if (combined.match(regex)) {
|
|
309
|
+
labels.push(`${config.labelPrefixes.type}${type}`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Special condition patterns (configurable)
|
|
314
|
+
const specialPatterns = {
|
|
315
|
+
'breaking-change': ['breaking.*change', 'breaking', 'incompatible'],
|
|
316
|
+
'security': ['security', 'vulnerability', 'cve', 'exploit', 'patch'],
|
|
317
|
+
'work-in-progress': pr.draft ? ['.*'] : ['wip', 'work.*in.*progress', 'draft', 'incomplete']
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
Object.entries(specialPatterns).forEach(([label, patterns]) => {
|
|
321
|
+
const regex = new RegExp(`\\b(${patterns.join('|')})\\b`);
|
|
322
|
+
if (combined.match(regex) || (label === 'work-in-progress' && pr.draft)) {
|
|
323
|
+
labels.push(label);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Complexity analysis (configurable)
|
|
328
|
+
let complexityScore = 0;
|
|
329
|
+
if (config.features.enableComplexity) {
|
|
330
|
+
if (totalChanges > 1000) complexityScore += 3;
|
|
331
|
+
else if (totalChanges > 500) complexityScore += 2;
|
|
332
|
+
else if (totalChanges > 200) complexityScore += 1;
|
|
333
|
+
|
|
334
|
+
if (filesChanged > 30) complexityScore += 2;
|
|
335
|
+
else if (filesChanged > 15) complexityScore += 1;
|
|
336
|
+
|
|
337
|
+
if (largeFiles > 5) complexityScore += 2;
|
|
338
|
+
else if (largeFiles > 0) complexityScore += 1;
|
|
339
|
+
|
|
340
|
+
if (complexityScore >= 4) {
|
|
341
|
+
labels.push('complex-review');
|
|
342
|
+
} else if (complexityScore >= 2) {
|
|
343
|
+
labels.push('moderate-review');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Filter out existing labels
|
|
348
|
+
const newLabels = labels.filter(label => !currentLabels.includes(label));
|
|
349
|
+
|
|
350
|
+
// Apply new labels
|
|
351
|
+
if (newLabels.length > 0) {
|
|
352
|
+
await github.rest.issues.addLabels({
|
|
353
|
+
owner: context.repo.owner,
|
|
354
|
+
repo: context.repo.repo,
|
|
355
|
+
issue_number: prNumber,
|
|
356
|
+
labels: newLabels
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
console.log(`Applied labels: ${newLabels.join(', ')}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
labels: newLabels,
|
|
364
|
+
allLabels: labels,
|
|
365
|
+
metrics: {
|
|
366
|
+
totalChanges,
|
|
367
|
+
filesChanged,
|
|
368
|
+
largeFiles,
|
|
369
|
+
hugeFiles,
|
|
370
|
+
testFiles,
|
|
371
|
+
complexityScore
|
|
372
|
+
},
|
|
373
|
+
config: config
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error('Error in PR analysis:', error);
|
|
378
|
+
return { labels: [], metrics: {}, error: error.message };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
- name: Add configurable PR analysis comment
|
|
382
|
+
if: ${{ env.ENABLE_AUTO_COMMENTS == 'true' }}
|
|
383
|
+
uses: actions/github-script@v7
|
|
384
|
+
with:
|
|
385
|
+
script: |
|
|
386
|
+
const analysisResult = ${{ steps.pr-analysis.outputs.result }};
|
|
387
|
+
|
|
388
|
+
if (!analysisResult || analysisResult.error) {
|
|
389
|
+
console.log('Skipping comment due to analysis error');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const shouldComment =
|
|
394
|
+
(context.payload.action === 'opened' && process.env.COMMENT_ON_OPENED_ONLY !== 'false') ||
|
|
395
|
+
(analysisResult.labels.length >= parseInt(process.env.MIN_LABELS_FOR_COMMENT || '2'));
|
|
396
|
+
|
|
397
|
+
if (!shouldComment) {
|
|
398
|
+
console.log('Skipping comment based on configuration');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const metrics = analysisResult.metrics;
|
|
403
|
+
const config = analysisResult.config;
|
|
404
|
+
|
|
405
|
+
// Generate configurable risk factors
|
|
406
|
+
const riskFactors = [];
|
|
407
|
+
if (metrics.totalChanges > parseInt(process.env.RISK_LARGE_CHANGESET || '1000')) {
|
|
408
|
+
riskFactors.push(`Very large changeset (>${process.env.RISK_LARGE_CHANGESET || 1000} changes)`);
|
|
409
|
+
}
|
|
410
|
+
if (metrics.filesChanged > parseInt(process.env.RISK_MANY_FILES || '30')) {
|
|
411
|
+
riskFactors.push(`Many files modified (>${process.env.RISK_MANY_FILES || 30})`);
|
|
412
|
+
}
|
|
413
|
+
if (metrics.largeFiles > parseInt(process.env.RISK_MULTIPLE_LARGE_FILES || '3')) {
|
|
414
|
+
riskFactors.push(`Multiple large files (>${process.env.RISK_MULTIPLE_LARGE_FILES || 3})`);
|
|
415
|
+
}
|
|
416
|
+
if (metrics.testFiles === 0 && metrics.filesChanged > parseInt(process.env.RISK_NO_TESTS_MIN_FILES || '5')) {
|
|
417
|
+
riskFactors.push('No test coverage detected');
|
|
418
|
+
}
|
|
419
|
+
if (metrics.hugeFiles > 0) {
|
|
420
|
+
riskFactors.push(`Extremely large files (>${config.thresholds.files.huge} lines)`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const comment = `## 📊 PR Analysis
|
|
424
|
+
|
|
425
|
+
### ⚙️ Current Configuration
|
|
426
|
+
- **Size Thresholds**: Tiny(≤${config.thresholds.size.tiny}), XS(≤${config.thresholds.size.xs}), S(≤${config.thresholds.size.s}), M(≤${config.thresholds.size.m}), L(≤${config.thresholds.size.l}), XL(≤${config.thresholds.size.xl})
|
|
427
|
+
- **File Thresholds**: Large(>${config.thresholds.files.large}), Many(>${config.thresholds.files.many})
|
|
428
|
+
- **Features Enabled**: ${Object.entries(config.features).filter(([k,v]) => v).map(([k,v]) => k.replace('enable', '')).join(', ')}
|
|
429
|
+
|
|
430
|
+
### 📈 Metrics
|
|
431
|
+
| Metric | Value | Threshold | Status |
|
|
432
|
+
|--------|-------|-----------|--------|
|
|
433
|
+
| **Total Changes** | ${metrics.totalChanges} | ${config.thresholds.size.l} | ${metrics.totalChanges > config.thresholds.size.l ? '⚠️' : '✅'} |
|
|
434
|
+
| **Files Changed** | ${metrics.filesChanged} | ${config.thresholds.files.many} | ${metrics.filesChanged > config.thresholds.files.many ? '⚠️' : '✅'} |
|
|
435
|
+
| **Large Files** | ${metrics.largeFiles} | >${config.thresholds.files.large} lines | ${metrics.largeFiles > 0 ? '⚠️' : '✅'} |
|
|
436
|
+
| **Huge Files** | ${metrics.hugeFiles} | >${config.thresholds.files.huge} lines | ${metrics.hugeFiles > 0 ? '🚨' : '✅'} |
|
|
437
|
+
| **Test Files** | ${metrics.testFiles} | min ${config.thresholds.tests.minFilesForRequirement} | ${metrics.testFiles > 0 ? '✅' : '⚠️'} |
|
|
438
|
+
${config.features.enableComplexity ? `| **Complexity Score** | ${metrics.complexityScore}/6 | moderate/complex | ${metrics.complexityScore >= 4 ? '🚨' : metrics.complexityScore >= 2 ? '⚠️' : '✅'} |` : ''}
|
|
439
|
+
|
|
440
|
+
### 🏷️ Applied Labels
|
|
441
|
+
${analysisResult.labels.length > 0 ? analysisResult.labels.map(label => `\`${label}\``).join(', ') : 'No new labels applied'}
|
|
442
|
+
|
|
443
|
+
${riskFactors.length > 0 ? `### ⚠️ Risk Factors\n${riskFactors.map(risk => `- ${risk}`).join('\n')}\n` : ''}
|
|
444
|
+
|
|
445
|
+
### 💡 Customization Guide
|
|
446
|
+
To customize this analysis, modify the environment variables in the workflow:
|
|
447
|
+
- \`SIZE_*_THRESHOLD\`: Adjust size classification
|
|
448
|
+
- \`LARGE_FILE_THRESHOLD\`: Change large file detection
|
|
449
|
+
- \`ENABLE_*\`: Toggle analysis features
|
|
450
|
+
- \`*_LABEL_PREFIX\`: Customize label prefixes
|
|
451
|
+
|
|
452
|
+
<details>
|
|
453
|
+
<summary>📋 Review Checklist</summary>
|
|
454
|
+
|
|
455
|
+
- [ ] Code follows team conventions
|
|
456
|
+
- [ ] Changes are well documented
|
|
457
|
+
- [ ] Tests cover new functionality
|
|
458
|
+
- [ ] No sensitive data exposed
|
|
459
|
+
- [ ] Performance impact considered
|
|
460
|
+
- [ ] Breaking changes documented
|
|
461
|
+
</details>
|
|
462
|
+
`;
|
|
463
|
+
|
|
464
|
+
await github.rest.issues.createComment({
|
|
465
|
+
owner: context.repo.owner,
|
|
466
|
+
repo: context.repo.repo,
|
|
467
|
+
issue_number: context.issue.number,
|
|
468
|
+
body: comment
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
- name: Create enhanced labels
|
|
472
|
+
uses: actions/github-script@v7
|
|
473
|
+
with:
|
|
474
|
+
script: |
|
|
475
|
+
const labels = [
|
|
476
|
+
// Size labels
|
|
477
|
+
{ name: 'size/tiny', color: '00ff00', description: 'Tiny PR (≤5 changes)' },
|
|
478
|
+
{ name: 'size/XS', color: '66ff66', description: 'Extra small PR (≤10 changes)' },
|
|
479
|
+
{ name: 'size/S', color: '99ff99', description: 'Small PR (≤50 changes)' },
|
|
480
|
+
{ name: 'size/M', color: 'ffff00', description: 'Medium PR (≤200 changes)' },
|
|
481
|
+
{ name: 'size/L', color: 'ff9900', description: 'Large PR (≤500 changes)' },
|
|
482
|
+
{ name: 'size/XL', color: 'ff6600', description: 'Extra large PR (≤1000 changes)' },
|
|
483
|
+
{ name: 'size/XXL', color: 'ff0000', description: 'Huge PR (>1000 changes)' },
|
|
484
|
+
|
|
485
|
+
// File complexity
|
|
486
|
+
{ name: 'many-files', color: 'ff6600', description: 'Many files changed (>20)' },
|
|
487
|
+
{ name: 'massive-changes', color: 'cc0000', description: 'Massive changes (>50 files)' },
|
|
488
|
+
{ name: 'large-files', color: 'ff3300', description: 'Large files (>500 lines)' },
|
|
489
|
+
{ name: 'huge-files', color: 'cc0000', description: 'Huge files (>1000 lines)' },
|
|
490
|
+
|
|
491
|
+
// Change patterns
|
|
492
|
+
{ name: 'mostly-additions', color: '00cc66', description: 'Mostly new code' },
|
|
493
|
+
{ name: 'mostly-deletions', color: 'ff6666', description: 'Mostly code removal' },
|
|
494
|
+
|
|
495
|
+
// Test coverage
|
|
496
|
+
{ name: 'has-tests', color: '00cc00', description: 'Includes test files' },
|
|
497
|
+
{ name: 'good-test-coverage', color: '006600', description: 'Good test coverage ratio' },
|
|
498
|
+
{ name: 'needs-tests', color: 'ffcc00', description: 'Needs test coverage' },
|
|
499
|
+
|
|
500
|
+
// Technology areas
|
|
501
|
+
{ name: 'documentation', color: '0066cc', description: 'Documentation changes' },
|
|
502
|
+
{ name: 'configuration', color: '6600cc', description: 'Configuration changes' },
|
|
503
|
+
{ name: 'terraform', color: '623ce4', description: 'Terraform infrastructure' },
|
|
504
|
+
{ name: 'kubernetes', color: '326ce5', description: 'Kubernetes resources' },
|
|
505
|
+
{ name: 'database', color: 'cc6600', description: 'Database changes' },
|
|
506
|
+
{ name: 'docker', color: '2496ed', description: 'Docker/container changes' },
|
|
507
|
+
{ name: 'cicd', color: '333333', description: 'CI/CD pipeline changes' },
|
|
508
|
+
{ name: 'scripts', color: '666666', description: 'Script/automation changes' },
|
|
509
|
+
{ name: 'security', color: 'ff6600', description: 'Security related' },
|
|
510
|
+
|
|
511
|
+
// Priority levels
|
|
512
|
+
{ name: 'priority/critical', color: 'ff0000', description: 'Critical priority' },
|
|
513
|
+
{ name: 'priority/high', color: 'ff6600', description: 'High priority' },
|
|
514
|
+
{ name: 'priority/low', color: '99ccff', description: 'Low priority' },
|
|
515
|
+
|
|
516
|
+
// Types
|
|
517
|
+
{ name: 'type/feature', color: '00ff99', description: 'New feature' },
|
|
518
|
+
{ name: 'type/bugfix', color: 'ff3333', description: 'Bug fix' },
|
|
519
|
+
{ name: 'type/refactor', color: '9933ff', description: 'Code refactoring' },
|
|
520
|
+
{ name: 'type/maintenance', color: '999999', description: 'Maintenance/chore' },
|
|
521
|
+
{ name: 'type/performance', color: 'ffff33', description: 'Performance improvement' },
|
|
522
|
+
|
|
523
|
+
// Special categories
|
|
524
|
+
{ name: 'breaking-change', color: 'ff0000', description: 'Breaking changes' },
|
|
525
|
+
{ name: 'work-in-progress', color: 'cccccc', description: 'Work in progress' },
|
|
526
|
+
{ name: 'dependencies', color: '0099ff', description: 'Dependency updates' },
|
|
527
|
+
{ name: 'security-update', color: 'ff3300', description: 'Security updates' },
|
|
528
|
+
|
|
529
|
+
// Review complexity
|
|
530
|
+
{ name: 'complex-review', color: 'ff9900', description: 'Complex review required' },
|
|
531
|
+
{ name: 'moderate-review', color: 'ffcc66', description: 'Moderate review complexity' },
|
|
532
|
+
|
|
533
|
+
// Review status
|
|
534
|
+
{ name: 'approved', color: '00cc00', description: 'PR approved' },
|
|
535
|
+
{ name: 'changes-requested', color: 'ff6600', description: 'Changes requested' },
|
|
536
|
+
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
for (const label of labels) {
|
|
540
|
+
try {
|
|
541
|
+
await github.rest.issues.createLabel({
|
|
542
|
+
owner: context.repo.owner,
|
|
543
|
+
repo: context.repo.repo,
|
|
544
|
+
name: label.name,
|
|
545
|
+
color: label.color,
|
|
546
|
+
description: label.description
|
|
547
|
+
});
|
|
548
|
+
console.log(`Created label: ${label.name}`);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (error.status === 422) {
|
|
551
|
+
// Label already exists, try to update it
|
|
552
|
+
try {
|
|
553
|
+
await github.rest.issues.updateLabel({
|
|
554
|
+
owner: context.repo.owner,
|
|
555
|
+
repo: context.repo.repo,
|
|
556
|
+
name: label.name,
|
|
557
|
+
color: label.color,
|
|
558
|
+
description: label.description
|
|
559
|
+
});
|
|
560
|
+
console.log(`Updated label: ${label.name}`);
|
|
561
|
+
} catch (updateError) {
|
|
562
|
+
console.log(`Label exists but couldn't update: ${label.name}`);
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
console.log(`Error with label ${label.name}: ${error.message}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|