---
title: 'WarpEngine Client'
url: 'https://wiki.teletypegames.org/hu/stores/warp-engine-client'
markdown: 'https://wiki.teletypegames.org/hu/stores/warp-engine-client.md'
lang: hu
date: '2026-09-10'
description: 'A WarpEngine Client egy WarpEngine-áruház asztali alkalmazása: a katalógus kártyarácsként, egy kattintás telepít egy címet az alkalmazásmenüdbe, egy elindítja, egy eltávolítja. Linux, macOS és Windows. A jelenlegi kiadás a 3.1.0, amely már ismeri a WarpEngine 1.0-t. Az áruházmotor az alkalmazás ré…'
taxonomy:
  tag:
    - engine
---

# WarpEngine Client

A **WarpEngine Client** egy WarpEngine-áruház asztali alkalmazása: a katalógus kártyarácsként, egy kattintás telepít egy címet az alkalmazásmenüdbe, egy elindítja, egy eltávolítja. Linux, macOS és Windows. A jelenlegi kiadás a **3.1.0**, amely már ismeri a WarpEngine 1.0-t.

![A WarpEngine Client ablaka: balra az áruház és a szűrőtengelyek, jobbra a katalógus játékkártyák rácsaként](https://wiki.teletypegames.org/wiki-assets/stores/warpengine-client.png)

**Az áruházmotor az alkalmazás része.** A katalógus beolvasása, a géphez illő kiadás kiválasztása, a kicsomagolás, a menübejegyzés kiírása és annak nyilvántartása, hogy mi hova került, mind a folyamaton belül történik. Nincs értelmező, amit meg kellene keresni, és nincs gyerekfolyamat, amit értelmezni kellene: az alkalmazásnak **egyetlen futásidejű függősége sincs**, még zip-könyvtára sem. Ami a lemezre kerül, az viszont nem változott: a `config.json` és a `state.json` megtartja azt az alakot, amit a shell-áruház írt, így a CLI-vel telepített könyvtárat változtatás nélkül felismeri.

Az, hogy melyik áruházból telepít, nincs belefordítva: a kliens megkérdez egy nyilvántartást (`GET /api/stores` az oldalon), és egy ottani rekord egy **név és egy katalógus**, semmi több. A viselkedés a kliens saját dolga, a játékmappa pedig a katalógushoszt slugja, így két áruház egy gépen soha nem nyúl egymás fájljaihoz.

# Mire van szüksége

Semmire. Nincs értelmező, nincs csomagkezelő, nincs rendszergazdai jog: az alkalmazás a saját futtatókörnyezetét viszi, az áruház pedig a saját felhasználói fiókod alá települ. A *fejlesztéséhez* Node 22 vagy újabb kell, az eszközláncot a `make setup` telepíti.

# Telepítés

Vedd le a gépedhez való csomagot a [kiadások](https://git.teletypegames.org/stores/warp-engine-client/releases) közül, és nyisd meg. Az első indításkor, ha nincs áruház a gépen, az ablak felajánlja, amit a nyilvántartás publikál, vagy elfogad egy beírt katalóguscímet. Ennyi az egész beállítás.

## Megnyitás macOS-en

A build ad hoc alá van írva, de **nincs notarizálva**, ezért a macOS rákérdez, mielőtt egy böngészőből érkezett példányt futtatna:

```sh
xattr -dr com.apple.quarantine "/Applications/WarpEngine Client.app"
```

A **Rendszerbeállítások ▸ Adatvédelem és biztonság** alatti *Megnyitás mindenképp* is működik, egy blokkolt próbálkozás után. Ezt a lépést csak a notarizálás szünteti meg, ahhoz pedig fizetős Apple Developer ID kell. Az áruház által később letöltött fájlokat ez nem érinti: az alkalmazás a saját HTTP-kliensével tölti le őket, amely nem állítja be a karanténjelzőt.

**A v1.0.0-t egyáltalán nem lehetett megnyitni** — azt jelentette, hogy *„is damaged"*. A csomag soha nem lett aláírva; csak a fő futtatható állományán volt rajta a linker ad hoc aláírása, tehát nem volt erőforrás-pecsét, és a Gatekeeper kérdés helyett elutasította. A `scripts/after-pack.js` most build közben aláírja a csomagot, az eredmény pedig `valid on disk` státusszal ellenőrizhető.

# Belépés, és a pénzbe kerülő címek

**Ebben a kliensben semmi nem tud semmit egyetlen konkrét áruházról sem.** Az ár, hogy kell-e fiók, hol lehet megvenni, hol lehet belépni — mindez a katalógus szerverétől jön: a WarpEngine 0.5 a `GET /api/service` végponton megmondja, mit kínál, és minden bejegyzésre rátesz egy `access` blokkot. Ezért szolgál ki egyetlen build bármilyen boltot. A végpontok és alakok, amiket a kliens olvas, a **WECP — WarpEngine Catalog Protocol**, [saját oldalán](https://wiki.teletypegames.org/hu/engines/warp-engine/wecp) leírva.

Ahol a szerver nem kínál belépést (minden 0.5 előtti WarpEngine, és minden olyan áruház, amely nem árul semmit), ott az ablak sem mutat ilyet. Ahol igen:

- az oldalmenü kap egy **Fiók** blokkot: *Belépés…*, majd *Kilépés*;
- a belépés egy **rövid kódot** mutat. A böngésző megnyitja az áruház oldalát, a kódot oda kell beírni, és a jóváhagyás belépteti ezt az eszközt. Jelszó soha nem jut el a klienshez — pont ez a kerülőút értelme.
- a token az **operációs rendszer kulcstartójában** él (Keychain, libsecret, DPAPI) az Electron `safeStorage` API-ján keresztül, áruházanként egy. Ha nincs kulcstartó, akkor egyáltalán nem tárolja el, nem pedig nyíltan kiírja; ennek az ára az, hogy a következő indításnál újra be kell lépni.

Egy kártyán az, hogy egy ember mit tehet, elválik attól, hogy a gép mit tud futtatni:

| Állapot | Mit kínál a kártya |
|---|---|
| birtokolt, vagy ingyenes | **Telepítés** |
| nem birtokolt | az **ár**, és a **Megveszem**, amely böngészőben nyitja meg az áruházi oldalt |
| kijelentkezve, a katalógus kapuzza | **Belépés a telepítéshez** — a katalógus addig nem tudja megmondani, kié, amíg nem tudja, ki kérdez |

A vásárlás a böngészőben történik: egy itt újraépített pénztár csak egy második hely lenne, ahol el lehet rontani a kártyakezelést. A **Frissítés** utána *Telepítéssé* alakítja a kártyát.

Ezzel jön két állapotszűrő is — **Birtokolt** és **Megveendő** —, mert a birtoklás nem ugyanaz, mint a telepítettség.

Egy meg nem vett cím **nincs** kiszürkítve. A kiszürkítés azt jelenti, hogy a *gép* nem tudja futtatni; a géppel semmi baj, ha egy cím egyszerűen pénzbe kerül.

Bearer token soha nem megy el olyan hosztra, amely nem adta ki. Egy kapuzott letöltés aláírt tárolóra irányít át — gyakran valaki más szerverére —, és egyes objektumtárolók visszautasítják azt a kérést, amely `Authorization` fejlécet és aláírást is visz.

# Melyik áruházból telepít

Az első indításkor a kliens lekéri a nyilvántartást. Egy áruház esetén nincs mit eldönteni; több esetén a beállítóképernyő választót mutat.

```json
[
  { "name": "Teletype Games", "catalogUrl": "https://teletypegames.org" },
  { "name": "Some Other Store", "catalogUrl": "https://games.example.org" }
]
```

**Egy név és egy katalógus a teljes rekord.** A kliens már tudja a hoszt–asset leképezést, a telepítési módokat, a platformokat és a viselkedést. Amit nem tudhat, az az identitás, és egy nyilvántartás csak az identitást mondja meg. Egy rekordból ezeket származtatja:

- **az áruházazonosító** — a katalógushoszt slugja (`teletypegames.org` → `teletypegames`), vagy a megjelenített névé, ha az nem megy. Ez adja az áruház könyvtárának és a játékmappának a nevét. Két rekord, amely ugyanarra a katalógusra mutat, ugyanaz az áruház, és ugyanoda kerül, így az újratelepítés soha nem hagy árvát.
- **a játékmappa** — ez a slug az operációs rendszer szokásos helyén a telepített programoknak. Ez egyben az egyetlen részfa, ahonnan ez az áruház valaha töröl.
- **mely címek jelennek meg** — a kiadott, az archivált és a demó. Az a katalógus, amely demót publikál, azt szánja is játszásra.

Egy katalógusnak egyáltalán nem kell a nyilvántartásban lennie: a beállításokból **bármilyen WarpEngine-katalóguscím** hozzáadható. Egy csupasz hosztnév is elég (a `https` alapértelmezett), a nevet pedig belőle veszi.

A nyilvántartás címe az utolsó oldalspecifikus dolog a kliensben, három helyen dönthető el, a legspecifikusabbal kezdve: egy futásidejű `STORES_API`, az a cím, amellyel a csomag **épült**, majd a miénk.

```sh
STORES_API=http://127.0.0.1:8731/stores npm start            # kipróbálás
make dist STORES_API=https://games.example.org/api/stores    # kiadás
```

A buildváltozat a becsomagolt alkalmazás `package.json` fájljába kerül (`warpEngine.registryUrl`), így egy más katalógusra épített kliens nem igényel forrásmódosítást, és a felhasználó gépén sem kell semmit beállítani. Egy áruház hozzáadása adatbázissor az oldalon, nem ennek az alkalmazásnak a kiadása.

# Brandelt build

Egy build **brandet** vihet: egy nevet és egy boltot. A `make dist BRAND_NAME="Teletype Games Store" BRAND_STORE=https://teletypegames.org BRAND_ID=teletype-store` belesüti a csomagba; a `BRAND_NAME=… BRAND_STORE=… npm start` kipróbálja. A név az ablak címe, a sáv fejléce és a `productName` (Dock, `/Applications`, NSIS-mappa, `.desktop` bejegyzés); a bolt az első indításnál nyilvántartás és választó nélkül települ, a brand nevén, ugyanabba a store home-ba, amit egy általános kliens is használna ehhez a katalógushoz. A brand **alapértelmezés, nem ketrec**: a gépen lévő minden bolt látszik, és a beállítások továbbra is hozzáadnak és törölnek. A `BRAND_ID` adja az `appId`-t és a csomagnevek előtagját, hogy a brandelt és az általános kliens egymás mellett élhessen; a `RELEASES_API` a brandelt build saját kiadásaira irányítja a *Frissítések keresését*. A Beállítások Általános lapja azt mondja: *WarpEngine Client 3.1.0 alapon*. A brand adat, amit a build ad — a forrásban semmi nem nevez meg boltot.

# Használat

Minden, ami nem cím, az **oldalmenüben** él; a menügomb becsukja, és az állapot túléli az újraindítást.

| Szakasz | Mit tartalmaz |
|---|---|
| **Áruházak** | a gépen lévő összes áruház, a nyitottat megjelölve. Egy másikra kattintva vált a rács, a szűrők és a mappák, és a kliens legközelebb ott nyílik meg. Ugyanabból a katalógusból származó, de más mappában lévő két áruház az azonosítója helyett a mappáját mutatja. Áruházat hozzáadni, átnevezni és törölni a beállításokban lehet. |
| **Fiók** | *Belépés…* / *Kilépés*, csak ott, ahol a katalógus kínál belépést. |
| **Szűrők** | négy, egymással kombinálható tengely: a cím **állapota** ezen a gépen (*Minden*, *Telepített*, *Frissítések*, *Nincs telepítve*, és ahol a katalógus kapuz valamit, *Birtokolt* és *Megveendő*), a **platform**, a **típus** (natív vagy hosztolt) és a **kategória**, ha a katalógus publikál ilyet — a WarpEngine 0.12 a metadata-fájl `category` mezőjét átviszi a katalógusba, így a tengely megjelenik, amint a címek meg vannak címkézve, a 0.13 szótára (`GET /api/software/vocabulary`) pedig címkét ad a chipeknek. Minden darabszám azt mondja, hány címet mutatna az adott opció a többi tengely mai állása mellett, és a *Törlés* megjelenik, amíg bármi szűkíti a katalógust. A tengelyek abból épülnek, amit a katalógus tartalmaz: a címek nélküli platform nem jelenik meg, az eltűnő választás pedig visszaesik a *Minden* értékre. |
| **Lábsor** | a **fogaskerék** (beállítások), a verziószám, a **Napló** és a **Frissítés**. A *Napló* az áruház saját kimenetét nyitja meg szó szerint, plusz azt a két mappát, ahova minden kerül. A *Frissítés* újraolvassa a katalógust. A címek egyesével, a kártyájukról telepíthetők; nincs „telepíts mindent" gomb. |

**A katalógus laponként érkezik, és a rács rajzol, ahogy jön.** A WarpEngine 0.13 lapozza a `GET /api/software`-t; a kliens sorban olvassa a lapokat, és mindegyiket megmutatja, amint megjött, a sáv addig a címeket számolja. Ami az egészet igényli — a *Frissítések* csoport, a *Mind frissítése*, a prune — megvárja, és a cache mindig az összefűzött egészet tartja, részlegest soha. A rács **ablakozott**: 48 címet rajzol, és 48-at még, amikor az aljához érsz, így egy nagy katalógus annyiba kerül, amennyi a képernyőn van. Egy szűrő vagy keresés elölről kezd; egy lap megérkezése nem mozdítja el a görgetést.

A sávban van egy **keresőmező**: ami bele van írva, azokra a címekre szűkíti a rácsot, amelyek címe vagy neve tartalmazza, kis- és nagybetűtől és ékezettől függetlenül, így a `love` megtalálja a *LÖVE*-ot. A szűrő része — a chipek darabszámai számolnak vele, a *Törlés* kiüríti, és az Escape is. A sáv kapcsolója a **rács** és egy **lista** között vált; a lista a kártya borítójával, verziójával és menüjével rajzol.

## Beállítások

![A beállítások ablaka az Áruházak lapon: a telepített áruház az azonosítójával és a katalóguscímével, és a hozzáadás két módja](https://wiki.teletypegames.org/wiki-assets/stores/warpengine-client-settings.png)

A fogaskerék egy párbeszédablakot nyit mindazzal, ami nem az előtted lévő katalógusról szól, három lapon:

- **Általános** — a **nyelv** (a rendszert követi, átváltható: angol és magyar) és az **alapértelmezett nézet**, rács vagy lista, ugyanaz a beállítás, amit a sáv kapcsolója ír.
- **Áruházak** — a gépen lévő összes áruház az azonosítójával és a katalóguscímével. A **ceruza** helyben átnevez: az Enter ment, az Escape elvet. Csak a név változhat, mert az azonosító, a mappa és a játékmappa mind a katalógusból származik, tehát egy másik katalógus másik áruház. A **kuka** eltávolít egy áruházat. A lista alatt az **Áruház hozzáadása a listáról** azt kínálja, amit a nyilvántartás publikál és ez a gép még nem tud, a **Vagy egy saját katalógus** pedig bármilyen katalóguscímet elfogad. A hozzáadás bezárja az ablakot, és megnyitja az új áruházat.
- **Frissítések** — két blokk, szándékosan külön, mert két különböző dologról szólnak. **Ez a kliens** a verziót mutatja, és egy **Frissítések keresése** gombot, amely a kliens saját kiadáslistáját kérdezi le a forge-on, és helyben válaszol: *Ez a legújabb verzió*, vagy *Az X verzió elérhető* egy gombbal, amely böngészőben nyitja meg a kiadás oldalát. Az alkalmazás nem frissíti magát, és magától nem is kérdez; semmi nem történik, amíg nem kattintasz. **Újabb verziójú címek**: azok a telepített címek, amelyeknél a katalógus továbblépett, mindegyik egy **Frissítés** gombbal, és **Mind frissítése**, ha több van.

**Egy áruház eltávolítása azt is eltávolítja, amit telepített.** A kuka előbb rákérdez, és megmondja, hány cím megy vele. Az áruház `state.json` fájlja az egyetlen nyilvántartás arról, mely csomagok, ikonok és menübejegyzések tartoznak hozzá, ezért a játékok hátrahagyása olyan árvákat hagyna, amelyeket már semmi nem tudna azonosítani. A katalógus-cache, a beállítások és az esetleges belépési token is megy vele.

## Kártyák

Egy kártya gombja **Telepítés**, **Frissítés**, vagy telepítés után **Játék** / **Megnyitás**. Egy telepített kártya hárompontos menüjében **Frissítés** (csak akkor élő, ha a katalógusban van újabb), **Újratelepítés** és **Eltávolítás** van. Az újratelepítés annak a példánynak való, amely telepítettnek látszik, de nem működik: a Frissítés és az Upgrade békén hagy egy épnek látszó telepítést, az Újratelepítés viszont előbb eltávolítja, majd újra kicsomagolja a katalógusból, ikonnal és menübejegyzéssel együtt. Minden kártya megmondja, hogy **natív**-e — kicsomagolva, helyben futtatva, offline is működik —, vagy **hosztolt**: a katalógus által kiszolgált böngészős build, amelynek a bejegyzése oldalt nyit meg, és hálózatot igényel.

**A katalógus teljes tartalma megjelenik, azt is beleértve, amit ez a gép nem tud telepíteni.** Ezek a kártyák ki vannak szürkítve, *nem támogatott platform* vagy *ehhez a géphez nincs build* jelvényt viselnek a motor magyarázatával, és nincs rajtuk mit megnyomni — mondjuk egy Raspberry Pi egy csak x86-os builddel szemben. Az az áruház, amely elrejti őket, találgatni hagyja a látogatót, hogy a katalógus kicsi-e, vagy a gépe fura. Saját állapotuk van, a *Nem ehhez a géphez*, és kimaradnak a natív/hosztolt darabszámokból, mert az a cím, amelyhez nincs build, nem tartozik egyik módhoz sem.

A 2.7.0-tól a **Commodore 64-es és Plus/4-es** címek is települnek: a WarpEngine 0.11-től a kiadásaik yapeSDL-csomagot visznek egy, a játékról elnevezett indítóval, amely beállítja a gépet és elindítja a kazettát, és a programkeresésben a játékról elnevezett program nyer. A kártyák minden platformot úgy neveznek, ahogy a katalógus — *Plus/4*, *TIC-80*, *LÖVE* —, a szerver build-mátrixából olvasva (`GET /api/builds`), így egy katalógus új kliens nélkül is felvehet platformot; a végpont nélküli motor a slugoknál marad.

Minden kártya azonos magasságú borítóképsávot visel — kép híján a cím első betűjét —, hogy a gombok soron belül egy vonalba essenek. Ez csendben el volt rontva addig, amíg le nem fényképeztük az ablakot: a sorok a tartalmuk követése helyett egyenlően osztották el az ablakmagasságot, ami összenyomta a képet és levágta a gombokat, miközben minden automatikus darabszám-ellenőrzés átment, mert a csomópontok mind ott voltak az oldalon.

Amíg az áruház dolgozik, csak azok a dolgok vannak letiltva, amelyek egy második hívást indítanának. A menü, a naplófiók és a szűrők tovább működnek, mert a képernyőt változtatják, a lemezen semmit.

Bármi, ami itt települ, normál menübejegyzés, tehát az indítódban, a Dockban vagy a Start menüben is megjelenik. A játékhoz nem kell futnia az alkalmazásnak.

# Fejlesztés

A `make` a bejárati ajtó; az npm-szkripteket csomagolja be. A `make` önmagában mindent kilistáz.

| Cél | Mit csinál |
|---|---|
| `make setup` | függőségek telepítése (előbb ellenőrzi a Node verzióját) |
| `make build` | TypeScript fordítása, a preload és a renderer bundle-ölése |
| `make typecheck` | típusellenőrzés, kimenet nélkül |
| `make lint` | a szigorú szabálykészlet (a `make lint-fix` javítja, amit tud) |
| `make check` | típusellenőrzés, lintelés és minden tesztkészlet — ez a kapu |
| `make start` | futtatás a telepített áruház ellen |
| `make icons` | minden ikonformátum kirenderelése a `resources/icon.svg` fájlból |
| `make smoke` | az áruház meghajtása ablak és Electron nélkül |
| `make storetest` | áruház hozzáadása, átnevezése és törlése homokozóban — az egyetlen kód, amely fát töröl |
| `make uitest` | az ablak egyszeri betöltése, és jelentés arról, mi rajzolódott ki (`SELFTEST_SHOT=shot.png` le is fényképezi, a beállítások lapjaival együtt) |
| `make test` | a három tesztkészlet |
| `make dist` | csomagolás ehhez a géphez (`dist-mac`, `dist-win`, `dist-linux`) |
| `make publish` | a `dist/` alatti csomagok feltöltése a Gitea-kiadásra |
| `make release` | csomagolás és publikálás egy menetben |
| `make clean` | a `build/` és a csomagok törlése (a `distclean` a `node_modules` mappát is elhagyja) |
| `make version` | az érintett verziók, azt is beleértve, hogy megvan-e a `tea` |

Az npm-szkriptek továbbra is működnek közvetlenül (`npm start`, `npm run dist:mac`). A Makefile a kiadási lépésen kívül nem tesz hozzá logikát, és minden alkalmazást futtató szkript előbb buildel, így nem lehet elavult bundle-t tesztelni.

## Folyamatos integráció

A `.woodpecker.yaml` ebben a repóban van, nem az update szerver konfigurációs kiterjesztésében: az a kiterjesztés játékplatform-pipeline-okat szolgál ki, amelyek kazettát építenek és az oldal katalógusába publikálnak, míg ez alkalmazást épít, és egy kiadásba publikál.

| Lépés | Image | Mit csinál |
|---|---|---|
| `check` | `warp-engine-client-ci:node22` | `node_modules` (lásd lent), típusellenőrzés, lintelés, füstpróba és az áruház-életciklus teszt |
| `linux` | `electronuserland/builder:22` | AppImage és deb |
| `windows` | `electronuserland/builder:22-wine` | NSIS-telepítő és hordozható exe, Wine-on keresztül |
| `release` | `ci-tools` (digest szerint) | csak tagre: létrehozza a kiadást, és hozzácsatolja, amit épített; a curl, a jq, a zip és a git benne van az image-ben, így semmit nem telepít |

A `check` image az `electronuserland/builder:22`, a repó `node_modules`-ával előtelepítve a `/ci` alatt, mellette annak a `package-lock.json`-nak a sha256-jával, amelyből készült. A [teletypegames](https://wiki.teletypegames.org/hu/services/public-site) repó `ci/warp-engine-client.Dockerfile`-ja építi, és az éjszakai cron újraépíti, ha a `master` lockjára még nincs `lock-<sha>` image; a tag mozog, ezért a lépés mindig pullolja. A `scripts/ci-npm.sh` átmásolja ezeket a modulokat, ha a lock egyezik (egy másodperc a 15 s-os `npm ci` helyett), és `npm ci`-t futtat, ha nem.

**A macOS helyi build marad** — az Apple eszközlánca csak Macen létezik —, így egy kiadásnak két fele van: egy `vX.Y.Z` tag pusholása felépíti a Linuxot és a Windowst, és **létrehozza a kiadást** a `RELEASE_NOTES.md` törzsével, a Macen futtatott `make release` pedig felteszi a macOS-csomagot ugyanarra a kiadásra. Bármelyik fél mehet előbb. Az ablakteszt is helyi: kijelző és a gépen lévő áruház kell hozzá.

A publikáláshoz `gitea_token` repótitok kell írási joggal. A Woodpecker ad a lépéseknek forge-hitelesítő adatot, és a szkript ezt használja, ha a titok hiányzik, de nem lehet rá támaszkodni: egy kézi buildnek megvan, a tag-webhook által indítottnak nincs. Két publikáló létezik — a `scripts/release.sh` a `tea` bejelentkezést vezérli egy munkaállomáson, a `scripts/ci-upload.sh` pedig az API-val beszél azzal a hitelesítő adattal, ami a CI-nak van.

Mindkét buildlépés azzal zárul, hogy ellenőrzi, mit gyártott: egy 10 MB alatti csomag nem készült el. Egy félbemaradt Wine-build a valódi telepítő *nevével* hagy ott egy csonkot, és az `ls` ezzel tökéletesen elégedett. A windowsos lépést Apple Silicon Macen nem lehet elpróbálni: a Wine 4 KB-os memórialapokat feltételez, a gépnek pedig 16 KB-osak vannak.

A windowsos telepítő **nincs aláírva**: a Windows ismeretlen kiadóra figyelmeztet, amíg nincs kódaláíró tanúsítvány. A Linux-csomagok konvenció szerint nem visznek aláírást.

## Kiadás publikálása

```sh
make release
make publish TAG=v1.0.2                 # a package.json-tól eltérő tag
scripts/release.sh dist/one-file.dmg    # csak egy csomag
```

A tag a `package.json` fájlból jön, tehát az `npm version patch` az egyetlen hely, ahol verziót állítunk. A kiadás létrejön, ha nincs, és az a melléklet, amelynek a neve már szerepel, **lecserélődik**, így egy újraépítés plusz egy második `make publish` felkerül, nem hibázik. A kiadási jegyzetek a `RELEASE_NOTES.md` fájlból jönnek, ha van. A repót az `origin` alapján olvassa, tehát egy fork a forkba publikál. A csomagnevekben nincs szóköz (`WarpEngineClient-2.3.0-arm64.dmg`), mert egy kiadási melléklet szóköze minden `curl`-ben szóköz, ami valaha hozzáér.

Telepített és bejelentkezett `tea` kell hozzá — a devarea repóban van `make tea`. Felülbírálható: `TAG`, `REPO`, `TEA_LOGIN`, `NOTES`, `DIST`.

**A telepítéshez Node 22+ kell**, a futtatáshoz nem: az Electron telepítője csak ESM, a régebbi Node pedig nem tudja `require()`-elni. A becsomagolt alkalmazás a saját futtatókörnyezetét viszi.

Az `npm run uitest` saját felhasználóiadat-könyvtárat használ, és kihagyja az egypéldány-zárat. Különben a felhasználónál már nyitva lévő példány elnyelné a tesztfolyamatot, amely 0-val lép ki, és sikeresnek látszik.

A tesztszkriptek elfogadnak egy homokozó-áruházat, így lehet ezt a repót anélkül tesztelni, hogy egy működő telepítéshez hozzányúlnánk. A `STORE_ROOT` **lecseréli** a keresési utat, nem elé fűz: most, hogy áruházat törölni is lehet, az a homokozó, amely elér egy működő telepítést, nem homokozó.

```sh
STORE_ROOT=/tmp/sandbox-root npm start
SMOKE_HOME=/tmp/sandbox-root/ttg-desktop npm run smoke
SMOKE_HOME=<dir> SMOKE_TOKEN=<bearer> npm run smoke      # belépett felhasználóként
```

## Hogyan áll össze

TypeScript rétegekben, a függőségek befelé mutatnak. A repóban lévő `STRUCTURE.md` a térkép — rétegek, minden használt minta, elnevezési szabályok —, és ez az a fájl, amit el kell olvasni, mielőtt bármit hozzáadnál.

| Réteg | Mi él ott |
|---|---|
| `src/shared/` | az IPC-csatornatábla, a hídszerződés, a DTO-k, az üzenetkészletek |
| `src/domain/` | modellek, portok és hibák — se Electron, se Node |
| `src/application/` | szolgáltatások és a domain–DTO leképezők |
| `src/infrastructure/` | adapterek: az áruházmotor, a HTTP, az archívumolvasó, a fájlrendszer, az Electron |
| `src/main/` | az ablak, az IPC-kontrollerek, a kompozíciós gyökér, az önteszt |
| `src/preload/` | a híd, egyetlen fájlba bundle-ölve — egy homokozott preload nem tud modult behúzni |
| `src/renderer/` | az állapottár, a nézetek és a renderer-kontrollerek |
| `src/scripts/` | a füstpróba és az áruház-életciklus teszt: ugyanazok a szolgáltatások ablak nélkül |

Két tulajdonság, amit a rétegek megvesznek:

- a katalógus **ablak és Electron nélkül** is meghajtható — a füstpróba ugyanazokat a szolgáltatásokat állítja össze ugyanazokra a portokra;
- az ablak **soha nem kap fájlrendszer-útvonalat**. Egy cím anélkül kel át a hídon, az indítást pedig név alapján kérjük, és a fő folyamat oldja fel az áruház saját állapotából.

Az ablak kizárólag az állapotából rajzol: a napló, a kapu, a beállítások ablaka és a szűrő egy tár mezői, minden nézetnek egyetlen `render(state)` metódusa van, és a bekötés csak a három kompozíciós gyökérben történik (az alkalmazás, a füstpróba, az életciklus-teszt). Ami a hídon vagy a lemezről jön, ellenőrzött olvasón megy át, soha nem castolással.

A linter szándékosan szigorú: explicit típusok minden szignatúrán, nincs `any`, nincs non-null assertion, kimerítő switchek. Az a fordítás, amelyből hiányzik egy kulcs, nem fordul le, és az a csatorna sem, amelyet a preload nem valósít meg.

**Az áruházmotor** az `src/infrastructure/engine/`, a `StoreCatalogGateway` port mögött: a katalóguskliens a cache-ével, a felmérő, amely megmondja, mely címeket tudja ez a gép telepíteni és a többit miért nem, a kiadásválasztó, a hosztfelismerő, a csomagtelepítő, a `.desktop` / `.app` / `.lnk` indítóírók és az állapotfájl. A port fölött semmi nem tud arról, hogy ezek léteznek. Egy RetroArch-áruház menübejegyzés helyett lejátszási listát írna, tehát az egy második gateway és egy második bejegyzés lenne a `STORE_ENGINES` listában, nem egy második kódút.

**Zip olvasása függőség nélkül.** A Node-nak nincs zip-olvasója, ezért a `ZipArchive` egy a `node:zlib` fölött: végigjárja a központi könyvtárat, kibontja a `stored` és `deflate` bejegyzéseket, visszaállítja a futtatható bitet és a szimbolikus linkeket a bejegyzések attribútumaiból — a yapeSDL `.app`-jában lévő SDL2 framework ilyenek fája. Visszautasítja a zip64-et, az ismeretlen tömörítési módot és a célból kiszökő útvonalat.

**Melyik WarpEngine szolgálta ki a katalógust.** Minden WarpEngine API-válasz visz egy `WarpEngine-Version` fejlécet. A `SUPPORTED_WARP_ENGINE_VERSIONS` felsorolja, mely verziókra írták ezt a klienst, és mindegyik arra a `CatalogDialect`-re képződik le, amely az adott katalógusalakot olvassa. A switch kimerítő, tehát egy új verzió felvétele megállítja a buildet, amíg valaki meg nem mondja, hogyan kell olvasni.

| Mit mond a fejléc | Mi történik |
|---|---|
| támogatott verzió (ma 0.2-től 1.0-ig, két dialektussal) | a dialektusa olvassa a katalógust, a napló megnevezi |
| semmi | a legrégebbi támogatott verzióként olvassa — egy 0.4.0-nál régebbi motor |
| régebbi bárminél, ami támogatott | ugyanaz, és a napló megmondja |
| újabb bárminél, ami támogatott | a legújabb dialektust próbálja, figyelmeztetéssel, hogy címek maradhatnak ki |

`contextIsolation` be, `nodeIntegration` ki, `sandbox` be, és olyan CSP, amely csak az alkalmazás saját szkriptjét és stíluslapját, valamint HTTPS-en érkező képeket enged. A linkek a valódi böngészőben nyílnak; az ablak soha nem navigál.

# Amit ellenőriztünk, és amit nem

Minden verziót a `make check` mér — nincs típushiba, nincs lint-találat, három zöld tesztkészlet —, és az ablak a `SELFTEST_SHOT`-tal lefényképezi magát; így találtuk meg az összenyomott rácssorokat (1.x) és a 2.6.0 üres ablakát: egy inline `<svg>` nem `HTMLElement`, ezért az elemellenőrzés az első konstruktorban dobott, és utána semmi nem futott. A fej nélküli füstpróba ezt nem láthatta; az ablakellenőrzés az első másodpercben elbukik rajta.

- **Az áruház életciklusa** — hozzáadás, átnevezés, törlés — minden `make check`-nél lefut egy ideiglenes gyökérben, és az átnevezést visszaolvassa a fájlból.
- **Az önteszt** megszámolja a kártyákat, a szűrősorokat és a kártyamenüket, megnyitja a beállításokat, összeveti az áruházsorait az oldalmenüével, és bezárja.
- **A nyilvántartási utat** helyi végponton próbáltuk ki, amely az oldal payloadját szolgálta ki; a slug `teletypegames` lett a katalógushosztból, és ugyanannak a rekordnak a kétszeri telepítése ugyanoda került.
- **Az aláírást** mértük, nem feltételeztük: karanténjelző beállítva a kiadási artifactból kicsomagolt példányon, a `codesign --verify --deep --strict` tiszta, a `syspolicy_check` pedig csak a várt *„adhoc signed"* figyelmeztetést jelentette.

**Linuxon és Windowson nem próbáltuk ki.** Az útvonalak és az indítási viselkedés meg vannak írva rájuk, és a CI megépíti a csomagokat, de a `.desktop` és `.lnk` indítók generálódtak és olvashatók, nem kattintottak rájuk. A notarizálás, a windowsos aláíró tanúsítvány és az alkalmazáson belüli frissítés mind nyitott — a [kiadások](https://git.teletypegames.org/stores/warp-engine-client/releases) mondják el, melyik verzió mit változtatott.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Áruházak](https://wiki.teletypegames.org/hu/stores.md)
- Previous: [WarpEngine Batocera Store](https://wiki.teletypegames.org/hu/stores/warp-engine-batocera-store.md)
- Next: [WarpEngine RetroArch Store](https://wiki.teletypegames.org/hu/stores/warp-engine-retroarch-store.md)
