Skip to content

Commit b536feb

Browse files
Merge branch 'main' into dev
2 parents 59efafe + f0c760f commit b536feb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1255
-306
lines changed

.env.sample

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ AZURE_SEARCH_FIELDS_TAG=tag
1414
AZURE_SEARCH_FIELDS_METADATA=metadata
1515
AZURE_SEARCH_FILENAME_COLUMN=filepath
1616
AZURE_SEARCH_TITLE_COLUMN=title
17+
AZURE_SEARCH_SOURCE_COLUMN=source
18+
AZURE_SEARCH_TEXT_COLUMN=text
19+
AZURE_SEARCH_LAYOUT_TEXT_COLUMN=layoutText
1720
AZURE_SEARCH_URL_COLUMN=url
1821
AZURE_SEARCH_CONVERSATIONS_LOG_INDEX=conversations-log
1922
AZURE_SEARCH_USE_INTEGRATED_VECTORIZATION=false
@@ -60,6 +63,8 @@ AZURE_SPEECH_SERVICE_REGION=
6063
AZURE_AUTH_TYPE=keys
6164
USE_KEY_VAULT=true
6265
AZURE_KEY_VAULT_ENDPOINT=
66+
# Application environment (e.g., dev, prod)
67+
APP_ENV="dev"
6368
# Chat conversation type to decide between custom or byod (bring your own data) conversation type
6469
CONVERSATION_FLOW=
6570
# Chat History CosmosDB Integration Settings

.github/workflows/broken-links-checker.yml

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,38 @@ jobs:
2020
with:
2121
fetch-depth: 0
2222

23-
- name: Get Added/Modified Markdown Files (PR only)
24-
id: changed-files
23+
# For PR : Get only changed markdown files
24+
- name: Get changed markdown files (PR only)
25+
id: changed-markdown-files
2526
if: github.event_name == 'pull_request'
26-
run: |
27-
git fetch origin ${{ github.base_ref }}
28-
files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.md$' || true)
29-
echo "md_files<<EOF" >> $GITHUB_OUTPUT
30-
echo "$files" >> $GITHUB_OUTPUT
31-
echo "EOF" >> $GITHUB_OUTPUT
32-
- name: Check Broken Links in Added/Modified Files (PR)
33-
if: github.event_name == 'pull_request' && steps.changed-files.outputs.md_files != ''
27+
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46
28+
with:
29+
files: |
30+
**/*.md
31+
32+
33+
# For PR: Check broken links only in changed files
34+
- name: Check Broken Links in Changed Markdown Files
35+
id: lychee-check-pr
36+
if: github.event_name == 'pull_request' && steps.changed-markdown-files.outputs.any_changed == 'true'
3437
uses: lycheeverse/[email protected]
3538
with:
3639
args: >
3740
--verbose --exclude-mail --no-progress --exclude ^https?://
38-
${{ steps.changed-files.outputs.md_files }}
41+
${{ steps.changed-markdown-files.outputs.all_changed_files }}
3942
failIfEmpty: false
4043
env:
4144
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4245

43-
- name: Check Broken Links in Entire Repo (Manual)
46+
# For manual trigger: Check all markdown files in repo
47+
- name: Check Broken Links in All Markdown Files in Entire Repo (Manual Trigger)
48+
id: lychee-check-manual
4449
if: github.event_name == 'workflow_dispatch'
4550
uses: lycheeverse/[email protected]
4651
with:
4752
args: >
4853
--verbose --exclude-mail --no-progress --exclude ^https?://
4954
'**/*.md'
5055
failIfEmpty: false
51-
output: lychee/out.md
5256
env:
5357
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/build-docker-images.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ jobs:
3232
dockerfile: docker/Frontend.Dockerfile
3333
uses: ./.github/workflows/build-docker.yml
3434
with:
35-
old_registry: ${{ github.ref_name == 'main' && 'fruoccopublic.azurecr.io' }}
3635
new_registry: 'cwydcontainerreg.azurecr.io'
37-
old_username: ${{ github.ref_name == 'main' && 'fruoccopublic' }}
3836
new_username: 'cwydcontainerreg'
3937
app_name: ${{ matrix.app_name }}
4038
dockerfile: ${{ matrix.dockerfile }}

.github/workflows/build-docker.yml

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ name: Reusable Docker build and push workflow
33
on:
44
workflow_call:
55
inputs:
6-
old_registry:
7-
required: true
8-
type: string
9-
old_username:
10-
required: true
11-
type: string
126
new_registry:
137
required: true
148
type: string
@@ -37,15 +31,6 @@ jobs:
3731
- name: Checkout
3832
uses: actions/checkout@v4
3933

