-
Notifications
You must be signed in to change notification settings - Fork 31
256 lines (232 loc) · 11.1 KB
/
Copy pathhygiene.yaml
File metadata and controls
256 lines (232 loc) · 11.1 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
256
name: Hygiene
on:
pull_request:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
branches:
- main
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint-pr:
name: Lint PR Title and Changesets
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
PR_TITLE: ${{ github.event.pull_request.title }}
GIT_REF: ${{ github.ref }}
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
steps:
- name: Validate PR title format
shell: bash
run: |
if [[ "${{ github.event_name }}" == "merge_group" || "$GIT_REF" == "refs/heads/main" ]]; then
echo "Skipping PR title validation on $GIT_REF (event: ${{ github.event_name }})."
exit 0
fi
if [[ "$PR_TITLE" =~ ^[a-z0-9_-]+(\([a-zA-Z0-9_-]+\))?!?:\ [a-zA-Z] ]]; then
echo "PR title format is valid."
else
echo "PR title must:"
echo '- Start with a lowercase qualifier (e.g. "chore", "fix", "feat", "mig") - alphanumeric, dashes, and underscores only'
echo "- ... optionally followed by a scope in parentheses - alphanumeric, dashes, and underscores only"
echo "- ... optionally followed by an exclamation mark (!) to indicate a breaking change"
echo "- ... followed by a colon and a space"
echo "- ... followed by a space"
echo "- ... followed by a short description of the change _starting with a lowercase letter_"
echo
echo 'Example: "feat: added user avatars to the dashboard"'
echo 'Example: "chore(deps): updated dependency xyz to v2"'
echo 'Example: "fix(auth)!: changed password hashing algorithm"'
echo
echo "Got: $PR_TITLE"
exit 1
fi
- name: Checkout
uses: useblacksmith/checkout@6fd481652155169ed4d2f25ebaf97464f685175f # v1
# .gitignore does not protect files that are already tracked (or added
# with `git add -f`), which is how build output like the old client/sdk
# esm tree and stray screenshots ended up committed. Fail if any tracked
# file matches a .gitignore pattern.
- name: Check for tracked ignored files
shell: bash
run: |
tracked_ignored=$(git ls-files --cached --ignored --exclude-standard)
if [[ -n "$tracked_ignored" ]]; then
echo "❌ These tracked files match .gitignore patterns:"
echo "$tracked_ignored" | sed 's/^/ - /'
echo ""
echo "Build output, caches, and generated artifacts must not be committed."
echo "Remove them with 'git rm --cached <file>' (add a .gitignore rule if missing),"
echo "or if the file is intentionally tracked, adjust the .gitignore pattern excluding it."
exit 1
fi
echo "✅ No tracked files match .gitignore patterns."
- name: Detect changed files
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: .github/filters.yaml
list-files: json
# Run a second paths-filter pass with `predicate-quantifier: every` so
# the `!` patterns below act as exclusions (every pattern must match,
# which inverts negations into proper exclusions). The default `some`
# quantifier on the step above would treat each `!` as its own positive
# predicate, matching every file outside the negated path.
- name: Detect server-internal changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter-internal
with:
predicate-quantifier: every
list-files: json
# Excludes SQLc-generated output paths (per `server/database/sqlc.yaml`
# `out:` directives) since they legitimately change alongside
# schema.sql edits.
filters: |
server-internal:
- "server/internal/**"
- "!server/internal/database/**"
- "!server/internal/**/repo/**"
- "!server/internal/testenv/testrepo/**"
- name: Block changes to deprecated paths
if: ${{ github.event_name != 'merge_group' && steps.filter.outputs.deprecated-paths == 'true' }}
shell: bash
env:
DEPRECATED_FILES: ${{ steps.filter.outputs.deprecated-paths_files }}
PR_BODY: ${{ github.event.pull_request.body }}
HAS_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'skip-deprecated-path-check') }}
run: |
set -e
if [[ "$PR_BODY" == *"[skip deprecated path check]"* || "$HAS_SKIP_LABEL" == "true" ]]; then
echo "Deprecated path check skipped via explicit directive."
exit 0
fi
echo "❌ This PR touches paths that were moved or retired:"
echo "$DEPRECATED_FILES" | jq -r '.[]' | sed 's/^/ - /'
echo ""
echo "These locations are deprecated (see the deprecated-paths filter in"
echo ".github/filters.yaml for where each one went). Changes here usually mean"
echo "a stale generator target or an old branch resurrecting a moved directory."
echo "Make the change in the path's new home instead."
echo "To explicitly allow this exception, add '[skip deprecated path check]'"
echo "to the PR body or apply the 'skip-deprecated-path-check' label."
exit 1
- name: "Require mig: prefix for migration PRs"
if: ${{ github.event_name != 'merge_group' && steps.filter.outputs.db-migrations == 'true' }}
shell: bash
env:
MIG_FILES: ${{ steps.filter.outputs.db-migrations_files }}
run: |
if [[ "$PR_TITLE" =~ ^mig(\([a-zA-Z0-9_-]+\))?!?:\ [a-zA-Z] ]]; then
echo "PR title is correctly qualified with 'mig:'."
exit 0
fi
echo "❌ This PR includes PostgreSQL or ClickHouse migration changes,"
echo "so its title must be qualified with the 'mig:' type (not 'feat', 'fix', or 'chore')."
echo ""
echo "Migration files changed:"
echo "$MIG_FILES" | jq -r '.[]' | sed 's/^/ - /'
echo ""
echo 'Example: "mig: add supports_dcr column to deployments"'
echo 'Example: "mig(catalog): version registry cache key"'
echo ""
echo "Got: $PR_TITLE"
exit 1
- name: Validate migration PR does not mix server changes
if: ${{ github.event_name != 'merge_group' && steps.filter.outputs.database-changes == 'true' && steps.filter-internal.outputs.server-internal == 'true' }}
shell: bash
env:
DB_FILES: ${{ steps.filter.outputs.database-changes_files }}
INTERNAL_FILES: ${{ steps.filter-internal.outputs.server-internal_files }}
PR_BODY: ${{ github.event.pull_request.body }}
HAS_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'skip-migration-check') }}
run: |
set -e
if [[ "$PR_BODY" == *"[skip migration check]"* || "$HAS_SKIP_LABEL" == "true" ]]; then
echo "Migration PR isolation check skipped via explicit directive."
exit 0
fi
echo "❌ This PR mixes database migration changes with other server changes."
echo ""
echo "Database migration files changed:"
echo "$DB_FILES" | jq -r '.[]' | sed 's/^/ - /'
echo ""
echo "Server implementation files changed:"
echo "$INTERNAL_FILES" | jq -r '.[]' | sed 's/^/ - /'
echo ""
echo "Database schema changes should be scheduled separately from business logic changes"
echo "so each can be reviewed and rolled out independently."
echo "To explicitly allow this exception, add '[skip migration check]'"
echo "to the PR body or apply the 'skip-migration-check' label."
echo "Please move the non-migration changes to a separate PR."
exit 1
- name: Check for significant changes
id: check
run: |
if [[ "${{ github.event_name }}" == "merge_group" || "$GIT_REF" == "refs/heads/main" ]]; then
echo "Skipping changesets-lint job on $GIT_REF (event: ${{ github.event_name }})."
elif [[ "$PR_TITLE" =~ ^(chore|mig)(\([a-zA-Z0-9_-]+\))?: ]]; then
echo "Skipping changesets-lint job for chore/mig PR."
else
echo "lint=true" >> $GITHUB_OUTPUT
fi
# Check out both the base and head refs so changeset status can compare
# against the base branch
- name: Checkout base ref
if: ${{ steps.check.outputs.lint == 'true' }}
run: git fetch origin "$BASE_REF" && git checkout "$BASE_REF"
- name: Checkout head ref
if: ${{ steps.check.outputs.lint == 'true' }}
run: git fetch origin "$HEAD_REF" && git checkout "$HEAD_REF"
- name: Setup Mise
if: ${{ steps.check.outputs.lint == 'true' }}
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
with:
install: true
cache: true
cache_key_prefix: mise-blacksmith-v1
env: false
- name: Prepare GitHub Actions environment
if: ${{ steps.check.outputs.lint == 'true' }}
run: mise run github
- name: Cache aube store
if: ${{ steps.check.outputs.lint == 'true' }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
key: ${{ env.GH_CACHE_AUBE_KEY }}
restore-keys: |
${{ env.GH_CACHE_AUBE_KEY }}
${{ env.GH_CACHE_AUBE_KEY_PARTIAL }}
path: |
${{ env.AUBE_STORE_PATH }}
- name: Install dependencies
if: ${{ steps.check.outputs.lint == 'true' }}
run: aube install --frozen-lockfile
- name: Lint changesets
if: ${{ steps.check.outputs.lint == 'true' }}
shell: bash
run: |
set -e
mkdir -p scratch
aube exec changeset -- status --output scratch/changeset-status.json
cat scratch/changeset-status.json
echo ""
echo ""
# Check if changesets array exists and has length > 0
if ! jq -e '.changesets | length > 0' scratch/changeset-status.json > /dev/null; then
echo "❌ Changeset validation failed!"
echo ""
echo "It looks like you've made significant changes but haven't added a changeset."
echo "Changesets help us track what's changed and generate release notes."
echo ""
echo "To fix this, run one of this command locally:"
echo " aube exec changeset"
echo ""
echo "Then commit the generated .changeset/*.md file with your changes."
exit 1
fi
echo "✅ Changeset validation passed!"
- name: Prune aube store
if: ${{ steps.check.outputs.lint == 'true' }}
run: aube store prune