freemium-ui 2.0.132 → 2.0.134
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/actionlint.yaml +6 -0
- package/.github/pr_validation_guide.md +305 -0
- package/.github/pull_request_template.md +189 -0
- package/.github/workflows/pr-preview-cleanup.yml +67 -0
- package/.github/workflows/pr-preview-deploy.yml +300 -0
- package/.github/workflows/pr-quality.yml +114 -0
- package/.github/workflows/pr-template-validation.yml +367 -0
- package/.github/workflows/sonarqube-main.yml +106 -0
- package/.github/workflows/stale.yml +112 -0
- package/.nvmrc +1 -0
- package/README.md +9 -0
- package/catalog-info.yaml +33 -0
- package/docs/adrs/index.md +11 -0
- package/docs/architecture/code-structure.md +109 -0
- package/docs/architecture/deep-dive.md +193 -0
- package/docs/getting-started.md +103 -0
- package/docs/index.md +35 -0
- package/docs/operations/alerts-configuration.md +11 -0
- package/docs/operations/how-to-debug.md +43 -0
- package/docs/operations/monitoring.md +16 -0
- package/docs/operations/recent-issues.md +14 -0
- package/docs/operations/sop.md +25 -0
- package/docs/operations/support.md +15 -0
- package/lib/README.md +9 -0
- package/lib/index.cjs.js +5342 -5299
- package/lib/index.d.ts +7 -1
- package/lib/index.js +5231 -5187
- package/lib/package.json +1 -1
- package/mkdocs.yml +38 -0
- package/package.json +3 -1
- package/sonar-project.properties +25 -0
- package/tech-docs/docs/architecture/components.md +49 -0
- package/tech-docs/docs/architecture/index.md +54 -0
- package/tech-docs/docs/development/build-publish.md +42 -0
- package/tech-docs/docs/development/storybook.md +37 -0
- package/tech-docs/docs/getting-started.md +67 -0
- package/tech-docs/docs/index.md +47 -0
- package/tech-docs/docs/repositories/index.md +22 -0
- package/tech-docs/mkdocs.yml +28 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
name: PR Template Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, edited, synchronize]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: write
|
|
9
|
+
issues: write
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
validate-template:
|
|
14
|
+
runs-on: ['self-hosted', 'Linux']
|
|
15
|
+
name: Validate PR Template Sections
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check if PR is from a bot
|
|
19
|
+
id: bot-check
|
|
20
|
+
uses: actions/github-script@v7
|
|
21
|
+
with:
|
|
22
|
+
script: |
|
|
23
|
+
const pr = context.payload.pull_request;
|
|
24
|
+
const author = pr.user.login;
|
|
25
|
+
const authorType = pr.user.type;
|
|
26
|
+
|
|
27
|
+
console.log('=== Bot Detection ===');
|
|
28
|
+
console.log(`PR Author: ${author}`);
|
|
29
|
+
console.log(`Author Type: ${authorType}`);
|
|
30
|
+
|
|
31
|
+
const botUsers = [
|
|
32
|
+
'dependabot[bot]',
|
|
33
|
+
'dependabot-preview[bot]',
|
|
34
|
+
'renovate[bot]',
|
|
35
|
+
'github-actions[bot]',
|
|
36
|
+
'mergify[bot]',
|
|
37
|
+
'snyk-bot'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const isBot =
|
|
41
|
+
botUsers.includes(author) ||
|
|
42
|
+
author.endsWith('[bot]') ||
|
|
43
|
+
authorType === 'Bot';
|
|
44
|
+
|
|
45
|
+
const isDependencyPR =
|
|
46
|
+
pr.title.toLowerCase().includes('bump') ||
|
|
47
|
+
pr.title.toLowerCase().includes('update dependencies') ||
|
|
48
|
+
pr.title.toLowerCase().includes('chore(deps)');
|
|
49
|
+
|
|
50
|
+
const shouldSkip = isBot || isDependencyPR;
|
|
51
|
+
|
|
52
|
+
console.log(`Is Bot: ${isBot}`);
|
|
53
|
+
console.log(`Is Dependency PR: ${isDependencyPR}`);
|
|
54
|
+
console.log(`Should Skip Validation: ${shouldSkip}`);
|
|
55
|
+
|
|
56
|
+
if (shouldSkip) {
|
|
57
|
+
console.log('⏭️ Skipping PR template validation for automated PR');
|
|
58
|
+
|
|
59
|
+
const botSkipMarker = '<!-- pr-bot-skip-marker -->';
|
|
60
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
61
|
+
owner: context.repo.owner,
|
|
62
|
+
repo: context.repo.repo,
|
|
63
|
+
issue_number: context.issue.number,
|
|
64
|
+
per_page: 100
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const existingComment = comments.find(c => c.body.includes(botSkipMarker));
|
|
68
|
+
if (!existingComment) {
|
|
69
|
+
await github.rest.issues.createComment({
|
|
70
|
+
owner: context.repo.owner,
|
|
71
|
+
repo: context.repo.repo,
|
|
72
|
+
issue_number: context.issue.number,
|
|
73
|
+
body: `${botSkipMarker}\n🤖 **Automated PR Detected** - Skipping PR template validation.\n\nThis PR appears to be from a bot or automated process. Template validation is not required.`
|
|
74
|
+
});
|
|
75
|
+
console.log('Created bot skip comment');
|
|
76
|
+
} else {
|
|
77
|
+
console.log('Bot skip comment already exists, skipping duplicate');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return shouldSkip;
|
|
82
|
+
|
|
83
|
+
- name: Validate PR Template Completion
|
|
84
|
+
if: steps.bot-check.outputs.result != 'true'
|
|
85
|
+
uses: actions/github-script@v7
|
|
86
|
+
with:
|
|
87
|
+
github-token: ${{ secrets.RUNWAYCI_GITHUB_PAT_TOKEN }}
|
|
88
|
+
script: |
|
|
89
|
+
const prBody = context.payload.pull_request.body || '';
|
|
90
|
+
const prTitle = context.payload.pull_request.title || '';
|
|
91
|
+
const errors = [];
|
|
92
|
+
const warnings = [];
|
|
93
|
+
|
|
94
|
+
console.log('=== Starting PR Template Validation ===');
|
|
95
|
+
console.log(`PR #${context.issue.number}: ${prTitle}`);
|
|
96
|
+
|
|
97
|
+
// 0. Validate PR Title (must match commit message format)
|
|
98
|
+
console.log('Checking PR Title format...');
|
|
99
|
+
const validTypes = ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'perf', 'ci', 'build', 'style', 'revert'];
|
|
100
|
+
const titleRegex = /^(feat|fix|chore|docs|refactor|test|perf|ci|build|style|revert):\s*\[(MP-\d+|FS-\d+|FSURVEY-\d+|DEVREL-\d+|PLAT-\d+|MISC|Docs)\]\s+\S.*$/;
|
|
101
|
+
const hasTitleFormat = titleRegex.test(prTitle);
|
|
102
|
+
|
|
103
|
+
if (!hasTitleFormat) {
|
|
104
|
+
errors.push(`**PR Title**: Invalid format. Required format: \`type: [TICKET-ID] description\`
|
|
105
|
+
|
|
106
|
+
**Valid types**: ${validTypes.join(', ')}
|
|
107
|
+
**Valid tickets**: MP-xxxx, FS-xxxx, FSURVEY-xxxx, DEVREL-xxxx, PLAT-xxxx, MISC, Docs
|
|
108
|
+
|
|
109
|
+
**Examples**:
|
|
110
|
+
- ✅ \`feat: [MP-12345] Add new Dropdown variant\`
|
|
111
|
+
- ✅ \`feat: [FS-1000] Add Tooltip component\`
|
|
112
|
+
- ✅ \`feat: [FSURVEY-2024] Add survey builder enhancement\`
|
|
113
|
+
- ✅ \`fix: [MISC] Fix Modal focus trap\`
|
|
114
|
+
- ✅ \`chore: [DEVREL-4720] Update dependencies\`
|
|
115
|
+
- ❌ \`Add Dropdown variant\` (missing type and ticket)
|
|
116
|
+
- ❌ \`[MP-12345] Add feature\` (missing type prefix)
|
|
117
|
+
|
|
118
|
+
**Your title**: \`${prTitle}\``);
|
|
119
|
+
} else {
|
|
120
|
+
const titleMatch = prTitle.match(titleRegex);
|
|
121
|
+
console.log(`✓ PR Title format valid: ${titleMatch[1]} - ${titleMatch[2]}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. Validate Type of Change (at least one checkbox selected)
|
|
125
|
+
console.log('Checking Type of Change...');
|
|
126
|
+
const typeRegex = /## Type of Change[\s\S]*?(?=##|$)/;
|
|
127
|
+
const typeSection = prBody.match(typeRegex)?.[0] || '';
|
|
128
|
+
const hasTypeSelected = /- \[x\]/i.test(typeSection);
|
|
129
|
+
|
|
130
|
+
if (!hasTypeSelected) {
|
|
131
|
+
errors.push('**Type of Change**: No type selected. Please check at least one box (Feature, Bugfix, Refactor, etc.).');
|
|
132
|
+
} else {
|
|
133
|
+
console.log('✓ Type of Change selected');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 2. Validate Ticket Reference (must have valid ticket ID)
|
|
137
|
+
console.log('Checking Ticket Reference...');
|
|
138
|
+
const ticketRegex = /-\s*\*\*Ticket ID\*\*:\s*\[(MP-\d+|FS-\d+|FSURVEY-\d+|DEVREL-\d+|PLAT-\d+|MISC|Docs)\]/;
|
|
139
|
+
const hasTicketId = ticketRegex.test(prBody);
|
|
140
|
+
|
|
141
|
+
if (!hasTicketId) {
|
|
142
|
+
errors.push('**Ticket Reference**: Missing or invalid ticket ID. Required format: `[MP-xxxx]`, `[FS-xxxx]`, `[FSURVEY-xxxx]`, `[DEVREL-xxxx]`, `[PLAT-xxxx]`, `[MISC]`, or `[Docs]`');
|
|
143
|
+
} else {
|
|
144
|
+
const ticketMatch = prBody.match(ticketRegex);
|
|
145
|
+
console.log(`✓ Ticket ID found: ${ticketMatch[1]}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 3. Validate Changes Made (minimum 3 bullet points with content)
|
|
149
|
+
console.log('Checking Changes Made...');
|
|
150
|
+
const changesRegex = /## Changes Made[\s\S]*?(?=##|$)/;
|
|
151
|
+
const changesSection = prBody.match(changesRegex)?.[0] || '';
|
|
152
|
+
const changesBullets = (changesSection.match(/^-\s+.+$/gm) || [])
|
|
153
|
+
.map(line => line.replace(/^-\s+/, '').trim())
|
|
154
|
+
.filter(content =>
|
|
155
|
+
content.length > 0 &&
|
|
156
|
+
!/^Change\s+\d+:?\s*$/i.test(content)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (changesBullets.length < 3) {
|
|
160
|
+
errors.push(`**Changes Made**: Insufficient items listed (found ${changesBullets.length}). Please provide at least 3 bullet points describing your changes.`);
|
|
161
|
+
} else {
|
|
162
|
+
console.log(`✓ Changes Made: ${changesBullets.length} item(s) listed (minimum 3 met)`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 4. Validate Affected Areas (at least one checkbox selected)
|
|
166
|
+
console.log('Checking Affected Areas...');
|
|
167
|
+
const affectedRegex = /## Affected Areas[\s\S]*?(?=##|$)/;
|
|
168
|
+
const affectedSection = prBody.match(affectedRegex)?.[0] || '';
|
|
169
|
+
const hasAreaSelected = /- \[x\]/i.test(affectedSection);
|
|
170
|
+
|
|
171
|
+
if (!hasAreaSelected) {
|
|
172
|
+
errors.push('**Affected Areas**: No area selected. Please check at least one affected area (Components, Styles/Themes, Stories, etc.).');
|
|
173
|
+
} else {
|
|
174
|
+
console.log('✓ Affected Areas selected');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 5. Validate Breaking Changes (must select exactly one option)
|
|
178
|
+
console.log('Checking Breaking Changes...');
|
|
179
|
+
const breakingRegex = /## Breaking Changes[\s\S]*?(?=##|$)/;
|
|
180
|
+
const breakingSection = prBody.match(breakingRegex)?.[0] || '';
|
|
181
|
+
const breakingSelections = breakingSection.match(
|
|
182
|
+
/^- \[[xX]\]\s+(Yes,?\s+this PR introduces breaking changes|No breaking changes.*)$/gim
|
|
183
|
+
) || [];
|
|
184
|
+
const hasBreakingSelection = breakingSelections.length === 1;
|
|
185
|
+
|
|
186
|
+
if (!hasBreakingSelection) {
|
|
187
|
+
errors.push('**Breaking Changes**: Select exactly one option (Yes or No).');
|
|
188
|
+
} else {
|
|
189
|
+
console.log('✓ Breaking Changes option selected');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 6. If breaking changes = yes, require description
|
|
193
|
+
const hasBreakingYes = /^-\s*\[[xX]\]\s+Yes,?\s+this PR introduces breaking changes/im.test(breakingSection);
|
|
194
|
+
if (hasBreakingYes) {
|
|
195
|
+
console.log('Breaking changes detected, checking for description...');
|
|
196
|
+
const descriptionPart = breakingSection
|
|
197
|
+
.replace(/^## Breaking Changes.*$/m, '')
|
|
198
|
+
.replace(/^- \[[ xX]\]\s+Yes,?\s+this PR introduces breaking changes.*$/m, '')
|
|
199
|
+
.replace(/^- \[[ xX]\]\s+No breaking changes.*$/m, '')
|
|
200
|
+
.replace(/<!---[\s\S]*?-->/g, '')
|
|
201
|
+
.trim();
|
|
202
|
+
const hasDescription = descriptionPart.length > 50;
|
|
203
|
+
|
|
204
|
+
if (!hasDescription) {
|
|
205
|
+
errors.push('**Breaking Changes**: You selected "Yes" but did not describe the breaking changes. Please provide details and migration steps.');
|
|
206
|
+
} else {
|
|
207
|
+
console.log('✓ Breaking changes description provided');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 7. Validate Testing section (test steps required)
|
|
212
|
+
console.log('Checking Testing section...');
|
|
213
|
+
const testingRegex = /## How Has This Been Tested\?[\s\S]*?(?=##|$)/;
|
|
214
|
+
const testingSection = prBody.match(testingRegex)?.[0] || '';
|
|
215
|
+
const testStepsContent = testingSection
|
|
216
|
+
.replace(/\*\*Test Steps:\*\*/g, '')
|
|
217
|
+
.replace(/<!---[\s\S]*?-->/g, '')
|
|
218
|
+
.trim();
|
|
219
|
+
const numberedSteps = testStepsContent.match(/^\d+\.\s+.+$/gm) || [];
|
|
220
|
+
const hasTestSteps = numberedSteps.some(
|
|
221
|
+
step => !/^\d+\.\s+Step\s+\d+\s*$/i.test(step.trim())
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (!hasTestSteps) {
|
|
225
|
+
warnings.push('**Testing**: Test steps are missing or contain only placeholders. Please describe how you tested your changes (steps 1, 2, 3...).');
|
|
226
|
+
} else {
|
|
227
|
+
console.log('✓ Test steps provided');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 8. Validate SDET Guidance (must be filled)
|
|
231
|
+
console.log('Checking SDET Guidance...');
|
|
232
|
+
const sdetRegex = /## What should the SDET know\?[\s\S]*?(?=##|$)/;
|
|
233
|
+
const sdetSection = prBody.match(sdetRegex)?.[0] || '';
|
|
234
|
+
const sdetContent = sdetSection
|
|
235
|
+
.split('## What should the SDET know?')[1] || '';
|
|
236
|
+
const sdetCleanContent = sdetContent
|
|
237
|
+
.replace(/<!---[\s\S]*?-->/g, '')
|
|
238
|
+
.replace(/^-\s+Test\s+scenario\s+\d+:?\s*$/gim, '')
|
|
239
|
+
.replace(/^-\s+Edge\s+case(\s+to\s+check)?:?\s*$/gim, '')
|
|
240
|
+
.replace(/^-\s+Focus\s+area:?\s*$/gim, '')
|
|
241
|
+
.trim();
|
|
242
|
+
const hasSdetGuidance = sdetCleanContent.length > 30;
|
|
243
|
+
|
|
244
|
+
if (!hasSdetGuidance) {
|
|
245
|
+
warnings.push('**SDET Guidance**: Please provide testing guidance for the QA team (test scenarios, edge cases, focus areas).');
|
|
246
|
+
} else {
|
|
247
|
+
console.log('✓ SDET guidance provided');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 9. Check Screenshots for UI changes
|
|
251
|
+
console.log('Checking for UI changes...');
|
|
252
|
+
const files = await github.paginate(github.rest.pulls.listFiles, {
|
|
253
|
+
owner: context.repo.owner,
|
|
254
|
+
repo: context.repo.repo,
|
|
255
|
+
pull_number: context.issue.number,
|
|
256
|
+
per_page: 100
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const uiFiles = files.filter(file =>
|
|
260
|
+
file.filename.endsWith('.tsx') &&
|
|
261
|
+
(
|
|
262
|
+
file.filename.startsWith('src/components/') ||
|
|
263
|
+
file.filename.startsWith('src/stories/')
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (uiFiles.length > 0) {
|
|
268
|
+
console.log(`UI changes detected in ${uiFiles.length} files`);
|
|
269
|
+
const screenshotsRegex = /## Screenshots\/Videos[\s\S]*?(?=##|$)/;
|
|
270
|
+
const screenshotsSection = prBody.match(screenshotsRegex)?.[0] || '';
|
|
271
|
+
const hasScreenshots = /!\[.*?\]\(.*?\)|https?:\/\/\S+\.(png|jpg|jpeg|gif|mp4|webm|mov)(?:\?\S+)?|https?:\/\/github\.com\/user-attachments\/assets\/[^\s)]+/i.test(screenshotsSection);
|
|
272
|
+
|
|
273
|
+
if (!hasScreenshots) {
|
|
274
|
+
warnings.push(`**Screenshots/Videos**: UI changes detected in ${uiFiles.length} file(s) but no screenshots provided. Please consider adding before/after screenshots or a Storybook link.`);
|
|
275
|
+
} else {
|
|
276
|
+
console.log('✓ Screenshots provided for UI changes');
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
console.log('No UI changes detected');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Generate validation report
|
|
283
|
+
console.log(`\nValidation Results: ${errors.length} errors, ${warnings.length} warnings`);
|
|
284
|
+
|
|
285
|
+
const docsRef = context.payload.pull_request.head.sha;
|
|
286
|
+
const repoUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}`;
|
|
287
|
+
const guideUrl = `${repoUrl}/blob/${docsRef}/.github/pr_validation_guide.md`;
|
|
288
|
+
const templateUrl = `${repoUrl}/blob/${docsRef}/.github/pull_request_template.md`;
|
|
289
|
+
|
|
290
|
+
const reportMarker = '<!-- pr-template-validation-report -->';
|
|
291
|
+
let comment = `${reportMarker}\n## 🔍 PR Template Validation Report\n\n`;
|
|
292
|
+
|
|
293
|
+
if (errors.length > 0) {
|
|
294
|
+
comment += '### ❌ Required Sections Missing\n\n';
|
|
295
|
+
comment += 'The following **required sections** must be completed:\n\n';
|
|
296
|
+
comment += errors.map(e => `- ${e}`).join('\n');
|
|
297
|
+
comment += '\n\n**⚠️ Action Required**: Please edit your PR description to include all required information.\n\n';
|
|
298
|
+
comment += `📖 **Verify against the rules:** [PR Validation Guide](${guideUrl})\n\n`;
|
|
299
|
+
comment += '---\n\n';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (warnings.length > 0) {
|
|
303
|
+
comment += '### ⚠️ Recommended Improvements\n\n';
|
|
304
|
+
comment += 'Please consider adding:\n\n';
|
|
305
|
+
comment += warnings.map(w => `- ${w}`).join('\n');
|
|
306
|
+
comment += '\n\n---\n\n';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
310
|
+
comment = `${reportMarker}\n## ✅ PR Template Complete\n\n`;
|
|
311
|
+
comment += 'All required sections are properly filled. Great job! 🎉\n';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Add helpful footer
|
|
315
|
+
comment += '\n---\n\n';
|
|
316
|
+
comment += '<details>\n';
|
|
317
|
+
comment += '<summary>📋 PR Template Requirements</summary>\n\n';
|
|
318
|
+
comment += '**Required Sections:**\n';
|
|
319
|
+
comment += '- Type of Change (select at least one)\n';
|
|
320
|
+
comment += '- Ticket Reference (valid ticket ID)\n';
|
|
321
|
+
comment += '- Changes Made (minimum 3 items)\n';
|
|
322
|
+
comment += '- Affected Areas (select at least one)\n';
|
|
323
|
+
comment += '- Breaking Changes (select Yes/No)\n\n';
|
|
324
|
+
comment += '**Recommended Sections:**\n';
|
|
325
|
+
comment += '- Testing (test steps)\n';
|
|
326
|
+
comment += '- SDET Guidance (QA testing guidance)\n';
|
|
327
|
+
comment += '- Screenshots (if UI changes)\n\n';
|
|
328
|
+
comment += `📖 [PR Validation Guide](${guideUrl}) — full rules, examples, and troubleshooting\n`;
|
|
329
|
+
comment += `📝 [Pull Request Template](${templateUrl}) — the template structure\n`;
|
|
330
|
+
comment += '</details>\n';
|
|
331
|
+
|
|
332
|
+
// Post or update comment
|
|
333
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
334
|
+
owner: context.repo.owner,
|
|
335
|
+
repo: context.repo.repo,
|
|
336
|
+
issue_number: context.issue.number,
|
|
337
|
+
per_page: 100
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const botComment = comments.find(c =>
|
|
341
|
+
c.body.includes(reportMarker)
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
if (botComment) {
|
|
345
|
+
await github.rest.issues.updateComment({
|
|
346
|
+
owner: context.repo.owner,
|
|
347
|
+
repo: context.repo.repo,
|
|
348
|
+
comment_id: botComment.id,
|
|
349
|
+
body: comment
|
|
350
|
+
});
|
|
351
|
+
console.log('Updated existing validation comment');
|
|
352
|
+
} else {
|
|
353
|
+
await github.rest.issues.createComment({
|
|
354
|
+
owner: context.repo.owner,
|
|
355
|
+
repo: context.repo.repo,
|
|
356
|
+
issue_number: context.issue.number,
|
|
357
|
+
body: comment
|
|
358
|
+
});
|
|
359
|
+
console.log('Created new validation comment');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Fail the check if there are errors (blocks merge with branch protection)
|
|
363
|
+
if (errors.length > 0) {
|
|
364
|
+
core.setFailed(`PR template validation failed: ${errors.length} required section(s) missing or incomplete`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log('✅ PR template validation passed!');
|
|
367
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
name: SonarQube Main Branch
|
|
2
|
+
|
|
3
|
+
# Analyses the main branch after every merge, per the Platforms CX guidance
|
|
4
|
+
# (https://confluence.freshworks.com/spaces/SHAR/pages/525401715/Sonarqube+integration+Frontend).
|
|
5
|
+
#
|
|
6
|
+
# This is what produces the overall project dashboard: total bugs, code smells,
|
|
7
|
+
# vulnerabilities, security hotspots, duplication and coverage across the whole
|
|
8
|
+
# codebase. Pull request analyses only report on changed code and never populate
|
|
9
|
+
# a branch, so without this workflow the project's main branch stays empty.
|
|
10
|
+
#
|
|
11
|
+
# It also gives the New Code baseline that PR analyses diff against.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
branches:
|
|
16
|
+
- main
|
|
17
|
+
# Allows a one-off baseline run without waiting for a merge.
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: sonarqube-main-${{ github.ref }}
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
analysis:
|
|
29
|
+
name: Analyse main branch
|
|
30
|
+
runs-on: fw-shared
|
|
31
|
+
timeout-minutes: 30
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- name: Checkout
|
|
35
|
+
uses: actions/checkout@v4
|
|
36
|
+
with:
|
|
37
|
+
# SonarQube needs full history for blame attribution and new-code detection.
|
|
38
|
+
fetch-depth: 0
|
|
39
|
+
# Do not leave the GITHUB_TOKEN in the workspace git config. The SonarQube
|
|
40
|
+
# scan action runs in a container with the workspace mounted, and no step
|
|
41
|
+
# here performs a git network operation that would need the credential.
|
|
42
|
+
persist-credentials: false
|
|
43
|
+
|
|
44
|
+
# Node 20 LTS — align with org runner or bump when standard changes
|
|
45
|
+
- name: Setup Node
|
|
46
|
+
uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: '20'
|
|
49
|
+
cache: 'npm'
|
|
50
|
+
|
|
51
|
+
# Configures npm to fetch @freshworks scoped packages from GitHub Packages —
|
|
52
|
+
# needed here because this package depends on @freshworks/dew-styles.
|
|
53
|
+
- name: Configure npm for GitHub Packages
|
|
54
|
+
run: |
|
|
55
|
+
echo "@freshdesk:registry=https://npm.pkg.github.com/" >> .npmrc
|
|
56
|
+
echo "@freshworks:registry=https://npm.pkg.github.com/" >> .npmrc
|
|
57
|
+
echo "@freshworksinc:registry=https://npm.pkg.github.com/" >> .npmrc
|
|
58
|
+
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_AUTH_TOKEN }}" >> .npmrc
|
|
59
|
+
|
|
60
|
+
# --ignore-scripts: same GH_AUTH_TOKEN-in-.npmrc exposure as the PR
|
|
61
|
+
# workflow, even though this one only ever runs already-merged main
|
|
62
|
+
# code. No dependency's install script is needed to build or test here.
|
|
63
|
+
- name: Install dependencies
|
|
64
|
+
run: npm ci --ignore-scripts
|
|
65
|
+
|
|
66
|
+
# The step above appends a literal GH_AUTH_TOKEN to the workspace .npmrc.
|
|
67
|
+
# Remove it so the token does not persist into later steps — the SonarQube
|
|
68
|
+
# scan action runs in a container with the workspace mounted. No step after
|
|
69
|
+
# this one installs packages. The tracked .npmrc in this repo is empty, so
|
|
70
|
+
# there's nothing to restore — just delete the appended file.
|
|
71
|
+
- name: Remove npm auth token from workspace
|
|
72
|
+
if: ${{ always() }}
|
|
73
|
+
run: git checkout -- .npmrc || rm -f .npmrc
|
|
74
|
+
|
|
75
|
+
- name: Test coverage
|
|
76
|
+
timeout-minutes: 15
|
|
77
|
+
env:
|
|
78
|
+
CI: true
|
|
79
|
+
run: npm run test:coverage
|
|
80
|
+
|
|
81
|
+
# Reads sonar-project.properties for scope and exclusions. No pull request
|
|
82
|
+
# parameters here — that is what makes this a branch analysis rather than a
|
|
83
|
+
# PR analysis, and therefore what populates the project dashboard.
|
|
84
|
+
#
|
|
85
|
+
# continue-on-error: the SonarQube instance is at its server-wide LOC
|
|
86
|
+
# licence limit, and branch analyses of net-new code are rejected with
|
|
87
|
+
# errorType LICENSING_LOC. Remove this once the limit is raised.
|
|
88
|
+
- name: SonarQube analysis
|
|
89
|
+
id: sonar
|
|
90
|
+
continue-on-error: true
|
|
91
|
+
# Pinned to v8.2.1 rather than the mutable @master ref — this action
|
|
92
|
+
# runs with SONAR_TOKEN and the workspace mounted.
|
|
93
|
+
uses: sonarsource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
|
|
94
|
+
env:
|
|
95
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
96
|
+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
97
|
+
|
|
98
|
+
# continue-on-error makes the job conclusion "success" regardless, which
|
|
99
|
+
# would hide auth, host, scanner and configuration failures.
|
|
100
|
+
# Caveat: this only catches scanner-side failures. A server-side rejection
|
|
101
|
+
# (e.g. LICENSING_LOC) still exits 0 — verify via the SonarQube task API:
|
|
102
|
+
# /api/ce/component?component=<projectKey>
|
|
103
|
+
- name: Flag SonarQube analysis failure
|
|
104
|
+
if: steps.sonar.outcome != 'success'
|
|
105
|
+
run: |
|
|
106
|
+
echo "::warning title=SonarQube main-branch analysis did not complete::Step outcome was '${{ steps.sonar.outcome }}'. The project dashboard may be stale — see the SonarQube analysis step log."
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: Stale PR Management
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 0 * * *' # Run daily at midnight UTC
|
|
6
|
+
|
|
7
|
+
workflow_dispatch: # Allow manual trigger
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
pull-requests: write
|
|
11
|
+
issues: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
stale:
|
|
15
|
+
runs-on: ['self-hosted', 'Linux']
|
|
16
|
+
name: Mark and Close Stale PRs
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
|
20
|
+
with:
|
|
21
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
|
|
23
|
+
# PR Configuration
|
|
24
|
+
days-before-stale: 2
|
|
25
|
+
days-before-close: 2
|
|
26
|
+
|
|
27
|
+
# Do not handle issues
|
|
28
|
+
days-before-issue-stale: -1
|
|
29
|
+
days-before-issue-close: -1
|
|
30
|
+
|
|
31
|
+
# Stale PR message (warning)
|
|
32
|
+
stale-pr-message: |
|
|
33
|
+
## ⏰ Stale PR Warning
|
|
34
|
+
|
|
35
|
+
This pull request has been **inactive for 2 days** with no commits, comments, or reviews.
|
|
36
|
+
|
|
37
|
+
### What happens next?
|
|
38
|
+
|
|
39
|
+
- This PR will be automatically closed in **2 days** if no activity occurs
|
|
40
|
+
- To prevent closure, you can:
|
|
41
|
+
- Push new commits
|
|
42
|
+
- Add a comment with updates or status
|
|
43
|
+
- Remove the `stale` label
|
|
44
|
+
- Request review from team members
|
|
45
|
+
|
|
46
|
+
### Why does this matter?
|
|
47
|
+
|
|
48
|
+
- Keeps our PR list clean and manageable
|
|
49
|
+
- Helps identify abandoned or forgotten work
|
|
50
|
+
- Encourages timely reviews and merges
|
|
51
|
+
|
|
52
|
+
### Need more time?
|
|
53
|
+
|
|
54
|
+
Simply comment on this PR to remove the stale label, or:
|
|
55
|
+
- Add `work-in-progress` label if still developing
|
|
56
|
+
- Add `blocked` label if waiting on external dependencies
|
|
57
|
+
- Add `on-hold` label if intentionally paused
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
💡 **Tip:** Keep your PRs active by:
|
|
62
|
+
- Requesting reviews when ready
|
|
63
|
+
- Responding to feedback promptly
|
|
64
|
+
- Rebasing on latest main/dev branch
|
|
65
|
+
- Adding status updates if blocked
|
|
66
|
+
|
|
67
|
+
# Close message (final notice)
|
|
68
|
+
close-pr-message: |
|
|
69
|
+
## 🔒 PR Automatically Closed
|
|
70
|
+
|
|
71
|
+
This pull request has been closed due to **4 days of inactivity** (2 days stale + 2 days grace period).
|
|
72
|
+
|
|
73
|
+
### What you can do:
|
|
74
|
+
|
|
75
|
+
✅ **Reopen this PR** if you want to continue working on it
|
|
76
|
+
✅ **Create a new PR** with updated changes
|
|
77
|
+
✅ **Link related issues** to track the work
|
|
78
|
+
|
|
79
|
+
### Why was this closed?
|
|
80
|
+
|
|
81
|
+
- Helps maintain a clean, manageable PR list
|
|
82
|
+
- Prevents outdated PRs from accumulating
|
|
83
|
+
- Encourages active development workflow
|
|
84
|
+
|
|
85
|
+
### Need help?
|
|
86
|
+
|
|
87
|
+
If this PR was closed in error or you need assistance:
|
|
88
|
+
- Comment mentioning a team member
|
|
89
|
+
- Reopen and add context about delays
|
|
90
|
+
- Reach out on team channels
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
# Label configuration
|
|
95
|
+
stale-pr-label: 'stale'
|
|
96
|
+
exempt-pr-labels: 'work-in-progress,blocked,on-hold,keep-open,dependencies'
|
|
97
|
+
exempt-draft-pr: true
|
|
98
|
+
|
|
99
|
+
# Activity that removes stale label
|
|
100
|
+
remove-stale-when-updated: true
|
|
101
|
+
|
|
102
|
+
# Operations limit (prevent API rate limiting)
|
|
103
|
+
operations-per-run: 100
|
|
104
|
+
|
|
105
|
+
# Sort order (process oldest first)
|
|
106
|
+
ascending: true
|
|
107
|
+
|
|
108
|
+
# Delete branch on close (optional - uncomment if desired)
|
|
109
|
+
# delete-branch: true
|
|
110
|
+
|
|
111
|
+
# Additional configuration
|
|
112
|
+
enable-statistics: true
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
18
|
package/README.md
CHANGED
|
@@ -85,3 +85,12 @@ Publish the package to npm
|
|
|
85
85
|
```
|
|
86
86
|
npm publish
|
|
87
87
|
```
|
|
88
|
+
|
|
89
|
+
## AIPDLC (SurveyServ)
|
|
90
|
+
|
|
91
|
+
This repo is onboarded to AIPDLC (Freshworks AI Product Development Lifecycle) for the SurveyServ team.
|
|
92
|
+
|
|
93
|
+
- Run AIPDLC phases with the SurveyServ scope, e.g. `/fw-system-design --scope=bu/surveyserv`.
|
|
94
|
+
- `/fw-init` generates a local `.aipdlc/workspace.json` pointer; it is **gitignored** (per-developer, per-epic state) — run `/fw-init` locally to (re)create it.
|
|
95
|
+
- Team-wide AIPDLC customization (dev/deploy overlays, rules) lives in `freshworks/ai-builder-artifacts` under `bu/platform/modules/surveyserv/`.
|
|
96
|
+
- This package pins **Node 18** (see `.nvmrc`; Storybook 8 requires Node ≥18) — run `nvm use` before installing.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Prism (Backstage) software catalog descriptor.
|
|
2
|
+
# Registers this repo as the shared UI component library of the SurveyServ
|
|
3
|
+
# system. The `platforms/surveyserv` System entity itself is declared in the
|
|
4
|
+
# freshdesk/surveyserv repo (see PR #5448) — not redeclared here to avoid a
|
|
5
|
+
# duplicate-entity conflict in the catalog.
|
|
6
|
+
# Schema: `get_catalog_schema` in the Prism MCP server is the source of truth.
|
|
7
|
+
apiVersion: backstage.io/v1alpha1
|
|
8
|
+
kind: Component
|
|
9
|
+
metadata:
|
|
10
|
+
name: freemium-ui
|
|
11
|
+
namespace: platforms
|
|
12
|
+
title: Freemium UI
|
|
13
|
+
description: >-
|
|
14
|
+
Lightweight, Styled-Components-based React UI component library shared
|
|
15
|
+
across SurveyServ/Freemium frontends. Published as an npm package.
|
|
16
|
+
annotations:
|
|
17
|
+
github.com/project-slug: freshdesk/freemium-ui
|
|
18
|
+
backstage.io/techdocs-ref: dir:./tech-docs
|
|
19
|
+
tags:
|
|
20
|
+
- react
|
|
21
|
+
- library
|
|
22
|
+
- typescript
|
|
23
|
+
- ui-components
|
|
24
|
+
- styled-components
|
|
25
|
+
labels:
|
|
26
|
+
exposure: internal
|
|
27
|
+
language: TypeScript
|
|
28
|
+
orgVersion: v1alpha1
|
|
29
|
+
spec:
|
|
30
|
+
type: library
|
|
31
|
+
lifecycle: production
|
|
32
|
+
owner: platforms/plat-maise-surveyserv
|
|
33
|
+
system: platforms/surveyserv
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# ADRs
|
|
2
|
+
|
|
3
|
+
No architecture decision records exist in this repository (no `adrs/` source files documenting an actual decision, and no dedicated ADR pages found in Confluence for `freemium-ui` specifically — this page and the empty `adrs/` scaffold directory are onboarding artifacts, not ADRs themselves). The `adrs/` directory at the repo root is scaffolding for future ADR source files — currently empty.
|
|
4
|
+
|
|
5
|
+
Key architectural decisions that _have_ been made but are only recorded implicitly in code/config (candidates for a retroactive ADR if the team wants one):
|
|
6
|
+
|
|
7
|
+
- Externalizing only `react`/`react-dom` at build time and bundling every other dependency (`rollup.config.ts`'s `external` array) — see [Deep Dive → Dependencies & coupling](../architecture/deep-dive.md#3-dependencies--coupling).
|
|
8
|
+
- Adopting Radix UI primitives + a shared shadow-DOM-aware portal pattern (`usePortalContainer`/`useShadowDomScrollFix`) for every overlay component — see [Deep Dive → Architecture & control flow](../architecture/deep-dive.md#1-architecture--control-flow).
|
|
9
|
+
- Adding a third `dew`/`DewTheme` theme, driven by CSS variables from `@freshworks/dew-styles`, alongside the original `light`/`dark` themes — see the Confluence "Reskinning Project - Freshworks Dew 2.0 Design System" page and [Deep Dive → Idiomatic vernacular](../architecture/deep-dive.md#4-idiomatic-vernacular).
|
|
10
|
+
|
|
11
|
+
No ADRs recorded yet.
|