68 Commits

Author SHA1 Message Date
GitHub Actions
c8b57ba217 chore: bump version to 4.11.168 [skip ci] 2026-07-24 06:52:49 +00:00
8d8809d0c7 Bump cinny: open DMs while space parent maps catch up.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Successful in 2m44s
Build / build-windows (push) Successful in 3m45s
Build / finalize-release (push) Successful in 4s
2026-07-24 16:52:35 +10:00
GitHub Actions
55a0f4f566 chore: bump version to 4.11.167 [skip ci] 2026-07-24 06:24:58 +00:00
f488f32d88 Fix PowerShell mangling GitHub upload URLs with ?name=.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 13s
Build / build-linux (push) Successful in 2m45s
Build / build-windows (push) Successful in 3m43s
Build / finalize-release (push) Successful in 4s
2026-07-24 16:24:44 +10:00
GitHub Actions
9154929de9 chore: bump version to 4.11.166 [skip ci] 2026-07-24 06:20:05 +00:00
c2cc0d5ed5 Fix PowerShell origin URL trimming for submodule checkout.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 13s
Build / build-linux (push) Successful in 2m44s
Build / build-windows (push) Failing after 3m22s
Build / finalize-release (push) Successful in 8s
2026-07-24 16:19:51 +10:00
GitHub Actions
aa38348fdd chore: bump version to 4.11.165 [skip ci] 2026-07-24 06:17:38 +00:00
02ab544a37 Fix CI Windows upload shell and submodule clone host.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Failing after 11s
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
Use PowerShell + curl.exe on the Windows runner (no WSL), and clone cinny via a relative/origin-derived URL so act runners do not hairpin through public HTTPS.
2026-07-24 16:17:23 +10:00
GitHub Actions
3707a6d38a chore: bump version to 4.11.164 [skip ci] 2026-07-24 06:09:58 +00:00
fe1f4abf2d Bypass flaky Actions artifacts for AppImage/exe uploads.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 13s
Build / build-windows (push) Failing after 3m27s
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
Publish build outputs straight to Gitea/GitHub releases via the REST API with no HTTP timeout, since Gitea's artifact pipeline keeps timing out on ~130MB binaries.
2026-07-24 16:09:44 +10:00
147389724e Point cinny submodule at git.ruv.wtf. 2026-07-24 16:02:45 +10:00
GitHub Actions
d4d4c73c2b chore: bump version to 4.11.163 [skip ci] 2026-07-24 06:01:21 +00:00
73fbc1c77a Bump cinny: hide Jump to Latest when scrolled to live bottom.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / build-windows (push) Successful in 3m30s
Build / build-linux (push) Failing after 7m44s
Build / create-release (push) Has been cancelled
2026-07-24 16:01:06 +10:00
GitHub Actions
3dc732521f chore: bump version to 4.11.162 [skip ci] 2026-07-23 05:18:40 +00:00
b76aa3fe4a Add mock auto-updater in dev and title-bar download progress.
All checks were successful
Build / increment-version (push) Successful in 11s
Build / build-linux (push) Successful in 1m54s
Build / build-windows (push) Successful in 3m36s
Build / create-release (push) Successful in 1m13s
2026-07-23 15:18:25 +10:00
GitHub Actions
c0d1e87b8d chore: bump version to 4.11.161 [skip ci] 2026-07-23 05:14:07 +00:00
04d4026e78 Fix Linux SSO return via paarrot://desktop and protocol handler repair.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Has been cancelled
2026-07-23 15:13:54 +10:00
GitHub Actions
cfbc59d6a9 chore: bump version to 4.11.160 [skip ci] 2026-07-23 04:58:11 +00:00
dfe42fa942 Sync cinny: remove editor autocorrect workaround.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m54s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Successful in 1m12s
2026-07-23 14:57:58 +10:00
GitHub Actions
6a8397425e chore: bump version to 4.11.159 [skip ci] 2026-07-23 04:52:37 +00:00
641ec26142 Sync cinny: Shared Media drawer and same-room navigation fixes.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m59s
Build / build-windows (push) Successful in 3m47s
Build / create-release (push) Successful in 1m12s
2026-07-23 14:52:23 +10:00
GitHub Actions
1eaf246806 chore: bump version to 4.11.158 [skip ci] 2026-07-22 14:24:31 +00:00
0e97e4d3b6 Sync cinny: Stationery and Stationery Dark themes.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m58s
Build / build-windows (push) Successful in 3m37s
Build / create-release (push) Successful in 1m13s
2026-07-23 00:24:17 +10:00
GitHub Actions
1636a8d0c6 chore: bump version to 4.11.157 [skip ci] 2026-07-22 10:29:14 +00:00
ac236750aa Sync cinny: fix jumbo emoji regex for production builds.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m52s
Build / build-windows (push) Successful in 3m40s
Build / create-release (push) Successful in 1m12s
2026-07-22 20:29:00 +10:00
GitHub Actions
7e747e6c36 chore: bump version to 4.11.156 [skip ci] 2026-07-22 10:14:27 +00:00
1b2cae5b12 Sync cinny: fix jumbo emoji in production builds.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 3m42s
Build / create-release (push) Successful in 1m11s
2026-07-22 20:14:15 +10:00
GitHub Actions
76921c4e79 chore: bump version to 4.11.155 [skip ci] 2026-07-22 10:00:52 +00:00
688efff7c9 Sync cinny submodule and stop Vite from opening a browser on dev.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 3m36s
Build / create-release (push) Successful in 1m13s
Pull in timeline scroll memory, media height reservation, avatar caching, jumbo emoji, and Unicode 15 emoji font coverage.
2026-07-22 20:00:38 +10:00
GitHub Actions
9bc377b62a chore: bump version to 4.11.154 [skip ci] 2026-07-22 09:11:11 +00:00
836ccce7a3 Revert cinny to pre-jumbo emoji baseline.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 3m37s
Build / create-release (push) Successful in 1m13s
Roll back submodule to 154f4dfd before jumbo emoji and related styling regressions.
2026-07-22 19:10:58 +10:00
GitHub Actions
34c8e6e38c chore: bump version to 4.11.153 [skip ci] 2026-07-22 09:03:00 +00:00
e69bda3aa6 Bump cinny: fix production chat text color in Electron builds.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m51s
Build / build-windows (push) Successful in 3m43s
Build / create-release (push) Successful in 1m13s
2026-07-22 19:02:46 +10:00
GitHub Actions
5d55e5d826 chore: bump version to 4.11.152 [skip ci] 2026-07-22 08:37:39 +00:00
b5902b9fc3 Bump cinny: fix production CSS loading in Electron build.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m51s
Build / build-windows (push) Successful in 3m34s
Build / create-release (push) Successful in 1m12s
2026-07-22 18:37:26 +10:00
GitHub Actions
f60cf0bd3c chore: bump version to 4.11.151 [skip ci] 2026-07-22 08:21:32 +00:00
83dec0ef54 Bump cinny: fix black chat pane and jumbo emoji rendering.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m50s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Successful in 1m12s
2026-07-22 18:21:18 +10:00
GitHub Actions
40a76f02cd chore: bump version to 4.11.150 [skip ci] 2026-07-22 08:02:08 +00:00
3def3cc456 Bump cinny: instant room switches, perf cleanup, members drawer prefs.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m53s
Build / build-windows (push) Successful in 3m42s
Build / create-release (push) Successful in 1m13s
2026-07-22 18:01:53 +10:00
GitHub Actions
30bd67d43a chore: bump version to 4.11.149 [skip ci] 2026-07-22 06:16:20 +00:00
ffeccee680 Bump cinny: media placeholders, avatar blob cache, jumbo emoji.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m58s
Build / build-windows (push) Successful in 3m50s
Build / create-release (push) Successful in 1m13s
2026-07-22 14:42:19 +10:00
GitHub Actions
facbeee024 chore: bump version to 4.11.148 [skip ci] 2026-07-21 06:21:51 +00:00
09102c7903 Bump cinny: fix jumbo emoji actually rendering at 100px.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 5m42s
Build / create-release (push) Successful in 1m13s
2026-07-21 16:21:37 +10:00
GitHub Actions
fa33ec7ac8 chore: bump version to 4.11.147 [skip ci] 2026-07-21 05:56:18 +00:00
c44f5cf14a Bump cinny: fix jumbo standalone emoji sizing.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 5m21s
Build / create-release (push) Successful in 1m14s
2026-07-21 15:56:03 +10:00
GitHub Actions
27793d7c4f chore: bump version to 4.11.146 [skip ci] 2026-07-21 05:38:44 +00:00
32b2902ef7 Bump cinny: fix jumbo emoji vanilla-extract build.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m2s
Build / build-windows (push) Successful in 3m33s
Build / create-release (push) Successful in 1m13s
2026-07-21 15:38:29 +10:00
GitHub Actions
5cdb318854 chore: bump version to 4.11.145 [skip ci] 2026-07-21 05:34:16 +00:00
a28f05bb88 Bump cinny: theme polish, jumbo emoji, and Windows autocorrect fix.
Some checks failed
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Failing after 44s
Build / build-windows (push) Failing after 1m4s
Build / create-release (push) Has been skipped
2026-07-21 15:34:00 +10:00
GitHub Actions
cecfe73b2e chore: bump version to 4.11.144 [skip ci] 2026-07-14 17:31:07 +00:00
b458c7918c chore: bump cinny submodule for emoji font spacing fix
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m3s
Build / build-windows (push) Successful in 5m10s
Build / create-release (push) Successful in 1m13s
2026-07-15 03:30:53 +10:00
GitHub Actions
d8660f4822 chore: bump version to 4.11.143 [skip ci] 2026-07-12 16:40:22 +00:00
dad83773a6 Bump cinny: Twemoji digit fix and Slate emoji focus crash
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 4m48s
Build / create-release (push) Successful in 1m12s
2026-07-13 02:40:08 +10:00
GitHub Actions
d2d35de46b chore: bump version to 4.11.142 [skip ci] 2026-07-12 16:25:25 +00:00
c752ec0b38 Bump cinny: fix noise/echo cancellation settings
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 5m5s
Build / create-release (push) Successful in 1m13s
2026-07-13 02:25:10 +10:00
GitHub Actions
28752eb56e chore: bump version to 4.11.141 [skip ci] 2026-07-12 08:15:59 +00:00
4f243a0fcf Bump cinny: fix vanilla-extract presence CSS build failure.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 4m48s
Build / create-release (push) Successful in 1m13s
2026-07-12 18:15:44 +10:00
GitHub Actions
9bce616f26 chore: bump version to 4.11.140 [skip ci] 2026-07-11 22:19:18 +00:00
f16ab73c9a Allow git deps in npm and bump cinny submodule.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Failing after 42s
Build / build-windows (push) Failing after 1m47s
Build / create-release (push) Has been skipped
2026-07-12 08:19:03 +10:00
GitHub Actions
c29a15e073 chore: bump version to 4.11.139 [skip ci] 2026-07-11 21:52:03 +00:00
f5fe61e3eb hidden shit, and online status bars, search settings
Some checks failed
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Failing after 42s
Build / build-windows (push) Failing after 1m29s
Build / create-release (push) Has been skipped
2026-07-12 07:51:47 +10:00
GitHub Actions
a1a752b0fb chore: bump version to 4.11.138 [skip ci] 2026-07-11 21:15:56 +00:00
7769250875 Removed invalid channels
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 4m36s
Build / create-release (push) Successful in 1m13s
2026-07-12 07:15:28 +10:00
GitHub Actions
b3af9f3185 chore: bump version to 4.11.137 [skip ci] 2026-07-10 11:40:45 +00:00
7906326790 update base
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 4m56s
Build / create-release (push) Successful in 1m13s
2026-07-10 21:40:27 +10:00
GitHub Actions
3b4babb9d7 chore: bump version to 4.11.136 [skip ci] 2026-07-08 18:22:44 +00:00
4b284ec747 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m0s
Build / build-windows (push) Successful in 3m28s
Build / create-release (push) Successful in 1m12s
2026-07-09 04:22:28 +10:00
e1b4cfb9c8 chore: bump cinny submodule for compact nav and back routing fixes 2026-07-09 04:22:14 +10:00
8 changed files with 605 additions and 229 deletions

