-
Notifications
You must be signed in to change notification settings - Fork 0
UN-4008 [FEAT] Add the unstract CLI to run extractions and API deployments from the terminal #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
Show all changes
87 commits
Select commit
Hold shift + click to select a range
b93b245
feat: CLI scaffold with config, output envelope and poll engine
chandrasekharan-zipstack ee211ee
feat: derive command flags from the committed API specs
chandrasekharan-zipstack bb46d73
feat: the v1 command surface for both products
chandrasekharan-zipstack af9b851
feat: --discover and a live probe for config doctor
chandrasekharan-zipstack b6ec319
test: pin the parameters no command can reach
chandrasekharan-zipstack 2d01eae
fix(whisper): read the highlight metadata the service actually returns
chandrasekharan-zipstack 14f7872
feat: connection flags, wider clients, and honest one-shot wording
chandrasekharan-zipstack 6dfc2a9
Forward the status parameters, and stop the doctor overstating itself
chandrasekharan-zipstack d0dc35f
Report which job a waited result belongs to
chandrasekharan-zipstack afb864b
Stop losing one-shot results, and stop printing keys
chandrasekharan-zipstack 52761ad
Print a table by default, and version the JSON
chandrasekharan-zipstack 14a1966
feat: expose the deployment client's socket timeout
chandrasekharan-zipstack e0a91ea
fix: report an interrupt as an interrupt
chandrasekharan-zipstack 19d6001
test: pin which of the three help sources wins
chandrasekharan-zipstack 4f80f52
fix: take allowed values from the spec, not from a copy of them
chandrasekharan-zipstack 5229320
fix: strip a restated default that contains a period
chandrasekharan-zipstack 6ac0646
build: move the client pins to the heads the specs were taken from
chandrasekharan-zipstack bc9b255
docs: trim comments that narrate rather than explain
chandrasekharan-zipstack a974329
feat: add the `clone` command
chandrasekharan-zipstack d515a6f
fix: resync the docstudio spec and pin the flags it derives
chandrasekharan-zipstack f919eb5
fix: keep the job handle on any mid-poll failure, and fail a failed s…
chandrasekharan-zipstack 50fd6e5
fix: report a failure as one, and never authenticate against a guess
chandrasekharan-zipstack b9f04ac
build: move the client pins to the heads carrying the transport fixes
chandrasekharan-zipstack dbf730c
fix: hold the clone's guards, and say what a clone left behind
chandrasekharan-zipstack a8b1ac9
build: move the deployment client pin to the poll-URL fix
chandrasekharan-zipstack 7092cdb
docs: draft the release notes, and move the client pin to its tip
chandrasekharan-zipstack 6ecd490
docs: shorten the top-level help to what a first run needs
chandrasekharan-zipstack 85e4e69
fix: do not let a discovered project config name the host or the key
chandrasekharan-zipstack 4d75745
fix: never write config through a symlink a checkout chose
chandrasekharan-zipstack 14bdda2
docs: one key can cover every deployment, and say where it is minted
chandrasekharan-zipstack 48ed23f
test: pin the trust classification and the config group's own warnings
chandrasekharan-zipstack f6da68e
test: snapshot what each derived flag accepts, not just its name
chandrasekharan-zipstack 473356d
minor: Edit in discover docstring to clarify intent
chandrasekharan-zipstack 014d28c
fix: treat an empty config value as unset
chandrasekharan-zipstack b42691f
docs: drop the release notes and the runbook
chandrasekharan-zipstack 88ee5c8
docs: install with uv, and answer what the README left open
chandrasekharan-zipstack f2553a4
docs: cut each comment back to the reason it exists
chandrasekharan-zipstack c62a14d
docs: say in the top-level help what the CLI can do
chandrasekharan-zipstack eb4414b
fix: close two local write windows on the config and result paths
chandrasekharan-zipstack 86ed7cf
feat: install with one command, without a Python of the right version
chandrasekharan-zipstack ea77498
feat: build on the released clients, against the specs they came from
chandrasekharan-zipstack 4a2d95a
ci: publish a release to PyPI from a dispatch
chandrasekharan-zipstack f14ee3c
ci: cut release candidates before a stable release
chandrasekharan-zipstack bad14d8
fix: answer honestly where a live run found the CLI lying
chandrasekharan-zipstack 14f7fef
fix: never truncate a working config ahead of a write that may fail
chandrasekharan-zipstack 4220be4
fix: say why a config in an unwritable directory cannot be saved
chandrasekharan-zipstack dbd2efd
fix: make raw output and a dead DNS guard answer honestly
chandrasekharan-zipstack 4991834
ci: compare both ends of the click range, not the lockfile's middle
chandrasekharan-zipstack 1c07d11
fix: put the new config on the disk before the rename that stands for it
chandrasekharan-zipstack 9bb89df
fix: refuse a symlinked --save target instead of replacing the link
chandrasekharan-zipstack d21da48
fix: check the save target again at the rename, not only ahead of the…
chandrasekharan-zipstack 08ce3f0
fix: keep one symlink check on the save path, at the point it can sti…
chandrasekharan-zipstack ba414ad
ci: install from the lockfile, on the uv the other packages release with
chandrasekharan-zipstack c20d09e
docs: add a skill for bumping the client pins and their specs
chandrasekharan-zipstack 8a5dfda
docs: check all four provenance fields, and hold version_bump across …
chandrasekharan-zipstack 7f5cdc7
Merge pull request #6 from Zipstack/docs/bump-client-pins-skill
chandrasekharan-zipstack 4e9b1d1
Remediate review findings across the CLI scaffold
chandrasekharan-zipstack fd35bd5
fix: make the release workflow parseable, and keep it that way
chandrasekharan-zipstack e70681c
fix: let a run name its documents as presigned URLs
chandrasekharan-zipstack f9dfd6b
fix: give an accepted extraction a raw answer of its own
chandrasekharan-zipstack f46b650
fix: write the starter config where it will be honoured
chandrasekharan-zipstack 4fd39a8
fix: read past an empty field when picking a raw answer
chandrasekharan-zipstack 72e4e90
fix: keep a rescued result whole when a save fails
chandrasekharan-zipstack 50a1122
fix: render a failure in the format the run resolved
chandrasekharan-zipstack 6071465
fix: do not lose a finished report to an unreadable config
chandrasekharan-zipstack 22709ce
fix: name the execution id when a run times out
chandrasekharan-zipstack b9481f5
test: cover building a deployment client
chandrasekharan-zipstack e46410b
ci: publish only after the revertible release steps have run
chandrasekharan-zipstack 753b0a3
docs: state how a deployment alias and the connection flags interact
chandrasekharan-zipstack fc2ea72
fix: bound the poll interval and floor the backoff
chandrasekharan-zipstack 2df7c5b
fix: preflight the write a save actually performs
chandrasekharan-zipstack 11c0a86
fix: stop reporting a local fault as a retryable server failure
chandrasekharan-zipstack 3eabc6a
feat: publish what omitting a spec-derived flag gets you
chandrasekharan-zipstack 3fccf12
test: check the token where gh actually reads it
chandrasekharan-zipstack 79e961c
fix: answer a bare invocation in the format that was asked for
chandrasekharan-zipstack 4d5171a
fix: put every diagnostic behind --quiet
chandrasekharan-zipstack dcd6f8b
keep the parts of a config file this CLI does not own
chandrasekharan-zipstack dcd327f
say on stderr when a clone left something behind
chandrasekharan-zipstack dfd7102
refuse a config setting the product does not have
chandrasekharan-zipstack c11e36c
name an overlay entry the specs do not declare
chandrasekharan-zipstack 16f0799
cover the failure paths that had no test
chandrasekharan-zipstack 3dbdca0
drop the header redaction nothing calls
chandrasekharan-zipstack cf38f3d
fix: give the deployment client a socket timeout by default
chandrasekharan-zipstack c7d22a7
ci: authenticate the release push the way the other repos do
chandrasekharan-zipstack 5bad0b9
docs: say what the vendored-spec hash does and does not catch
chandrasekharan-zipstack c8233e4
test: tie each vendored spec to the client pin it was synced for
chandrasekharan-zipstack 1ac518e
fix: a completed run with a failed document exits VALIDATION
chandrasekharan-zipstack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| --- | ||
| name: bump-client-pins | ||
| description: Bump the exact `unstract-client` / `llmwhisperer-client` pins, re-sync the vendored specs to match, and cut a CLI release. Use whenever a new version of either client is released, when `tests/test_specs.py` or `tests/test_contract.py` fails, when the CLI is missing a flag for an endpoint the API already has, or when someone asks to "bump the client", "update the pins", "refresh the specs", or "release the CLI". Reach for this even when the request sounds like a plain dependency bump — the pins, the vendored specs and `provenance.json` have to move together or the CLI derives flags the pinned client cannot carry. | ||
| --- | ||
|
|
||
| # Bumping the client pins | ||
|
|
||
| The CLI derives its flags and help text from two published clients and from | ||
| vendored copies of the specs those clients were generated from. That makes a pin | ||
| bump three coupled edits, not one: the pin, the spec, and the provenance record. | ||
| Move one without the others and the tests say so — which is the point of them. | ||
|
|
||
| ## The pieces | ||
|
|
||
| | Thing | Where | | ||
| |---|---| | ||
| | Exact pins | `pyproject.toml`, `[project].dependencies` | | ||
| | Vendored specs | `src/unstract_cli/specs/{docstudio,llmwhisperer}.json` | | ||
| | Provenance | `src/unstract_cli/specs/provenance.json` (client pin, upstream repo, commit, sha256) | | ||
| | Coherence tests | `tests/test_specs.py`, `tests/test_contract.py`, `tests/derived_flags.json` | | ||
| | Release | `.github/workflows/release.yml`, `workflow_dispatch` | | ||
|
|
||
| `src/unstract_cli/specs/README.md` explains the vendoring rule in place; read it | ||
| if any of the below is unclear. | ||
|
|
||
| ## The sequence | ||
|
|
||
| 1. **Move the pins** in `pyproject.toml` to the released versions you are | ||
| upgrading to. They are exact (`==`) on purpose: the CLI's published surface is | ||
| derived from these clients, so a client that moves reshapes the CLI. | ||
|
|
||
| 2. **Relock:** `uv lock` then `uv sync --extra dev --python 3.12`. CI installs | ||
| from `uv.lock`, not from a fresh resolve, so a lockfile left behind means the | ||
| gate tests a dependency set nobody ships. | ||
|
|
||
| 3. **Re-sync each vendored spec from the commit the pinned client was generated | ||
| from.** The chain is: the client repo's release tag → its `tools/gen_sdk.sh`, | ||
| which records the upstream service repo, path and revision the spec was copied | ||
| from → the spec file committed in that client at that tag. Copy that file here | ||
| byte-for-byte. Copying from anywhere else — upstream `main`, a newer service | ||
| commit — is what `tests/test_contract.py` guards: a spec parameter the pinned | ||
| client has no argument for cannot become a flag. | ||
|
|
||
| 4. **Update `provenance.json`** for each spec you moved. Set `client` to the | ||
| exact pin you wrote in `pyproject.toml` (`unstract-client==X.Y.Z`); the | ||
| tests compare the two, so a pin moved without its spec fails here. Check | ||
| `repo`, `path` and `commit` against what that client's `tools/gen_sdk.sh` | ||
| records, because an upstream that moved its spec file leaves `repo` and | ||
| `path` stale and the tests cannot see it: they check the pin, the `sha256` | ||
| and the entry names, nothing about where the file came from. The `sha256` | ||
| is of the file you just wrote (`sha256sum src/unstract_cli/specs/<file>`). | ||
| This record is what lets the next person tell a current copy from a stale | ||
| one. | ||
|
|
||
| 5. **Run the tests:** `uv run pytest -q`. | ||
|
|
||
| - `test_specs.py` fails if a vendored file stops matching its recorded | ||
| sha256, if its `client` no longer equals the pin in `pyproject.toml`, or if | ||
| a spec has no provenance entry. It is the cheap check that steps 1, 3 and | ||
| 4 actually agree. | ||
| - `test_contract.py` fails if a spec parameter the pinned client cannot accept | ||
| would have become a flag, and separately if the derived flags stop matching | ||
| `tests/derived_flags.json`. | ||
|
|
||
| 6. **If `derived_flags.json` fails, read the difference before refreshing it.** | ||
| The failure names the flags that moved. A flag missing from the new set is a | ||
| flag the CLI has stopped offering; a narrowed choice or changed type is a value | ||
| the CLI used to take and now rejects. Once you have decided the change is | ||
| intended, refresh it deliberately: | ||
|
|
||
| ```bash | ||
| UNSTRACT_CLI_REFRESH_FLAG_SNAPSHOT=1 uv run pytest -q tests/test_contract.py | ||
| ``` | ||
|
|
||
| and commit the snapshot in the same PR, so the diff shows what the CLI's | ||
| surface gained or lost. | ||
|
|
||
| 7. **Lint:** `uv run ruff check . && uv run ruff format --check .` — the release | ||
| run repeats exactly this, so a failure here is a failure there. | ||
|
|
||
| ## Versioning and release | ||
|
|
||
| Choose the bump by what changed for CLI users: **minor** for new or changed | ||
| flags and commands, **patch** for fixes that leave the surface identical. | ||
|
|
||
| Do not bump `__version__` in `src/unstract_cli/__init__.py` in your PR. The | ||
| in-repo value names the last released version; `release.yml` reads it, applies | ||
| the bump chosen at dispatch and commits the result itself. | ||
|
|
||
| Release by dispatching **Release Tag and Publish Package** on `main`: | ||
|
|
||
| - `version_bump: none` publishes the version already in the repo — what the | ||
| first release of a version needs. | ||
| - `pre_release: true` publishes `<next-version>rcN` and deliberately leaves the | ||
| committed version alone, counting N up from the rc tags already published for | ||
| that target. To promote to stable, dispatch again with it off **and the same | ||
| `version_bump`**: the workflow recomputes the target from that input every | ||
| time, so a different bump publishes a different version than the one the rc | ||
| tested. | ||
| - It publishes to PyPI **before** it tags and releases, because publishing is the | ||
| only step that cannot be undone: a failure before it leaves nothing to | ||
| unpublish, and one after it is retried by hand against a live artifact. | ||
|
|
||
| ## Upstream | ||
|
|
||
| If a client pin is missing an endpoint the service already offers, the fix is in | ||
| that client, not here — see the `spec-upgrade` skill in `unstract-python-client` | ||
| and `llm-whisperer-python-client`. Bump the pin here once it is released. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| name: ci | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| # Offline by design: no network, no credentials, sub-second. Live round trips | ||
| # are a manual pre-release step, not a per-PR gate. | ||
| test: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: astral-sh/setup-uv@v6 | ||
| with: | ||
| version: "0.6.14" | ||
| enable-cache: true | ||
| # Synced from uv.lock rather than resolved fresh, so the gate tests the | ||
| # dependency set an install actually gets. | ||
| - run: uv sync --extra dev --python 3.12 | ||
| - run: uv run ruff check . | ||
| - run: uv run ruff format --check . | ||
| - run: uv run pytest -q | ||
| # `uv run` resolves from uv.lock, whose click sits in the middle of the | ||
| # range the pin allows. Both ends are what an install in the wild gets, | ||
| # and discovery is a published contract read out of Click's own objects, | ||
| # so both ends are run and their answers compared. | ||
| - run: uv pip install 'click~=8.1.0' | ||
| - run: .venv/bin/python -m pytest -q | ||
| - run: .venv/bin/python -m unstract_cli -o json --discover full > floor.json | ||
| - run: uv pip install -U 'click>=8.1,<9' | ||
| - run: .venv/bin/python -m pytest -q | ||
| - run: .venv/bin/python -m unstract_cli -o json --discover full > latest.json | ||
| - run: diff floor.json latest.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| name: Release Tag and Publish Package | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version_bump: | ||
| description: "Version bump type. `none` publishes the version already in the repo, which is what the first release needs." | ||
| required: true | ||
| default: "patch" | ||
| type: choice | ||
| options: | ||
| - patch | ||
| - minor | ||
| - major | ||
| - none | ||
| pre_release: | ||
| description: "Publish a release candidate (`<version>rcN`) instead of the version itself. Dispatch again with this off to promote the same version to stable." | ||
| required: false | ||
| default: false | ||
| type: boolean | ||
| release_notes: | ||
| description: "Release notes (optional)" | ||
| required: false | ||
| type: string | ||
|
|
||
| concurrency: | ||
| group: release | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| release-and-publish: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| # Publishing is by PyPI Trusted Publisher, so there is no API token. | ||
| id-token: write | ||
| steps: | ||
| - name: Generate GitHub App Token | ||
| id: generate-token | ||
| uses: actions/create-github-app-token@v3 | ||
| with: | ||
| client-id: ${{ vars.PUSH_TO_MAIN_APP_CLIENT_ID }} | ||
| private-key: ${{ secrets.PUSH_TO_MAIN_APP_PRIVATE_KEY }} | ||
| owner: Zipstack | ||
| repositories: | | ||
| unstract-cli | ||
|
|
||
| - uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ steps.generate-token.outputs.token }} | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Configure Git | ||
| run: | | ||
| git config --global user.name "github-actions[bot]" | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - uses: astral-sh/setup-uv@v6 | ||
| with: | ||
| version: "0.6.14" | ||
| enable-cache: true | ||
|
|
||
| # The same install as ci.yml, so what the release run lints and tests is | ||
| # what the PR gate lints and tests. | ||
| - run: uv sync --extra dev --python 3.12 | ||
|
|
||
| # Staged locally only: nothing is committed, tagged or released until the | ||
| # checks and the build have passed, so a failure leaves main untouched. | ||
| - name: Compute new version | ||
| id: version | ||
| run: | | ||
| VERSION_FILE=src/unstract_cli/__init__.py | ||
| CURRENT_VERSION=$(sed -nE 's/^__version__ = "(.*)"/\1/p' "$VERSION_FILE") | ||
| echo "Current version: $CURRENT_VERSION" | ||
|
|
||
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" | ||
| case "${{ github.event.inputs.version_bump }}" in | ||
| major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; | ||
| minor) MINOR=$((MINOR + 1)); PATCH=0 ;; | ||
| patch) PATCH=$((PATCH + 1)) ;; | ||
| esac | ||
| NEXT_VERSION="$MAJOR.$MINOR.$PATCH" | ||
|
|
||
| # A pre-release is a candidate for NEXT_VERSION, not a version of its | ||
| # own, so it never moves the committed one: the file keeps naming the | ||
| # last stable release, and repeat dispatches count up from the rc tags | ||
| # already published for that target. | ||
| if [ "${{ github.event.inputs.pre_release }}" = "true" ]; then | ||
| HIGHEST_RC=$(git tag -l "v${NEXT_VERSION}rc*" \ | ||
| | sed -nE "s/^v${NEXT_VERSION}rc([0-9]+)$/\1/p" | sort -n | tail -1) | ||
| NEW_VERSION="${NEXT_VERSION}rc$(( ${HIGHEST_RC:-0} + 1 ))" | ||
| else | ||
| NEW_VERSION="$NEXT_VERSION" | ||
| fi | ||
|
|
||
| echo "New version: $NEW_VERSION" | ||
| echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT" | ||
|
|
||
| sed -i "s/^__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" "$VERSION_FILE" | ||
|
|
||
| if git rev-parse -q --verify "refs/tags/v$NEW_VERSION" >/dev/null; then | ||
| echo "Tag v$NEW_VERSION already exists. Exiting..." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Verify version update | ||
| run: | | ||
| BUILT_VERSION=$(uv run python -c "import unstract_cli; print(unstract_cli.__version__)") | ||
| echo "Package version: $BUILT_VERSION" | ||
| echo "Target version: ${{ steps.version.outputs.version }}" | ||
| if [ "$BUILT_VERSION" != "${{ steps.version.outputs.version }}" ]; then | ||
| echo "Version mismatch! Exiting..." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Run linting | ||
| run: | | ||
| uv run ruff check . | ||
| uv run ruff format --check . | ||
|
|
||
| - name: Run tests | ||
| run: uv run pytest -q | ||
|
|
||
| - name: Build package | ||
| run: uv build | ||
|
|
||
| # Everything revertible runs first: a tag, a branch and a release can all | ||
| # be deleted, and publishing cannot. Failing here leaves nothing on PyPI | ||
| # to reconcile; failing the other way around leaves a released version | ||
| # that no tag names. | ||
| - name: Commit version bump and create release | ||
| env: | ||
| RELEASE_NOTES: ${{ github.event.inputs.release_notes }} | ||
| GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} | ||
| run: | | ||
| NEW_VERSION="${{ steps.version.outputs.version }}" | ||
|
|
||
| # A pre-release leaves the committed version alone, and `none` | ||
| # publishes the version already in the file, so both reach here with | ||
| # nothing to commit. | ||
| if [ "${{ github.event.inputs.pre_release }}" = "true" ]; then | ||
| git checkout -- src/unstract_cli/__init__.py | ||
| elif ! git diff --quiet; then | ||
| git add src/unstract_cli/__init__.py | ||
| git commit -m "chore: bump version to $NEW_VERSION [skip ci]" | ||
| git push origin main | ||
| fi | ||
|
|
||
| git tag "v$NEW_VERSION" | ||
| git push origin "v$NEW_VERSION" | ||
|
|
||
| if [ -z "$RELEASE_NOTES" ]; then | ||
| gh release create "v$NEW_VERSION" \ | ||
| --title "Release v$NEW_VERSION" \ | ||
| --generate-notes \ | ||
| ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} | ||
| else | ||
| gh release create "v$NEW_VERSION" \ | ||
| --title "Release v$NEW_VERSION" \ | ||
| --notes "$RELEASE_NOTES" \ | ||
| --generate-notes \ | ||
| ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} | ||
| fi | ||
|
|
||
| echo "Created release v$NEW_VERSION" | ||
|
|
||
| - name: Publish to PyPI | ||
| run: uv publish | ||
|
|
||
| - name: Success message | ||
| run: | | ||
| echo "Published ${{ steps.version.outputs.version }} to PyPI with uv publish using Trusted Publishers" | ||
| echo "Release: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}" | ||
| echo "PyPI: https://pypi.org/project/unstract-cli/${{ steps.version.outputs.version }}/" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| .venv/ | ||
| __pycache__/ | ||
| *.egg-info/ | ||
| .pytest_cache/ | ||
| .ruff_cache/ | ||
| dist/ | ||
| build/ | ||
| .coverage | ||
| .coverage.* | ||
| htmlcov/ |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Medium] [Lens 11] — the reorder fixes finding #5 but trades one unrecoverable state for another, and the comment overstates what survives
The order is now push-main → tag → release → publish. That does fix the original hazard. But
uv publishis the step most likely to fail transiently (PyPI outage, OIDC hiccup, network), and when it does you are left with a public GitHub release for a version that is not installable:maincarries the bump, tagvXexists, the release is live, PyPI has nothing.The retry is blocked.
version_bump: nonerecomputes X and exits at the existing-tag guard (:105-108—fetch-depth: 0at:51means the tag is visible), whilepatchpublishes X+1 and leaves X permanently absent behind a live release.The comment at
:131-134says "a tag, a branch and a release can all be deleted" — omitting the fourth artifact, the bump commit already pushed to the default branch, which needs a revert push rather than a delete.Fix: create the release as a draft and un-draft it after
uv publishsucceeds; or let a retry skip the tag/push steps whenvXalready exists rather than exiting 1. If the order is intentional as-is, at minimum correct the comment to name the commit onmainand record the manual cleanup sequence.