perf(fleet): scan persona dirs once per provision
Fix #665 test timeout.
This commit is contained in:
@@ -234,6 +234,21 @@ export async function resolvePersona(
|
||||
extractClassesFromDir(rolesDir),
|
||||
extractClassesFromDir(overrideDir),
|
||||
]);
|
||||
return resolvePersonaFrom(klass, { rolesDir, overrideDir, base, over });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a single class against ALREADY-EXTRACTED layer maps. Callers that
|
||||
* resolve many classes against the same two directories (e.g. provisioning a
|
||||
* full roster) should {@link extractClassesFromDir} each dir ONCE and reuse the
|
||||
* result here, rather than paying a full directory re-scan per class. Precedence
|
||||
* is identical to {@link resolvePersona}: override layer wins, then baseline.
|
||||
*/
|
||||
export async function resolvePersonaFrom(
|
||||
klass: string,
|
||||
layers: { rolesDir: string; overrideDir: string; base: DirClasses; over: DirClasses },
|
||||
): Promise<PersonaResolution | null> {
|
||||
const { rolesDir, overrideDir, base, over } = layers;
|
||||
|
||||
const fromLayer = async (
|
||||
dir: string,
|
||||
|
||||
@@ -33,7 +33,12 @@ import {
|
||||
defaultRolesDir,
|
||||
listPersonaClassesWithOverrides,
|
||||
} from './fleet-profiles.js';
|
||||
import { defaultOverrideDir, resolvePersona, type PersonaLayer } from './fleet-personas.js';
|
||||
import {
|
||||
defaultOverrideDir,
|
||||
extractClassesFromDir,
|
||||
resolvePersonaFrom,
|
||||
type PersonaLayer,
|
||||
} from './fleet-personas.js';
|
||||
|
||||
function defaultMosaicHome(): string {
|
||||
return process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
|
||||
@@ -172,12 +177,21 @@ export async function generateRoster(
|
||||
? profile.roster
|
||||
: profile.roster.filter((e) => floor.has(e.class));
|
||||
|
||||
// Scan the persona directories ONCE, then resolve every roster entry against
|
||||
// the in-memory maps. resolvePersona() would otherwise re-scan both dirs per
|
||||
// entry — O(entries × files) redundant reads that push --full provisioning
|
||||
// past the test timeout on slow/contended filesystems.
|
||||
const [base, over] = await Promise.all([
|
||||
extractClassesFromDir(rolesDir),
|
||||
extractClassesFromDir(overrideDir),
|
||||
]);
|
||||
|
||||
const seats: GeneratedSeat[] = [];
|
||||
for (const entry of selected) {
|
||||
const isFloor = floor.has(entry.class);
|
||||
const isLead = entry.class === lead;
|
||||
|
||||
const resolved = await resolvePersona(entry.class, { rolesDir, overrideDir });
|
||||
const resolved = await resolvePersonaFrom(entry.class, { rolesDir, overrideDir, base, over });
|
||||
if (!resolved) {
|
||||
// Defensive: validateProfile already guards this, but a class can resolve
|
||||
// for membership yet have no readable file. Fail loudly rather than emit a
|
||||
|
||||
Reference in New Issue
Block a user