Files
quixos-protocol/src/descriptor.ts
T

86 lines
2.5 KiB
TypeScript
Raw Normal View History

2026-07-12 12:55:03 -07:00
import childProcess from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fromBinary, toJsonString } from "@bufbuild/protobuf";
import type { PackageDescriptor } from "./gen/quixos/package_pb.js";
import { PackageDescriptorSchema } from "./gen/quixos/package_pb.js";
2026-07-12 12:55:03 -07:00
const protoPaths = () => {
const candidates = [
...(process.env.QUIXOS_PROTO_PATH ?? "")
.split(path.delimiter)
.map((entry) => entry.trim())
.filter(Boolean),
path.resolve(process.cwd(), "proto"),
path.resolve(process.cwd(), "quixos-protocol/proto"),
];
return [...new Set(candidates)].filter((candidate) =>
fs.existsSync(path.join(candidate, "quixos/package.proto")),
);
};
export const parsePackageDescriptorTextproto = (
text: string,
): PackageDescriptor => {
const paths = protoPaths();
if (paths.length === 0) {
throw new Error(
"QUIXOS_PROTO_PATH must include quixos-protocol/proto to parse package descriptors",
);
}
const result = childProcess.spawnSync(
"protoc",
[
...paths.map((protoPath) => `--proto_path=${protoPath}`),
"--encode=quixos.PackageDescriptor",
"quixos/package.proto",
],
{
input: Buffer.from(text),
maxBuffer: 1024 * 1024 * 8,
},
);
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw new Error(
`Failed to parse package descriptor textproto:\n${result.stderr.toString().trim()}`,
);
}
return fromBinary(PackageDescriptorSchema, result.stdout);
};
export const packageDescriptorToJson = (descriptor: PackageDescriptor) =>
toJsonString(PackageDescriptorSchema, descriptor, {
prettySpaces: 2,
});
export const validatePackageDescriptor = (
descriptor: PackageDescriptor,
): string[] => {
const errors: string[] = [];
if (!descriptor.packageId) {
errors.push("packageId is required");
2026-07-12 12:55:03 -07:00
}
if (!descriptor.packageRevisionId) {
errors.push("packageRevisionId is required");
2026-07-12 12:55:03 -07:00
}
if (!descriptor.runtimeProtocolVersion) {
errors.push("runtimeProtocolVersion is required");
2026-07-12 12:55:03 -07:00
}
const seenExports = new Set<string>();
for (const [index, entry] of descriptor.exports.entries()) {
if (!entry.exportId) errors.push(`exports[${index}].exportId is required`);
if (!entry.runtimeSymbol) {
errors.push(`exports[${index}].runtimeSymbol is required`);
2026-07-12 12:55:03 -07:00
}
if (seenExports.has(entry.exportId)) {
errors.push(`duplicate export: ${entry.exportId}`);
2026-07-12 12:55:03 -07:00
}
seenExports.add(entry.exportId);
2026-07-12 12:55:03 -07:00
}
return errors;
};