63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import type { ControlPlaneState } from '../../src/shared/contracts';
|
|
import { createDefaultState } from './config';
|
|
|
|
export class StateStore {
|
|
constructor(private readonly statePath: string) {}
|
|
|
|
async read(): Promise<ControlPlaneState> {
|
|
await fs.mkdir(path.dirname(this.statePath), { recursive: true });
|
|
|
|
try {
|
|
const raw = await fs.readFile(this.statePath, 'utf8');
|
|
const state = JSON.parse(raw) as ControlPlaneState;
|
|
const migrated = migrateLegacyAdminServices(state);
|
|
|
|
if (migrated !== state) {
|
|
await this.write(migrated);
|
|
}
|
|
|
|
return migrated;
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
|
|
const fallback = createDefaultState();
|
|
await this.write(fallback);
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
async write(state: ControlPlaneState): Promise<void> {
|
|
await fs.mkdir(path.dirname(this.statePath), { recursive: true });
|
|
await fs.writeFile(this.statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
}
|
|
}
|
|
|
|
function migrateLegacyAdminServices(state: ControlPlaneState): ControlPlaneState {
|
|
const legacyServiceIds = new Set(
|
|
state.system.services
|
|
.filter((service) => (service as { command?: unknown }).command === 'admin')
|
|
.map((service) => service.id),
|
|
);
|
|
|
|
if (legacyServiceIds.size === 0) {
|
|
return state;
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
service: {
|
|
...state.service,
|
|
lastEvent: 'Legacy admin service removed from stored panel state',
|
|
},
|
|
userRecords: state.userRecords.filter((user) => !legacyServiceIds.has(user.serviceId)),
|
|
system: {
|
|
...state.system,
|
|
services: state.system.services.filter((service) => !legacyServiceIds.has(service.id)),
|
|
},
|
|
};
|
|
}
|