Skip to content

Commit 2d54ecc

Browse files
committed
fix: missing chhtome tauri
1 parent d2282b2 commit 2d54ecc

21 files changed

Lines changed: 1456 additions & 66 deletions

.github/workflows/desktop-release.yml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ jobs:
2424
fail-fast: false
2525
matrix:
2626
include:
27-
- platform: macos-latest
27+
- platform: macos-13
2828
args: --target x86_64-apple-darwin
29+
rust_triple: x86_64-apple-darwin
2930
- platform: macos-latest
3031
args: --target aarch64-apple-darwin
32+
rust_triple: aarch64-apple-darwin
3133
- platform: windows-latest
3234
args: ""
35+
rust_triple: x86_64-pc-windows-msvc
3336
runs-on: ${{ matrix.platform }}
3437

3538
defaults:
@@ -61,6 +64,39 @@ jobs:
6164
rustup target add aarch64-apple-darwin
6265
rustup target add x86_64-apple-darwin
6366
67+
- name: Setup Python 3.11
68+
uses: actions/setup-python@v6
69+
with:
70+
python-version: '3.11'
71+
72+
- name: Install Python deps + PyInstaller
73+
shell: bash
74+
working-directory: api
75+
run: |
76+
python -m pip install --upgrade pip
77+
pip install -r requirements.txt
78+
pip install pyinstaller==6.11.1
79+
80+
- name: Build Vinted local worker (PyInstaller)
81+
shell: bash
82+
working-directory: api
83+
env:
84+
MATRIX_TRIPLE: ${{ matrix.rust_triple }}
85+
run: |
86+
set -e
87+
pyinstaller desktop_vinted_server.spec --noconfirm --clean
88+
mkdir -p ../web/src-tauri/binaries
89+
if [[ "$RUNNER_OS" == "Windows" ]]; then
90+
mv "dist/goupix-vinted-worker.exe" \
91+
"../web/src-tauri/binaries/goupix-vinted-worker-${MATRIX_TRIPLE}.exe"
92+
ls -la "../web/src-tauri/binaries/"
93+
else
94+
mv "dist/goupix-vinted-worker" \
95+
"../web/src-tauri/binaries/goupix-vinted-worker-${MATRIX_TRIPLE}"
96+
chmod +x "../web/src-tauri/binaries/goupix-vinted-worker-${MATRIX_TRIPLE}"
97+
ls -la "../web/src-tauri/binaries/"
98+
fi
99+
64100
- name: Install frontend dependencies
65101
run: npm ci
66102

api/desktop_vinted_server.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,46 @@
4141

4242
ensure_proactor_event_loop()
4343

44-
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s %(message)s")
44+
45+
def _resolve_log_dir() -> str:
46+
"""Local logs directory (also writable when packaged via PyInstaller `--noconsole`)."""
47+
if sys.platform == "win32":
48+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
49+
return os.path.join(base, "GoupixDex", "logs")
50+
if sys.platform == "darwin":
51+
return os.path.join(os.path.expanduser("~"), "Library", "Logs", "GoupixDex")
52+
return os.path.join(os.path.expanduser("~"), ".local", "share", "GoupixDex", "logs")
53+
54+
55+
def _configure_logging() -> None:
56+
"""Console + rotating file. The file handler is the only useful sink when the
57+
worker is packaged via PyInstaller with `--noconsole` (no stdout / stderr)."""
58+
from logging.handlers import RotatingFileHandler
59+
60+
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
61+
root = logging.getLogger()
62+
root.setLevel(logging.INFO)
63+
64+
console = logging.StreamHandler()
65+
console.setFormatter(fmt)
66+
root.addHandler(console)
67+
68+
try:
69+
log_dir = _resolve_log_dir()
70+
os.makedirs(log_dir, exist_ok=True)
71+
file_handler = RotatingFileHandler(
72+
os.path.join(log_dir, "vinted-worker.log"),
73+
maxBytes=2_000_000,
74+
backupCount=3,
75+
encoding="utf-8",
76+
)
77+
file_handler.setFormatter(fmt)
78+
root.addHandler(file_handler)
79+
except OSError:
80+
pass
81+
82+
83+
_configure_logging()
4584
logger = logging.getLogger("goupixdex.vinted_local")
4685

