Skip to content

Commit 96cf545

Browse files
authored
ci: Add workflow to bump @workos-inc/node on major release (#1569)
1 parent a7a1e6d commit 96cf545

1 file changed

Lines changed: 223 additions & 0 deletions

File tree

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
name: Bump workos-node dependency in workos repos
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: 'Target version (e.g. 9, 9.0.0, or v9.0.0) — major is extracted automatically'
10+
required: true
11+
type: string
12+
dry_run:
13+
description: 'Log what would happen without opening any PRs'
14+
required: false
15+
default: true
16+
type: boolean
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
check-version:
23+
runs-on: ubuntu-latest
24+
outputs:
25+
skip: ${{ steps.version.outputs.skip }}
26+
major: ${{ steps.version.outputs.major }}
27+
tag: ${{ steps.version.outputs.tag }}
28+
steps:
29+
- name: Determine target major version
30+
id: version
31+
env:
32+
EVENT_NAME: ${{ github.event_name }}
33+
RELEASE_TAG: ${{ github.event.release.tag_name }}
34+
INPUT_VERSION: ${{ inputs.version }}
35+
run: |
36+
if [ "$EVENT_NAME" = "release" ]; then
37+
TAG="$RELEASE_TAG"
38+
VERSION="${TAG#v}"
39+
MAJOR=$(echo "$VERSION" | cut -d. -f1)
40+
MINOR=$(echo "$VERSION" | cut -d. -f2)
41+
PATCH=$(echo "$VERSION" | cut -d. -f3)
42+
43+
echo "Tag: $TAG → major=$MAJOR minor=$MINOR patch=$PATCH"
44+
45+
if [ "$MINOR" != "0" ] || [ "$PATCH" != "0" ]; then
46+
echo "Not a major release ($TAG) — skipping."
47+
echo "skip=true" >> "$GITHUB_OUTPUT"
48+
exit 0
49+
fi
50+
else
51+
INPUT="$INPUT_VERSION"
52+
VERSION="${INPUT#v}"
53+
MAJOR=$(echo "$VERSION" | cut -d. -f1)
54+
55+
if ! [[ "$MAJOR" =~ ^[0-9]+$ ]]; then
56+
echo "Invalid version input: $INPUT"
57+
exit 1
58+
fi
59+
60+
TAG="v${MAJOR}.0.0"
61+
62+
echo "Input: $INPUT → major=$MAJOR"
63+
fi
64+
65+
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
66+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
67+
echo "skip=false" >> "$GITHUB_OUTPUT"
68+
69+
bump-workos-node-deps:
70+
needs: check-version
71+
if: needs.check-version.outputs.skip != 'true'
72+
runs-on: ubuntu-latest
73+
steps:
74+
- name: Generate token
75+
id: generate-token
76+
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # 3.1.1
77+
with:
78+
app-id: ${{ vars.SDK_BOT_APP_ID }}
79+
private-key: ${{ secrets.SDK_BOT_PRIVATE_KEY }}
80+
owner: workos
81+
82+
- name: Discover repos
83+
env:
84+
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
85+
run: |
86+
REPO_PATTERNS=("authkit-")
87+
88+
> /tmp/repos.txt
89+
for PATTERN in "${REPO_PATTERNS[@]}"; do
90+
gh repo list workos \
91+
--limit 200 \
92+
--json name \
93+
--jq ".[] | select(.name | startswith(\"$PATTERN\")) | .name" \
94+
>> /tmp/repos.txt
95+
done
96+
97+
if [ ! -s /tmp/repos.txt ]; then
98+
echo "No repos found matching patterns: ${REPO_PATTERNS[*]}"
99+
else
100+
echo "Found repos:"
101+
cat /tmp/repos.txt
102+
fi
103+
104+
- name: Bump dependencies
105+
env:
106+
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
107+
NEW_MAJOR: ${{ needs.check-version.outputs.major }}
108+
DRY_RUN: ${{ inputs.dry_run || 'false' }}
109+
RELEASE_TAG: ${{ needs.check-version.outputs.tag }}
110+
run: |
111+
echo "Target major: $NEW_MAJOR | Dry run: $DRY_RUN"
112+
113+
if [ ! -s /tmp/repos.txt ]; then
114+
echo "No repos to process."
115+
exit 0
116+
fi
117+
118+
while IFS= read -r REPO_NAME; do
119+
[ -z "$REPO_NAME" ] && continue
120+
REPO="workos/$REPO_NAME"
121+
echo "── $REPO"
122+
123+
# Fetch package.json via API (avoids a full clone for the read phase)
124+
PKG_CONTENT=$(gh api "repos/$REPO/contents/package.json" \
125+
--jq '.content' 2>/dev/null) || {
126+
echo " No package.json — skipping"
127+
continue
128+
}
129+
PKG_JSON=$(echo "$PKG_CONTENT" | base64 -d)
130+
131+
# Extract current @workos-inc/node version (deps or devDeps)
132+
CURRENT=$(echo "$PKG_JSON" | jq -r '
133+
(.dependencies["@workos-inc/node"] //
134+
.devDependencies["@workos-inc/node"] //
135+
"")
136+
')
137+
if [ -z "$CURRENT" ]; then
138+
echo " @workos-inc/node not present — skipping"
139+
continue
140+
fi
141+
142+
# Compare majors — strip any non-numeric prefix (^, ~, >=, etc.)
143+
CURRENT_MAJOR=$(echo "$CURRENT" | sed 's/^[^0-9]*//' | cut -d. -f1)
144+
145+
if ! [[ "$CURRENT_MAJOR" =~ ^[0-9]+$ ]]; then
146+
echo " Unable to parse version: $CURRENT — skipping"
147+
continue
148+
fi
149+
150+
if [ "$CURRENT_MAJOR" -ge "$NEW_MAJOR" ]; then
151+
echo " Already at major $CURRENT_MAJOR (>= $NEW_MAJOR) — skipping"
152+
continue
153+
fi
154+
155+
NEW_CONSTRAINT="^${NEW_MAJOR}.0.0"
156+
echo " Update: $CURRENT → $NEW_CONSTRAINT"
157+
158+
if [ "$DRY_RUN" = "true" ]; then
159+
echo " [DRY RUN] Would open PR in $REPO"
160+
echo " [DRY RUN] Branch: deps/bump-workos-node-to-v${NEW_MAJOR}"
161+
echo " [DRY RUN] Change: @workos-inc/node $CURRENT → $NEW_CONSTRAINT"
162+
continue
163+
fi
164+
165+
BRANCH="deps/bump-workos-node-to-v${NEW_MAJOR}"
166+
WORK_DIR="/tmp/${REPO_NAME}"
167+
168+
rm -rf "$WORK_DIR"
169+
gh repo clone "$REPO" "$WORK_DIR" || { echo " Clone failed — skipping"; continue; }
170+
171+
# Subshell isolates errexit so one repo failure doesn't abort the loop
172+
(
173+
cd "$WORK_DIR"
174+
git checkout -b "$BRANCH"
175+
176+
# sed preserves exact file formatting; | delimiter avoids conflicts with /
177+
sed -i "s|\"@workos-inc/node\": \"${CURRENT}\"|\"@workos-inc/node\": \"${NEW_CONSTRAINT}\"|g" \
178+
package.json
179+
180+
git config user.email "workos-sdk-bot[bot]@users.noreply.github.com"
181+
git config user.name "workos-sdk-bot[bot]"
182+
183+
# Regenerate lockfile based on detected package manager
184+
if [ -f "pnpm-lock.yaml" ]; then
185+
corepack enable pnpm
186+
pnpm install --lockfile-only
187+
elif [ -f "package-lock.json" ]; then
188+
npm install --package-lock-only
189+
fi
190+
191+
git add package.json package-lock.json pnpm-lock.yaml 2>/dev/null || true
192+
git commit -m "chore!: bump @workos-inc/node to ^${NEW_MAJOR}.0.0"
193+
194+
# Push idempotently — force-with-lease if branch exists from a prior run
195+
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
196+
echo " Branch $BRANCH already exists — updating"
197+
git push --force-with-lease origin "$BRANCH"
198+
else
199+
git push origin "$BRANCH"
200+
fi
201+
202+
# Only create PR if one doesn't already exist for this branch
203+
EXISTING_PR=$(gh pr list --repo "$REPO" --head "$BRANCH" --json number --jq '.[0].number')
204+
if [ -n "$EXISTING_PR" ]; then
205+
echo " PR #$EXISTING_PR already exists — skipping"
206+
else
207+
DEFAULT_BRANCH=$(gh repo view "$REPO" \
208+
--json defaultBranchRef \
209+
--jq '.defaultBranchRef.name')
210+
211+
gh pr create \
212+
--repo "$REPO" \
213+
--base "$DEFAULT_BRANCH" \
214+
--head "$BRANCH" \
215+
--title "chore!: bump @workos-inc/node to ^${NEW_MAJOR}.0.0" \
216+
--body "Bumps \`@workos-inc/node\` from \`${CURRENT}\` to \`${NEW_CONSTRAINT}\` following the [v${NEW_MAJOR}.0.0 release](https://github.com/workos/workos-node/releases/tag/${RELEASE_TAG})."
217+
fi
218+
) || echo " Failed — skipping"
219+
220+
rm -rf "$WORK_DIR"
221+
echo " Done ✓"
222+
223+
done < /tmp/repos.txt

0 commit comments

Comments
 (0)