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,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
|