feat(mosaic): unified first-run UX wizard -> gateway install -> verify (#418)
This commit was merged in pull request #418.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { createInterface } from 'node:readline';
|
||||
import type { GatewayMeta } from './daemon.js';
|
||||
import {
|
||||
@@ -21,6 +22,39 @@ import {
|
||||
|
||||
const MOSAIC_CONFIG_FILE = join(GATEWAY_HOME, 'mosaic.config.json');
|
||||
|
||||
// ─── Wizard session state (transient, CU-07-02) ──────────────────────────────
|
||||
|
||||
const INSTALL_STATE_FILE = join(
|
||||
process.env['XDG_RUNTIME_DIR'] ?? process.env['TMPDIR'] ?? tmpdir(),
|
||||
'mosaic-install-state.json',
|
||||
);
|
||||
|
||||
interface InstallSessionState {
|
||||
wizardCompletedAt: string;
|
||||
mosaicHome: string;
|
||||
}
|
||||
|
||||
function readInstallState(): InstallSessionState | null {
|
||||
if (!existsSync(INSTALL_STATE_FILE)) return null;
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(INSTALL_STATE_FILE, 'utf-8')) as InstallSessionState;
|
||||
// Only trust state that is < 10 minutes old
|
||||
const age = Date.now() - new Date(raw.wizardCompletedAt).getTime();
|
||||
if (age > 10 * 60 * 1000) return null;
|
||||
return raw;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function clearInstallState(): void {
|
||||
try {
|
||||
unlinkSync(INSTALL_STATE_FILE);
|
||||
} catch {
|
||||
// Ignore — file may already be gone
|
||||
}
|
||||
}
|
||||
|
||||
interface InstallOpts {
|
||||
host: string;
|
||||
port: number;
|
||||
@@ -41,6 +75,30 @@ export async function runInstall(opts: InstallOpts): Promise<void> {
|
||||
}
|
||||
|
||||
async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOpts): Promise<void> {
|
||||
// CU-07-02: Check for a fresh wizard session state and apply it.
|
||||
const sessionState = readInstallState();
|
||||
if (sessionState) {
|
||||
const defaultHome = join(homedir(), '.config', 'mosaic');
|
||||
const customHome = sessionState.mosaicHome !== defaultHome ? sessionState.mosaicHome : null;
|
||||
|
||||
if (customHome && !process.env['MOSAIC_GATEWAY_HOME']) {
|
||||
// The wizard ran with a custom MOSAIC_HOME that differs from the default.
|
||||
// GATEWAY_HOME is derived from MOSAIC_GATEWAY_HOME (or defaults to
|
||||
// ~/.config/mosaic/gateway). Set the env var so the rest of this install
|
||||
// inherits the correct location. This must be set before GATEWAY_HOME is
|
||||
// evaluated by any imported helper — helpers that re-evaluate the path at
|
||||
// call time will pick it up automatically.
|
||||
process.env['MOSAIC_GATEWAY_HOME'] = join(customHome, 'gateway');
|
||||
console.log(
|
||||
`Resuming from wizard session — gateway home set to ${process.env['MOSAIC_GATEWAY_HOME']}\n`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Resuming from wizard session — using ${sessionState.mosaicHome} from earlier.\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const existing = readMeta();
|
||||
const envExists = existsSync(ENV_FILE);
|
||||
const mosaicConfigExists = existsSync(MOSAIC_CONFIG_FILE);
|
||||
@@ -218,6 +276,13 @@ async function doInstall(rl: ReturnType<typeof createInterface>, opts: InstallOp
|
||||
console.log(` Config: ${GATEWAY_HOME}`);
|
||||
console.log(` Logs: mosaic gateway logs`);
|
||||
console.log(` Status: mosaic gateway status`);
|
||||
|
||||
// Step 7: Post-install verification (CU-07-03)
|
||||
const { runPostInstallVerification } = await import('./verify.js');
|
||||
await runPostInstallVerification(host, port);
|
||||
|
||||
// CU-07-02: Clear transient wizard session state on successful install.
|
||||
clearInstallState();
|
||||
}
|
||||
|
||||
async function runConfigWizard(
|
||||
|
||||
Reference in New Issue
Block a user