freemium-ui 2.0.124 → 2.0.127

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.
@@ -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
+