Format authored monorepo code with pinned language formatters
This commit is contained in:
@@ -1,38 +1,52 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {execFile as callback, spawn} from "node:child_process";
|
||||
import { execFile as callback, spawn } from "node:child_process";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import {promisify} from "node:util";
|
||||
import {fileURLToPath} from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
const execFile = promisify(callback);
|
||||
const environment = () => ({...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", GIT_TERMINAL_PROMPT: "0"});
|
||||
export const checkerIdentity = () => process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const environment = () => ({ ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", GIT_TERMINAL_PROMPT: "0" });
|
||||
export const checkerIdentity = () =>
|
||||
process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
|
||||
/** Checking snapshots jj, but never publishes or activates the working copy. */
|
||||
export async function snapshotCommit(root: string): Promise<string> {
|
||||
const run = async (...args: string[]) => (await execFile("jj", args, {cwd: root, env: environment()})).stdout.trim();
|
||||
const run = async (...args: string[]) =>
|
||||
(await execFile("jj", args, { cwd: root, env: environment() })).stdout.trim();
|
||||
await run("status");
|
||||
// jj resolve --list exits 1 on a clean revision. Query structured revision
|
||||
// metadata instead of depending on diagnostic wording or swallowing errors.
|
||||
if (await run("--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "conflict") !== "false") throw new Error("Resolve source conflicts before verification");
|
||||
if ((await run("--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "conflict")) !== "false")
|
||||
throw new Error("Resolve source conflicts before verification");
|
||||
const commit = await run("--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "commit_id");
|
||||
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commit)) throw new Error("Verification requires an exact jj commit");
|
||||
await execFile("git", ["diff", "--exit-code", "--no-ext-diff", "--no-textconv", commit, "--"], {cwd: root, env: environment()});
|
||||
const {stdout} = await execFile("git", ["ls-files", "--others", "--exclude-standard", "-z"], {cwd: root});
|
||||
await execFile("git", ["diff", "--exit-code", "--no-ext-diff", "--no-textconv", commit, "--"], {
|
||||
cwd: root,
|
||||
env: environment(),
|
||||
});
|
||||
const { stdout } = await execFile("git", ["ls-files", "--others", "--exclude-standard", "-z"], { cwd: root });
|
||||
if (stdout) throw new Error("Source contains files not captured by jj; inspect jj tracking before verification");
|
||||
return commit;
|
||||
}
|
||||
|
||||
/** Use Git's actual committed tree, never dirty overlays labelled as old pins. */
|
||||
export async function checkoutCommit(root: string, commit: string, destination: string) {
|
||||
await fs.mkdir(destination, {recursive: true});
|
||||
const archive = await execFile("git", ["archive", "--format=tar", commit], {cwd: root, encoding: "buffer", maxBuffer: 128 * 1024 * 1024});
|
||||
await fs.mkdir(destination, { recursive: true });
|
||||
const archive = await execFile("git", ["archive", "--format=tar", commit], {
|
||||
cwd: root,
|
||||
encoding: "buffer",
|
||||
maxBuffer: 128 * 1024 * 1024,
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const child = spawn("tar", ["-xf", "-", "-C", destination], {stdio: ["pipe", "ignore", "pipe"]});
|
||||
const child = spawn("tar", ["-xf", "-", "-C", destination], { stdio: ["pipe", "ignore", "pipe"] });
|
||||
let error = "";
|
||||
child.stderr.on("data", chunk => {error += chunk;});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
error += chunk;
|
||||
});
|
||||
child.on("error", reject);
|
||||
child.on("close", code => code === 0 ? resolve() : reject(new Error(`Cannot extract committed source: ${error}`)));
|
||||
child.on("close", (code) =>
|
||||
code === 0 ? resolve() : reject(new Error(`Cannot extract committed source: ${error}`)),
|
||||
);
|
||||
child.stdin.on("error", reject);
|
||||
child.stdin.end(archive.stdout);
|
||||
});
|
||||
@@ -43,21 +57,47 @@ export async function checkoutCommit(root: string, commit: string, destination:
|
||||
* not a certificate authored or approved by the workspace agent.
|
||||
*/
|
||||
export async function buildCheckedPackage(source: string, schema: string, packageRevisionId: string): Promise<string> {
|
||||
const generator = process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
if (!/^\/nix\/store\/[^/]+$/.test(generator)) throw new Error("Run verification with the installed Quixos tooling (its exact Nix checker is required)");
|
||||
const generator =
|
||||
process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
if (!/^\/nix\/store\/[^/]+$/.test(generator))
|
||||
throw new Error("Run verification with the installed Quixos tooling (its exact Nix checker is required)");
|
||||
const builder = path.join(generator, "share/checked-package.nix");
|
||||
await fs.access(builder);
|
||||
return await new Promise((resolve, reject) => {
|
||||
const child = spawn("nix", ["build", "--impure", "--file", builder,
|
||||
"--argstr", "source", source, "--argstr", "schema", schema,
|
||||
"--argstr", "generator", generator, "--argstr", "packageRevisionId", packageRevisionId,
|
||||
"--no-link", "--print-out-paths", "-L"], {env: environment(), stdio: ["ignore", "pipe", "inherit"]});
|
||||
const child = spawn(
|
||||
"nix",
|
||||
[
|
||||
"build",
|
||||
"--impure",
|
||||
"--file",
|
||||
builder,
|
||||
"--argstr",
|
||||
"source",
|
||||
source,
|
||||
"--argstr",
|
||||
"schema",
|
||||
schema,
|
||||
"--argstr",
|
||||
"generator",
|
||||
generator,
|
||||
"--argstr",
|
||||
"packageRevisionId",
|
||||
packageRevisionId,
|
||||
"--no-link",
|
||||
"--print-out-paths",
|
||||
"-L",
|
||||
],
|
||||
{ env: environment(), stdio: ["ignore", "pipe", "inherit"] },
|
||||
);
|
||||
let output = "";
|
||||
child.stdout.on("data", chunk => {output += chunk;});
|
||||
child.stdout.on("data", (chunk) => {
|
||||
output += chunk;
|
||||
});
|
||||
child.on("error", reject);
|
||||
child.on("close", code => {
|
||||
child.on("close", (code) => {
|
||||
const artifact = output.trim();
|
||||
if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(artifact)) reject(new Error(`Checked Nix build failed (${code}); see build diagnostics above`));
|
||||
if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(artifact))
|
||||
reject(new Error(`Checked Nix build failed (${code}); see build diagnostics above`));
|
||||
else resolve(artifact);
|
||||
});
|
||||
});
|
||||
@@ -65,31 +105,80 @@ export async function buildCheckedPackage(source: string, schema: string, packag
|
||||
|
||||
/** Exact remote source DAG; the Nix checker owns schema construction and all
|
||||
* nested fetches. Keep build noise in a named log, with a bounded failure tail. */
|
||||
export async function buildImmutableCandidate(source: { repository: string; commit: string }, kind: "workspace" | "interface" | "package", logFile: string, contractOnly = false): Promise<string> {
|
||||
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit)) throw new Error("An immutable candidate requires an exact commit");
|
||||
export async function buildImmutableCandidate(
|
||||
source: { repository: string; commit: string },
|
||||
kind: "workspace" | "interface" | "package",
|
||||
logFile: string,
|
||||
contractOnly = false,
|
||||
): Promise<string> {
|
||||
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit))
|
||||
throw new Error("An immutable candidate requires an exact commit");
|
||||
const url = new URL(source.repository);
|
||||
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Candidate origin must be credential-free HTTPS");
|
||||
const generator = process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
|
||||
throw new Error("Candidate origin must be credential-free HTTPS");
|
||||
const generator =
|
||||
process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
if (!/^\/nix\/store\/[^/]+$/.test(generator)) throw new Error("Use the installed Quixos checker");
|
||||
const builder = path.join(generator, "share/checked-candidate.nix");
|
||||
await fs.access(builder);
|
||||
const log = createWriteStream(logFile, { flags: "wx", mode: 0o600 });
|
||||
await new Promise<void>((resolve, reject) => { log.once("open", () => resolve()); log.once("error", reject); });
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
log.once("open", () => resolve());
|
||||
log.once("error", reject);
|
||||
});
|
||||
return await new Promise((resolve, reject) => {
|
||||
let output = "", tail = "", failure: Error | undefined;
|
||||
const child = spawn("nix", ["build", "--impure", "--file", builder,
|
||||
"--argstr", "repository", source.repository, "--argstr", "commit", source.commit,
|
||||
"--argstr", "kind", kind, "--argstr", "generator", generator,
|
||||
"--arg", "contractOnly", contractOnly ? "true" : "false",
|
||||
"--no-link", "--print-out-paths", "-L"], { env: environment(), stdio: ["ignore", "pipe", "pipe"] });
|
||||
log.on("error", error => { failure = error; child.kill(); });
|
||||
child.stdout.on("data", chunk => { output += chunk; });
|
||||
child.stderr.on("data", chunk => { log.write(chunk); tail = (tail + String(chunk)).slice(-6000); });
|
||||
child.on("error", error => { failure = error; });
|
||||
child.on("close", code => log.end(() => {
|
||||
if (failure) reject(failure);
|
||||
else if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(output.trim())) reject(new Error(`Candidate Nix check failed (${code}). Full log: ${logFile}\n${tail}`));
|
||||
else resolve(output.trim());
|
||||
}));
|
||||
let output = "",
|
||||
tail = "",
|
||||
failure: Error | undefined;
|
||||
const child = spawn(
|
||||
"nix",
|
||||
[
|
||||
"build",
|
||||
"--impure",
|
||||
"--file",
|
||||
builder,
|
||||
"--argstr",
|
||||
"repository",
|
||||
source.repository,
|
||||
"--argstr",
|
||||
"commit",
|
||||
source.commit,
|
||||
"--argstr",
|
||||
"kind",
|
||||
kind,
|
||||
"--argstr",
|
||||
"generator",
|
||||
generator,
|
||||
"--arg",
|
||||
"contractOnly",
|
||||
contractOnly ? "true" : "false",
|
||||
"--no-link",
|
||||
"--print-out-paths",
|
||||
"-L",
|
||||
],
|
||||
{ env: environment(), stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
log.on("error", (error) => {
|
||||
failure = error;
|
||||
child.kill();
|
||||
});
|
||||
child.stdout.on("data", (chunk) => {
|
||||
output += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
log.write(chunk);
|
||||
tail = (tail + String(chunk)).slice(-6000);
|
||||
});
|
||||
child.on("error", (error) => {
|
||||
failure = error;
|
||||
});
|
||||
child.on("close", (code) =>
|
||||
log.end(() => {
|
||||
if (failure) reject(failure);
|
||||
else if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(output.trim()))
|
||||
reject(new Error(`Candidate Nix check failed (${code}). Full log: ${logFile}\n${tail}`));
|
||||
else resolve(output.trim());
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user