Skip to content

Commit 12821d0

Browse files
authored
Merge pull request #13932 from MicrosoftDocs/workflows-test
Add GUID workflows, update protected files list
2 parents 38571a3 + ba216f9 commit 12821d0

3 files changed

Lines changed: 288 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: GUID assign
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
PayloadJson:
7+
required: true
8+
type: string
9+
secrets:
10+
AccessToken:
11+
required: true
12+
13+
jobs:
14+
assign-guids:
15+
if: github.repository_owner == 'MicrosoftDocs'
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout main
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 2
22+
token: ${{ secrets.AccessToken }}
23+
24+
- name: Assign GUIDs to new markdown files
25+
run: |
26+
# Find files added or modified in the merge commit
27+
NEW_FILES=$(git diff --name-only --diff-filter=AM HEAD~1...HEAD -- '*.md' '**/*.md')
28+
29+
if [ -z "$NEW_FILES" ]; then
30+
echo "No new articles in this push."
31+
echo "guid_changed=0" >> "$GITHUB_ENV"
32+
exit 0
33+
fi
34+
35+
CHANGED=0
36+
while IFS= read -r file; do
37+
# Skip files without frontmatter
38+
if ! head -1 "$file" | sed 's/^\xEF\xBB\xBF//' | tr -d '\r' | grep -q '^---$'; then
39+
echo "Skipping $file (no frontmatter)"
40+
continue
41+
fi
42+
43+
# Skip includes directory
44+
if echo "$file" | grep -q '/includes/'; then
45+
echo "Skipping $file (includes directory)"
46+
continue
47+
fi
48+
49+
# Extract awa-articleGuid line from frontmatter
50+
GUID_LINE=$(sed '1s/^\xEF\xBB\xBF//' "$file" | tr -d '\r' | sed -n '2,/^---$/p' | grep -i '^awa-articleGuid:' || true)
51+
52+
if [ -z "$GUID_LINE" ]; then
53+
# No awa-articleGuid field — insert one before the closing ---
54+
NEW_UUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
55+
CLOSE_LINE=$(sed '1s/^\xEF\xBB\xBF//' "$file" | tr -d '\r' | awk '/^---$/{count++; if(count==2){print NR; exit}}')
56+
sed -i "${CLOSE_LINE}i\\awa-articleGuid: ${NEW_UUID}" "$file"
57+
echo "Assigned GUID $NEW_UUID to $file (field was missing)"
58+
git add -- "$file"
59+
CHANGED=1
60+
else
61+
# Field exists — check if value is empty
62+
GUID_VALUE=$(echo "$GUID_LINE" | sed 's/^[Aa][Ww][Aa]-[Aa][Rr][Tt][Ii][Cc][Ll][Ee][Gg][Uu][Ii][Dd]:[[:space:]]*//' | tr -d '[:space:]')
63+
if [ -z "$GUID_VALUE" ]; then
64+
NEW_UUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
65+
sed -i "s/^[Aa][Ww][Aa]-[Aa][Rr][Tt][Ii][Cc][Ll][Ee][Gg][Uu][Ii][Dd]:.*/awa-articleGuid: ${NEW_UUID}/" "$file"
66+
echo "Assigned GUID $NEW_UUID to $file (field was empty)"
67+
git add -- "$file"
68+
CHANGED=1
69+
else
70+
echo "File $file already has GUID: $GUID_VALUE"
71+
fi
72+
fi
73+
done <<< "$NEW_FILES"
74+
75+
echo "guid_changed=$CHANGED" >> "$GITHUB_ENV"
76+
77+
- name: Commit GUID assignments
78+
if: env.guid_changed == '1'
79+
run: |
80+
git config user.name "github-actions[bot]"
81+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
82+
git commit -m "Auto-assign awa-articleGuid to new articles"
83+
git push
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
name: GUIDs in PR are unique
2+
3+
permissions:
4+
pull-requests: write
5+
contents: read
6+
7+
on:
8+
workflow_call:
9+
secrets:
10+
AccessToken:
11+
required: true
12+
13+
jobs:
14+
check-duplicate-guids:
15+
if: github.repository_owner == 'MicrosoftDocs'
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
token: ${{ secrets.AccessToken }}
23+
24+
- name: Extract GUIDs from PR files
25+
id: extract
26+
run: |
27+
LC_ALL=C
28+
export LC_ALL
29+
30+
# Get markdown files added or modified in this PR
31+
CHANGED_FILES=$(git diff --name-only --diff-filter=AM origin/${{ github.base_ref }}...HEAD -- '*.md' '**/*.md' | grep -v '/includes/' || true)
32+
33+
if [ -z "$CHANGED_FILES" ]; then
34+
echo "No markdown files changed in this PR."
35+
echo "has_guids=false" >> "$GITHUB_OUTPUT"
36+
exit 0
37+
fi
38+
39+
echo "Changed files:"
40+
echo "$CHANGED_FILES"
41+
echo ""
42+
43+
# Extract GUIDs from changed files into a pattern file
44+
PATTERN_FILE=$(mktemp)
45+
echo "pattern_file=$PATTERN_FILE" >> "$GITHUB_OUTPUT"
46+
47+
while IFS= read -r file; do
48+
[ -f "$file" ] || continue
49+
GUID=$(sed '1s/^\xEF\xBB\xBF//' "$file" | tr -d '\r' | sed -n '2,/^---$/p' | grep -i '^awa-articleGuid:' | sed 's/^[^:]*:\s*//' | tr -d '[:space:]')
50+
if [ -n "$GUID" ]; then
51+
echo "$GUID" >> "$PATTERN_FILE"
52+
echo "Found GUID $GUID in $file"
53+
else
54+
echo "No GUID in $file (will be assigned on merge)"
55+
fi
56+
done <<< "$CHANGED_FILES"
57+
58+
if [ ! -s "$PATTERN_FILE" ]; then
59+
echo ""
60+
echo "No GUIDs to check."
61+
echo "has_guids=false" >> "$GITHUB_OUTPUT"
62+
exit 0
63+
fi
64+
65+
echo "has_guids=true" >> "$GITHUB_OUTPUT"
66+
67+
- name: Check for duplicates across repo
68+
id: check
69+
if: steps.extract.outputs.has_guids == 'true'
70+
run: |
71+
LC_ALL=C
72+
export LC_ALL
73+
74+
PATTERN_FILE="${{ steps.extract.outputs.pattern_file }}"
75+
76+
DUPES=$(find . -name '*.md' -not -path '*/includes/*' -print0 | \
77+
xargs -0 awk '
78+
BEGIN {
79+
while ((getline g < "'"$PATTERN_FILE"'") > 0) {
80+
gsub(/[[:space:]]/, "", g)
81+
if (length(g) > 0) guids[tolower(g)] = 1
82+
}
83+
}
84+
FNR > 50 { nextfile }
85+
tolower($0) ~ /awa-articleguid:/ {
86+
val = $0
87+
sub(/^[^:]*:[[:space:]]*/, "", val)
88+
gsub(/[[:space:]]/, "", val)
89+
val = tolower(val)
90+
if (val in guids) {
91+
count[val]++
92+
files[val] = files[val] FILENAME "|"
93+
}
94+
nextfile
95+
}
96+
END {
97+
for (g in count) {
98+
if (count[g] > 1) {
99+
print g "\t" files[g]
100+
}
101+
}
102+
}
103+
' || true)
104+
105+
rm "$PATTERN_FILE"
106+
107+
if [ -n "$DUPES" ]; then
108+
echo "$DUPES" > /tmp/dupes.txt
109+
echo "has_dupes=true" >> "$GITHUB_OUTPUT"
110+
echo "::error::Duplicate awa-articleGuid values found in this PR!"
111+
exit 1
112+
fi
113+
114+
echo "All GUIDs in this PR are unique across the repository."
115+
116+
- name: Comment on PR
117+
if: always() && steps.check.outputs.has_dupes == 'true'
118+
uses: actions/github-script@v7
119+
with:
120+
github-token: ${{ secrets.AccessToken }}
121+
script: |
122+
const fs = require('fs');
123+
const crypto = require('crypto');
124+
const { execSync } = require('child_process');
125+
126+
const dupes = fs.readFileSync('/tmp/dupes.txt', 'utf8').trim();
127+
const baseRef = process.env.GITHUB_BASE_REF || 'main';
128+
129+
// Check if a file had this GUID on the base branch
130+
function hadGuidOnBase(file, guid) {
131+
try {
132+
const content = execSync(`git show "origin/${baseRef}:${file}" 2>/dev/null`, { encoding: 'utf8' });
133+
const match = content.match(/awa-articleGuid:\s*(\S+)/i);
134+
return match && match[1].toLowerCase() === guid.toLowerCase();
135+
} catch {
136+
return false;
137+
}
138+
}
139+
140+
let body = '## One or more duplicate GUIDs were found\n\n';
141+
body += 'Update the `awa-articleGuid` field in each file in the **Action required** table with the provided GUID.\n\n';
142+
143+
for (const line of dupes.split('\n')) {
144+
const [guid, fileStr] = line.split('\t');
145+
const allFiles = fileStr.split('|').filter(Boolean).map(f => f.replace(/^\.\//, ''));
146+
147+
const keepFiles = allFiles.filter(f => hadGuidOnBase(f, guid));
148+
const changeFiles = allFiles.filter(f => !hadGuidOnBase(f, guid));
149+
150+
// If all files are new to this GUID, keep the first one and change the rest
151+
if (keepFiles.length === 0 && changeFiles.length > 1) {
152+
keepFiles.push(changeFiles.shift());
153+
}
154+
155+
body += `**Duplicate GUID:** \`${guid}\`\n\n`;
156+
157+
if (changeFiles.length > 0) {
158+
body += 'Action required:\n';
159+
body += '| File path | Update to | New GUID |\n| --- | :---: | --- |\n';
160+
for (const f of changeFiles) {
161+
const newGuid = crypto.randomUUID();
162+
body += `| \`${f}\` | \u2192 | \`${newGuid}\` |\n`;
163+
}
164+
body += '\n';
165+
}
166+
167+
if (keepFiles.length > 0) {
168+
body += 'Do not change:\n';
169+
body += '| File path | Current GUID |\n| --- | --- |\n';
170+
for (const f of keepFiles) {
171+
body += `| \`${f}\` | \`${guid}\` |\n`;
172+
}
173+
body += '\n';
174+
}
175+
}
176+
177+
// Delete previous duplicate GUID comments
178+
const comments = await github.rest.issues.listComments({
179+
owner: context.repo.owner,
180+
repo: context.repo.repo,
181+
issue_number: context.issue.number
182+
});
183+
184+
for (const c of comments.data) {
185+
if (c.body.startsWith('## One or more duplicate GUIDs were found')) {
186+
await github.rest.issues.deleteComment({
187+
owner: context.repo.owner,
188+
repo: context.repo.repo,
189+
comment_id: c.id
190+
});
191+
}
192+
}
193+
194+
await github.rest.issues.createComment({
195+
owner: context.repo.owner,
196+
repo: context.repo.repo,
197+
issue_number: context.issue.number,
198+
body: body
199+
});

.github/workflows/Shared-ProtectedFiles.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ jobs:
3535
"ThirdPartyNotices",
3636
".acrolinx-config.edn",
3737
".gitattributes",
38+
"AutoIssueAssign.yml",
3839
"AutoLabelAssign.yml",
3940
"AutoLabelMsftContributor.yml",
4041
"AutoPublish.yml",
4142
"BackgroundTasks.yml",
4243
"BuildValidation.yml",
44+
"GuidAssign.yml",
45+
"GuidCheck.yml",
4346
"LiveMergeCheck.yml",
4447
"M365Endpoints.yml",
4548
"PrFileCount.yml",
@@ -48,11 +51,14 @@ jobs:
4851
"StaleBranch.yml",
4952
"TierManagement.yml",
5053
"workflow-status-report.yml",
54+
"Shared-AutoIssueAssign.yml"
5155
"Shared-AutoLabelAssign.yml",
5256
"Shared-AutoLabelMsftContributor.yml",
5357
"Shared-AutoPublish.yml",
5458
"Shared-BuildValidation.yml",
5559
"Shared-ExtractPayload.yml",
60+
"Shared-GuidAssign.yml",
61+
"Shared-GuidCheck.yml",
5662
"Shared-LiveMergeCheck.yml",
5763
"Shared-PrFileCount.yml",
5864
"Shared-ProtectedFiles.yml",

0 commit comments

Comments
 (0)