Skip to content

Commit ad6465e

Browse files
mikolalysenkoclaude
andcommitted
feat: combined release workflow, PyPI package, and UUID shortcut
- Combine publish.yml and release.yml into a single workflow_dispatch release workflow with bump-and-tag, build, github-release, cargo-publish, npm-publish, and pypi-publish jobs - Make Cargo.toml the single source of truth for version - Add PyPI package (socket-patch) that distributes platform binaries - Update version-sync.sh to sync Cargo.toml, package.json, and pyproject.toml - Add UUID shortcut: `socket-patch <UUID>` works like `get <UUID>` - Add global lifecycle and UUID shortcut e2e tests for npm and pypi Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cc267a6 commit ad6465e

File tree

9 files changed

+534
-65
lines changed

9 files changed

+534
-65
lines changed

.github/workflows/publish.yml

Lines changed: 0 additions & 51 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,62 @@
11
name: Release
22

33
on:
4-
push:
5-
tags:
6-
- 'v*'
4+
workflow_dispatch:
5+
inputs:
6+
version-bump:
7+
description: 'Version bump type'
8+
required: true
9+
type: choice
10+
options:
11+
- patch
12+
- minor
13+
- major
714

815
permissions:
916
contents: write
1017
id-token: write
1118

1219
jobs:
20+
bump-and-tag:
21+
runs-on: ubuntu-latest
22+
outputs:
23+
version: ${{ steps.bump.outputs.VERSION }}
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
30+
with:
31+
node-version: '20'
32+
33+
- name: Configure Git
34+
run: |
35+
git config user.name "github-actions[bot]"
36+
git config user.email "github-actions[bot]@users.noreply.github.com"
37+
38+
- name: Bump version and sync
39+
id: bump
40+
run: |
41+
CURRENT=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
42+
VERSION=$(node -e "
43+
const [major, minor, patch] = '$CURRENT'.split('.').map(Number);
44+
const bump = '${{ inputs.version-bump }}';
45+
if (bump === 'major') console.log((major+1)+'.0.0');
46+
else if (bump === 'minor') console.log(major+'.'+(minor+1)+'.0');
47+
else console.log(major+'.'+minor+'.'+(patch+1));
48+
")
49+
bash scripts/version-sync.sh "$VERSION"
50+
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
51+
git add Cargo.toml npm/ pypi/
52+
git commit -m "v$VERSION"
53+
git tag "v$VERSION"
54+
55+
- name: Push changes and tag
56+
run: git push && git push --tags
57+
1358
build:
59+
needs: bump-and-tag
1460
strategy:
1561
matrix:
1662
include:
@@ -38,6 +84,8 @@ jobs:
3884
steps:
3985
- name: Checkout
4086
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
87+
with:
88+
ref: v${{ needs.bump-and-tag.outputs.version }}
4189

4290
- name: Install Rust
4391
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
@@ -85,7 +133,7 @@ jobs:
85133
path: socket-patch-${{ matrix.target }}.zip
86134

87135
github-release:
88-
needs: build
136+
needs: [bump-and-tag, build]
89137
runs-on: ubuntu-latest
90138
steps:
91139
- name: Download all artifacts
@@ -98,21 +146,23 @@ jobs:
98146
env:
99147
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
100148
run: |
101-
TAG="${GITHUB_REF_NAME}"
149+
TAG="v${{ needs.bump-and-tag.outputs.version }}"
102150
gh release create "$TAG" \
103151
--repo "$GITHUB_REPOSITORY" \
104152
--generate-notes \
105153
artifacts/*
106154
107155
cargo-publish:
108-
needs: build
156+
needs: [bump-and-tag, build]
109157
runs-on: ubuntu-latest
110158
permissions:
111159
contents: read
112160
id-token: write
113161
steps:
114162
- name: Checkout
115163
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
164+
with:
165+
ref: v${{ needs.bump-and-tag.outputs.version }}
116166

117167
- name: Install Rust
118168
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
@@ -132,11 +182,13 @@ jobs:
132182
run: cargo publish -p socket-patch-cli
133183

134184
npm-publish:
135-
needs: build
185+
needs: [bump-and-tag, build]
136186
runs-on: ubuntu-latest
137187
steps:
138188
- name: Checkout
139189
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
190+
with:
191+
ref: v${{ needs.bump-and-tag.outputs.version }}
140192

141193
- name: Download all artifacts
142194
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
@@ -150,12 +202,6 @@ jobs:
150202
node-version: '20'
151203
registry-url: 'https://registry.npmjs.org'
152204

153-
- name: Extract version and sync
154-
run: |
155-
VERSION="${GITHUB_REF_NAME#v}"
156-
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
157-
bash scripts/version-sync.sh "$VERSION"
158-
159205
- name: Stage binaries
160206
run: |
161207
mkdir -p npm/socket-patch/bin
@@ -175,3 +221,57 @@ jobs:
175221
env:
176222
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
177223
run: npm publish npm/socket-patch --provenance --access public
224+
225+
pypi-publish:
226+
needs: [bump-and-tag, build]
227+
runs-on: ubuntu-latest
228+
steps:
229+
- name: Checkout
230+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
231+
with:
232+
ref: v${{ needs.bump-and-tag.outputs.version }}
233+
234+
- name: Download all artifacts
235+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
236+
with:
237+
path: artifacts
238+
merge-multiple: true
239+
240+
- name: Setup Python
241+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
242+
with:
243+
python-version: '3.12'
244+
245+
- name: Install build tools
246+
run: pip install build twine
247+
248+
- name: Stage binaries
249+
run: |
250+
mkdir -p pypi/socket-patch/socket_patch/bin
251+
tar xzf artifacts/socket-patch-aarch64-apple-darwin.tar.gz -C pypi/socket-patch/socket_patch/bin/
252+
mv pypi/socket-patch/socket_patch/bin/socket-patch pypi/socket-patch/socket_patch/bin/socket-patch-darwin-arm64
253+
tar xzf artifacts/socket-patch-x86_64-apple-darwin.tar.gz -C pypi/socket-patch/socket_patch/bin/
254+
mv pypi/socket-patch/socket_patch/bin/socket-patch pypi/socket-patch/socket_patch/bin/socket-patch-darwin-x64
255+
tar xzf artifacts/socket-patch-x86_64-unknown-linux-musl.tar.gz -C pypi/socket-patch/socket_patch/bin/
256+
mv pypi/socket-patch/socket_patch/bin/socket-patch pypi/socket-patch/socket_patch/bin/socket-patch-linux-x64
257+
tar xzf artifacts/socket-patch-aarch64-unknown-linux-gnu.tar.gz -C pypi/socket-patch/socket_patch/bin/
258+
mv pypi/socket-patch/socket_patch/bin/socket-patch pypi/socket-patch/socket_patch/bin/socket-patch-linux-arm64
259+
cd pypi/socket-patch/socket_patch/bin
260+
unzip ../../../../artifacts/socket-patch-x86_64-pc-windows-msvc.zip
261+
mv socket-patch.exe socket-patch-win32-x64.exe
262+
263+
- name: Set executable permissions
264+
run: |
265+
chmod +x pypi/socket-patch/socket_patch/bin/socket-patch-darwin-arm64
266+
chmod +x pypi/socket-patch/socket_patch/bin/socket-patch-darwin-x64
267+
chmod +x pypi/socket-patch/socket_patch/bin/socket-patch-linux-x64
268+
chmod +x pypi/socket-patch/socket_patch/bin/socket-patch-linux-arm64
269+
270+
- name: Build package
271+
run: python -m build pypi/socket-patch
272+
273+
- name: Publish to PyPI
274+
env:
275+
TWINE_USERNAME: __token__
276+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
277+
run: twine upload pypi/socket-patch/dist/*

crates/socket-patch-cli/src/main.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,39 @@ enum Commands {
4343
Repair(commands::repair::RepairArgs),
4444
}
4545

46+
/// Check whether `s` looks like a UUID (8-4-4-4-12 hex pattern).
47+
fn looks_like_uuid(s: &str) -> bool {
48+
let parts: Vec<&str> = s.split('-').collect();
49+
if parts.len() != 5 {
50+
return false;
51+
}
52+
let expected = [8, 4, 4, 4, 12];
53+
parts
54+
.iter()
55+
.zip(expected.iter())
56+
.all(|(p, &len)| p.len() == len && p.chars().all(|c| c.is_ascii_hexdigit()))
57+
}
58+
4659
#[tokio::main]
4760
async fn main() {
48-
let cli = Cli::parse();
61+
let cli = match Cli::try_parse() {
62+
Ok(cli) => cli,
63+
Err(err) => {
64+
// If parsing failed, check whether the user passed a bare UUID
65+
// (e.g. `socket-patch 80630680-...`) and retry as `get <UUID> ...`.
66+
let args: Vec<String> = std::env::args().collect();
67+
if args.len() >= 2 && looks_like_uuid(&args[1]) {
68+
let mut new_args = vec![args[0].clone(), "get".into()];
69+
new_args.extend_from_slice(&args[1..]);
70+
match Cli::try_parse_from(&new_args) {
71+
Ok(cli) => cli,
72+
Err(_) => err.exit(),
73+
}
74+
} else {
75+
err.exit()
76+
}
77+
}
78+
};
4979

5080
let exit_code = match cli.command {
5181
Commands::Apply(args) => commands::apply::run(args).await,

0 commit comments

Comments
 (0)