Format authored monorepo code with pinned language formatters
This commit is contained in:
@@ -6,34 +6,51 @@ import { promisify } from "node:util";
|
||||
import { createHash } from "node:crypto";
|
||||
import { compileWorkspaceRepository, compileCapabilityResourceRepository } from "./assembly.js";
|
||||
import { createGitCapabilityResolver } from "./git-resolver.js";
|
||||
import { contentDigest, planEvolution, type EvolutionReview, type WorkspaceRevision } from "../capability-model/index.js";
|
||||
import {
|
||||
contentDigest,
|
||||
planEvolution,
|
||||
type EvolutionReview,
|
||||
type WorkspaceRevision,
|
||||
} from "../capability-model/index.js";
|
||||
import { bindingSchema } from "../bindings/index.js";
|
||||
import {snapshotCommit, checkoutCommit, buildCheckedPackage} from "./checked-build.js";
|
||||
import { snapshotCommit, checkoutCommit, buildCheckedPackage } from "./checked-build.js";
|
||||
const execFile = promisify(execFileCallback);
|
||||
const bytesDigest = (value: Uint8Array) => `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
||||
|
||||
export const localResourceSnapshots = async (root: string, filename?: string): Promise<{resources: {kind: string; repository: string; commit: string; directory: string}[]}> => {
|
||||
export const localResourceSnapshots = async (
|
||||
root: string,
|
||||
filename?: string,
|
||||
): Promise<{ resources: { kind: string; repository: string; commit: string; directory: string }[] }> => {
|
||||
if (filename) {
|
||||
const document = JSON.parse(await fs.readFile(filename, "utf8"));
|
||||
return {resources: document.resources.map((entry: {directory: string}) => ({...entry, directory: path.resolve(path.dirname(filename), entry.directory)}))};
|
||||
return {
|
||||
resources: document.resources.map((entry: { directory: string }) => ({
|
||||
...entry,
|
||||
directory: path.resolve(path.dirname(filename), entry.directory),
|
||||
})),
|
||||
};
|
||||
}
|
||||
let directory = await fs.realpath(root);
|
||||
for (;;) {
|
||||
let graphText: string | undefined;
|
||||
try {graphText = await fs.readFile(path.join(directory, ".quixos/resource-graph.json"), "utf8");}
|
||||
catch (error) {if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;}
|
||||
try {
|
||||
graphText = await fs.readFile(path.join(directory, ".quixos/resource-graph.json"), "utf8");
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||
}
|
||||
if (graphText !== undefined) {
|
||||
const graph = JSON.parse(graphText);
|
||||
const resources = [];
|
||||
for (const entry of graph.resources) {
|
||||
const location = await fs.realpath(path.resolve(directory, entry.directory));
|
||||
if (!location.startsWith(`${directory}/resources/`)) throw new Error("Workbench resource escapes managed directory");
|
||||
resources.push({kind: entry.kind, ...entry.source, directory: location});
|
||||
if (!location.startsWith(`${directory}/resources/`))
|
||||
throw new Error("Workbench resource escapes managed directory");
|
||||
resources.push({ kind: entry.kind, ...entry.source, directory: location });
|
||||
}
|
||||
return {resources};
|
||||
return { resources };
|
||||
}
|
||||
const parent = path.dirname(directory);
|
||||
if (parent === directory) return {resources: []};
|
||||
if (parent === directory) return { resources: [] };
|
||||
directory = parent;
|
||||
}
|
||||
};
|
||||
@@ -41,18 +58,33 @@ export const localResourceSnapshots = async (root: string, filename?: string): P
|
||||
/** Copy actual authoring files without snapshotting jj or creating a Git commit. */
|
||||
export const snapshotRepository = async (source: string, destination: string) => {
|
||||
const root = await fs.realpath(source);
|
||||
const files = async () => (await execFile("git", ["-C", root, "ls-files", "--cached", "--others", "--exclude-standard", "-z"], { maxBuffer: 16 * 1024 * 1024 })).stdout.split("\0").filter(Boolean).sort();
|
||||
const files = async () =>
|
||||
(
|
||||
await execFile("git", ["-C", root, "ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
})
|
||||
).stdout
|
||||
.split("\0")
|
||||
.filter(Boolean)
|
||||
.sort();
|
||||
const names = [...new Set(await files())];
|
||||
if (names.length > 50_000) throw new Error("Candidate source exceeds 50000 files");
|
||||
const contents: {name: string; digest: string; mode: number}[] = [];
|
||||
const contents: { name: string; digest: string; mode: number }[] = [];
|
||||
let bytes = 0;
|
||||
await fs.mkdir(destination, { recursive: true, mode: 0o700 });
|
||||
for (const name of names) {
|
||||
if (path.isAbsolute(name) || name.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".jj")) throw new Error("Invalid candidate source path");
|
||||
if (path.isAbsolute(name) || name.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".jj"))
|
||||
throw new Error("Invalid candidate source path");
|
||||
const file = path.join(root, name);
|
||||
let metadata;
|
||||
try { metadata = await fs.lstat(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") continue; throw error; }
|
||||
if (!metadata.isFile() || metadata.isSymbolicLink() || !(await fs.realpath(file)).startsWith(`${root}${path.sep}`)) throw new Error(`Candidate source must be a regular file: ${name}`);
|
||||
try {
|
||||
metadata = await fs.lstat(file);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
||||
throw error;
|
||||
}
|
||||
if (!metadata.isFile() || metadata.isSymbolicLink() || !(await fs.realpath(file)).startsWith(`${root}${path.sep}`))
|
||||
throw new Error(`Candidate source must be a regular file: ${name}`);
|
||||
const data = await fs.readFile(file);
|
||||
bytes += data.length;
|
||||
if (bytes > 128 * 1024 * 1024) throw new Error("Candidate source exceeds 128 MiB");
|
||||
@@ -60,28 +92,38 @@ export const snapshotRepository = async (source: string, destination: string) =>
|
||||
await fs.mkdir(path.dirname(path.join(destination, name)), { recursive: true });
|
||||
await fs.writeFile(path.join(destination, name), data, { flag: "wx", mode: metadata.mode & 0o777 });
|
||||
}
|
||||
if (JSON.stringify([...new Set(await files())]) !== JSON.stringify(names)) throw new Error("Source files changed during candidate snapshot");
|
||||
for (const entry of contents) if (bytesDigest(await fs.readFile(path.join(root, entry.name))) !== entry.digest) throw new Error(`Source changed during candidate snapshot: ${entry.name}`);
|
||||
if (JSON.stringify([...new Set(await files())]) !== JSON.stringify(names))
|
||||
throw new Error("Source files changed during candidate snapshot");
|
||||
for (const entry of contents)
|
||||
if (bytesDigest(await fs.readFile(path.join(root, entry.name))) !== entry.digest)
|
||||
throw new Error(`Source changed during candidate snapshot: ${entry.name}`);
|
||||
return { source: root, directory: destination, treeDigest: contentDigest(contents), files: contents };
|
||||
};
|
||||
|
||||
// Local mirrors accelerate resolution, but only their committed locked trees
|
||||
// may stand in for published dependencies. Never relabel dirty files as a pin.
|
||||
async function committedResolver(root: string, temporary: string, filename?: string, publishedOnly = false) {
|
||||
const map = publishedOnly ? {resources: []} : await localResourceSnapshots(root, filename);
|
||||
const map = publishedOnly ? { resources: [] } : await localResourceSnapshots(root, filename);
|
||||
const resources = [];
|
||||
for (const [index, entry] of map.resources.entries()) {
|
||||
const directory = path.join(temporary, `dependency-${index}`);
|
||||
await checkoutCommit(entry.directory, entry.commit, directory);
|
||||
resources.push({...entry, directory});
|
||||
resources.push({ ...entry, directory });
|
||||
}
|
||||
const snapshotMap = path.join(temporary, "snapshots.json");
|
||||
await fs.writeFile(snapshotMap, JSON.stringify({resources}));
|
||||
return createGitCapabilityResolver({checkoutRoot: path.join(temporary, "resolved"), snapshotMap});
|
||||
await fs.writeFile(snapshotMap, JSON.stringify({ resources }));
|
||||
return createGitCapabilityResolver({ checkoutRoot: path.join(temporary, "resolved"), snapshotMap });
|
||||
}
|
||||
|
||||
export const checkResourceCandidate = async (options: {root: string; output: string; kind: "package" | "interface"; source: {repository: string; commit: string}; snapshotMap?: string; publishedOnly?: boolean}) => {
|
||||
await fs.mkdir(options.output, {mode: 0o700});
|
||||
export const checkResourceCandidate = async (options: {
|
||||
root: string;
|
||||
output: string;
|
||||
kind: "package" | "interface";
|
||||
source: { repository: string; commit: string };
|
||||
snapshotMap?: string;
|
||||
publishedOnly?: boolean;
|
||||
}) => {
|
||||
await fs.mkdir(options.output, { mode: 0o700 });
|
||||
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "qx-resource-check-"));
|
||||
const blockers: string[] = [];
|
||||
let treeDigest: string | undefined, commit: string | undefined, artifactPath: string | undefined;
|
||||
@@ -90,62 +132,128 @@ export const checkResourceCandidate = async (options: {root: string; output: str
|
||||
treeDigest = (await snapshotRepository(options.root, path.join(temporary, "observed"))).treeDigest;
|
||||
const root = path.join(temporary, "source");
|
||||
await checkoutCommit(options.root, commit, root);
|
||||
const resolveResource = await committedResolver(options.root, temporary, options.snapshotMap, options.publishedOnly);
|
||||
const compiled = await compileCapabilityResourceRepository({rootDirectory: root, kind: options.kind, source: {resolver: "git", repository: options.source.repository, commit}, resolveResource});
|
||||
const resolveResource = await committedResolver(
|
||||
options.root,
|
||||
temporary,
|
||||
options.snapshotMap,
|
||||
options.publishedOnly,
|
||||
);
|
||||
const compiled = await compileCapabilityResourceRepository({
|
||||
rootDirectory: root,
|
||||
kind: options.kind,
|
||||
source: { resolver: "git", repository: options.source.repository, commit },
|
||||
resolveResource,
|
||||
});
|
||||
if (compiled.resource.kind === "package") {
|
||||
const schema = path.join(temporary, "bindings.json");
|
||||
await fs.writeFile(schema, JSON.stringify(bindingSchema(compiled)));
|
||||
artifactPath = await buildCheckedPackage(root, schema, compiled.resource.revision.revisionId);
|
||||
}
|
||||
if (await snapshotCommit(options.root) !== commit) throw new Error("Source changed during verification; run the check again");
|
||||
if ((await snapshotCommit(options.root)) !== commit)
|
||||
throw new Error("Source changed during verification; run the check again");
|
||||
await fs.writeFile(path.join(options.output, "candidate.json"), JSON.stringify(compiled.resource, null, 2));
|
||||
} catch (error) {
|
||||
blockers.push(error instanceof Error ? error.message : String(error));
|
||||
} finally {await fs.rm(temporary, {recursive: true, force: true});}
|
||||
const result = {candidateOnly: true, activationEvidence: false, commit, treeDigest, artifactPath, blockers,
|
||||
note: "Checked immutable candidate; cutover independently checks current migration/review requirements. No publication or activation performed."};
|
||||
} finally {
|
||||
await fs.rm(temporary, { recursive: true, force: true });
|
||||
}
|
||||
const result = {
|
||||
candidateOnly: true,
|
||||
activationEvidence: false,
|
||||
commit,
|
||||
treeDigest,
|
||||
artifactPath,
|
||||
blockers,
|
||||
note: "Checked immutable candidate; cutover independently checks current migration/review requirements. No publication or activation performed.",
|
||||
};
|
||||
await fs.writeFile(path.join(options.output, "report.json"), JSON.stringify(result, null, 2));
|
||||
return result;
|
||||
};
|
||||
|
||||
export const checkWorkspaceCandidate = async (options: {root: string; output: string; snapshotMap?: string; baseline?: string; reviews?: string}) => {
|
||||
await fs.mkdir(options.output, {mode: 0o700});
|
||||
export const checkWorkspaceCandidate = async (options: {
|
||||
root: string;
|
||||
output: string;
|
||||
snapshotMap?: string;
|
||||
baseline?: string;
|
||||
reviews?: string;
|
||||
}) => {
|
||||
await fs.mkdir(options.output, { mode: 0o700 });
|
||||
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "qx-workspace-check-"));
|
||||
const blockers: string[] = [], checks: {packageRevisionId: string; artifactPath: string}[] = [];
|
||||
const blockers: string[] = [],
|
||||
checks: { packageRevisionId: string; artifactPath: string }[] = [];
|
||||
let commit: string | undefined;
|
||||
try {
|
||||
commit = await snapshotCommit(options.root);
|
||||
for (const entry of (await localResourceSnapshots(options.root, options.snapshotMap)).resources) {
|
||||
const current = await snapshotCommit(entry.directory);
|
||||
const tree = async (revision: string) => (await execFile("git", ["rev-parse", `${revision}^{tree}`], {cwd: entry.directory})).stdout.trim();
|
||||
if (await tree(current) !== await tree(entry.commit)) throw new Error(`Edited resource is not in the root's locked candidate: ${entry.directory}. Check that resource, then run qx-workspace resource upgrade --publish to propagate its revision.`);
|
||||
const tree = async (revision: string) =>
|
||||
(await execFile("git", ["rev-parse", `${revision}^{tree}`], { cwd: entry.directory })).stdout.trim();
|
||||
if ((await tree(current)) !== (await tree(entry.commit)))
|
||||
throw new Error(
|
||||
`Edited resource is not in the root's locked candidate: ${entry.directory}. Check that resource, then run qx-workspace resource upgrade --publish to propagate its revision.`,
|
||||
);
|
||||
}
|
||||
const root = path.join(temporary, "source");
|
||||
await checkoutCommit(options.root, commit, root);
|
||||
const resolveResource = await committedResolver(options.root, temporary, options.snapshotMap);
|
||||
const compiled = await compileWorkspaceRepository({rootDirectory: root, sourceRootCommit: commit, resolveResource});
|
||||
const baseline = options.baseline ? JSON.parse(await fs.readFile(options.baseline, "utf8")) as WorkspaceRevision : null;
|
||||
const reviews = options.reviews ? JSON.parse(await fs.readFile(options.reviews, "utf8")) as EvolutionReview[] : [];
|
||||
const evolution = planEvolution(baseline, compiled.workspace, {reviews});
|
||||
const compiled = await compileWorkspaceRepository({
|
||||
rootDirectory: root,
|
||||
sourceRootCommit: commit,
|
||||
resolveResource,
|
||||
});
|
||||
const baseline = options.baseline
|
||||
? (JSON.parse(await fs.readFile(options.baseline, "utf8")) as WorkspaceRevision)
|
||||
: null;
|
||||
const reviews = options.reviews
|
||||
? (JSON.parse(await fs.readFile(options.reviews, "utf8")) as EvolutionReview[])
|
||||
: [];
|
||||
const evolution = planEvolution(baseline, compiled.workspace, { reviews });
|
||||
blockers.push(...evolution.blockers);
|
||||
for (const resource of compiled.resources.filter(entry => entry.kind === "package")) {
|
||||
for (const resource of compiled.resources.filter((entry) => entry.kind === "package")) {
|
||||
// Per-package recursive schema, identical to host activation, not unrelated
|
||||
// workspace declarations that would unnecessarily invalidate build caches.
|
||||
const candidate = await compileCapabilityResourceRepository({rootDirectory: resource.directory, kind: "package", source: resource.source, resolveResource});
|
||||
const candidate = await compileCapabilityResourceRepository({
|
||||
rootDirectory: resource.directory,
|
||||
kind: "package",
|
||||
source: resource.source,
|
||||
resolveResource,
|
||||
});
|
||||
const schema = path.join(temporary, "bindings.json");
|
||||
await fs.writeFile(schema, JSON.stringify(bindingSchema(candidate)));
|
||||
const artifactPath = await buildCheckedPackage(resource.directory, schema, candidate.resource.revision.revisionId);
|
||||
checks.push({packageRevisionId: candidate.resource.revision.revisionId, artifactPath});
|
||||
const artifactPath = await buildCheckedPackage(
|
||||
resource.directory,
|
||||
schema,
|
||||
candidate.resource.revision.revisionId,
|
||||
);
|
||||
checks.push({ packageRevisionId: candidate.resource.revision.revisionId, artifactPath });
|
||||
}
|
||||
if (await snapshotCommit(options.root) !== commit) throw new Error("Source changed during verification; run the check again");
|
||||
const result = {schemaVersion: 1, candidateOnly: true, activationEvidence: false, commit, evolution, checks, blockers,
|
||||
note: "Checks the committed root and its exact locked dependencies. Resource edits must be verified and repinned before they enter this candidate. No publication or activation performed."};
|
||||
if ((await snapshotCommit(options.root)) !== commit)
|
||||
throw new Error("Source changed during verification; run the check again");
|
||||
const result = {
|
||||
schemaVersion: 1,
|
||||
candidateOnly: true,
|
||||
activationEvidence: false,
|
||||
commit,
|
||||
evolution,
|
||||
checks,
|
||||
blockers,
|
||||
note: "Checks the committed root and its exact locked dependencies. Resource edits must be verified and repinned before they enter this candidate. No publication or activation performed.",
|
||||
};
|
||||
await fs.writeFile(path.join(options.output, "candidate.json"), JSON.stringify(compiled.workspace, null, 2));
|
||||
await fs.writeFile(path.join(options.output, "report.json"), JSON.stringify(result, null, 2));
|
||||
return result;
|
||||
} catch (error) {
|
||||
const result = {schemaVersion: 1, candidateOnly: true, activationEvidence: false, commit, checks, blockers: [...blockers, error instanceof Error ? error.message : String(error)]};
|
||||
const result = {
|
||||
schemaVersion: 1,
|
||||
candidateOnly: true,
|
||||
activationEvidence: false,
|
||||
commit,
|
||||
checks,
|
||||
blockers: [...blockers, error instanceof Error ? error.message : String(error)],
|
||||
};
|
||||
await fs.writeFile(path.join(options.output, "report.json"), JSON.stringify(result, null, 2));
|
||||
return result;
|
||||
} finally {await fs.rm(temporary, {recursive: true, force: true});}
|
||||
} finally {
|
||||
await fs.rm(temporary, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user