| @@ -285,6 +285,14 @@ Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset st | |||||
| Il preset è un file fisso: **`factory/preset.zip`** alla radice del progetto (fuori da `data/`, quindi non viene toccato dai reset; fuori da `public/`, quindi non scaricabile via web). | Il preset è un file fisso: **`factory/preset.zip`** alla radice del progetto (fuori da `data/`, quindi non viene toccato dai reset; fuori da `public/`, quindi non scaricabile via web). | ||||
| ### Bootstrap automatico all'avvio | |||||
| All'avvio del processo Next, un hook ([instrumentation.ts](instrumentation.ts) → [lib/bootstrap.ts](lib/bootstrap.ts)) controlla `data/cards.txt`: | |||||
| - Se è **assente o `[]` (zero card)** **E** esiste `factory/preset.zip`, il preset viene estratto automaticamente sopra a `data/` (stessa logica del Factory Reset; la eventuale `data/` precedente viene rinominata `data.bak-<timestamp>/`). | |||||
| - In tutti gli altri casi (cards.txt esiste con contenuto, o il preset manca) il bootstrap non fa nulla. | |||||
| Questo permette di committare nel repo un `factory/preset.zip` e avere ogni macchina appena clonata/distribuita che parte direttamente nello stato standard, senza interventi manuali in admin. Le righe `[bootstrap]` nei log del server segnalano se ha agito. | |||||
| ### Dall'interfaccia | ### Dall'interfaccia | ||||
| - **Factory Reset** — sempre visibile. Ripristina tutto al preset (disabilitato se il preset non esiste). La `data/` precedente resta come `data.bak-<timestamp>/`. | - **Factory Reset** — sempre visibile. Ripristina tutto al preset (disabilitato se il preset non esiste). La `data/` precedente resta come `data.bak-<timestamp>/`. | ||||
| - **Save as Factory Preset (dev)** — visibile solo con `FACTORY_PRESET_SAVE_ENABLED = true`. Congela lo stato corrente in `factory/preset.zip`. | - **Save as Factory Preset (dev)** — visibile solo con `FACTORY_PRESET_SAVE_ENABLED = true`. Congela lo stato corrente in `factory/preset.zip`. | ||||
| @@ -0,0 +1,15 @@ | |||||
| // Hook eseguito una sola volta all'avvio del server Next. | |||||
| // L'unico compito attuale è il bootstrap del factory preset quando data/ è vuoto. | |||||
| export async function register() { | |||||
| // Esegui solo nel runtime Node (l'instrumentation può girare anche su Edge, | |||||
| // dove fs/spawn non esistono). | |||||
| if (process.env.NEXT_RUNTIME !== 'nodejs') return; | |||||
| const { bootstrapFromFactoryPresetIfEmpty } = await import('@/lib/bootstrap'); | |||||
| try { | |||||
| await bootstrapFromFactoryPresetIfEmpty(); | |||||
| } catch (err) { | |||||
| console.warn('[instrumentation] Bootstrap error:', err); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| // Bootstrap "stato di fabbrica" automatico all'avvio. | |||||
| // | |||||
| // Se all'avvio del server NON ci sono card in data/cards.txt (file mancante o `[]`) | |||||
| // E esiste factory/preset.zip, il preset viene estratto sopra a data/. | |||||
| // È quello che permette a una macchina appena clonata/distribuita di partire con | |||||
| // lo stato standard senza interventi manuali. | |||||
| import { readFile, access } from 'node:fs/promises'; | |||||
| import path from 'node:path'; | |||||
| import { restoreFromZipFile } from './restore-zip'; | |||||
| const PROJECT_ROOT = process.cwd(); | |||||
| const CARDS_FILE = path.join(PROJECT_ROOT, 'data', 'cards.txt'); | |||||
| const PRESET_PATH = path.join(PROJECT_ROOT, 'factory', 'preset.zip'); | |||||
| async function hasCards(): Promise<boolean> { | |||||
| try { | |||||
| const raw = await readFile(CARDS_FILE, 'utf-8'); | |||||
| const parsed = JSON.parse(raw || '[]'); | |||||
| return Array.isArray(parsed) && parsed.length > 0; | |||||
| } catch { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| export async function bootstrapFromFactoryPresetIfEmpty(): Promise<void> { | |||||
| // Niente preset di fabbrica → niente da fare, sezione opzionale. | |||||
| try { | |||||
| await access(PRESET_PATH); | |||||
| } catch { | |||||
| return; | |||||
| } | |||||
| // Stato non vuoto → l'admin ha già contenuti, non sovrascriviamo. | |||||
| if (await hasCards()) return; | |||||
| console.log('[bootstrap] No cards found; restoring factory preset from factory/preset.zip'); | |||||
| const result = await restoreFromZipFile(PRESET_PATH); | |||||
| if (result.ok) { | |||||
| console.log(`[bootstrap] Restored ${result.cards} cards, ${result.portals} portals from factory preset`); | |||||
| } else { | |||||
| console.warn(`[bootstrap] Factory restore failed: ${result.error}${result.detail ? ` (${result.detail})` : ''}`); | |||||
| } | |||||
| } | |||||