View File

@@ -8,6 +8,9 @@ on:
tags: tags:
- 'v*' - 'v*'
# Large AppImage/exe uploads time out on Gitea's actions artifact pipeline.
# Build jobs publish assets straight to Gitea/GitHub releases via the REST API.
jobs: jobs:
increment-version: increment-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -105,10 +108,101 @@ jobs:
echo "VERSION=${{ steps.decide.outputs.CURRENT_VERSION }}" >> $GITHUB_OUTPUT echo "VERSION=${{ steps.decide.outputs.CURRENT_VERSION }}" >> $GITHUB_OUTPUT
fi fi
prepare-release:
runs-on: ubuntu-latest
needs: increment-version
if: always() && needs.increment-version.outputs.should_build == 'true'
outputs:
version: ${{ steps.version.outputs.VERSION }}
tag: ${{ steps.version.outputs.TAG_NAME }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Pull latest (after version bump)
if: github.ref == 'refs/heads/main'
run: git pull origin main
- name: Resolve version
id: version
run: |
if [ -n "${{ needs.increment-version.outputs.version }}" ]; then
VERSION="${{ needs.increment-version.outputs.version }}"
else
VERSION=$(grep -Po '"version":\s*"\K[^"]+' package.json | head -1)
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "TAG_NAME=v$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Create and push tag
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "Tag $TAG_NAME already exists"
else
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
git push origin "$TAG_NAME"
fi
- name: Ensure Gitea release
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
API_BASE="${{ github.server_url }}/api/v1"
REPO="${{ github.repository }}"
RELEASE_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty' || true)
if [ -z "$RELEASE_ID" ]; then
echo "Creating Gitea release ${TAG_NAME}..."
RELEASE_ID=$(curl -fsS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"${API_BASE}/repos/${REPO}/releases" | jq -r '.id')
fi
echo "Gitea release id: ${RELEASE_ID}"
- name: Ensure GitHub release
env:
GH_TOKEN: ${{ secrets.GHTOKEN }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
GITHUB_REPO="Paarrot/Paarrot-Desktop"
RELEASE_ID=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty' || true)
if [ -z "$RELEASE_ID" ]; then
echo "Creating GitHub release ${TAG_NAME}..."
RELEASE_ID=$(curl -fsS -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.id')
fi
echo "GitHub release id: ${RELEASE_ID}"
build-windows: build-windows:
runs-on: windows runs-on: windows
needs: increment-version needs: [increment-version, prepare-release]
if: always() && (needs.increment-version.outputs.should_build == 'true' || needs.increment-version.result == 'skipped') if: always() && needs.prepare-release.result == 'success'
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -120,16 +214,29 @@ jobs:
- name: Checkout submodules manually - name: Checkout submodules manually
shell: powershell shell: powershell
run: | run: |
$ErrorActionPreference = "Stop"
# Use the same host/scheme the runner already used for origin (not public HTTPS).
$origin = (git remote get-url origin).Trim()
$origin = $origin.TrimEnd('/')
if ($origin.EndsWith('.git')) { $origin = $origin.Substring(0, $origin.Length - 4) }
$base = $origin.Substring(0, $origin.LastIndexOf('/'))
$cinnyUrl = "$base/cinny.git"
Write-Host "Rewriting submodule URL to $cinnyUrl"
git config submodule.cinny.url $cinnyUrl
git submodule sync --recursive git submodule sync --recursive
if (git submodule update --init --recursive) { git submodule update --init --recursive
if ($LASTEXITCODE -eq 0) {
Write-Host "Submodules checked out at pinned commits." Write-Host "Submodules checked out at pinned commits."
exit 0 exit 0
} }
Write-Warning "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD." Write-Warning "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD."
git submodule deinit -f --all git submodule deinit -f --all
git config submodule.cinny.url $cinnyUrl
git submodule sync --recursive
git submodule update --init --recursive --remote git submodule update --init --recursive --remote
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Host "Resolved submodule commit:" Write-Host "Resolved submodule commit:"
git -C cinny rev-parse HEAD git -C cinny rev-parse HEAD
@@ -162,17 +269,92 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
- name: Upload NSIS Installer (x64) - name: Upload Windows assets to releases
uses: actions/upload-artifact@v3 shell: powershell
with: env:
name: Paarrot-Setup-x64.exe GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
path: dist-electron/Paarrot-*-win-x64.exe GH_TOKEN: ${{ secrets.GHTOKEN }}
run: |
- name: Upload latest.yml $ErrorActionPreference = "Stop"
uses: actions/upload-artifact@v3 $TAG_NAME = "${{ needs.prepare-release.outputs.tag }}"
with: $API_BASE = "${{ github.server_url }}/api/v1"
name: latest.yml $REPO = "${{ github.repository }}"
path: dist-electron/latest.yml $GITHUB_REPO = "Paarrot/Paarrot-Desktop"
function Invoke-JsonCurl([string[]]$CurlArgs) {
$raw = & curl.exe @CurlArgs
if ($LASTEXITCODE -ne 0) { throw "curl failed: $($CurlArgs -join ' ')" }
return ($raw | ConvertFrom-Json)
}
$giteaRelease = Invoke-JsonCurl @(
"-fsS", "-H", "Authorization: token $env:GITEA_TOKEN",
"$API_BASE/repos/$REPO/releases/tags/$TAG_NAME"
)
$ghRelease = Invoke-JsonCurl @(
"-fsS", "-H", "Authorization: token $env:GH_TOKEN",
"https://api.github.com/repos/$GITHUB_REPO/releases/tags/$TAG_NAME"
)
# GitHub returns upload_url like .../assets{?name,label}
$ghUploadUrl = [string]$ghRelease.upload_url
if ([string]::IsNullOrWhiteSpace($ghUploadUrl)) {
throw "GitHub release $($ghRelease.id) has no upload_url"
}
$ghUploadUrl = $ghUploadUrl -replace '\{\?[^}]*\}', ''
Write-Host "GitHub upload base: $ghUploadUrl"
function Upload-One([string]$File) {
$filename = [System.IO.Path]::GetFileName($File)
$sizeMb = [math]::Round((Get-Item $File).Length / 1MB, 1)
Write-Host "Uploading $filename (${sizeMb} MB)..."
$giteaAssets = Invoke-JsonCurl @(
"-fsS", "-H", "Authorization: token $env:GITEA_TOKEN",
"$API_BASE/repos/$REPO/releases/$($giteaRelease.id)/assets"
)
foreach ($asset in @($giteaAssets)) {
if ($asset.name -eq $filename) {
& curl.exe -fsS -X DELETE -H "Authorization: token $env:GITEA_TOKEN" `
"$API_BASE/repos/$REPO/releases/$($giteaRelease.id)/assets/$($asset.id)" | Out-Null
}
}
& curl.exe -fsS --max-time 0 --retry 5 --retry-delay 10 `
-X POST `
-H "Authorization: token $env:GITEA_TOKEN" `
-H "Content-Type: application/octet-stream" `
--data-binary "@$File" `
"$API_BASE/repos/$REPO/releases/$($giteaRelease.id)/assets?name=$filename" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Gitea upload failed for $filename" }
Write-Host " Gitea: ok"
$ghAssets = Invoke-JsonCurl @(
"-fsS", "-H", "Authorization: token $env:GH_TOKEN",
"https://api.github.com/repos/$GITHUB_REPO/releases/$($ghRelease.id)/assets"
)
foreach ($asset in @($ghAssets)) {
if ($asset.name -eq $filename) {
& curl.exe -fsS -X DELETE -H "Authorization: token $env:GH_TOKEN" `
"https://api.github.com/repos/$GITHUB_REPO/releases/assets/$($asset.id)" | Out-Null
}
}
# Brace the variable — `$url?name=` is parsed as PowerShell's null-conditional.
$ghAssetUrl = "${ghUploadUrl}?name=$([uri]::EscapeDataString($filename))"
Write-Host " GitHub URL: $ghAssetUrl"
& curl.exe -fsS --max-time 0 --retry 5 --retry-delay 10 `
-X POST `
-H "Authorization: token $env:GH_TOKEN" `
-H "Content-Type: application/octet-stream" `
--data-binary "@$File" `
$ghAssetUrl | Out-Null
if ($LASTEXITCODE -ne 0) { throw "GitHub upload failed for $filename" }
Write-Host " GitHub: ok"
}
Get-ChildItem -Path dist-electron -File |
Where-Object { $_.Name -like 'Paarrot-*-win-x64.exe' -or $_.Name -eq 'latest.yml' } |
ForEach-Object { Upload-One $_.FullName }
- name: List build output - name: List build output
shell: powershell shell: powershell
@@ -181,8 +363,8 @@ jobs:
build-linux: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: increment-version needs: [increment-version, prepare-release]
if: always() && (needs.increment-version.outputs.should_build == 'true' || needs.increment-version.result == 'skipped') if: always() && needs.prepare-release.result == 'success'
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -194,6 +376,14 @@ jobs:
- name: Checkout submodules manually - name: Checkout submodules manually
run: | run: |
set -euo pipefail set -euo pipefail
# Use the same host/scheme the runner already used for origin (not public HTTPS).
ORIGIN_URL=$(git remote get-url origin)
ORIGIN_URL=${ORIGIN_URL%/}
ORIGIN_URL=${ORIGIN_URL%.git}
BASE_URL=${ORIGIN_URL%/*}
CINNY_URL="${BASE_URL}/cinny.git"
echo "Rewriting submodule URL to ${CINNY_URL}"
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive git submodule sync --recursive
if git submodule update --init --recursive; then if git submodule update --init --recursive; then
@@ -202,7 +392,10 @@ jobs:
fi fi
echo "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD." echo "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD."
git submodule deinit -f --all git submodule deinit -f --all || true
rm -rf cinny .git/modules/cinny
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive
git submodule update --init --recursive --remote git submodule update --init --recursive --remote
echo "Resolved submodule commit:" echo "Resolved submodule commit:"
@@ -233,208 +426,126 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
- name: Upload AppImage - name: Upload Linux assets to releases
uses: actions/upload-artifact@v3 env:
with: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Paarrot-Linux-x64.AppImage GH_TOKEN: ${{ secrets.GHTOKEN }}
path: dist-electron/Paarrot-*.AppImage run: |
set -euo pipefail
- name: Upload latest-linux.yml VERSION="${{ needs.prepare-release.outputs.version }}"
uses: actions/upload-artifact@v3 TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
with: API_BASE="${{ github.server_url }}/api/v1"
name: latest-linux.yml REPO="${{ github.repository }}"
path: dist-electron/latest-linux.yml GITHUB_REPO="Paarrot/Paarrot-Desktop"
create-release: GITEA_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_UPLOAD_URL=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
upload_one() {
local FILE="$1"
local FILENAME
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME} ($(du -h "$FILE" | cut -f1))..."
OLD_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_ID:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets/${OLD_ID}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets?name=${FILENAME}" \
>/dev/null
echo " Gitea: ok"
OLD_GH=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_GH:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/assets/${OLD_GH}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${GH_UPLOAD_URL}?name=${FILENAME}" \
>/dev/null
echo " GitHub: ok"
}
shopt -s nullglob
for FILE in dist-electron/Paarrot-*.AppImage dist-electron/latest-linux.yml; do
upload_one "$FILE"
done
finalize-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [increment-version, build-windows, build-linux] needs: [increment-version, prepare-release, build-windows, build-linux]
if: always() && (needs.build-windows.result == 'success' || needs.build-linux.result == 'success') if: always() && needs.prepare-release.result == 'success' && (needs.build-windows.result == 'success' || needs.build-linux.result == 'success')
steps: steps:
- name: Checkout repository - name: Cleanup old Gitea releases
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Pull latest (after version bump)
if: github.ref == 'refs/heads/main'
run: git pull origin main
- name: Get version
id: version
run: |
# Use version from increment-version job if available, otherwise read from file
if [ -n "${{ needs.increment-version.outputs.version }}" ]; then
VERSION="${{ needs.increment-version.outputs.version }}"
else
VERSION=$(grep -Po '"version":\s*"\K[^"]+' package.json | head -1)
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Check if tag exists
id: tag_check
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "Tag v$VERSION already exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Tag v$VERSION does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create and push tag
if: steps.tag_check.outputs.exists == 'false'
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag -a "v${{ steps.version.outputs.VERSION }}" -m "Release v${{ steps.version.outputs.VERSION }}"
git push origin "v${{ steps.version.outputs.VERSION }}"
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: artifacts
- name: Prepare release files
run: |
mkdir -p release-files
find artifacts -type f \( -name "*.exe" -o -name "*.AppImage" -o -name "*.yml" \) -exec cp {} release-files/ \;
ls -la release-files/
- name: Create or Update Release
env: env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
VERSION="${{ steps.version.outputs.VERSION }}" set -euo pipefail
TAG_NAME="v${VERSION}" TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
API_BASE="http://synbox.ruv.wtf:8418/api/v1" API_BASE="${{ github.server_url }}/api/v1"
REPO="litruv/cinny-desktop" REPO="${{ github.repository }}"
# Delete old releases (keep only current) echo "Cleaning up old Gitea releases (keeping ${TAG_NAME})..."
echo "Cleaning up old releases..." OLD_RELEASES=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
OLD_RELEASES=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ "${API_BASE}/repos/${REPO}/releases" | jq -r --arg t "$TAG_NAME" '.[] | select(.tag_name != $t) | .id')
"${API_BASE}/repos/${REPO}/releases" | jq -r '.[] | select(.tag_name != "'${TAG_NAME}'") | .id')
for OLD_ID in $OLD_RELEASES; do for OLD_ID in $OLD_RELEASES; do
echo "Deleting old release ${OLD_ID}..." [ -n "$OLD_ID" ] || continue
OLD_TAG=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ OLD_TAG=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${OLD_ID}" | jq -r '.tag_name') "${API_BASE}/repos/${REPO}/releases/${OLD_ID}" | jq -r '.tag_name')
echo "Deleting Gitea release ${OLD_ID} (${OLD_TAG})..."
# Delete release curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ "${API_BASE}/repos/${REPO}/releases/${OLD_ID}" >/dev/null || true
"${API_BASE}/repos/${REPO}/releases/${OLD_ID}" if [ -n "$OLD_TAG" ] && [ "$OLD_TAG" != "null" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
# Delete tag "${API_BASE}/repos/${REPO}/tags/${OLD_TAG}" >/dev/null || true
if [ -n "$OLD_TAG" ]; then
echo "Deleting old tag ${OLD_TAG}..."
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/tags/${OLD_TAG}"
fi fi
done done
# Check if release exists
RELEASE_ID=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then
echo "Creating new release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"${API_BASE}/repos/${REPO}/releases" | jq -r '.id')
echo "Created release with ID: ${RELEASE_ID}"
else
echo "Release exists with ID: ${RELEASE_ID}"
# Delete existing assets
echo "Deleting existing assets..."
ASSETS=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets" | jq -r '.[].id')
for ASSET_ID in $ASSETS; do
echo "Deleting asset ${ASSET_ID}..."
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
done
fi
# Upload new assets
echo "Uploading assets..."
for FILE in release-files/*; do
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME}..."
curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${FILE}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
echo ""
done
echo "Release complete!"
- name: Create GitHub Release - name: Cleanup old GitHub releases
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
run: | run: |
VERSION="${{ steps.version.outputs.VERSION }}" set -euo pipefail
TAG_NAME="v${VERSION}" TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
GITHUB_REPO="Paarrot/Paarrot-Desktop" GITHUB_REPO="Paarrot/Paarrot-Desktop"
# Delete old GitHub releases (keep only current) echo "Cleaning up old GitHub releases (keeping ${TAG_NAME})..."
echo "Cleaning up old GitHub releases..." OLD_RELEASES=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
OLD_RELEASES=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ "https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r --arg t "$TAG_NAME" '.[] | select(.tag_name != $t) | .id')
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.[] | select(.tag_name != "'${TAG_NAME}'") | .id')
for OLD_ID in $OLD_RELEASES; do for OLD_ID in $OLD_RELEASES; do
echo "Deleting old GitHub release ${OLD_ID}..." [ -n "$OLD_ID" ] || continue
OLD_TAG=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ OLD_TAG=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" | jq -r '.tag_name') "https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" | jq -r '.tag_name')
echo "Deleting GitHub release ${OLD_ID} (${OLD_TAG})..."
# Delete release curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
curl -s -X DELETE -H "Authorization: token ${GH_TOKEN}" \ "https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" >/dev/null || true
"https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" if [ -n "$OLD_TAG" ] && [ "$OLD_TAG" != "null" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
# Delete tag "https://api.github.com/repos/${GITHUB_REPO}/git/refs/tags/${OLD_TAG}" >/dev/null || true
if [ -n "$OLD_TAG" ]; then
echo "Deleting old GitHub tag ${OLD_TAG}..."
curl -s -X DELETE -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/git/refs/tags/${OLD_TAG}"
fi fi
done done
# Check if release exists on GitHub echo "Release finalize complete for ${TAG_NAME}"
RELEASE_ID=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then
echo "Creating new GitHub release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.id')
echo "Created GitHub release with ID: ${RELEASE_ID}"
else
echo "GitHub release exists with ID: ${RELEASE_ID}"
fi
# Upload assets to GitHub
echo "Uploading assets to GitHub..."
UPLOAD_URL=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
for FILE in release-files/*; do
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME} to GitHub..."
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${FILE}" \
"${UPLOAD_URL}?name=${FILENAME}"
echo ""
done
echo "GitHub release complete!"

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "cinny"] [submodule "cinny"]
path = cinny path = cinny
url = http://synbox.ruv.wtf:8418/litruv/cinny.git url = ../cinny.git

3
.npmrc
View File

@@ -1 +1,2 @@
save-exact=true save-exact=true
allow-git=all

2
cinny

Submodule cinny updated: b4258278d8...99a294791e

View File

@@ -247,6 +247,10 @@ function extractDeepLinkFromArgv(argv) {
/** /**
* Converts a paarrot:// deep link into an in-app hash route. * Converts a paarrot:// deep link into an in-app hash route.
* Supports:
* - Hash routes: paarrot://anything/#/login/server/
* - Path routes (SSO-safe): paarrot://desktop/login/server/?loginToken=...
* Preserves SSO loginToken from the URL query (Synapse inserts it before `#`).
* @param {string} deepLink - The incoming protocol URL. * @param {string} deepLink - The incoming protocol URL.
* @returns {string|null} Hash route value without leading #. * @returns {string|null} Hash route value without leading #.
*/ */
@@ -259,17 +263,63 @@ function deepLinkToHashRoute(deepLink) {
return null; return null;
} }
const hashIndex = deepLink.indexOf('#'); let hashRoute = null;
if (hashIndex === -1) { let loginToken = null;
return null;
try {
const parsed = new URL(deepLink);
loginToken = parsed.searchParams.get('loginToken');
if (parsed.hash && parsed.hash.length > 1) {
const hashValue = parsed.hash.slice(1);
hashRoute = hashValue.startsWith('/') ? hashValue : `/${hashValue}`;
} else if (parsed.pathname && parsed.pathname !== '/') {
// paarrot://desktop/login/... → /login/...
hashRoute = parsed.pathname;
if (parsed.search && parsed.search.length > 1) {
// Keep non-loginToken query on the route; loginToken merged below
const params = new URLSearchParams(parsed.search);
params.delete('loginToken');
const rest = params.toString();
if (rest) {
hashRoute += `?${rest}`;
}
}
}
} catch {
const hashIndex = deepLink.indexOf('#');
if (hashIndex !== -1) {
const hashValue = deepLink.slice(hashIndex + 1);
if (hashValue) {
hashRoute = hashValue.startsWith('/') ? hashValue : `/${hashValue}`;
}
}
const match = deepLink.match(/[?&]loginToken=([^&#]+)/i);
if (match) {
try {
loginToken = decodeURIComponent(match[1]);
} catch {
loginToken = match[1];
}
}
} }
const hashValue = deepLink.slice(hashIndex + 1); if (loginToken) {
if (!hashValue) { if (!hashRoute) {
return null; hashRoute = '/';
}
const queryIndex = hashRoute.indexOf('?');
const pathPart = queryIndex === -1 ? hashRoute : hashRoute.slice(0, queryIndex);
const params = new URLSearchParams(queryIndex === -1 ? '' : hashRoute.slice(queryIndex + 1));
if (!params.has('loginToken')) {
params.set('loginToken', loginToken);
}
const query = params.toString();
hashRoute = query ? `${pathPart}?${query}` : pathPart;
} }
return hashValue.startsWith('/') ? hashValue : `/${hashValue}`; return hashRoute;
} }
/** /**
@@ -389,6 +439,129 @@ function syncProtocolRegistrationState() {
console.log(`[Protocol] ${PROTOCOL_SCHEME}:// before=${protocolRegistrationState.before} registerCall=${protocolRegistrationState.registerCall} after=${protocolRegistrationState.after}${protocolRegistrationState.error ? ` error=${protocolRegistrationState.error}` : ''}`); console.log(`[Protocol] ${PROTOCOL_SCHEME}:// before=${protocolRegistrationState.before} registerCall=${protocolRegistrationState.registerCall} after=${protocolRegistrationState.after}${protocolRegistrationState.error ? ` error=${protocolRegistrationState.error}` : ''}`);
} }
/**
* Refresh Linux xdg-mime association for paarrot:// to the current app desktop file.
* AppImage upgrades often leave a stale desktop id in mimeapps.list.
*/
async function ensureLinuxProtocolHandler() {
const home = app.getPath('home');
const searchDirs = [
path.join(home, '.local', 'share', 'applications'),
'/usr/share/applications',
'/var/lib/flatpak/exports/share/applications',
];
let desktopId = null;
for (const dir of searchDirs) {
try {
if (!fs.existsSync(dir)) continue;
const entries = fs.readdirSync(dir);
// Prefer the newest AppImageLauncher desktop entry when several exist.
const matches = entries
.filter((name) => /paarrot/i.test(name) && name.endsWith('.desktop'))
.map((name) => {
try {
return { name, mtime: fs.statSync(path.join(dir, name)).mtimeMs };
} catch {
return { name, mtime: 0 };
}
})
.sort((a, b) => b.mtime - a.mtime);
if (matches.length > 0) {
desktopId = matches[0].name;
break;
}
} catch {
// keep looking
}
}
if (!desktopId) {
desktopId = 'paarrot.desktop';
}
// Rewrite mimeapps.list so stale AppImage ids cannot win over xdg-mime default.
try {
const mimeappsPath = path.join(home, '.config', 'mimeapps.list');
let content = '';
try {
content = fs.readFileSync(mimeappsPath, 'utf8');
} catch {
content = '';
}
const schemeLine = `x-scheme-handler/${PROTOCOL_SCHEME}=${desktopId}`;
const lines = content ? content.split(/\r?\n/) : [];
let section = null;
const next = [];
let wroteDefault = false;
let wroteAdded = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === '[Default Applications]' || trimmed === '[Added Associations]') {
section = trimmed;
next.push(line);
continue;
}
if (trimmed.startsWith('x-scheme-handler/paarrot=')) {
if (section === '[Default Applications]') {
next.push(schemeLine);
wroteDefault = true;
} else if (section === '[Added Associations]') {
next.push(`${schemeLine};`);
wroteAdded = true;
}
continue;
}
next.push(line);
}
if (!next.some((l) => l.trim() === '[Default Applications]')) {
next.push('', '[Default Applications]');
}
if (!wroteDefault) {
const idx = next.findIndex((l) => l.trim() === '[Default Applications]');
next.splice(idx + 1, 0, schemeLine);
}
if (!next.some((l) => l.trim() === '[Added Associations]')) {
next.unshift('[Added Associations]');
}
if (!wroteAdded) {
const idx = next.findIndex((l) => l.trim() === '[Added Associations]');
next.splice(idx + 1, 0, `${schemeLine};`);
}
fs.mkdirSync(path.dirname(mimeappsPath), { recursive: true });
fs.writeFileSync(mimeappsPath, `${next.join('\n').replace(/\n{3,}/g, '\n\n').trim()}\n`);
console.log(`[Protocol] mimeapps.list → ${desktopId} for x-scheme-handler/${PROTOCOL_SCHEME}`);
} catch (error) {
console.warn('[Protocol] Failed to rewrite mimeapps.list:', error);
}
await new Promise((resolve) => {
execFile(
'xdg-mime',
['default', desktopId, `x-scheme-handler/${PROTOCOL_SCHEME}`],
{ timeout: 5000 },
(error, _stdout, stderr) => {
if (error) {
console.warn(`[Protocol] xdg-mime default failed (${desktopId}):`, error.message, stderr);
} else {
console.log(`[Protocol] xdg-mime default → ${desktopId} for x-scheme-handler/${PROTOCOL_SCHEME}`);
}
resolve();
}
);
});
await new Promise((resolve) => {
execFile('update-desktop-database', [path.join(home, '.local', 'share', 'applications')], { timeout: 5000 }, () => {
resolve();
});
});
}
/** /**
* Reads a named Windows registry value. * Reads a named Windows registry value.
* @param {string} keyPath - Registry key path. * @param {string} keyPath - Registry key path.
@@ -920,6 +1093,12 @@ app.whenReady().then(() => {
syncProtocolRegistrationState(); syncProtocolRegistrationState();
if (process.platform === 'linux') {
ensureLinuxProtocolHandler().catch((error) => {
console.warn('[Protocol] Startup Linux protocol ensure failed:', error);
});
}
createWindow(); createWindow();
createTray(); createTray();
@@ -933,18 +1112,27 @@ app.whenReady().then(() => {
console.error('Failed to start API server:', err); console.error('Failed to start API server:', err);
}); });
// Check for updates on startup (not in development mode) // Check for updates on startup
if (!isDev) { setTimeout(() => {
// Check for updates on start (after 3 seconds) if (isDev) {
setTimeout(() => { // Exercise the title-bar updater UI with a mock update.
autoUpdater.checkForUpdates().catch(err => { sendUpdaterEvent('update-available', {
console.error('Failed to check for updates:', err); version: '99.0.0-dev',
releaseNotes: 'Mock update for development — download to preview the title-bar progress UI.',
releaseDate: new Date().toISOString(),
mock: true,
}); });
}, 3000); return;
}
autoUpdater.checkForUpdates().catch((err) => {
console.error('Failed to check for updates:', err);
});
}, 2500);
if (!isDev) {
// Check for updates every 6 hours // Check for updates every 6 hours
setInterval(() => { setInterval(() => {
autoUpdater.checkForUpdates().catch(err => { autoUpdater.checkForUpdates().catch((err) => {
console.error('Failed to check for updates:', err); console.error('Failed to check for updates:', err);
}); });
}, 6 * 60 * 60 * 1000); }, 6 * 60 * 60 * 1000);
@@ -1053,6 +1241,14 @@ ipcMain.handle('protocol:repair', async () => {
syncProtocolRegistrationState(); syncProtocolRegistrationState();
if (process.platform === 'linux') {
try {
await ensureLinuxProtocolHandler();
} catch (error) {
console.warn('[Protocol] Failed to refresh Linux protocol handler:', error);
}
}
const status = await getProtocolStatusPayload(); const status = await getProtocolStatusPayload();
return { return {
success: true, success: true,
@@ -1195,10 +1391,61 @@ ipcMain.handle('get-background-sync-state', async () => {
return { success: true, data: 'NotApplicable' }; return { success: true, data: 'NotApplicable' };
}); });
// Auto-updater IPC handlers // Auto-updater IPC handlers (mock in development so title-bar UI can be exercised)
let mockDownloadTimer = null;
let mockDownloadPercent = 0;
function sendUpdaterEvent(channel, payload) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(channel, payload);
}
}
function startMockDownload() {
if (mockDownloadTimer) {
clearInterval(mockDownloadTimer);
}
mockDownloadPercent = 0;
sendUpdaterEvent('update-download-progress', {
percent: 0,
transferred: 0,
total: 100,
mock: true,
});
mockDownloadTimer = setInterval(() => {
mockDownloadPercent = Math.min(100, mockDownloadPercent + 4 + Math.random() * 6);
const percent = Math.round(mockDownloadPercent);
sendUpdaterEvent('update-download-progress', {
percent,
transferred: percent,
total: 100,
mock: true,
});
if (percent >= 100) {
clearInterval(mockDownloadTimer);
mockDownloadTimer = null;
sendUpdaterEvent('update-downloaded', {
version: '99.0.0-dev',
releaseNotes: 'Mock update for development.',
mock: true,
});
}
}, 180);
}
ipcMain.handle('check-for-updates', async () => { ipcMain.handle('check-for-updates', async () => {
if (isDev) { if (isDev) {
return { success: false, error: 'Updates are not available in development mode' }; sendUpdaterEvent('update-checking', { mock: true });
await new Promise((resolve) => setTimeout(resolve, 600));
sendUpdaterEvent('update-available', {
version: '99.0.0-dev',
releaseNotes: 'Mock update for development — download to preview the title-bar progress UI.',
releaseDate: new Date().toISOString(),
mock: true,
});
return { success: true, data: { updateInfo: { version: '99.0.0-dev' }, mock: true } };
} }
try { try {
const result = await autoUpdater.checkForUpdates(); const result = await autoUpdater.checkForUpdates();
@@ -1210,7 +1457,8 @@ ipcMain.handle('check-for-updates', async () => {
ipcMain.handle('download-update', async () => { ipcMain.handle('download-update', async () => {
if (isDev) { if (isDev) {
return { success: false, error: 'Updates are not available in development mode' }; startMockDownload();
return { success: true, data: { mock: true } };
} }
try { try {
await autoUpdater.downloadUpdate(); await autoUpdater.downloadUpdate();
@@ -1222,7 +1470,12 @@ ipcMain.handle('download-update', async () => {
ipcMain.handle('install-update', () => { ipcMain.handle('install-update', () => {
if (isDev) { if (isDev) {
return { success: false, error: 'Updates are not available in development mode' }; sendUpdaterEvent('update-not-available', {
version: app.getVersion(),
mock: true,
message: 'Mock install — restart skipped in development.',
});
return { success: true, data: { mock: true } };
} }
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
return { success: true }; return { success: true };
@@ -1230,7 +1483,8 @@ ipcMain.handle('install-update', () => {
ipcMain.handle('download-and-install-update', async () => { ipcMain.handle('download-and-install-update', async () => {
if (isDev) { if (isDev) {
return { success: false, error: 'Updates are not available in development mode' }; startMockDownload();
return { success: true, data: { mock: true } };
} }
try { try {
await autoUpdater.downloadUpdate(); await autoUpdater.downloadUpdate();
@@ -1241,6 +1495,8 @@ ipcMain.handle('download-and-install-update', async () => {
} }
}); });
ipcMain.handle('updater-is-mock', () => ({ success: true, data: { mock: isDev } }));
// Screen capture / desktopCapturer handler // Screen capture / desktopCapturer handler
ipcMain.handle('get-desktop-sources', async (event, opts) => { ipcMain.handle('get-desktop-sources', async (event, opts) => {
try { try {

View File

@@ -130,6 +130,7 @@ contextBridge.exposeInMainWorld('electron', {
updater: { updater: {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadAndInstallUpdate: () => ipcRenderer.invoke('download-and-install-update'), downloadAndInstallUpdate: () => ipcRenderer.invoke('download-and-install-update'),
isMock: () => ipcRenderer.invoke('updater-is-mock'),
onUpdateAvailable: (callback) => { onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (_, info) => callback(info)); ipcRenderer.on('update-available', (_, info) => callback(info));
}, },
@@ -139,6 +140,9 @@ contextBridge.exposeInMainWorld('electron', {
onUpdateDownloaded: (callback) => { onUpdateDownloaded: (callback) => {
ipcRenderer.on('update-downloaded', (_, info) => callback(info)); ipcRenderer.on('update-downloaded', (_, info) => callback(info));
}, },
onUpdateNotAvailable: (callback) => {
ipcRenderer.on('update-not-available', (_, info) => callback(info));
},
downloadUpdate: () => ipcRenderer.invoke('download-update'), downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'), installUpdate: () => ipcRenderer.invoke('install-update'),
}, },

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "paarrot", "name": "paarrot",
"version": "4.11.133", "version": "4.11.137",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "paarrot", "name": "paarrot",
"version": "4.11.133", "version": "4.11.137",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.18", "adm-zip": "^0.5.18",

View File

@@ -1,6 +1,6 @@
{ {
"name": "paarrot", "name": "paarrot",
"version": "4.11.135", "version": "4.11.168",
"description": "Paarrot - A Matrix client based on Cinny", "description": "Paarrot - A Matrix client based on Cinny",
"homepage": "https://github.com/Paarrot/Paarrot-Desktop", "homepage": "https://github.com/Paarrot/Paarrot-Desktop",
"repository": { "repository": {
@@ -13,7 +13,7 @@
}, },
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev:vite\" \"npm run dev:electron\"", "dev": "concurrently \"npm run dev:vite\" \"npm run dev:electron\"",
"dev:vite": "cd cinny && npm start", "dev:vite": "cross-env BROWSER=none sh -c 'cd cinny && npm start'",
"dev:electron": "wait-on http://localhost:38347 && cross-env NODE_ENV=development electron .", "dev:electron": "wait-on http://localhost:38347 && cross-env NODE_ENV=development electron .",
"build": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder --publish always", "build": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder --publish always",
"build:local": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder", "build:local": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder",
@@ -47,5 +47,9 @@
"png2icons": "2.0.1", "png2icons": "2.0.1",
"sharp": "0.35.3", "sharp": "0.35.3",
"wait-on": "9.0.10" "wait-on": "9.0.10"
},
"allowScripts": {
"electron-winstaller@5.4.0": true,
"electron@43.1.0": true
} }
} }