declapract-typescript-ehmpathy 0.46.14 → 0.47.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/practices/cicd-common/bad-practices/has-changelog-file/changelog.md.declapract.ts +11 -0
- package/dist/practices/cicd-common/best-practice/.github/actions/please-release/action.yml +421 -0
- package/dist/practices/cicd-common/best-practice/.github/workflows/release.yml +10 -57
- package/dist/practices/conventional-commits/best-practice/commitlint.config.js +17 -1
- package/dist/practices/rhachet/best-practice/package.json +2 -2
- package/package.json +3 -3
package/dist/practices/cicd-common/bad-practices/has-changelog-file/changelog.md.declapract.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* .what = flag repos that have changelog.md
|
|
5
|
+
* .why = our new please-release action embeds changelog in PR description, no file needed
|
|
6
|
+
*/
|
|
7
|
+
export const check = FileCheckType.EXISTS;
|
|
8
|
+
|
|
9
|
+
export const fix: FileFixFunction = () => {
|
|
10
|
+
return { contents: null }; // delete the file
|
|
11
|
+
};
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
name: please-release
|
|
2
|
+
description: upsert release pr or cutta release tag
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
github-token:
|
|
6
|
+
description: token for gh cli calls
|
|
7
|
+
required: true
|
|
8
|
+
commit-message:
|
|
9
|
+
description: the head commit message
|
|
10
|
+
required: true
|
|
11
|
+
commit-sha:
|
|
12
|
+
description: the head commit sha
|
|
13
|
+
required: true
|
|
14
|
+
repository:
|
|
15
|
+
description: the repository (owner/repo)
|
|
16
|
+
required: true
|
|
17
|
+
|
|
18
|
+
outputs:
|
|
19
|
+
action:
|
|
20
|
+
description: what action was taken (noop, created, updated, released)
|
|
21
|
+
value: ${{ steps.result.outputs.action }}
|
|
22
|
+
version:
|
|
23
|
+
description: the computed or released version
|
|
24
|
+
value: ${{ steps.result.outputs.version }}
|
|
25
|
+
pr-number:
|
|
26
|
+
description: the release pr number
|
|
27
|
+
value: ${{ steps.result.outputs.pr-number }}
|
|
28
|
+
|
|
29
|
+
runs:
|
|
30
|
+
using: "composite"
|
|
31
|
+
steps:
|
|
32
|
+
# step 1: cutta tag, if release commit
|
|
33
|
+
- name: cutta tag, if release commit
|
|
34
|
+
id: cutta
|
|
35
|
+
shell: bash
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ inputs.github-token }}
|
|
38
|
+
run: |
|
|
39
|
+
COMMIT_MSG="${{ inputs.commit-message }}"
|
|
40
|
+
REPO_URL="https://github.com/${{ inputs.repository }}"
|
|
41
|
+
|
|
42
|
+
# check if this is a release commit
|
|
43
|
+
if [[ "$COMMIT_MSG" != chore\(release\):* ]]; then
|
|
44
|
+
echo "🔭 cutta tag"
|
|
45
|
+
echo " └── skipped (not a release commit)"
|
|
46
|
+
echo "did-cutta=false" >> $GITHUB_OUTPUT
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
echo "🔭 cutta tag"
|
|
51
|
+
|
|
52
|
+
# extract version from commit message
|
|
53
|
+
VERSION=$(echo "$COMMIT_MSG" | sed -n 's/^chore(release): \(v[0-9.]*\).*/\1/p')
|
|
54
|
+
|
|
55
|
+
# failfast if version not found
|
|
56
|
+
if [ -z "$VERSION" ]; then
|
|
57
|
+
echo " └── ⛈️ could not extract version from: $COMMIT_MSG"
|
|
58
|
+
echo "::error::could not extract version from commit message: $COMMIT_MSG"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# find the release pr that was merged (by head branch, not search - avoids indexing delay)
|
|
63
|
+
PR_NUMBER=$(gh pr list --state merged --head "release/${VERSION}" --json number --jq '.[0].number')
|
|
64
|
+
|
|
65
|
+
# failfast if release pr not found
|
|
66
|
+
if [ -z "$PR_NUMBER" ]; then
|
|
67
|
+
echo " └── ⛈️ could not find merged release pr for: $VERSION"
|
|
68
|
+
echo "::error::could not find merged release pr for version: $VERSION"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo " ├── version: $VERSION"
|
|
73
|
+
echo " ├── release-pr: #$PR_NUMBER"
|
|
74
|
+
|
|
75
|
+
# create and push tag
|
|
76
|
+
git tag "$VERSION"
|
|
77
|
+
git push origin "$VERSION"
|
|
78
|
+
echo " ├── ✨ tag pushed"
|
|
79
|
+
|
|
80
|
+
# create github release
|
|
81
|
+
RELEASE_URL=$(gh release create "$VERSION" \
|
|
82
|
+
--title "$VERSION" \
|
|
83
|
+
--generate-notes \
|
|
84
|
+
--latest)
|
|
85
|
+
echo " └── ✨ release created: $RELEASE_URL"
|
|
86
|
+
|
|
87
|
+
# comment on the release pr
|
|
88
|
+
gh pr comment "$PR_NUMBER" --body "🐢 released at ${RELEASE_URL}"
|
|
89
|
+
|
|
90
|
+
# extract pr links from the release pr body and comment on each
|
|
91
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body')
|
|
92
|
+
REFERENCED_PRS=$(echo "$PR_BODY" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+' | sort -u || true)
|
|
93
|
+
|
|
94
|
+
for REF_PR in $REFERENCED_PRS; do
|
|
95
|
+
gh pr comment "$REF_PR" --body "🐢 released at ${RELEASE_URL}" 2>/dev/null || true
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
echo "🌊 cutta tag complete"
|
|
99
|
+
echo "did-cutta=true" >> $GITHUB_OUTPUT
|
|
100
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
101
|
+
echo "action=released" >> $GITHUB_OUTPUT
|
|
102
|
+
|
|
103
|
+
# step 2: crunch future semver, if other commit
|
|
104
|
+
#
|
|
105
|
+
# version resolution strategy:
|
|
106
|
+
# 1. if latest-pkg-version > latest-tag-version: user has manually bumped, use package.json version as-is
|
|
107
|
+
# 2. otherwise: auto-compute next version from max(latest-pkg-version, latest-tag-version) based on conventional commits
|
|
108
|
+
#
|
|
109
|
+
# this lets users manually control versions when needed (e.g., major releases, pre-releases)
|
|
110
|
+
# while still providing automatic semver bumping for typical workflows
|
|
111
|
+
#
|
|
112
|
+
- name: crunch future semver, if other commit
|
|
113
|
+
id: semver
|
|
114
|
+
if: ${{ steps.cutta.outputs.did-cutta == 'false' }}
|
|
115
|
+
shell: bash
|
|
116
|
+
run: |
|
|
117
|
+
echo "🔭 crunch semver"
|
|
118
|
+
|
|
119
|
+
# helper: compare semver (returns 0 if $1 > $2, 1 otherwise)
|
|
120
|
+
version_gt() {
|
|
121
|
+
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -n1)" = "$1" ] && [ "$1" != "$2" ]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# get versions from all sources
|
|
125
|
+
LATEST_PACKAGE_VERSION=$(jq -r '.version' package.json)
|
|
126
|
+
LATEST_GITTAG_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0")
|
|
127
|
+
|
|
128
|
+
# find last release commit (shared with changelog step via output)
|
|
129
|
+
LAST_RELEASE_SHA=$(git log --pretty=format:"%H" --grep="^chore(release):" -n 1 || true)
|
|
130
|
+
LAST_RELEASE_MSG=$(git log --pretty=format:"%s" --grep="^chore(release):" -n 1 2>/dev/null || true)
|
|
131
|
+
LATEST_RELEASE_VERSION=$(echo "$LAST_RELEASE_MSG" | sed -n 's/^chore(release): v\([0-9.]*\).*/\1/p')
|
|
132
|
+
|
|
133
|
+
echo "last-release-sha=$LAST_RELEASE_SHA" >> $GITHUB_OUTPUT
|
|
134
|
+
echo " ├── package-version: $LATEST_PACKAGE_VERSION"
|
|
135
|
+
echo " ├── gittag-version: $LATEST_GITTAG_VERSION"
|
|
136
|
+
echo " ├── release-version: ${LATEST_RELEASE_VERSION:-"(none)"}"
|
|
137
|
+
echo " ├── release-sha: ${LAST_RELEASE_SHA:0:7}${LAST_RELEASE_SHA:+...}"
|
|
138
|
+
|
|
139
|
+
# case 1: package.json > release-version means user manually bumped AFTER last release
|
|
140
|
+
# (if package == release-version, that was set by automation, not user)
|
|
141
|
+
if [ -n "$LATEST_RELEASE_VERSION" ] && version_gt "$LATEST_PACKAGE_VERSION" "$LATEST_RELEASE_VERSION"; then
|
|
142
|
+
echo " ├── ✨ pkg > release: user manually bumped"
|
|
143
|
+
echo " └── 🌊 next-version: v${LATEST_PACKAGE_VERSION}"
|
|
144
|
+
echo "next-version=v${LATEST_PACKAGE_VERSION}" >> $GITHUB_OUTPUT
|
|
145
|
+
echo "current-tag=v${LATEST_GITTAG_VERSION}" >> $GITHUB_OUTPUT
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# case 2: auto-compute next version from the greater of tag vs package.json
|
|
150
|
+
# (handles edge cases like tag existing but package.json being behind)
|
|
151
|
+
if version_gt "$LATEST_GITTAG_VERSION" "$LATEST_PACKAGE_VERSION"; then
|
|
152
|
+
BASE_VERSION="$LATEST_GITTAG_VERSION"
|
|
153
|
+
echo " ├── base: gittag ($LATEST_GITTAG_VERSION)"
|
|
154
|
+
else
|
|
155
|
+
BASE_VERSION="$LATEST_PACKAGE_VERSION"
|
|
156
|
+
echo " ├── base: package ($LATEST_PACKAGE_VERSION)"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# handle first release
|
|
160
|
+
if [ "$BASE_VERSION" = "0.0.0" ]; then
|
|
161
|
+
echo " ├── ✨ first release"
|
|
162
|
+
echo " └── 🌊 next-version: v0.1.0"
|
|
163
|
+
echo "next-version=v0.1.0" >> $GITHUB_OUTPUT
|
|
164
|
+
echo "current-tag=v0.0.0" >> $GITHUB_OUTPUT
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# get commits since last release (reuse LAST_RELEASE_SHA computed above)
|
|
169
|
+
if [ -n "$LAST_RELEASE_SHA" ]; then
|
|
170
|
+
COMMITS=$(git log ${LAST_RELEASE_SHA}..HEAD --pretty=format:"%s")
|
|
171
|
+
else
|
|
172
|
+
COMMITS=$(git log --pretty=format:"%s")
|
|
173
|
+
fi
|
|
174
|
+
COMMIT_COUNT=$(echo "$COMMITS" | grep -c . || echo "0")
|
|
175
|
+
echo " ├── commits: $COMMIT_COUNT"
|
|
176
|
+
|
|
177
|
+
# determine bump type
|
|
178
|
+
BUMP="patch" # default
|
|
179
|
+
|
|
180
|
+
if echo "$COMMITS" | grep -qE "^break(\(.+\))?:"; then
|
|
181
|
+
BUMP="major"
|
|
182
|
+
elif echo "$COMMITS" | grep -qE "^feat(\(.+\))?:"; then
|
|
183
|
+
BUMP="minor"
|
|
184
|
+
fi
|
|
185
|
+
echo " ├── bump: $BUMP"
|
|
186
|
+
|
|
187
|
+
# compute next version
|
|
188
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
|
|
189
|
+
|
|
190
|
+
case $BUMP in
|
|
191
|
+
major) NEXT_VERSION="v$((MAJOR + 1)).0.0" ;;
|
|
192
|
+
minor) NEXT_VERSION="v${MAJOR}.$((MINOR + 1)).0" ;;
|
|
193
|
+
patch) NEXT_VERSION="v${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
|
|
194
|
+
esac
|
|
195
|
+
|
|
196
|
+
echo " └── 🌊 next-version: $NEXT_VERSION"
|
|
197
|
+
echo "next-version=$NEXT_VERSION" >> $GITHUB_OUTPUT
|
|
198
|
+
echo "current-tag=v${BASE_VERSION}" >> $GITHUB_OUTPUT
|
|
199
|
+
|
|
200
|
+
# step 3: crunch future changelog, if other commit
|
|
201
|
+
- name: crunch future changelog, if other commit
|
|
202
|
+
id: changelog
|
|
203
|
+
if: ${{ steps.cutta.outputs.did-cutta == 'false' }}
|
|
204
|
+
shell: bash
|
|
205
|
+
run: |
|
|
206
|
+
echo "🔭 crunch changelog"
|
|
207
|
+
REPO_URL="https://github.com/${{ inputs.repository }}"
|
|
208
|
+
PREV_TAG="${{ steps.semver.outputs.current-tag }}"
|
|
209
|
+
NEXT_TAG="${{ steps.semver.outputs.next-version }}"
|
|
210
|
+
LAST_RELEASE_SHA="${{ steps.semver.outputs.last-release-sha }}"
|
|
211
|
+
TODAY=$(date +%Y-%m-%d)
|
|
212
|
+
|
|
213
|
+
# get commits since last release (reuse SHA from semver step)
|
|
214
|
+
if [ -n "$LAST_RELEASE_SHA" ]; then
|
|
215
|
+
COMMITS_SINCE_LAST_RELEASE=$(git log ${LAST_RELEASE_SHA}..HEAD --pretty=format:"%H %cs %s")
|
|
216
|
+
else
|
|
217
|
+
COMMITS_SINCE_LAST_RELEASE=$(git log --pretty=format:"%H %cs %s")
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# group commits by type (hash, date, subject)
|
|
221
|
+
FEATS=$(echo "$COMMITS_SINCE_LAST_RELEASE" | grep -E "^[a-f0-9]+ [0-9-]+ feat(\(.+\))?:" || true)
|
|
222
|
+
FIXES=$(echo "$COMMITS_SINCE_LAST_RELEASE" | grep -E "^[a-f0-9]+ [0-9-]+ fix(\(.+\))?:" || true)
|
|
223
|
+
BREAKS=$(echo "$COMMITS_SINCE_LAST_RELEASE" | grep -E "^[a-f0-9]+ [0-9-]+ break(\(.+\))?:" || true)
|
|
224
|
+
|
|
225
|
+
# format changelog - write to temp file to avoid yaml parsing issues with multiline strings
|
|
226
|
+
CHANGELOG_FILE="/tmp/changelog.md"
|
|
227
|
+
echo "## [${NEXT_TAG#v}](${REPO_URL}/compare/${PREV_TAG}...${NEXT_TAG}) (${TODAY})" > "$CHANGELOG_FILE"
|
|
228
|
+
|
|
229
|
+
format_commits() {
|
|
230
|
+
local commits="$1"
|
|
231
|
+
while IFS= read -r line; do
|
|
232
|
+
[ -z "$line" ] && continue
|
|
233
|
+
HASH="${line%% *}"
|
|
234
|
+
REST="${line#* }"
|
|
235
|
+
DATE="${REST%% *}"
|
|
236
|
+
COMMIT_MESSAGE="${REST#* }"
|
|
237
|
+
SHORT_HASH="${HASH:0:7}"
|
|
238
|
+
# extract pr number from message if present
|
|
239
|
+
PR_NUM=$(echo "$COMMIT_MESSAGE" | grep -oE '\(#[0-9]+\)' | head -1 | tr -d '(#)')
|
|
240
|
+
echo " ○ ${COMMIT_MESSAGE}"
|
|
241
|
+
if [ -n "$PR_NUM" ]; then
|
|
242
|
+
echo " ├── ${DATE}"
|
|
243
|
+
echo " ├── [#${PR_NUM}](${REPO_URL}/pull/${PR_NUM})"
|
|
244
|
+
echo " └── [${SHORT_HASH}](${REPO_URL}/commit/${HASH})"
|
|
245
|
+
else
|
|
246
|
+
echo " ├── ${DATE}"
|
|
247
|
+
echo " └── [${SHORT_HASH}](${REPO_URL}/commit/${HASH})"
|
|
248
|
+
fi
|
|
249
|
+
echo ""
|
|
250
|
+
done <<< "$commits"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# count non-empty lines (handles empty vars gracefully)
|
|
254
|
+
BREAK_COUNT=0; [ -n "$BREAKS" ] && BREAK_COUNT=$(echo "$BREAKS" | wc -l | tr -d ' ')
|
|
255
|
+
FEAT_COUNT=0; [ -n "$FEATS" ] && FEAT_COUNT=$(echo "$FEATS" | wc -l | tr -d ' ')
|
|
256
|
+
FIX_COUNT=0; [ -n "$FIXES" ] && FIX_COUNT=$(echo "$FIXES" | wc -l | tr -d ' ')
|
|
257
|
+
|
|
258
|
+
if [ -n "$BREAKS" ]; then
|
|
259
|
+
{ echo ""; echo "### breaks"; echo ""; format_commits "$BREAKS"; } >> "$CHANGELOG_FILE"
|
|
260
|
+
fi
|
|
261
|
+
if [ -n "$FEATS" ]; then
|
|
262
|
+
{ echo ""; echo "### feats"; echo ""; format_commits "$FEATS"; } >> "$CHANGELOG_FILE"
|
|
263
|
+
fi
|
|
264
|
+
if [ -n "$FIXES" ]; then
|
|
265
|
+
{ echo ""; echo "### fixes"; echo ""; format_commits "$FIXES"; } >> "$CHANGELOG_FILE"
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
echo " ├── breaks: $BREAK_COUNT"
|
|
269
|
+
echo " ├── feats: $FEAT_COUNT"
|
|
270
|
+
echo " └── fixes: $FIX_COUNT"
|
|
271
|
+
|
|
272
|
+
echo "changelog-file=$CHANGELOG_FILE" >> $GITHUB_OUTPUT
|
|
273
|
+
|
|
274
|
+
# step 4: upsert release pr, if other commit
|
|
275
|
+
- name: upsert release pr, if other commit
|
|
276
|
+
id: upsert
|
|
277
|
+
if: ${{ steps.cutta.outputs.did-cutta == 'false' }}
|
|
278
|
+
shell: bash
|
|
279
|
+
env:
|
|
280
|
+
GITHUB_TOKEN: ${{ inputs.github-token }}
|
|
281
|
+
run: |
|
|
282
|
+
echo "🔭 upsert release pr"
|
|
283
|
+
NEXT_VERSION="${{ steps.semver.outputs.next-version }}"
|
|
284
|
+
CHANGELOG=$(cat "${{ steps.changelog.outputs.changelog-file }}")
|
|
285
|
+
|
|
286
|
+
# failfast if package.json doesnt exist
|
|
287
|
+
if [ ! -f package.json ]; then
|
|
288
|
+
echo " └── ⛈️ package.json not found"
|
|
289
|
+
echo "::error::package.json not found"
|
|
290
|
+
exit 1
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
REPO="${{ inputs.repository }}"
|
|
294
|
+
|
|
295
|
+
# find release pr by head branch pattern (more reliable than search)
|
|
296
|
+
FOUND_PR=$(gh pr list --state open --json number,headRefName --jq '[.[] | select(.headRefName | startswith("release/"))] | .[0]')
|
|
297
|
+
echo " ├── version: $NEXT_VERSION"
|
|
298
|
+
|
|
299
|
+
# build pr body in temp file to avoid yaml parsing issues
|
|
300
|
+
PR_BODY_FILE="/tmp/pr-body.md"
|
|
301
|
+
echo "🐢 noice! ready to let these changes ride?" > "$PR_BODY_FILE"
|
|
302
|
+
echo "---" >> "$PR_BODY_FILE"
|
|
303
|
+
echo "" >> "$PR_BODY_FILE"
|
|
304
|
+
cat "${{ steps.changelog.outputs.changelog-file }}" >> "$PR_BODY_FILE"
|
|
305
|
+
PR_BODY=$(cat "$PR_BODY_FILE")
|
|
306
|
+
|
|
307
|
+
# operation: upsert rebased commit on a branch (atomic, no PR auto-close)
|
|
308
|
+
# - creates blob, tree, commit based on parent_sha
|
|
309
|
+
# - upserts branch ref (create if missing, update if exists)
|
|
310
|
+
upsert_rebased_commit() {
|
|
311
|
+
local branch="$1"
|
|
312
|
+
local parent_sha="$2"
|
|
313
|
+
local message="$3"
|
|
314
|
+
|
|
315
|
+
# update package.json version locally
|
|
316
|
+
CURRENT_VERSION=$(jq -r '.version' package.json)
|
|
317
|
+
if [ "$CURRENT_VERSION" != "${NEXT_VERSION#v}" ]; then
|
|
318
|
+
npm version "${NEXT_VERSION#v}" --no-git-tag-version --ignore-scripts
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# create blob with updated package.json
|
|
322
|
+
PKG_CONTENT=$(base64 -w0 package.json)
|
|
323
|
+
BLOB_SHA=$(gh api "repos/${REPO}/git/blobs" \
|
|
324
|
+
-f content="$PKG_CONTENT" \
|
|
325
|
+
-f encoding="base64" \
|
|
326
|
+
--jq '.sha')
|
|
327
|
+
|
|
328
|
+
# get parent's tree
|
|
329
|
+
PARENT_TREE=$(gh api "repos/${REPO}/git/commits/${parent_sha}" --jq '.tree.sha')
|
|
330
|
+
|
|
331
|
+
# create new tree with updated package.json
|
|
332
|
+
NEW_TREE=$(gh api "repos/${REPO}/git/trees" \
|
|
333
|
+
-f base_tree="$PARENT_TREE" \
|
|
334
|
+
-f 'tree[][path]=package.json' \
|
|
335
|
+
-f 'tree[][mode]=100644' \
|
|
336
|
+
-f 'tree[][type]=blob' \
|
|
337
|
+
-f "tree[][sha]=$BLOB_SHA" \
|
|
338
|
+
--jq '.sha')
|
|
339
|
+
|
|
340
|
+
# create commit (auto-verified by github!)
|
|
341
|
+
COMMIT_SHA=$(gh api "repos/${REPO}/git/commits" \
|
|
342
|
+
-f message="$message" \
|
|
343
|
+
-f tree="$NEW_TREE" \
|
|
344
|
+
-f "parents[]=$parent_sha" \
|
|
345
|
+
--jq '.sha')
|
|
346
|
+
|
|
347
|
+
# upsert branch ref (create or update, never equals main)
|
|
348
|
+
if gh api "repos/${REPO}/git/ref/heads/${branch}" --jq '.object.sha' 2>/dev/null; then
|
|
349
|
+
gh api "repos/${REPO}/git/refs/heads/${branch}" -X PATCH -f sha="$COMMIT_SHA" -F force=true > /dev/null
|
|
350
|
+
else
|
|
351
|
+
gh api "repos/${REPO}/git/refs" -X POST -f ref="refs/heads/${branch}" -f sha="$COMMIT_SHA" > /dev/null
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
echo "$COMMIT_SHA"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# get main head (shared by both flows)
|
|
358
|
+
MAIN_SHA=$(gh api "repos/${REPO}/git/ref/heads/main" --jq '.object.sha')
|
|
359
|
+
|
|
360
|
+
if [ -n "$FOUND_PR" ] && [ "$FOUND_PR" != "null" ]; then
|
|
361
|
+
PR_NUMBER=$(echo "$FOUND_PR" | jq -r '.number')
|
|
362
|
+
BRANCH=$(echo "$FOUND_PR" | jq -r '.headRefName')
|
|
363
|
+
echo " ├── found-pr: #$PR_NUMBER ($BRANCH)"
|
|
364
|
+
|
|
365
|
+
# upsert rebased commit
|
|
366
|
+
echo " ├── upserting rebased commit..."
|
|
367
|
+
COMMIT_SHA=$(upsert_rebased_commit "$BRANCH" "$MAIN_SHA" "chore(release): ${NEXT_VERSION} 🎉")
|
|
368
|
+
echo " ├── commit: ${COMMIT_SHA:0:7}"
|
|
369
|
+
|
|
370
|
+
# update pr metadata
|
|
371
|
+
echo " ├── updating pr metadata..."
|
|
372
|
+
gh pr edit "$PR_NUMBER" \
|
|
373
|
+
--title "chore(release): ${NEXT_VERSION} 🎉" \
|
|
374
|
+
--body "$PR_BODY"
|
|
375
|
+
|
|
376
|
+
echo " ├── ✨ pr updated: #$PR_NUMBER"
|
|
377
|
+
echo " └── 🌊 action: updated"
|
|
378
|
+
echo "action=updated" >> $GITHUB_OUTPUT
|
|
379
|
+
echo "pr-number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
380
|
+
else
|
|
381
|
+
BRANCH="release/${NEXT_VERSION}"
|
|
382
|
+
echo " ├── found-pr: (none)"
|
|
383
|
+
echo " ├── branch: $BRANCH"
|
|
384
|
+
|
|
385
|
+
# upsert rebased commit (creates branch if missing)
|
|
386
|
+
echo " ├── upserting rebased commit..."
|
|
387
|
+
COMMIT_SHA=$(upsert_rebased_commit "$BRANCH" "$MAIN_SHA" "chore(release): ${NEXT_VERSION} 🎉")
|
|
388
|
+
echo " ├── commit: ${COMMIT_SHA:0:7}"
|
|
389
|
+
|
|
390
|
+
# create pr
|
|
391
|
+
echo " ├── creating pr..."
|
|
392
|
+
PR_URL=$(gh pr create \
|
|
393
|
+
--title "chore(release): ${NEXT_VERSION} 🎉" \
|
|
394
|
+
--body "$PR_BODY" \
|
|
395
|
+
--base main \
|
|
396
|
+
--head "$BRANCH")
|
|
397
|
+
|
|
398
|
+
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
|
|
399
|
+
echo " ├── ✨ pr created: #$PR_NUMBER"
|
|
400
|
+
echo " └── 🌊 action: created"
|
|
401
|
+
|
|
402
|
+
echo "action=created" >> $GITHUB_OUTPUT
|
|
403
|
+
echo "pr-number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT
|
|
407
|
+
|
|
408
|
+
# step 5: aggregate outputs
|
|
409
|
+
- name: aggregate outputs
|
|
410
|
+
id: result
|
|
411
|
+
if: always()
|
|
412
|
+
shell: bash
|
|
413
|
+
run: |
|
|
414
|
+
if [ "${{ steps.cutta.outputs.did-cutta }}" = "true" ]; then
|
|
415
|
+
echo "action=${{ steps.cutta.outputs.action }}" >> $GITHUB_OUTPUT
|
|
416
|
+
echo "version=${{ steps.cutta.outputs.version }}" >> $GITHUB_OUTPUT
|
|
417
|
+
else
|
|
418
|
+
echo "action=${{ steps.upsert.outputs.action }}" >> $GITHUB_OUTPUT
|
|
419
|
+
echo "version=${{ steps.upsert.outputs.version }}" >> $GITHUB_OUTPUT
|
|
420
|
+
echo "pr-number=${{ steps.upsert.outputs.pr-number }}" >> $GITHUB_OUTPUT
|
|
421
|
+
fi
|
|
@@ -9,20 +9,6 @@ jobs:
|
|
|
9
9
|
please-release:
|
|
10
10
|
runs-on: ubuntu-24.04
|
|
11
11
|
steps:
|
|
12
|
-
- uses: actions/checkout@v4
|
|
13
|
-
with:
|
|
14
|
-
fetch-depth: 0 # need full history for tags
|
|
15
|
-
|
|
16
|
-
- name: check tags
|
|
17
|
-
id: check-tags
|
|
18
|
-
run: |
|
|
19
|
-
if git tag | grep -q .; then
|
|
20
|
-
echo "has-tags=true" >> $GITHUB_OUTPUT
|
|
21
|
-
else
|
|
22
|
-
echo "has-tags=false" >> $GITHUB_OUTPUT
|
|
23
|
-
echo "No tags found - will start at v0.1.0"
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
12
|
- name: get github token
|
|
27
13
|
id: github-token
|
|
28
14
|
uses: actions/create-github-app-token@v2
|
|
@@ -32,48 +18,15 @@ jobs:
|
|
|
32
18
|
app-id: ${{ vars.RHELEASE_APP_ID }}
|
|
33
19
|
private-key: ${{ secrets.RHELEASE_APP_PRIVATE_KEY }}
|
|
34
20
|
|
|
35
|
-
-
|
|
36
|
-
id: release
|
|
37
|
-
uses: google-github-actions/release-please-action@v3.7.6 # https://github.com/googleapis/release-please-action/issues/840
|
|
21
|
+
- uses: actions/checkout@v4
|
|
38
22
|
with:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
release-as: ${{ steps.check-tags.outputs.has-tags == 'false' && '0.1.0' || null }} # ensures new packages start at a sane choice of v0, instead of their default of v1
|
|
42
|
-
pull-request-title-pattern: "chore(release): v${version} 🎉"
|
|
43
|
-
changelog-path: changelog.md
|
|
44
|
-
|
|
45
|
-
- name: upvibe the pr, on upsert
|
|
46
|
-
if: ${{ steps.release.outputs.pr }}
|
|
47
|
-
run: |
|
|
48
|
-
PR="${{ fromJson(steps.release.outputs.pr).number }}"
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
token: ${{ steps.github-token.outputs.token }} # enables git push with app token
|
|
49
25
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
gh pr edit "$PR" --body "$updated"
|
|
59
|
-
env:
|
|
60
|
-
GH_TOKEN: ${{ steps.github-token.outputs.token }}
|
|
61
|
-
|
|
62
|
-
- name: upvibe the pr, on release
|
|
63
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
64
|
-
run: |
|
|
65
|
-
PR="${{ fromJson(steps.release.outputs.pr).number }}"
|
|
66
|
-
TAG="${{ steps.release.outputs.tag_name }}"
|
|
67
|
-
RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/${TAG}"
|
|
68
|
-
|
|
69
|
-
# find the release-please comment with the robot emoji and edit it
|
|
70
|
-
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR}/comments" \
|
|
71
|
-
--jq '.[] | select(.body | startswith(":robot: Release is at")) | .id' | head -1)
|
|
72
|
-
|
|
73
|
-
if [ -n "$COMMENT_ID" ]; then
|
|
74
|
-
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \
|
|
75
|
-
-X PATCH \
|
|
76
|
-
-f body="🌊 released at ${RELEASE_URL}"
|
|
77
|
-
fi
|
|
78
|
-
env:
|
|
79
|
-
GH_TOKEN: ${{ steps.github-token.outputs.token }}
|
|
26
|
+
- name: please release
|
|
27
|
+
uses: ./.github/actions/please-release
|
|
28
|
+
with:
|
|
29
|
+
github-token: ${{ steps.github-token.outputs.token }}
|
|
30
|
+
commit-message: ${{ github.event.head_commit.message }}
|
|
31
|
+
commit-sha: ${{ github.sha }}
|
|
32
|
+
repository: ${{ github.repository }}
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
extends: ['@commitlint/config-conventional'],
|
|
3
|
-
rules: {
|
|
3
|
+
rules: {
|
|
4
|
+
'header-max-length': [1, 'always', 140],
|
|
5
|
+
'type-enum': [
|
|
6
|
+
2,
|
|
7
|
+
'always',
|
|
8
|
+
[
|
|
9
|
+
'break', // use break: instead of feat!: or BREAKING CHANGE footer
|
|
10
|
+
'feat',
|
|
11
|
+
'fix',
|
|
12
|
+
'docs',
|
|
13
|
+
'chore',
|
|
14
|
+
'revert',
|
|
15
|
+
],
|
|
16
|
+
],
|
|
17
|
+
// forbid ! prefix (use break: instead)
|
|
18
|
+
'subject-exclamation-mark': [2, 'never'],
|
|
19
|
+
},
|
|
4
20
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"devDependencies": {
|
|
3
|
-
"rhachet": "@declapract{check.minVersion('1.13.
|
|
4
|
-
"rhachet-roles-ehmpathy": "@declapract{check.minVersion('1.15.
|
|
3
|
+
"rhachet": "@declapract{check.minVersion('1.13.11')}",
|
|
4
|
+
"rhachet-roles-ehmpathy": "@declapract{check.minVersion('1.15.10')}"
|
|
5
5
|
},
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepare:rhachet": "rhachet init && rhachet roles link --role mechanic && .agent/repo=ehmpathy/role=mechanic/skills/.skills/init.claude.sh"
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "declapract-typescript-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.47.2",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"esbuild-register": "3.6.0",
|
|
76
76
|
"husky": "8.0.3",
|
|
77
77
|
"jest": "30.2.0",
|
|
78
|
-
"rhachet": "1.13.
|
|
79
|
-
"rhachet-roles-ehmpathy": "1.15.
|
|
78
|
+
"rhachet": "1.13.11",
|
|
79
|
+
"rhachet-roles-ehmpathy": "1.15.10",
|
|
80
80
|
"tsc-alias": "1.8.10",
|
|
81
81
|
"tsx": "4.20.6",
|
|
82
82
|
"type-fns": "0.8.1",
|