Skip to content

Commit d11dcd2

Browse files
Add Github Actions to Update Catalog & Verify Zip
@W-21609208 - Added Github Actions to Update Catalog & Verify Zip
2 parents cf388ea + 2d4b37e commit d11dcd2

2 files changed

Lines changed: 379 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: Update Catalog.json w New App Version PR
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- '**/*.zip'
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
update-catalog-pr:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout main
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Detect changed ZIPs, create tags, update catalog.json (in working tree)
24+
shell: bash
25+
env:
26+
BEFORE_SHA: ${{ github.event.before }}
27+
AFTER_SHA: ${{ github.sha }}
28+
run: |
29+
set -euo pipefail
30+
31+
# 1) Find ZIP files changed in this push to main (merge commit)
32+
mapfile -t changed_zips < <(git diff --name-only "$BEFORE_SHA" "$AFTER_SHA" -- '**/*.zip')
33+
34+
if [[ ${#changed_zips[@]} -eq 0 ]]; then
35+
echo "No .zip files changed. Exiting."
36+
exit 0
37+
fi
38+
39+
echo "Changed ZIPs:"
40+
printf ' - %s\n' "${changed_zips[@]}"
41+
42+
git config user.name "github-actions[bot]"
43+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
44+
45+
updated_any=false
46+
47+
for zip_path in "${changed_zips[@]}"; do
48+
# Only act on ZIPs that exist after the merge (skip deletions)
49+
[[ -f "$zip_path" ]] || continue
50+
51+
zip_dir="$(dirname "$zip_path")"
52+
catalog_path="$zip_dir/catalog.json"
53+
manifest_path="$zip_dir/manifest.json"
54+
55+
# 2) If no catalog.json next to this zip, exit (no-op)
56+
if [[ ! -f "$catalog_path" ]]; then
57+
echo "No catalog.json at $catalog_path. Exiting."
58+
exit 0
59+
fi
60+
61+
if [[ ! -f "$manifest_path" ]]; then
62+
echo "::error file=$manifest_path::manifest.json not found next to ZIP ($zip_path)"
63+
exit 1
64+
fi
65+
66+
# Version from manifest.json (expects .version)
67+
version="$(jq -r '.version // empty' "$manifest_path")"
68+
if [[ -z "$version" || "$version" == "null" ]]; then
69+
echo "::error file=$manifest_path::Missing .version in manifest.json"
70+
exit 1
71+
fi
72+
73+
# 3) Tag name = zip filename without .zip
74+
zip_file="$(basename "$zip_path")"
75+
tag_name="${zip_file%.zip}"
76+
77+
# Create + push tag (skip if already exists)
78+
if ! git rev-parse -q --verify "refs/tags/$tag_name" >/dev/null; then
79+
git tag -a "$tag_name" -m "Release $tag_name"
80+
fi
81+
if ! git ls-remote --exit-code --tags origin "refs/tags/$tag_name" >/dev/null 2>&1; then
82+
git push origin "refs/tags/$tag_name"
83+
fi
84+
85+
# 4) Update latest and 5) Append a new versions entry
86+
tmp="$(mktemp)"
87+
jq --arg v "$version" --arg t "$tag_name" '
88+
.versions = (.versions // []) |
89+
.latest = {"version": $v, "tag": $t} |
90+
.versions = (.versions + [{"version": $v, "tag": $t}])
91+
' "$catalog_path" > "$tmp"
92+
93+
mv "$tmp" "$catalog_path"
94+
git add "$catalog_path"
95+
updated_any=true
96+
done
97+
98+
if [[ "$updated_any" != "true" ]]; then
99+
echo "No catalog updates to propose. Exiting."
100+
exit 0
101+
fi
102+
103+
if git diff --cached --quiet; then
104+
echo "No effective catalog changes staged. Exiting."
105+
exit 0
106+
fi
107+
108+
- name: Create PR with catalog.json update
109+
if: ${{ !cancelled() }}
110+
uses: peter-evans/create-pull-request@v6
111+
with:
112+
token: ${{ secrets.GITHUB_TOKEN }}
113+
branch: CI/update-catalog-${{ github.run_id }}
114+
delete-branch: true
115+
commit-message: "CI: update catalog.json after ZIP merge"
116+
title: "CI: update catalog.json after ZIP merge"
117+
body: |
118+
This PR was auto-generated after a ZIP file was merged into `main`.
119+
120+
- Creates a tag matching the ZIP filename (without `.zip`)
121+
- Updates `catalog.json` `latest`
122+
- Appends a new entry to `catalog.json` `versions`
123+
labels: |
124+
automation

.github/workflows/verify-zip.yml

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
name: Verify CAP Checksum & Structure
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize, edited, ready_for_review]
6+
paths:
7+
- '**/*.zip'
8+
9+
jobs:
10+
verify-zips:
11+
runs-on: ubuntu-latest
12+
env:
13+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
14+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
15+
16+
steps:
17+
- name: Checkout PR HEAD
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Step 1 - Verify sha256 in manifest.json for changed ZIPs
23+
shell: bash
24+
run: |
25+
set -euo pipefail
26+
27+
# Collect changed ZIPs into a file that the next step can reuse
28+
: > changed_zips.txt
29+
30+
while IFS= read -r -d '' f; do
31+
[[ "$f" == *.zip ]] && printf '%s\n' "$f" >> changed_zips.txt
32+
done < <(git diff --name-only -z "$BASE_SHA" "$HEAD_SHA")
33+
34+
if [[ ! -s changed_zips.txt ]]; then
35+
echo "No .zip files changed in this PR. Nothing to verify."
36+
exit 0
37+
fi
38+
39+
echo "Changed ZIP files:"
40+
sed 's/^/ - /' changed_zips.txt
41+
42+
while IFS= read -r zip_path; do
43+
# If deleted in PR head, skip
44+
if [[ ! -f "$zip_path" ]]; then
45+
echo "Skipping (not present in PR head): $zip_path"
46+
continue
47+
fi
48+
49+
dir="$(dirname "$zip_path")"
50+
manifest_path="$dir/manifest.json"
51+
52+
if [[ ! -f "$manifest_path" ]]; then
53+
echo "::error file=$manifest_path::manifest.json not found next to ZIP ($zip_path)"
54+
exit 1
55+
fi
56+
57+
# Compute checksum of the ZIP
58+
computed="$(sha256sum "$zip_path" | awk '{print $1}' | tr '[:upper:]' '[:lower:]')"
59+
60+
# Read sha256 from manifest.json
61+
manifest_sha="$(jq -r '.sha256 // empty' "$manifest_path" | tr '[:upper:]' '[:lower:]')"
62+
63+
if [[ -z "$manifest_sha" || "$manifest_sha" == "null" ]]; then
64+
echo "::error file=$manifest_path::Missing or empty \"sha256\" field in manifest.json"
65+
exit 1
66+
fi
67+
68+
echo "ZIP: $zip_path"
69+
echo "Computed: $computed"
70+
echo "Manifest: $manifest_sha"
71+
72+
if [[ "$computed" != "$manifest_sha" ]]; then
73+
echo "::error file=$manifest_path::sha256 mismatch for $zip_path (computed=$computed, manifest=$manifest_sha)"
74+
exit 1
75+
fi
76+
done < changed_zips.txt
77+
78+
- name: Step 2 - Verify manifest zip matches file + manifest version is unique
79+
shell: bash
80+
run: |
81+
set -euo pipefail
82+
83+
[[ -s changed_zips.txt ]] || exit 0
84+
85+
while IFS= read -r zip_path; do
86+
[[ -f "$zip_path" ]] || continue
87+
88+
dir="$(dirname "$zip_path")"
89+
manifest_path="$dir/manifest.json"
90+
catalog_path="$dir/catalog.json"
91+
92+
if [[ ! -f "$catalog_path" ]]; then
93+
echo "::error file=$catalog_path::catalog.json not found next to ZIP ($zip_path)"
94+
exit 1
95+
fi
96+
97+
if [[ ! -f "$manifest_path" ]]; then
98+
echo "::error file=$manifest_path::manifest.json not found next to ZIP ($zip_path)"
99+
exit 1
100+
fi
101+
102+
zip_file="$(basename "$zip_path")"
103+
manifest_zip="$(jq -r '.zip // empty' "$manifest_path")"
104+
if [[ -z "$manifest_zip" || "$manifest_zip" == "null" ]]; then
105+
echo "::error file=$manifest_path::Missing \"zip\" field in manifest.json"
106+
exit 1
107+
fi
108+
109+
version="$(jq -r '.version // empty' "$manifest_path")"
110+
if [[ -z "$version" || "$version" == "null" ]]; then
111+
echo "::error file=$manifest_path::Missing \"version\" field in manifest.json"
112+
exit 1
113+
fi
114+
115+
# Verify zip field in manifest matches file name
116+
if [[ "$manifest_zip" != "$zip_file" ]]; then
117+
echo "::error file=$manifest_path::manifest.json \"zip\" ($manifest_zip) does not match changed zip filename ($zip_file)"
118+
exit 1
119+
fi
120+
121+
# Verify new version in manifest is unique
122+
if jq -e --arg v "$version" '.versions[]? | select(.version == $v)' "$catalog_path" >/dev/null; then
123+
echo "::error file=$catalog_path::Version $version already exists in catalog.json"
124+
exit 1
125+
fi
126+
done < changed_zips.txt
127+
128+
- name: Step 3 - Unzip and verify top level folder structure
129+
shell: bash
130+
run: |
131+
set -euo pipefail
132+
133+
# List of allowed top level folder names
134+
allowed=("impex" "app-configuration" "storefront-next" "cartridges")
135+
136+
[[ -s changed_zips.txt ]] || exit 0
137+
138+
while IFS= read -r zip_path; do
139+
[[ -f "$zip_path" ]] || continue
140+
141+
# Unzip file to temp dir
142+
tmpdir="$(mktemp -d)"
143+
unzip -q "$zip_path" -d "$tmpdir"
144+
145+
# Root should be exactly one directory (the wrapper folder)
146+
mapfile -t roots < <(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | grep -v '^__MACOSX$' | sort -u)
147+
if [[ ${#roots[@]} -ne 1 ]]; then
148+
echo "::error file=$zip_path::Expected exactly 1 root directory after unzip, found ${#roots[@]}: ${roots[*]}"
149+
rm -rf "$tmpdir"
150+
exit 1
151+
fi
152+
root="$tmpdir/${roots[0]}"
153+
154+
# Check immediate child directories of root are allowed
155+
mapfile -t children < <(find "$root" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | grep -v '^__MACOSX$' | sort -u)
156+
for c in "${children[@]}"; do
157+
ok=false
158+
for a in "${allowed[@]}"; do
159+
[[ "$c" == "$a" ]] && ok=true && break
160+
done
161+
if [[ "$ok" == "false" ]]; then
162+
echo "::error file=$zip_path::Disallowed directory under root: \"$c\". Allowed: ${allowed[*]}"
163+
rm -rf "$tmpdir"
164+
exit 1
165+
fi
166+
done
167+
rm -rf "$tmpdir"
168+
done < changed_zips.txt
169+
170+
- name: Step 4 - [Tax] Verify hooks exist and scripts resolve
171+
shell: bash
172+
run: |
173+
set -euo pipefail
174+
175+
required_hooks=(
176+
"dw.apps.checkout.tax.calculate"
177+
"dw.apps.checkout.tax.commit"
178+
"dw.apps.checkout.tax.cancel"
179+
)
180+
181+
[[ -s changed_zips.txt ]] || exit 0
182+
183+
is_tax_app=false
184+
185+
while IFS= read -r zip_path; do
186+
[[ -f "$zip_path" ]] || continue
187+
188+
# Only run for ZIPs under tax/ at repo root
189+
case "$zip_path" in
190+
tax/*) is_tax_app=true ;;
191+
*) continue ;;
192+
esac
193+
194+
tmpdir="$(mktemp -d)"
195+
trap 'rm -rf "$tmpdir"' RETURN
196+
197+
unzip -q "$zip_path" -d "$tmpdir"
198+
199+
# Find cartridges/site_cartridges anywhere under the unzip root
200+
base="$(find "$tmpdir" -type d -path '*/cartridges/site_cartridges' -print -quit)"
201+
if [[ -z "$base" || ! -d "$base" ]]; then
202+
echo "::error file=$zip_path::Missing directory cartridges/site_cartridges in ZIP"
203+
exit 1
204+
fi
205+
206+
hooks_file="$(find "$base" -type f -name hooks.json -print -quit)"
207+
if [[ -z "$hooks_file" ]]; then
208+
echo "::error file=$zip_path::hooks.json not found under cartridges/site_cartridges"
209+
exit 1
210+
fi
211+
212+
hooks_dir="$(dirname "$hooks_file")"
213+
echo "ZIP: $zip_path"
214+
echo "hooks.json: $hooks_file"
215+
216+
# Ensure there are no non-required hook names
217+
while IFS= read -r name; do
218+
ok=false
219+
for hook in "${required_hooks[@]}"; do
220+
[[ "$name" == "$hook" ]] && ok=true && break
221+
done
222+
if [[ "$ok" == "false" ]]; then
223+
echo "::error file=$hooks_file::Disallowed hook \"$name\". Only allowed: ${required_hooks[*]}"
224+
exit 1
225+
fi
226+
done < <(jq -r '.hooks[]?.name // empty' "$hooks_file")
227+
228+
# Validate required hooks are present, and each has a script that exists
229+
for hook in "${required_hooks[@]}"; do
230+
script="$(jq -r --arg name "$hook" '.hooks[]? | select(.name == $name) | .script // empty' "$hooks_file" | head -n 1)"
231+
232+
if [[ -z "$script" || "$script" == "null" ]]; then
233+
echo "::error file=$hooks_file::Missing hook or script for \"$hook\""
234+
exit 1
235+
fi
236+
237+
# script is relative to hooks.json location (strip leading ./)
238+
rel="${script#./}"
239+
target="$hooks_dir/$rel"
240+
241+
if [[ ! -f "$target" ]]; then
242+
echo "::error file=$hooks_file::Script for \"$hook\" points to missing file: $script (resolved: $target)"
243+
exit 1
244+
fi
245+
done
246+
247+
echo "SUCCESS - required tax hooks and scripts verified for $zip_path"
248+
249+
rm -rf "$tmpdir"
250+
trap - RETURN
251+
done < changed_zips.txt
252+
253+
if [[ "$is_tax_app" == "false" ]]; then
254+
echo "No changed tax apps. Skipping Step 3."
255+
fi

0 commit comments

Comments
 (0)