40-
# Login for 'main' branch to both registries
41-
- name: Docker Login to fruoccopublic (Main)
42-
if: ${{ inputs.push == true && github.ref_name == 'main' }}
43-
uses: docker/login-action@v3
44-
with:
45-
registry: ${{ inputs.old_registry }}
46-
username: ${{ inputs.old_username }}
47-
password: ${{ secrets.DOCKER_PASSWORD }}
48-
4934
- name: Docker Login to cwydcontainerreg (Main)
5035
if: ${{ inputs.push == true && github.ref_name == 'main' }}
5136
uses: docker/login-action@v3
@@ -70,18 +55,6 @@ jobs:
7055
id: date
7156
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
7257

73-
- name: Build Docker Image and optionally push (Old Registry)
74-
if: ${{ github.ref_name == 'main' }}
75-
uses: docker/build-push-action@v6
76-
with:
77-
context: .
78-
file: ${{ inputs.dockerfile }}
79-
push: ${{ inputs.push }}
80-
cache-from: type=registry,ref=${{ inputs.old_registry }}/${{ inputs.app_name }}:${{ github.ref_name == 'main' && 'latest' || github.head_ref || github.ref_name }}
81-
tags: |
82-
${{ inputs.old_registry }}/${{ inputs.app_name }}:${{ github.ref_name == 'main' && 'latest' || github.head_ref || 'default' }}
83-
${{ inputs.old_registry }}/${{ inputs.app_name }}:${{ steps.date.outputs.date }}_${{ github.run_number }}
84-
8558
- name: Build Docker Image and optionally push (New Registry)
8659
if: ${{ github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'demo'|| github.ref_name == 'dependabotchanges' }}
8760
uses: docker/build-push-action@v6
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Workflow: Group Dependabot PRs
2+
# Description:
3+
# This GitHub Actions workflow automatically groups open Dependabot PRs by ecosystem (pip, npm).
4+
# It cherry-picks individual PR changes into grouped branches, resolves merge conflicts automatically, and opens consolidated PRs.
5+
# It also closes the original Dependabot PRs and carries over their labels and metadata.
6+
# Improvements:
7+
# - Handles multiple conflicting files during cherry-pick
8+
# - Deduplicates entries in PR description
9+
# - Avoids closing original PRs unless grouped PR creation succeeds
10+
# - More efficient retry logic
11+
# - Ecosystem grouping is now configurable via native YAML map
12+
# - Uses safe namespaced branch naming (e.g. actions/grouped-...) to avoid developer conflict
13+
# - Ensures PR body formatting uses real newlines for better readability
14+
# - Adds strict error handling for script robustness
15+
# - Accounts for tool dependencies (jq, gh) and race conditions
16+
# - Optimized PR metadata lookup by preloading into associative array
17+
# - Supports --dry-run mode for validation/testing without side effects
18+
# - Note: PRs created during workflow execution will be picked up in the next scheduled run.
19+
20+
name: Group Dependabot PRs
21+
22+
on:
23+
schedule:
24+
- cron: '0 0 * * *' # Run daily at midnight UTC
25+
workflow_dispatch:
26+
inputs:
27+
group_config_pip:
28+
description: "Group name for pip ecosystem"
29+
required: false
30+
default: "backend"
31+
group_config_npm:
32+
description: "Group name for npm ecosystem"
33+
required: false
34+
default: "frontend"
35+
group_config_yarn:
36+
description: "Group name for yarn ecosystem"
37+
required: false
38+
default: "frontend"
39+
dry_run:
40+
description: "Run in dry-run mode (no changes will be pushed or PRs created/closed)"
41+
required: false
42+
default: false
43+
type: boolean
44+
45+
jobs:
46+
group-dependabot-prs:
47+
runs-on: ubuntu-latest
48+
permissions:
49+
contents: write
50+
pull-requests: write
51+
env:
52+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
TARGET_BRANCH: "main"
54+
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
55+
GROUP_CONFIG_PIP: ${{ github.event.inputs.group_config_pip || 'backend' }}
56+
GROUP_CONFIG_NPM: ${{ github.event.inputs.group_config_npm || 'frontend' }}
57+
GROUP_CONFIG_YARN: ${{ github.event.inputs.group_config_yarn || 'frontend' }}
58+
steps:
59+
- name: Checkout default branch
60+
uses: actions/checkout@v4
61+
62+
- name: Set up Git
63+
run: |
64+
git config --global user.name "github-actions"
65+
git config --global user.email "[email protected]"
66+
67+
- name: Install required tools
68+
uses: awalsh128/[email protected]
69+
with:
70+
packages: "jq gh"
71+
72+
- name: Enable strict error handling
73+
shell: bash
74+
run: |
75+
set -euo pipefail
76+
77+
- name: Fetch open Dependabot PRs targeting main
78+
id: fetch_prs
79+
run: |
80+
gh pr list \
81+
--search "author:dependabot[bot] base:$TARGET_BRANCH is:open" \
82+
--limit 100 \
83+
--json number,title,headRefName,labels,files,url \
84+
--jq '[.[] | {number, title, url, ref: .headRefName, labels: [.labels[].name], files: [.files[].path]}]' > prs.json
85+
cat prs.json
86+
87+
- name: Validate prs.json
88+
run: |
89+
jq empty prs.json 2> jq_error.log || { echo "Malformed JSON in prs.json: $(cat jq_error.log)"; exit 1; }
90+
91+
- name: Check if any PRs exist
92+
id: check_prs
93+
run: |
94+
count=$(jq length prs.json)
95+
echo "Found $count PRs"
96+
if [ "$count" -eq 0 ]; then
97+
echo "No PRs to group. Exiting."
98+
echo "skip=true" >> $GITHUB_OUTPUT
99+
fi
100+
101+
- name: Exit early if no PRs
102+
if: steps.check_prs.outputs.skip == 'true'
103+
run: exit 0
104+
105+
- name: Dry-run validation (CI/test only)
106+
if: env.DRY_RUN == 'true'
107+
run: |
108+
echo "Running in dry-run mode. No changes will be pushed or PRs created/closed."
109+
# Optionally, add more validation logic here (e.g., check grouped files, print planned actions).
110+
111+
- name: Group PRs by ecosystem and cherry-pick with retry
112+
run: |
113+
declare -A GROUP_CONFIG=(
114+
[pip]="${GROUP_CONFIG_PIP:-backend}"
115+
[npm]="${GROUP_CONFIG_NPM:-frontend}"
116+
[yarn]="${GROUP_CONFIG_YARN:-frontend}"
117+
)
118+
mkdir -p grouped
119+
jq -c '.[]' prs.json | while read pr; do
120+
ref=$(echo "$pr" | jq -r '.ref')
121+
number=$(echo "$pr" | jq -r '.number')
122+
group="misc"
123+
for key in "${!GROUP_CONFIG[@]}"; do
124+
if [[ "$ref" == *"$key"* ]]; then
125+
group="${GROUP_CONFIG[$key]}"
126+
break
127+
fi
128+
done
129+
echo "$number $ref $group" >> grouped/$group.txt
130+
done
131+
132+
shopt -s nullglob
133+
grouped_files=(grouped/*.txt)
134+
135+
if [ ${#grouped_files[@]} -eq 0 ]; then
136+
echo "No groups were formed. Exiting."
137+
exit 0
138+
fi
139+
140+
declare -A pr_metadata_map
141+
while IFS=$'\t' read -r number title url labels; do
142+
pr_metadata_map["$number"]="$title|$url|$labels"
143+
done < <(jq -r '.[] | "\(.number)\t\(.title)\t\(.url)\t\(.labels | join(","))"' prs.json)
144+
145+
for file in "${grouped_files[@]}"; do
146+
group_name=$(basename "$file" .txt)
147+
# Sanitize group_name: allow only alphanum, dash, underscore
148+
safe_group_name=$(echo "$group_name" | tr -c '[:alnum:]_-' '-')
149+
branch_name="security/grouped-${safe_group_name}-updates"
150+
git checkout -B "$branch_name"
151+
152+
while read -r number ref group; do
153+
git fetch origin "$ref"
154+
if ! git cherry-pick FETCH_HEAD; then
155+
echo "Conflict found in $ref. Attempting to resolve."
156+
conflict_files=($(git diff --name-only --diff-filter=U))
157+
if [ ${#conflict_files[@]} -gt 0 ]; then
158+
echo "Resolving conflicts in files: ${conflict_files[*]}"
159+
for conflict_file in "${conflict_files[@]}"; do
160+
echo "Resolving conflict in $conflict_file"
161+
git checkout --theirs "$conflict_file"
162+
git add "$conflict_file"
163+
done
164+
git cherry-pick --continue || {
165+
echo "Failed to continue cherry-pick. Aborting."
166+
git cherry-pick --abort
167+
continue 2
168+
}
169+
else
170+
echo "No conflicting files found. Aborting."
171+
git cherry-pick --abort
172+
continue 2
173+
fi
174+
fi
175+
done < "$file"
176+
177+
# Non-destructive push: check for drift before force-pushing
178+
if [ "$DRY_RUN" == "true" ]; then
179+
echo "[DRY-RUN] Skipping git push for $branch_name"
180+
else
181+
remote_hash=$(git ls-remote origin "$branch_name" | awk '{print $1}')
182+
local_hash=$(git rev-parse "$branch_name")
183+
if [ -n "$remote_hash" ] && [ "$remote_hash" != "$local_hash" ]; then
184+
echo "Remote branch $branch_name has diverged. Skipping force-push to avoid overwriting changes."
185+
continue
186+
fi
187+
git push --force-with-lease origin "$branch_name"
188+
fi
189+
190+
new_lines=""
191+
while read -r number ref group; do
192+
IFS="|" read -r title url _ <<< "${pr_metadata_map["$number"]}"
193+
new_lines+="$title - [#$number]($url)\n"
194+
done < "$file"
195+
196+
pr_title="chore(deps): bump grouped $group_name Dependabot updates"
197+
# Add --state open to ensure only open PRs are considered
198+
existing_url=$(gh pr list --head "$branch_name" --base "$TARGET_BRANCH" --state open --json url --jq '.[0].url // empty')
199+
200+
if [ -n "$existing_url" ]; then
201+
echo "PR already exists: $existing_url"
202+
pr_url="$existing_url"
203+
current_body=$(gh pr view "$pr_url" --json body --jq .body)
204+
# Simplified duplicate-detection using Bash array
205+
IFS=$'\n' read -d '' -r -a current_lines < <(printf '%s\0' "$current_body")
206+
IFS=$'\n' read -d '' -r -a new_lines_arr < <(printf '%b\0' "$new_lines")
207+
declare -A seen
208+
for line in "${current_lines[@]}"; do
209+
seen["$line"]=1
210+
done
211+
filtered_lines=""
212+
for line in "${new_lines_arr[@]}"; do
213+
if [[ -n "$line" && -z "${seen["$line"]}" ]]; then
214+
filtered_lines+="$line\n"
215+
fi
216+
done
217+
# Ensure a newline separator between the existing body and new lines
218+
if [ -n "$filtered_lines" ]; then
219+
new_body="$current_body"$'\n'"$filtered_lines"
220+
else
221+
new_body="$current_body"
222+
fi
223+
if [ "$DRY_RUN" == "true" ]; then
224+
echo "[DRY-RUN] Would update PR body for $pr_url"
225+
else
226+
tmpfile=$(mktemp)
227+
printf '%s' "$new_body" > "$tmpfile"
228+
gh pr edit "$pr_url" --body-file "$tmpfile"
229+
rm -f "$tmpfile"
230+
fi
231+
else
232+
pr_body=$(printf "This PR groups multiple open PRs by Dependabot for %s.\n\n%b" "$group_name" "$new_lines")
233+
if [ "$DRY_RUN" == "true" ]; then
234+
echo "[DRY-RUN] Would create PR titled: $pr_title"
235+
echo "$pr_body"
236+
pr_url=""
237+
else
238+
pr_url=$(gh pr create \
239+
--title "$pr_title" \
240+
--body "$pr_body" \
241+
--base "$TARGET_BRANCH" \
242+
--head "$branch_name")
243+
fi
244+
fi
245+
246+
if [ -n "$pr_url" ]; then
247+
for number in $(cut -d ' ' -f1 "$file"); do
248+
IFS="|" read -r _ _ labels <<< "${pr_metadata_map["$number"]}"
249+
IFS="," read -ra label_arr <<< "$labels"
250+
for label in "${label_arr[@]}"; do
251+
if [ "$DRY_RUN" == "true" ]; then
252+
echo "[DRY-RUN] Would add label $label to $pr_url"
253+
else
254+
gh pr edit "$pr_url" --add-label "$label"
255+
fi
256+
done
257+
if [ "$DRY_RUN" == "true" ]; then
258+
echo "[DRY-RUN] Would close PR #$number"
259+
else
260+
gh pr close "$number" --comment "Grouped into $pr_url."
261+
fi
262+
done
263+
echo "Grouped PR created. Leaving branch $branch_name for now."
264+
else
265+
echo "Grouped PR was not created. Skipping closing of original PRs."
266+
fi
267+
done

0 commit comments

Comments
 (0)