Darkwing's round 2 candidate, approved by Filbert (#1518 comment 26730). build-r2.patch a27890d5, manifest 869168c7, 34 files, applied on HEAD and checked 34/34. Integration gate on an export of HEAD plus the patch: business 60/60 on Node 24 and 26, every package test and every scripts/test-*.sh green, test-task 98/98 with the live-provider cases. Conductor, queue, conversation and discord confirmed in git worktrees of HEAD with and without the patch, identical results. Lead decision 63 accepts the vocabulary location, the example path and the business branch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
37 lines
1.7 KiB
JavaScript
37 lines
1.7 KiB
JavaScript
// The project file, <root>/.mosaic/project.json. It lives in the project's
|
|
// repository and changes by reviewed commits there. The stack only reads it.
|
|
|
|
import { isAbsolute, join, normalize } from "node:path";
|
|
import { refuse } from "./errors.mjs";
|
|
import { checkVars } from "./vars.mjs";
|
|
import { requireObject, rejectUnknownKeys, requireId, readJsonFile, deepFreeze } from "./util.mjs";
|
|
|
|
export function projectFilePath(root) {
|
|
return join(root, ".mosaic", "project.json");
|
|
}
|
|
|
|
export function validateProjectDocument(document, file) {
|
|
requireObject(document, "project file");
|
|
rejectUnknownKeys(document, ["projectVersion", "id", "vars", "roles"], "project file");
|
|
if (document.projectVersion !== 1) refuse(`project file "projectVersion" must be 1 (${file})`);
|
|
requireId(document.id, "project id");
|
|
const vars = checkVars(document.vars ?? {}, "project", `${file} vars`);
|
|
const roles = {};
|
|
if (document.roles !== undefined) {
|
|
requireObject(document.roles, `${file} roles`);
|
|
for (const [instance, entry] of Object.entries(document.roles)) {
|
|
requireId(instance, `${file} role instance`);
|
|
requireObject(entry, `${file} roles.${instance}`);
|
|
rejectUnknownKeys(entry, ["vars"], `${file} roles.${instance}`);
|
|
roles[instance] = { vars: checkVars(entry.vars ?? {}, "project", `${file} roles.${instance}.vars`) };
|
|
}
|
|
}
|
|
return deepFreeze({ projectVersion: 1, id: document.id, file, vars, roles });
|
|
}
|
|
|
|
export function loadProject(root) {
|
|
if (typeof root !== "string" || !isAbsolute(root) || normalize(root) !== root) refuse(`project root must be a normalized absolute path (got ${JSON.stringify(root)})`);
|
|
const file = projectFilePath(root);
|
|
return validateProjectDocument(readJsonFile(file, "project file"), file);
|
|
}
|