Skip to content

Commit 35f985d

Browse files
Merge pull request #17 from Zipstack/fix/release-workflow-lint-and-ordering
UN-3479 [FIX] release workflow: validate before tag/publish + lint cleanup
2 parents 9b5d4d8 + 73bb811 commit 35f985d

12 files changed

Lines changed: 53 additions & 71 deletions

File tree

‎.github/workflows/main.yml‎

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -67,78 +67,31 @@ jobs:
6767
- name: Install dependencies
6868
run: uv sync --dev
6969

70-
# Handle workflow_dispatch (manual trigger)
71-
- name: Bump version and create release
72-
if: github.event_name == 'workflow_dispatch'
73-
id: create_release
70+
# Compute and stage the new version locally — defer commit/tag/release
71+
# until lint+tests+build+publish all pass, so any failure leaves main untouched.
72+
- name: Compute new version
73+
id: version
7474
run: |
75-
# Get current version from __init__.py using grep/sed (avoid importing)
7675
CURRENT_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
7776
echo "Current version: $CURRENT_VERSION"
7877
79-
# Calculate new version based on input
8078
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
8179
MAJOR=${VERSION_PARTS[0]}
8280
MINOR=${VERSION_PARTS[1]}
8381
PATCH=${VERSION_PARTS[2]}
8482
8583
case "${{ github.event.inputs.version_bump }}" in
86-
"major")
87-
MAJOR=$((MAJOR + 1))
88-
MINOR=0
89-
PATCH=0
90-
;;
91-
"minor")
92-
MINOR=$((MINOR + 1))
93-
PATCH=0
94-
;;
95-
"patch")
96-
PATCH=$((PATCH + 1))
97-
;;
84+
"major") MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
85+
"minor") MINOR=$((MINOR + 1)); PATCH=0 ;;
86+
"patch") PATCH=$((PATCH + 1)) ;;
9887
esac
9988
10089
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
10190
echo "New version: $NEW_VERSION"
10291
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
10392
104-
# Update version in __init__.py
10593
sed -i "s/__version__ = \"$CURRENT_VERSION\"/__version__ = \"$NEW_VERSION\"/" src/unstract/api_deployments/__init__.py
10694
107-
# Commit version changes
108-
git add src/unstract/api_deployments/__init__.py
109-
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
110-
git push origin main
111-
112-
# Create git tag
113-
git tag "v$NEW_VERSION"
114-
git push origin "v$NEW_VERSION"
115-
116-
# Create GitHub release
117-
RELEASE_NOTES="${{ github.event.inputs.release_notes }}"
118-
if [ -z "$RELEASE_NOTES" ]; then
119-
gh release create "v$NEW_VERSION" \
120-
--title "Release v$NEW_VERSION" \
121-
--generate-notes \
122-
${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }}
123-
else
124-
gh release create "v$NEW_VERSION" \
125-
--title "Release v$NEW_VERSION" \
126-
--notes "$RELEASE_NOTES" \
127-
--generate-notes \
128-
${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }}
129-
fi
130-
131-
echo "Created release v$NEW_VERSION"
132-
env:
133-
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
134-
135-
# Set version for subsequent steps
136-
- name: Set version output
137-
id: version
138-
run: |
139-
echo "version=${{ steps.create_release.outputs.version }}" >> $GITHUB_OUTPUT
140-
141-
# Verify the version was updated correctly
14295
- name: Verify version update
14396
run: |
14497
PACKAGE_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
@@ -149,27 +102,53 @@ jobs:
149102
exit 1
150103
fi
151104
152-
# Create test environment
153105
- name: Create test env
154106
run: cp tests/sample.env tests/.env
155107

156-
# Run linting
157108
- name: Run linting
158109
run: uv run ruff check src/
159110

160-
# Run tests
161111
- name: Run tests
162112
run: uv run pytest tests/
163113

164-
# Build the package
165114
- name: Build package
166115
run: uv build
167116

168-
# Publish to PyPI using Trusted Publishers
117+
# PyPI publish first because it is the only step that cannot be undone —
118+
# if a later commit/tag/release step fails, the PyPI artifact is the
119+
# source of truth and the git metadata can be retried manually.
169120
- name: Publish to PyPI
170121
run: uv publish
171122

