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.
Files changed (39) hide show
  1. package/.github/actionlint.yaml +6 -0
  2. package/.github/pr_validation_guide.md +305 -0
  3. package/.github/pull_request_template.md +189 -0
  4. package/.github/workflows/pr-preview-cleanup.yml +67 -0
  5. package/.github/workflows/pr-preview-deploy.yml +300 -0
  6. package/.github/workflows/pr-quality.yml +114 -0
  7. package/.github/workflows/pr-template-validation.yml +367 -0
  8. package/.github/workflows/sonarqube-main.yml +106 -0
  9. package/.github/workflows/stale.yml +112 -0
  10. package/.nvmrc +1 -0
  11. package/README.md +9 -0
  12. package/catalog-info.yaml +33 -0
  13. package/docs/adrs/index.md +11 -0
  14. package/docs/architecture/code-structure.md +109 -0
  15. package/docs/architecture/deep-dive.md +193 -0
  16. package/docs/getting-started.md +103 -0
  17. package/docs/index.md +35 -0
  18. package/docs/operations/alerts-configuration.md +11 -0
  19. package/docs/operations/how-to-debug.md +43 -0
  20. package/docs/operations/monitoring.md +16 -0
  21. package/docs/operations/recent-issues.md +14 -0
  22. package/docs/operations/sop.md +25 -0
  23. package/docs/operations/support.md +15 -0
  24. package/lib/README.md +9 -0
  25. package/lib/index.cjs.js +5342 -5299
  26. package/lib/index.d.ts +7 -1
  27. package/lib/index.js +5231 -5187
  28. package/lib/package.json +1 -1
  29. package/mkdocs.yml +38 -0
  30. package/package.json +3 -1
  31. package/sonar-project.properties +25 -0
  32. package/tech-docs/docs/architecture/components.md +49 -0
  33. package/tech-docs/docs/architecture/index.md +54 -0
  34. package/tech-docs/docs/development/build-publish.md +42 -0
  35. package/tech-docs/docs/development/storybook.md +37 -0
  36. package/tech-docs/docs/getting-started.md +67 -0
  37. package/tech-docs/docs/index.md +47 -0
  38. package/tech-docs/docs/repositories/index.md +22 -0
  39. package/tech-docs/mkdocs.yml +28 -0
