Read a store's config from the registry record
`GET /api/stores` records now carry a `config` field — a store's `config.json` moved into the record that already said what the store is — and the client applies it directly. Installing a store no longer depends on a second repository existing and staying reachable, and a store can be configured from the site's admin alone. The order is registry config, then a repository's `config.json`, then the engine's defaults. The middle one is why nothing has to move at once: a registry whose stores have not been migrated is read exactly as before. The window cannot supply a config. It is handed stores to show and hands one back to install, but only as an identity: `RegistryStoreDtoMapper.toModel` drops the config and `StoreProvisioningService` reads the record again from the registry first. A config decides where files are written and, through `paths.subfolder`, which subtree the store may later delete from — not a decision the renderer gets to make, for the same reason a `GameDto` carries no paths. Tested by installing from a record carrying `subfolder: "ATTACKER"` and `install_root: "/tmp/pwned"` and finding neither on disk. A store that has left the registry, or a registry that cannot be re-read, still installs: it falls back to the engine's defaults rather than refusing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,24 +76,43 @@ class SmokeTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* A store needs no repository, and a repository needs no config.json: either way
|
||||
* the engine's defaults carry it. So both absences are reported, not failed.
|
||||
* 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', 'no repository — the engine defaults would be used')
|
||||
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`)
|
||||
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