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