import { promises as fs } from 'node:fs' import { LibIds } from '../../lib/ids' import type { RouteInternals } from './route' import type { ErrorBoundaryInternals } from './error-boundary' import type { CordoCommand } from './command' export namespace LockfileInternals { export type ParsedLockfile = { version: number reg: { idCounter: number lutCounter: number } routes: Array> lut: Array $runtime: { routeImpls: Map errorBoundaries: Array registeredCommands: Map } } export const Const = { idLength: 3 } function createEmptyLockfile(version: number): ParsedLockfile { return { version, reg: { idCounter: 0, lutCounter: 0 }, routes: [], lut: [], $runtime: { routeImpls: new Map(), errorBoundaries: [], registeredCommands: new Map() } } } export async function readOrCreateLockfile(path: string): Promise { const lockfile = await readLockfile(path) if (lockfile) return lockfile const out = createEmptyLockfile(1) await writeLockfile(path, out) return out } export async function readLockfile(path: string): Promise { try { const content = await fs.readFile(path, 'ascii') const out = createEmptyLockfile(0) let block = '' for (const line of content.split('\n')) { // Comments if (line.startsWith('#')) continue // Empty lines if (line.length === 0) continue if (line.replace(/^\w+/, '').length === 0) continue // Version declaration if (line.startsWith('cordo lockfile version ')) { out.version = parseInt(line.slice('cordo lockfile version '.length)) continue } if (line.startsWith('[') && line.endsWith(']')) { block = line.slice(1, -1) continue } if (out.version === 0) { // Invalid lockfile return null } if (block === 'reg') { const [ key, value ] = line.split(' ') if (key === 'idc') out.reg.idCounter = parseInt(value) ?? 0 else if (key === 'lutc') out.reg.lutCounter = parseInt(value) ?? 0 } if (block === 'routes') { const [ name, path ] = line.split(' ') out.routes.push({ name, path: path.replace(/\.\w+$/, ''), filePath: path }) } if (block === 'lut') { const [ id, value ] = line.split(' ') out.lut[LibIds.parse(id)] = value } } return out } catch (ex) { return null } } export async function writeLockfile(path: string, data: ParsedLockfile, typeDest?: string | null) { const content = [ '# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.', `cordo lockfile version ${data.version}`, '', '[reg]', `idc ${data.reg.idCounter}`, `lutc ${data.reg.lutCounter}`, '', '[routes]', ...data.routes.map(route => `${route.name} ${route.filePath}`), '', '[lut]', ...data.lut.map((value, idx) => `${LibIds.stringify(idx, Const.idLength)} ${value}`), '', '# EOF' ] await fs.writeFile(path, content.join('\n')) if (typeDest) { const routes = data.routes .filter(route => route.name && data.$runtime.routeImpls.has(route.name)) .map(route => route.path!.replaceAll(/\[.+\]/g, '${string}')) .map(route => route.replace(/\/index$/, '') || '/') const types = [ 'declare module "cordo" {', ` export type DynamicTypes = {`, ` Route: ${routes.map(route => `\`${route}\``).join(' | ')}`, ' }', '}', '' ] await fs.writeFile(typeDest, types.join('\n')) } } }