A registry record is a name and a catalog
`config` — added this morning in 2.1.0 — is gone, and `storeRepositoryUrl` with it, along with the two store repositories they pointed at. 2.1.0 had the registry say how each store behaves. Wrong shape: how a store behaves is fixed per installed client, and this application is the only thing that can see the machine it runs on. A copy of that on a server was a second authority over decisions this side had already made correctly — including which directories the store may delete from — and two authorities are a way to disagree. Keeping two stores on one machine apart needs none of it. It is a subfolder, derived here: the store id is a slug of the catalog host, the home is `<id>-desktop`, the games folder is `<id>`, and that folder is the only subtree the store will ever delete from. Derived from the *catalog* on purpose — the catalog is what a store is, so two records naming the same one are the same store and land in the same place, which makes installing twice idempotent instead of a way to orphan what is already there. Existing installations keep their identity: a store home is recognised by its own `config.json`, so one installed as `ttg` stays `ttg` in `ttg-desktop` with its games where they are. Only a new install derives its id. `StoreProvisioningService` no longer re-reads the registry before installing. That existed to keep the renderer from supplying a config, and with no config in the record there is nothing left to protect: a name and a catalog have no paths in them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@ import path from 'node:path'
|
||||
import { GameDtoMapper } from '../application/mappers/GameDtoMapper'
|
||||
import type { CatalogListing } from '../domain/models/CatalogListing'
|
||||
import type { InstalledStore } from '../domain/models/InstalledStore'
|
||||
import type { RegistryStore } from '../domain/models/RegistryStore'
|
||||
import { DESKTOP_STORE_ENGINE } from '../domain/models/StoreEngine'
|
||||
import { deriveStoreId } from '../domain/models/StoreIdentity'
|
||||
import { NativeStoreCatalogGateway } from '../infrastructure/engine/NativeStoreCatalogGateway'
|
||||
@@ -66,53 +65,17 @@ class SmokeTest {
|
||||
return
|
||||
}
|
||||
this.reportOk('registry', `${String(stores.length)} store(s) from ${this.registry.sourceUrl}`)
|
||||
// The slug is worth printing: it names the store home and the games subfolder, and
|
||||
// it is derived here rather than told to us, so a wrong catalog URL shows up as a
|
||||
// wrong folder name before anything is installed.
|
||||
for (const store of stores) {
|
||||
this.reportOk(` ${store.name}`, `${store.catalogUrl} · ${deriveStoreId(store)}`)
|
||||
await this.checkStoreConfig(store)
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.reportBad('registry', `${this.registry.sourceUrl}: ${this.describe(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Where this store's configuration would come from, in the order the installer asks.
|
||||
*
|
||||
* A store needs no config and no repository: either way the engine's defaults carry
|
||||
* it, so every absence here is reported rather than failed. What is worth seeing is
|
||||
* *which* source answered — a store still being configured by a repository is a store
|
||||
* that has not moved over to the registry yet.
|
||||
*/
|
||||
private async checkStoreConfig (store: RegistryStore): Promise<void> {
|
||||
if (store.config !== null) {
|
||||
const subfolder = this.readSubfolder(store.config)
|
||||
this.reportOk(' config', `${String(Object.keys(store.config).length)} sections ` +
|
||||
`from the registry${subfolder === null ? '' : `, subfolder ${subfolder}`}`)
|
||||
return
|
||||
}
|
||||
if (store.storeRepositoryUrl === null) {
|
||||
this.reportOk(' config', 'none, and no repository — the engine defaults would be used')
|
||||
return
|
||||
}
|
||||
const url = `${store.storeRepositoryUrl.replace(/\/+$/, '')}/raw/branch/master/config.json`
|
||||
try {
|
||||
const config: unknown = JSON.parse(await this.httpClient.readText(url))
|
||||
const sections = typeof config === 'object' && config !== null ? Object.keys(config).length : 0
|
||||
this.reportOk(' config.json', `${String(sections)} sections from the repository ` +
|
||||
'(not yet moved to the registry)')
|
||||
} catch (error: unknown) {
|
||||
this.reportOk(' config.json', `absent (${this.describe(error)}) — defaults would be used`)
|
||||
}
|
||||
}
|
||||
|
||||
/** The prune boundary, which is the field worth seeing at a glance. */
|
||||
private readSubfolder (config: Readonly<Record<string, unknown>>): string | null {
|
||||
const paths = config['paths']
|
||||
if (typeof paths !== 'object' || paths === null) return null
|
||||
const subfolder = (paths as Readonly<Record<string, unknown>>)['subfolder']
|
||||
return typeof subfolder === 'string' ? subfolder : null
|
||||
}
|
||||
|
||||
private findStore (): InstalledStore | null {
|
||||
const sandbox = process.env['SMOKE_HOME']
|
||||
if (sandbox !== undefined && sandbox.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user