76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
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";
|
|
|
|
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");
|
|
}
|
|
if (!descriptor.packageRevisionId) {
|
|
errors.push("packageRevisionId is required");
|
|
}
|
|
if (!descriptor.runtimeProtocolVersion) {
|
|
errors.push("runtimeProtocolVersion is required");
|
|
}
|
|
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`);
|
|
}
|
|
if (seenExports.has(entry.exportId)) {
|
|
errors.push(`duplicate export: ${entry.exportId}`);
|
|
}
|
|
seenExports.add(entry.exportId);
|
|
}
|
|
|
|
return errors;
|
|
};
|