@@ -0,0 +1,300 @@
1
+ # Ephemeral Storybook preview per PR (pattern from Freshworks preview-env post):
2
+ # build → S3 previews/{PR} → optional CloudFront invalidation → PR comment.
3
+ #
4
+ # Secrets: AWS_IAM_ROLE (role ARN to assume), PREVIEW_S3_BUCKET — optional: CLOUDFRONT_DISTRIBUTION_ID
5
+ # Variables: PREVIEW_BASE_URL (public origin, e.g. https://xxx.cloudfront.net), AWS_REGION
6
+
7
+ name: Preview Deploy
8
+
9
+ on:
10
+ pull_request:
11
+ types: [opened, synchronize, reopened, ready_for_review]
12
+ branches: [main]
13
+ issue_comment:
14
+ types: [created]
15
+
16
+ permissions:
17
+ contents: read
18
+ pull-requests: write
19
+ deployments: write
20
+
21
+ # Prevent concurrent deploys for the same PR; never cancel an in-flight run
22
+ # so a deploy is never left in a partial state.
23
+ concurrency:
24
+ group: preview-${{ github.event.pull_request.number || github.event.issue.number }}
25
+ cancel-in-progress: false
26
+
27
+ jobs:
28
+ preview:
29
+ runs-on: fw-shared
30
+ timeout-minutes: 30
31
+ # Run only for non-draft, non-NO_PREVIEW PRs from the same repo (not forks),
32
+ # or when a repo member manually triggers via /preview-deploy comment on an
33
+ # open PR. Remaining validations (base.ref, collaborator) are enforced inside
34
+ # the Resolve PR head step where full PR data is available.
35
+ if: |
36
+ (github.event_name == 'pull_request' && !github.event.pull_request.draft && !contains(github.event.pull_request.labels.*.name, 'NO_PREVIEW') && github.event.pull_request.head.repo.full_name == github.repository)
37
+ || (github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.issue.state == 'open' && contains(github.event.comment.body, '/preview-deploy') && github.event.comment.user.type != 'Bot')
38
+
39
+ steps:
40
+ # Resolves the PR number and head SHA for both pull_request and issue_comment
41
+ # events. For issue_comment, also validates that the PR is not a fork, not a
42
+ # draft, and does not carry the NO_PREVIEW label before proceeding.
43
+ - name: Resolve PR head
44
+ id: pr
45
+ uses: actions/github-script@v7
46
+ with:
47
+ script: |
48
+ const prNumber =
49
+ context.eventName === 'pull_request'
50
+ ? context.payload.pull_request.number
51
+ : context.issue.number;
52
+ const { data: pr } = await github.rest.pulls.get({
53
+ owner: context.repo.owner,
54
+ repo: context.repo.repo,
55
+ pull_number: prNumber,
56
+ });
57
+ if (context.eventName === 'issue_comment') {
58
+ if (pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`)
59
+ core.setFailed('Preview deploy is not supported for forked PRs.');
60
+ if (pr.base.ref !== 'main')
61
+ core.setFailed(`Preview deploy is only supported for PRs targeting main (this PR targets '${pr.base.ref}').`);
62
+ if (pr.draft) core.setFailed('Draft PRs cannot be previewed via /preview-deploy.');
63
+ if (pr.labels.some((l) => l.name === 'NO_PREVIEW'))
64
+ core.setFailed('This PR has the NO_PREVIEW label.');
65
+ // Allow the PR author or any repo collaborator to trigger /preview-deploy.
66
+ const commenter = context.payload.comment.user.login;
67
+ if (commenter !== pr.user.login) {
68
+ try {
69
+ await github.rest.repos.checkCollaborator({
70
+ ...context.repo,
71
+ username: commenter,
72
+ });
73
+ } catch {
74
+ core.setFailed(`User '${commenter}' is not the PR author or a repository collaborator and cannot trigger /preview-deploy.`);
75
+ }
76
+ }
77
+ }
78
+ core.setOutput('number', String(pr.number));
79
+ core.setOutput('sha', pr.head.sha);
80
+
81
+ # Registers a GitHub Deployment record for this PR so the native
82
+ # "Deployments" section on the PR page tracks the preview environment.
83
+ # The deployment ID is captured as an output for subsequent status updates.
84
+ - name: Create GitHub Deployment
85
+ id: deployment
86
+ uses: actions/github-script@v7
87
+ with:
88
+ script: |
89
+ const { data } = await github.rest.repos.createDeployment({
90
+ ...context.repo,
91
+ ref: '${{ steps.pr.outputs.sha }}',
92
+ environment: 'preview',
93
+ auto_merge: false,
94
+ required_contexts: [],
95
+ description: 'PR #${{ steps.pr.outputs.number }} preview',
96
+ });
97
+ core.setOutput('id', String(data.id));
98
+
99
+ # Marks the deployment as in_progress immediately so the PR shows a
100
+ # spinning indicator while the build and upload are running.
101
+ - name: Set deployment in_progress
102
+ uses: actions/github-script@v7
103
+ with:
104
+ script: |
105
+ await github.rest.repos.createDeploymentStatus({
106
+ ...context.repo,
107
+ deployment_id: Number('${{ steps.deployment.outputs.id }}'),
108
+ state: 'in_progress',
109
+ log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
110
+ });
111
+
112
+ # Checks out the exact commit from the PR head (not the merge commit)
113
+ # so the preview reflects what is in the PR branch.
114
+ - uses: actions/checkout@v4
115
+ with:
116
+ ref: ${{ steps.pr.outputs.sha }}
117
+ fetch-depth: 1
118
+
119
+ # Sets up Node.js 20 with npm cache enabled to speed up subsequent installs.
120
+ - uses: actions/setup-node@v4
121
+ with:
122
+ node-version: 20
123
+ cache: npm
124
+
125
+ # Configures npm to fetch @freshworks scoped packages from GitHub Packages.
126
+ - name: Configure npm for GitHub Packages
127
+ run: |
128
+ echo "@freshdesk:registry=https://npm.pkg.github.com/" >> .npmrc
129
+ echo "@freshworks:registry=https://npm.pkg.github.com/" >> .npmrc
130
+ echo "@freshworksinc:registry=https://npm.pkg.github.com/" >> .npmrc
131
+ echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_AUTH_TOKEN }}" >> .npmrc
132
+
133
+ # Installs exact dependency versions from package-lock.json.
134
+ - run: npm ci
135
+
136
+ # Assumes the IAM role specified by AWS_IAM_ROLE using the runner's base
137
+ # credentials (instance profile). Exports temporary credentials to
138
+ # GITHUB_ENV so all subsequent steps can make authenticated AWS calls.
139
+ # Fails fast with a clear error if assume-role or any credential extraction fails.
140
+ - name: Configure AWS credentials
141
+ run: |
142
+ set -euo pipefail
143
+
144
+ SESSION=$(aws sts assume-role \
145
+ --role-arn "${{ secrets.AWS_IAM_ROLE }}" \
146
+ --role-session-name "GitHubActionsPreviewSession") \
147
+ || { echo "ERROR: aws sts assume-role failed"; exit 1; }
148
+
149
+ AWS_ACCESS_KEY_ID=$(echo "$SESSION" | jq -re '.Credentials.AccessKeyId') \
150
+ || { echo "ERROR: Failed to extract AccessKeyId from assume-role response"; exit 1; }
151
+ AWS_SECRET_ACCESS_KEY=$(echo "$SESSION" | jq -re '.Credentials.SecretAccessKey') \
152
+ || { echo "ERROR: Failed to extract SecretAccessKey from assume-role response"; exit 1; }
153
+ AWS_SESSION_TOKEN=$(echo "$SESSION" | jq -re '.Credentials.SessionToken') \
154
+ || { echo "ERROR: Failed to extract SessionToken from assume-role response"; exit 1; }
155
+
156
+ echo "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" >> $GITHUB_ENV
157
+ echo "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" >> $GITHUB_ENV
158
+ echo "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}" >> $GITHUB_ENV
159
+ echo "AWS_DEFAULT_REGION=${{ vars.AWS_REGION || 'us-east-1' }}" >> $GITHUB_ENV
160
+
161
+ # Ensures PREVIEW_BASE_URL is set before the build so PREVIEW_URL is never
162
+ # constructed from an empty base, which would produce broken relative URLs.
163
+ - name: Validate required variables
164
+ run: |
165
+ if [ -z "${{ vars.PREVIEW_BASE_URL }}" ]; then
166
+ echo "ERROR: Repository variable PREVIEW_BASE_URL is not set. Set it under Settings → Variables."
167
+ exit 1
168
+ fi
169
+
170
+ # Builds the Storybook static output into storybook-static/.
171
+ # PREVIEW_URL tells Storybook the public base path it will be served from.
172
+ - name: Build Storybook
173
+ run: npm run build-storybook
174
+ env:
175
+ PREVIEW_URL: ${{ vars.PREVIEW_BASE_URL }}/previews/${{ steps.pr.outputs.number }}/
176
+
177
+ # Uploads the built Storybook to S3 under previews/{PR_NUM}/.
178
+ # Entry points (index.html, iframe.html) are uploaded with no-cache so
179
+ # browsers always fetch the latest HTML on each redeploy.
180
+ # All other assets are synced with a 1-year TTL since their filenames
181
+ # are content-hashed and safe to cache indefinitely.
182
+ - name: Upload to S3
183
+ env:
184
+ BUCKET: ${{ secrets.PREVIEW_S3_BUCKET }}
185
+ PR_NUM: ${{ steps.pr.outputs.number }}
186
+ run: |
187
+ if [ -z "$BUCKET" ]; then
188
+ echo "Set repository secret PREVIEW_S3_BUCKET."
189
+ exit 1
190
+ fi
191
+ # Upload entry points with no-cache so browsers always fetch the latest HTML
192
+ for f in index.html iframe.html; do
193
+ if [ -f "storybook-static/$f" ]; then
194
+ aws s3 cp "storybook-static/$f" "s3://${BUCKET}/previews/${PR_NUM}/$f" \
195
+ --cache-control "no-cache, must-revalidate"
196
+ else
197
+ echo "ERROR: Expected Storybook output file 'storybook-static/$f' is missing. Ensure the build completed successfully."
198
+ exit 1
199
+ fi
200
+ done
201
+ # Sync remaining assets with a long TTL (safe because filenames are content-hashed)
202
+ aws s3 sync storybook-static/ "s3://${BUCKET}/previews/${PR_NUM}/" \
203
+ --delete \
204
+ --exclude "index.html" \
205
+ --exclude "iframe.html" \
206
+ --cache-control "max-age=31536000,public"
207
+
208
+ # Invalidates the CloudFront cache for this PR's preview path so the
209
+ # updated files are served immediately. Skipped if CLOUDFRONT_DISTRIBUTION_ID
210
+ # is not configured.
211
+ - name: Invalidate CloudFront
212
+ env:
213
+ DIST_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
214
+ PR_NUM: ${{ steps.pr.outputs.number }}
215
+ run: |
216
+ if [ -z "$DIST_ID" ]; then
217
+ echo "Skipping CloudFront invalidation (CLOUDFRONT_DISTRIBUTION_ID not set)."
218
+ exit 0
219
+ fi
220
+ aws cloudfront create-invalidation \
221
+ --distribution-id "$DIST_ID" \
222
+ --paths "/previews/${PR_NUM}/*"
223
+
224
+ # Marks the GitHub Deployment as success and sets the environment_url to
225
+ # the preview link so it appears in the native Deployments section of the PR.
226
+ - name: Set deployment success
227
+ uses: actions/github-script@v7
228
+ env:
229
+ BASE_URL: ${{ vars.PREVIEW_BASE_URL }}
230
+ with:
231
+ script: |
232
+ const base = (process.env.BASE_URL || '').replace(/\/$/, '');
233
+ const previewUrl = `${base}/previews/${{ steps.pr.outputs.number }}/index.html`;
234
+ await github.rest.repos.createDeploymentStatus({
235
+ ...context.repo,
236
+ deployment_id: Number('${{ steps.deployment.outputs.id }}'),
237
+ state: 'success',
238
+ environment_url: previewUrl,
239
+ log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
240
+ });
241
+
242
+ # Posts or updates a single bot comment on the PR with the preview URL.
243
+ # Uses a hidden HTML marker to find and update the existing comment on
244
+ # re-deploys instead of posting a new one each time.
245
+ # Paginates through all comments (100 per page) to reliably find the marker
246
+ # even on PRs with many comments.
247
+ - name: PR comment with preview URL
248
+ uses: actions/github-script@v7
249
+ env:
250
+ BASE_URL: ${{ vars.PREVIEW_BASE_URL }}
251
+ with:
252
+ script: |
253
+ const prNumber = Number('${{ steps.pr.outputs.number }}');
254
+ const base = (process.env.BASE_URL || '').replace(/\/$/, '');
255
+ const previewUrl = `${base}/previews/${prNumber}/index.html`;
256
+ const marker = '<!-- preview-deploy-comment -->';
257
+ const body = [
258
+ marker,
259
+ '### Preview environment ready',
260
+ '',
261
+ `**Preview:** [${previewUrl}](${previewUrl})`,
262
+ '',
263
+ `_Updated: ${new Date().toUTCString()}_`,
264
+ ].join('\n');
265
+
266
+ const comments = await github.paginate(github.rest.issues.listComments, {
267
+ ...context.repo,
268
+ issue_number: prNumber,
269
+ per_page: 100,
270
+ });
271
+ const existing = comments.find((c) => c.body?.includes(marker));
272
+
273
+ if (existing) {
274
+ await github.rest.issues.updateComment({
275
+ ...context.repo,
276
+ comment_id: existing.id,
277
+ body,
278
+ });
279
+ } else {
280
+ await github.rest.issues.createComment({
281
+ ...context.repo,
282
+ issue_number: prNumber,
283
+ body,
284
+ });
285
+ }
286
+
287
+ # Marks the GitHub Deployment as failure if any earlier step failed,
288
+ # so the PR's Deployments section reflects the broken state.
289
+ # Only runs if the deployment record was successfully created.
290
+ - name: Set deployment failure
291
+ if: failure() && steps.deployment.outputs.id != ''
292
+ uses: actions/github-script@v7
293
+ with:
294
+ script: |
295
+ await github.rest.repos.createDeploymentStatus({
296
+ ...context.repo,
297
+ deployment_id: Number('${{ steps.deployment.outputs.id }}'),
298
+ state: 'failure',
299
+ log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
300
+ });
@@ -0,0 +1,114 @@
1
+ name: PR Quality
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+
8
+ concurrency:
9
+ group: pr-quality-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ quality:
17
+ name: Lint, format, test, and build
18
+ runs-on: fw-shared
19
+
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+ with:
24
+ # SonarQube needs full history for blame attribution and new-code
25
+ # detection, plus the base branch to diff the pull request against.
26
+ fetch-depth: 0
27
+ # Do not leave the GITHUB_TOKEN in the workspace git config. The SonarQube
28
+ # scan action runs in a container with the workspace mounted, and no step
29
+ # here performs a git network operation that would need the credential.
30
+ persist-credentials: false
31
+
32
+ # Node 20 LTS — align with org runner or bump when standard changes
33
+ - name: Setup Node
34
+ uses: actions/setup-node@v4
35
+ with:
36
+ node-version: '20'
37
+ cache: 'npm'
38
+
39
+ # Configures npm to fetch @freshworks scoped packages from GitHub Packages —
40
+ # needed here because this package depends on @freshworks/dew-styles.
41
+ - name: Configure npm for GitHub Packages
42
+ run: |
43
+ echo "@freshdesk:registry=https://npm.pkg.github.com/" >> .npmrc
44
+ echo "@freshworks:registry=https://npm.pkg.github.com/" >> .npmrc
45
+ echo "@freshworksinc:registry=https://npm.pkg.github.com/" >> .npmrc
46
+ echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_AUTH_TOKEN }}" >> .npmrc
47
+
48
+ # --ignore-scripts: this job runs PR-controlled code, and npm ci runs
49
+ # lifecycle scripts (this repo's own `prepare` script, plus any
50
+ # dependency's install/postinstall) before the token is removed below.
51
+ # No dependency here has an install script that's actually needed to
52
+ # build or test (verified against the lockfile) — husky's `prepare` is
53
+ # pointless in CI anyway, since no local commit happens here.
54
+ - name: Install dependencies
55
+ run: npm ci --ignore-scripts
56
+
57
+ # The step above appends a literal GH_AUTH_TOKEN to the workspace .npmrc.
58
+ # Remove it so the token does not persist into later steps — the SonarQube
59
+ # scan action runs in a container with the workspace mounted. No step after
60
+ # this one installs packages. The tracked .npmrc in this repo is empty, so
61
+ # there's nothing to restore — just delete the appended file.
62
+ - name: Remove npm auth token from workspace
63
+ if: ${{ always() }}
64
+ run: git checkout -- .npmrc || rm -f .npmrc
65
+
66
+ - name: Prettier check
67
+ run: npm run check
68
+
69
+ - name: ESLint
70
+ run: npm run lint:ci
71
+
72
+ # Coverage is collected here (not a plain test run) so SonarQube has an
73
+ # lcov report to read.
74
+ - name: Unit tests
75
+ timeout-minutes: 15
76
+ env:
77
+ CI: true
78
+ run: npm run test:coverage
79
+
80
+ # Reads sonar-project.properties for scope/exclusions; the pull request
81
+ # parameters below are the only per-run values.
82
+ # continue-on-error: the SonarQube instance is at its server-wide LOC
83
+ # licence limit, so analyses can fail server-side. A quality signal must
84
+ # not block PRs until that is resolved and the quality gate is reviewed.
85
+ - name: SonarQube analysis
86
+ id: sonar
87
+ continue-on-error: true
88
+ # Pinned to v8.2.1 rather than the mutable @master ref — this action
89
+ # runs with SONAR_TOKEN and the PR workspace mounted.
90
+ uses: sonarsource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
91
+ env:
92
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
93
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
94
+ SONAR_SCANNER_OPTS: >
95
+ -Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
96
+ -Dsonar.pullrequest.branch=${{ github.head_ref }}
97
+ -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }}
98
+
99
+ # continue-on-error makes the job conclusion "success" regardless, which
100
+ # would hide auth, host, scanner and configuration failures. Surface the
101
+ # real outcome as an annotation without blocking the PR.
102
+ # Caveat: this only catches scanner-side failures. A server-side rejection
103
+ # (e.g. LICENSING_LOC) still exits 0 — verify via the SonarQube task API:
104
+ # /api/ce/component?component=<projectKey>
105
+ - name: Flag SonarQube analysis failure
106
+ if: steps.sonar.outcome != 'success'
107
+ run: |
108
+ echo "::warning title=SonarQube analysis did not complete::Step outcome was '${{ steps.sonar.outcome }}'. Quality data for this PR may be missing — see the SonarQube analysis step log."
109
+
110
+ # ESLint is already run as a dedicated step above. The real library build
111
+ # is `build` (rollup -> lib/, then a post-build script) — not
112
+ # `build-react-app`, which is a vestigial CRA-scaffold script.
113
+ - name: Production build
114
+ run: DISABLE_ESLINT_PLUGIN=true npm run build