chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { mkdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
buildManifestArtifactSchema,
|
||||
dependencyInventoryArtifactSchema,
|
||||
jsonSchemaDocumentArtifactSchema,
|
||||
registrySnapshotArtifactSchema,
|
||||
supplyChainVerificationArtifactSchema,
|
||||
} from "./contracts/release-artifacts.ts";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
|
||||
|
||||
const ARTIFACT_SCHEMA_DEFINITIONS = Object.freeze([
|
||||
{
|
||||
relativePath: "schemas/artifacts/build-manifest.schema.json",
|
||||
id: undefined,
|
||||
schema: buildManifestArtifactSchema,
|
||||
},
|
||||
{
|
||||
relativePath: "schemas/artifacts/dependency-inventory.schema.json",
|
||||
id: "https://clean-architecture-frontend.local/schemas/dependency-inventory.schema.json",
|
||||
schema: dependencyInventoryArtifactSchema,
|
||||
},
|
||||
{
|
||||
relativePath: "schemas/artifacts/registry-snapshot.schema.json",
|
||||
id: undefined,
|
||||
schema: registrySnapshotArtifactSchema,
|
||||
},
|
||||
{
|
||||
relativePath: "schemas/artifacts/supply-chain-verification.schema.json",
|
||||
id: "https://clean-architecture-frontend.local/schemas/supply-chain-verification.schema.json",
|
||||
schema: supplyChainVerificationArtifactSchema,
|
||||
},
|
||||
] as const);
|
||||
|
||||
type GenerateArtifactSchemasOptions = Readonly<{
|
||||
root?: string;
|
||||
check?: boolean;
|
||||
}>;
|
||||
|
||||
function canonicalize(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(canonicalize);
|
||||
if (value && typeof value === "object") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([key, item]) => [key, canonicalize(item)]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function generatedSchema(
|
||||
definition: (typeof ARTIFACT_SCHEMA_DEFINITIONS)[number],
|
||||
): Readonly<Record<string, unknown>> {
|
||||
const schema = z.toJSONSchema(definition.schema, {
|
||||
target: "draft-2020-12",
|
||||
});
|
||||
return canonicalize({
|
||||
...schema,
|
||||
...(definition.id === undefined ? {} : { $id: definition.id }),
|
||||
}) as Readonly<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export async function generateArtifactSchemas(
|
||||
options: GenerateArtifactSchemasOptions = {},
|
||||
): Promise<void> {
|
||||
const root = path.resolve(options.root ?? process.cwd());
|
||||
const documents = ARTIFACT_SCHEMA_DEFINITIONS.map((definition) => ({
|
||||
definition,
|
||||
document: generatedSchema(definition),
|
||||
}));
|
||||
const drift: string[] = [];
|
||||
|
||||
for (const { definition, document } of documents) {
|
||||
const destination = path.join(root, definition.relativePath);
|
||||
const validatedDocument = jsonSchemaDocumentArtifactSchema.parse(document);
|
||||
const expected = `${JSON.stringify(validatedDocument, null, 2)}\n`;
|
||||
if (options.check) {
|
||||
let actual: string | null = null;
|
||||
try {
|
||||
actual = await readFile(destination, "utf8");
|
||||
} catch {
|
||||
// A missing or unreadable checked-in schema is drift.
|
||||
}
|
||||
if (actual !== expected) drift.push(definition.relativePath);
|
||||
continue;
|
||||
}
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
await writeValidatedJsonArtifact({
|
||||
path: destination,
|
||||
schema: jsonSchemaDocumentArtifactSchema,
|
||||
value: document,
|
||||
});
|
||||
}
|
||||
|
||||
if (drift.length > 0) {
|
||||
throw new Error(`Artifact JSON Schema drift:\n- ${drift.join("\n- ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
function argumentValue(name: string): string | undefined {
|
||||
const index = process.argv.indexOf(name);
|
||||
return index >= 0 ? process.argv[index + 1] : undefined;
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1];
|
||||
if (
|
||||
invokedPath !== undefined &&
|
||||
import.meta.url === pathToFileURL(invokedPath).href
|
||||
) {
|
||||
try {
|
||||
await generateArtifactSchemas({
|
||||
root: argumentValue("--root"),
|
||||
check: process.argv.includes("--check"),
|
||||
});
|
||||
process.stdout.write(
|
||||
process.argv.includes("--check")
|
||||
? "Artifact JSON Schemas: PASS\n"
|
||||
: "Artifact JSON Schemas: GENERATED\n",
|
||||
);
|
||||
} catch (error) {
|
||||
process.stderr.write(
|
||||
`${error instanceof Error ? error.message : String(error)}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user