--- /dev/null
+name: Paperless-ngx Release
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Release version (e.g., 2.18.3)"
+ required: true
+ type: string
+permissions:
+ contents: write
+ actions: read
+concurrency:
+ group: release-main
+ cancel-in-progress: false
+jobs:
+ release:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout (full)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Configure git
+ run: |
+ git config user.name "${{ github.actor }}"
+ git config user.email "${{ github.actor }}@users.noreply.github.com"
+ - name: Sanitize & validate input
+ id: ver
+ shell: bash
+ run: |
+ RAW="${{ github.event.inputs.version }}"
+ # trim spaces + strip leading 'v' if present
+ RAW="${RAW//[[:space:]]/}"
+ RAW="${RAW#v}"
+
+ # basic semver X.Y.Z
+ if [[ ! "$RAW" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "❌ Invalid version: '$RAW' (expected X.Y.Z or vX.Y.Z)"; exit 1
+ fi
+
+ MAJOR="${RAW%%.*}"
+ REST="${RAW#*.}"
+ MINOR="${REST%%.*}"
+ PATCH="${REST#*.}"
+
+ echo "version=$RAW" >> "$GITHUB_OUTPUT"
+ echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
+ echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
+ echo "patch=$PATCH" >> "$GITHUB_OUTPUT"
+
+ echo "✅ Using version $RAW"
+ - name: Ensure tag does not already exist
+ run: |
+ git fetch --tags
+ if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
+ echo "❌ Tag v${{ steps.ver.outputs.version }} already exists"; exit 1
+ fi
+ - name: Update local branches
+ run: |
+ git fetch origin main dev
+ - name: Fast-forward main to dev (no merge commits)
+ run: |
+ # Reset local main to remote, then try fast-forward to dev.
+ git checkout main
+ git reset --hard origin/main
+ # --ff-only ensures the workflow fails if the branches diverged.
+ git merge --ff-only origin/dev
+ echo "✅ main fast-forwarded to dev at $(git rev-parse --short HEAD)"
+ - name: Bump versions in files
+ shell: bash
+ run: |
+ VER="${{ steps.ver.outputs.version }}"
+ MAJ="${{ steps.ver.outputs.major }}"
+ MIN="${{ steps.ver.outputs.minor }}"
+ PAT="${{ steps.ver.outputs.patch }}"
+
+ # 1) pyproject.toml: [project] version = "X.Y.Z"
+ sed -i -E 's/^version = "[0-9]+\.[0-9]+\.[0-9]+"/version = "'"$VER"'"/' pyproject.toml
+
+ # 2) src-ui/package.json: "version": "X.Y.Z"
+ # Use jq if available; otherwise sed fallback.
+ if command -v jq >/dev/null 2>&1; then
+ tmp=$(mktemp)
+ jq --arg v "$VER" '.version=$v' src-ui/package.json > "$tmp" && mv "$tmp" src-ui/package.json
+ else
+ sed -i -E 's/"version": "[0-9]+\.[0-9]+\.[0-9]+"/"version": "'"$VER"'"/' src-ui/package.json
+ fi
+
+ # 3) src-ui/src/environments/environment.prod.ts: version: 'X.Y.Z'
+ sed -i -E "s/version: '[0-9]+\.[0-9]+\.[0-9]+'/version: '$VER'/" src-ui/src/environments/environment.prod.ts
+
+ # 4) src/paperless/version.py: __version__ = (X, Y, Z)
+ sed -i -E "s/__version__:\s*Final\[tuple\[int,\s*int,\s*int\]\]\s*=\s*\([0-9]+,\s*[0-9]+,\s*[0-9]+\)/__version__: Final[tuple[int, int, int]] = ($MAJ, $MIN, $PAT)/" src/paperless/version.py
+
+ # 5) uv.lock: in the [[package]] name = "paperless-ngx" block, set version = "X.Y.Z"
+ # This awk edits only the block for paperless-ngx.
+ awk -v ver="$VER" '
+ BEGIN{inpkg=0}
+ /^\[\[package\]\]/{inpkg=0}
+ /^\[\[package\]\]/{print; next}
+ {print > "/dev/stdout"}
+ ' uv.lock >/dev/null 2>&1 # noop to ensure awk exists
+
+ # More robust in-place edit with awk:
+ awk -v ver="$VER" '
+ BEGIN{inpkg=0}
+ /^\[\[package\]\]/{inpkg=0; print; next}
+ /^name = "paperless-ngx"/{inpkg=1; print; next}
+ inpkg && /^version = "/{
+ sub(/version = "[0-9]+\.[0-9]+\.[0-9]+"/, "version = \"" ver "\"")
+ print; next
+ }
+ {print}
+ ' uv.lock > uv.lock.new && mv uv.lock.new uv.lock
+
+ echo "✅ Files updated to $VER"
+ - name: Commit bump (if changes)
+ run: |
+ if git diff --quiet; then
+ echo "ℹ️ No changes to commit (versions may already match)";
+ else
+ git add pyproject.toml src-ui/package.json src-ui/src/environments/environment.prod.ts src/paperless/version.py uv.lock
+ git commit -m "Bump version to ${{ steps.ver.outputs.version }}"
+ fi
+ - name: Push main
+ run: |
+ # Push branch (even if no commit, ensures remote main == local)
+ git push origin HEAD:main
+ - name: Create and push tag
+ run: |
+ VER="${{ steps.ver.outputs.version }}"
+ git tag -a "v${VER}" -m "Release v${VER}"
+ git push origin "v${VER}"
+ - name: Done
+ run: echo "🎉 Release v${{ steps.ver.outputs.version }} created and pushed."