4786
_LISTING_IMAGE_UA = (

api/desktop_vinted_server.spec

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
# PyInstaller spec for the Vinted local worker shipped as a Tauri sidecar.
3+
# Build:
4+
# pyinstaller api/desktop_vinted_server.spec --noconfirm --clean
5+
# Output: ./dist/goupix-vinted-worker(.exe)
6+
#
7+
# Notes:
8+
# - On Windows we want `console=False` so launching the Tauri app does not pop a cmd window.
9+
# Logs are written to `%LOCALAPPDATA%\GoupixDex\logs\vinted-worker.log` (see desktop_vinted_server.py).
10+
# - `nodriver` and `uvicorn` heavily use lazy imports → declare them as hidden imports.
11+
12+
from __future__ import annotations
13+
14+
import os
15+
from pathlib import Path
16+
17+
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
18+
19+
# `pyinstaller` exécute toujours le .spec depuis le répertoire courant ; on lance la commande
20+
# depuis `api/` (CI : `working-directory: api`, dev : `cd api && pyinstaller …`).
21+
SPEC_DIR = Path(os.getcwd())
22+
if not (SPEC_DIR / "desktop_vinted_server.py").is_file():
23+
candidate = Path(__file__).resolve().parent if "__file__" in globals() else None
24+
if candidate is not None and (candidate / "desktop_vinted_server.py").is_file():
25+
SPEC_DIR = candidate
26+
else:
27+
raise RuntimeError(
28+
"desktop_vinted_server.spec doit être lancé depuis le dossier `api/` du dépôt."
29+
)
30+
31+
ENTRY_SCRIPT = str(SPEC_DIR / "desktop_vinted_server.py")
32+
33+
hiddenimports = []
34+
hiddenimports += collect_submodules("nodriver")
35+
hiddenimports += collect_submodules("uvicorn")
36+
hiddenimports += collect_submodules("anyio")
37+
hiddenimports += collect_submodules("httpx")
38+
hiddenimports += collect_submodules("services")
39+
hiddenimports += collect_submodules("services.vinted_wardrobe")
40+
hiddenimports += collect_submodules("core")
41+
hiddenimports += collect_submodules("schemas")
42+
hiddenimports += collect_submodules("models")
43+
hiddenimports += collect_submodules("app_types")
44+
hiddenimports += [
45+
"pymysql",
46+
"bcrypt",
47+
"browser_cookie3",
48+
"vinted_scraper",
49+
]
50+
51+
datas = []
52+
datas += collect_data_files("nodriver", include_py_files=False)
53+
54+
block_cipher = None
55+
56+
a = Analysis(
57+
[ENTRY_SCRIPT],
58+
pathex=[str(SPEC_DIR)],
59+
binaries=[],
60+
datas=datas,
61+
hiddenimports=hiddenimports,
62+
hookspath=[],
63+
hooksconfig={},
64+
runtime_hooks=[],
65+
excludes=[
66+
"tkinter",
67+
"PIL.ImageTk",
68+
],
69+
win_no_prefer_redirects=False,
70+
win_private_assemblies=False,
71+
cipher=block_cipher,
72+
noarchive=False,
73+
)
74+
75+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
76+
77+
exe = EXE(
78+
pyz,
79+
a.scripts,
80+
a.binaries,
81+
a.zipfiles,
82+
a.datas,
83+
[],
84+
name="goupix-vinted-worker",
85+
debug=False,
86+
bootloader_ignore_signals=False,
87+
strip=False,
88+
upx=False,
89+
runtime_tmpdir=None,
90+
# `console=False` on Windows = no cmd window pops up when Tauri spawns the worker.
91+
# On macOS/Linux it has no visible effect (Tauri inherits no terminal anyway).
92+
console=False,
93+
disable_windowed_traceback=False,
94+
argv_emulation=False,
95+
target_arch=None,
96+
codesign_identity=None,
97+
entitlements_file=None,
98+
)

web/README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ The **admin** is the user seeded from the API (`SEED_USER_EMAIL`). The sidebar c
2626

2727
- **Web**: Vinted publishing & wardrobe sync are disabled — the UI points users to `/downloads`.
2828
- **Desktop (Tauri)**: Vinted features call the local Python worker on `127.0.0.1:18766` (configurable via `NUXT_PUBLIC_VINTED_LOCAL_BASE`). Composables: `useVintedPublishStream`, `useVintedBatchStream`, `useWardrobeLocalSync`, `useWardrobeSyncStream`. The runtime is detected through the Tauri WebView.
29+
- The desktop installer bundles the worker as a **PyInstaller sidecar** (`bundle.externalBin = ["binaries/goupix-vinted-worker"]`). End users do **not** need a system-wide Python install.
30+
- The worker is spawned by `web/src-tauri/src/lib.rs` on app startup and killed on exit. In dev (`tauri:dev`), `lib.rs` falls back to `python desktop_vinted_server.py` from the local `api/` folder so you can iterate without rebuilding the sidecar (set `GOUPIX_PYTHON` to override the interpreter).
31+
- Worker logs are written to `%LOCALAPPDATA%\GoupixDex\logs\vinted-worker.log` (Windows) / `~/Library/Logs/GoupixDex/vinted-worker.log` (macOS) so they survive `--noconsole` PyInstaller mode.
32+
33+
## Browser requirement (desktop)
34+
35+
The Vinted worker drives a **Chrome** install via [`nodriver`](https://github.com/ultrafunkamsterdam/nodriver). At app startup the Tauri command `check_browser_availability` (Rust, see `lib.rs`) probes the standard install paths and:
36+
37+
- if **Chrome** is found → it is passed to the worker via `VINTED_CHROME_EXECUTABLE`,
38+
- otherwise if **Edge** is found (always present on Win 10/11) → Edge is used as a fallback,
39+
- otherwise the frontend mounts [`BrowserMissingModal.vue`](app/components/BrowserMissingModal.vue) (composable: [`useBrowserAvailability`](app/composables/useBrowserAvailability.ts)) blocking the UI until the user installs Chrome.
2940

3041
## Prerequisites
3142

@@ -70,6 +81,8 @@ npm run build
7081
npm run tauri:dev
7182
```
7283

84+
The script first runs `vinted-worker:stub` which writes an empty placeholder under `web/src-tauri/binaries/goupix-vinted-worker-<host-triple>(.exe)` so Tauri's `bundle.externalBin` validation passes. In `cfg(debug_assertions)` builds, `lib.rs` ignores the placeholder and spawns `python desktop_vinted_server.py` from `api/` instead. The folder `web/src-tauri/binaries/` is `.gitignore`d.
85+
7386
If you see a Cargo error like:
7487

7588
`this version of Cargo is older than the 2024 edition`
@@ -104,13 +117,13 @@ npm run tauri:build
104117

105118
The command first builds the Nuxt frontend in desktop mode (`NUXT_DESKTOP_BUILD=1`), then bundles the app with Tauri.
106119

107-
The desktop bundle ships a Python sidecar (`api/desktop_vinted_server.py`) that exposes the local Vinted worker the frontend talks to.
120+
The desktop bundle ships a Python sidecar built from `api/desktop_vinted_server.py` via PyInstaller (`api/desktop_vinted_server.spec`) — see the *CI/CD* section below for the per-OS build steps.
108121

109122
## CI/CD
110123

111124
- `deploy-web.yml` — web build and deploy (Nitro + PM2).
112125
- `deploy-api.yml` — backend build / VPS deployment (systemd + Xvfb for Vinted).
113-
- `desktop-release.yml` — Windows/macOS desktop **release** builds (Windows binary without console) and a stable GitHub Release **per version** (`v0.1.x`, not prerelease).
126+
- `desktop-release.yml` — Windows/macOS desktop **release** builds (Windows binary without console) and a stable GitHub Release **per version** (`v0.1.x`, not prerelease). For each OS the workflow first sets up Python 3.11, installs `api/requirements.txt` + PyInstaller, and runs `pyinstaller desktop_vinted_server.spec` to produce `goupix-vinted-worker-<rust-triple>(.exe)` under `web/src-tauri/binaries/` before invoking `tauri-action`. The macOS Intel target uses the `macos-13` runner so the produced sidecar is x86_64.
114127

115128
## Tauri updater: key generation and GitHub secrets
116129

web/app/assets/css/main.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@
3131
[role='button']:not([aria-disabled='true']):not([data-disabled]) {
3232
cursor: pointer;
3333
}
34+
35+
/* Masque l'œil natif d'Edge / WebView2 (Tauri Windows) sur les <input type="password">,
36+
pour ne garder que notre toggle custom dans PasswordInput.vue. */
37+
input[type='password']::-ms-reveal,
38+
input[type='password']::-ms-clear {
39+
display: none;
40+
}
3441
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<script setup lang="ts">
2+
/**
3+
* Modale affichée au lancement de l'app desktop si aucun navigateur supporté
4+
* (Chrome ou Edge) n'est installé. Vinted (publication + synchro) en a besoin :
5+
* le worker local pilote ce navigateur via nodriver.
6+
*
7+
* Côté web (sans Tauri), `noBrowser` reste à `false` → la modale ne s'affiche jamais.
8+
*/
9+
const { isDesktopApp } = useDesktopRuntime()
10+
const { state, checked, check, openExternal } = useBrowserAvailability()
11+
12+
const open = ref(false)
13+
14+
async function refresh() {
15+
await check(true)
16+
open.value = Boolean(state.value?.noBrowser)
17+
}
18+
19+
onMounted(async () => {
20+
if (!isDesktopApp.value) {
21+
return
22+
}
23+
await check()
24+
open.value = Boolean(state.value?.noBrowser)
25+
})
26+
27+
watch(
28+
() => state.value?.noBrowser,
29+
(v) => {
30+
if (checked.value) {
31+
open.value = Boolean(v)
32+
}
33+
}
34+
)
35+
36+
async function installChrome() {
37+
await openExternal(state.value?.chromeInstallUrl || 'https://www.google.com/intl/fr_fr/chrome/')
38+
}
39+
</script>
40+
41+
<template>
42+
<UModal
43+
v-model:open="open"
44+
:dismissible="false"
45+
:close="false"
46+
title="Google Chrome est requis"
47+
:ui="{ content: 'max-w-lg' }"
48+
>
49+
<template #body>
50+
<div class="space-y-4">
51+
<p class="text-sm text-default">
52+
GoupixDex pilote <strong>Vinted</strong> (publication d'annonces et synchronisation
53+
de votre dressing) à travers <strong>Google&nbsp;Chrome</strong> installé sur votre
54+
machine. Microsoft&nbsp;Edge est utilisé comme solution de repli s'il est présent.
55+
</p>
56+
<p class="text-sm text-muted">
57+
Aucun de ces deux navigateurs n'a été détecté. Installez Chrome (gratuit, ~2&nbsp;min)
58+
puis cliquez sur « J'ai installé Chrome ».
59+
</p>
60+
61+
<div class="rounded-md border border-default bg-elevated/50 p-3 text-xs text-muted space-y-1">
62+
<p>
63+
<span class="font-medium text-highlighted">Pourquoi&nbsp;?</span>
64+
Vinted bloque les requêtes serveur classiques (Cloudflare). GoupixDex contourne
65+
cela en automatisant un vrai navigateur installé chez vous, depuis votre IP
66+
résidentielle.
67+
</p>
68+
</div>
69+
</div>
70+
</template>
71+
72+
<template #footer>
73+
<div class="flex w-full justify-end gap-2">
74+
<UButton
75+
color="neutral"
76+
variant="ghost"
77+
icon="i-lucide-refresh-cw"
78+
@click="refresh"
79+
>
80+
J'ai installé Chrome
81+
</UButton>
82+
<UButton
83+
icon="i-lucide-download"
84+
@click="installChrome"
85+
>
86+
Télécharger Chrome
87+
</UButton>
88+
</div>
89+
</template>
90+
</UModal>
91+
</template>

0 commit comments

Comments
 (0)