Files
quixos-protocol/src/descriptor.ts
T
Timothy J. Aveni f4987093f6 Introduce repository-backed capability workspaces
- define and validate the capability and resource-lock languages
- provision workspace source repositories through Central Gitea
- build package runtimes from pinned standalone sources
- replace legacy schema compilation with workspace persistence plans
- add stable optimistic registers and Automerge CRDT documents
- modernize TypeScript/Nix package builds and runtime activation readiness
2026-09-04 19:08:10 -07:00

86 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;
};