172-
# Output success message
123+
- name: Commit version bump and create release
124+
run: |
125+
NEW_VERSION="${{ steps.version.outputs.version }}"
126+
127+
git add src/unstract/api_deployments/__init__.py
128+
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
129+
git push origin main
130+
131+
git tag "v$NEW_VERSION"
132+
git push origin "v$NEW_VERSION"
133+
134+
RELEASE_NOTES="${{ github.event.inputs.release_notes }}"
135+
if [ -z "$RELEASE_NOTES" ]; then
136+
gh release create "v$NEW_VERSION" \
137+
--title "Release v$NEW_VERSION" \
138+
--generate-notes \
139+
${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }}
140+
else
141+
gh release create "v$NEW_VERSION" \
142+
--title "Release v$NEW_VERSION" \
143+
--notes "$RELEASE_NOTES" \
144+
--generate-notes \
145+
${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }}
146+
fi
147+
148+
echo "Created release v$NEW_VERSION"
149+
env:
150+
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
151+
173152
- name: Success message
174153
run: |
175154
echo "Successfully published version ${{ steps.version.outputs.version }} to PyPI using uv publish with Trusted Publishers"

‎.github/workflows/test.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ jobs:
3232
- name: Create test env
3333
run: cp tests/sample.env tests/.env
3434

35+
- name: Lint (ruff)
36+
run: uv run ruff check src/
37+
3538
- name: Tests (pytest)
3639
run: uv run pytest tests/ -v

‎src/unstract/api_deployments/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.4.0"
1+
__version__ = "1.2.1"
22

33
from .client import APIDeploymentsClient as APIDeploymentsClient
44

‎src/unstract/clone/exceptions.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(
1818

1919

2020
class NameConflictError(CloneError):
21-
"""Raised when ``on_name_conflict='abort'`` and the target has a like-named entity."""
21+
"""Raised on name collision when ``on_name_conflict='abort'``."""
2222

2323

2424
class DependencyMissingError(CloneError):

‎src/unstract/clone/phases/adapter.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _clone_one(
8383
tgt = existing[0]
8484
if self.ctx.options.on_name_conflict == "abort":
8585
raise NameConflictError(
86-
f"adapter '{name}' [{atype}] already exists in target as {tgt['id']}"
86+
f"adapter '{name}' [{atype}] already on target as {tgt['id']}"
8787
)
8888
with lock:
8989
result.adopted += 1

‎src/unstract/clone/phases/api_deployment.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def _clone_one(
7676
tgt_wf_id = self.ctx.remap.resolve("workflow", src_wf_id)
7777
if not tgt_wf_id:
7878
logger.warning(
79-
"no workflow remap for api_deployment '%s' (src workflow %s) — skipping",
79+
"no workflow remap for api_deployment '%s' (src wf %s) — skipping",
8080
api_name,
8181
src_wf_id,
8282
)
@@ -99,7 +99,7 @@ def _clone_one(
9999
tgt = existing[0]
100100
if self.ctx.options.on_name_conflict == "abort":
101101
raise NameConflictError(
102-
f"api_deployment '{api_name}' already exists in target as {tgt['id']}"
102+
f"api_deployment '{api_name}' already on target as {tgt['id']}"
103103
)
104104
with lock:
105105
result.adopted += 1

‎src/unstract/clone/phases/connector.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _clone_one(
8686
metadata = src.get("connector_metadata") or {}
8787
if not metadata:
8888
logger.info(
89-
"skipping connector '%s' (src=%s, catalog=%s) — source returned no metadata",
89+
"skipping connector '%s' (src=%s, catalog=%s) — no source metadata",
9090
name,
9191
src_id,
9292
src.get("connector_id"),

‎src/unstract/clone/phases/custom_tool.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def _create_fresh(
270270
with lock:
271271
result.failed += 1
272272
result.errors.append(
273-
f"import {tool_name}: missing target adapter remap for default profile"
273+
f"import {tool_name}: missing target adapter remap for default"
274274
)
275275
return None
276276

‎src/unstract/clone/phases/files.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ def _ensure_default_doc(
384384
tgt_docs = self.ctx.target.list_prompt_documents(tgt_tool_id)
385385
except Exception as e:
386386
logger.warning(
387-
"files: skipping default-doc set for tool=%s — list tgt docs failed: %s",
387+
"files: skipping default-doc set for tool=%s — tgt list failed: %s",
388388
tool_name,
389389
e,
390390
)

‎src/unstract/clone/phases/pipeline.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def run(self, report: CloneReport) -> PhaseResult:
5555
skipped_types = len(src_pipelines) - len(migratable)
5656
if skipped_types:
5757
logger.info(
58-
"Found %d source pipeline(s); skipping %d of unsupported type (DEFAULT/APP)",
58+
"Found %d source pipeline(s); skipping %d unsupported (DEFAULT/APP)",
5959
len(src_pipelines),
6060
skipped_types,
6161
)

0 commit comments

Comments
 (0)