Move quixos protocol to repository root
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
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 {
|
||||
FunctionCapabilityKind,
|
||||
PackageDescriptorSchema,
|
||||
} from "./gen/quixos/package_pb.js";
|
||||
|
||||
const functionKey = (value: {
|
||||
packageNamespace: string;
|
||||
packageName: string;
|
||||
symbol: string;
|
||||
versionRef: string;
|
||||
}) =>
|
||||
[
|
||||
value.packageNamespace,
|
||||
value.packageName,
|
||||
value.symbol,
|
||||
value.versionRef,
|
||||
].join(":");
|
||||
|
||||
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.packageNamespace) {
|
||||
errors.push("packageNamespace is required");
|
||||
}
|
||||
if (!descriptor.packageName) {
|
||||
errors.push("packageName is required");
|
||||
}
|
||||
if (descriptor.descriptorVersion === 0) {
|
||||
errors.push("descriptorVersion must be greater than zero");
|
||||
}
|
||||
if (
|
||||
descriptor.interfaceCompatibleBackTo !== 0 &&
|
||||
descriptor.interfaceCompatibleBackTo > descriptor.descriptorVersion
|
||||
) {
|
||||
errors.push("interfaceCompatibleBackTo must not exceed descriptorVersion");
|
||||
}
|
||||
if (
|
||||
descriptor.runnerCompatibleBackTo !== 0 &&
|
||||
descriptor.runnerCompatibleBackTo > descriptor.descriptorVersion
|
||||
) {
|
||||
errors.push("runnerCompatibleBackTo must not exceed descriptorVersion");
|
||||
}
|
||||
|
||||
const seenFunctions = new Set<string>();
|
||||
for (const [index, entry] of descriptor.functionExports.entries()) {
|
||||
const fn = entry.function;
|
||||
if (!fn) {
|
||||
errors.push(`functionExports[${index}].function is required`);
|
||||
continue;
|
||||
}
|
||||
if (!fn.packageNamespace) {
|
||||
fn.packageNamespace = descriptor.packageNamespace;
|
||||
}
|
||||
if (!fn.packageName) {
|
||||
fn.packageName = descriptor.packageName;
|
||||
}
|
||||
if (
|
||||
fn.packageNamespace !== descriptor.packageNamespace ||
|
||||
fn.packageName !== descriptor.packageName
|
||||
) {
|
||||
errors.push(
|
||||
`functionExports[${index}] must export from this package (${descriptor.packageNamespace}/${descriptor.packageName})`,
|
||||
);
|
||||
}
|
||||
if (!fn.symbol) {
|
||||
errors.push(`functionExports[${index}].function.symbol is required`);
|
||||
}
|
||||
if (entry.interfaceVersion === 0) {
|
||||
errors.push(`functionExports[${index}].interfaceVersion is required`);
|
||||
}
|
||||
if (entry.capabilityKind === FunctionCapabilityKind.FUNCTION_CAPABILITY_KIND_UNSPECIFIED) {
|
||||
errors.push(`functionExports[${index}].capabilityKind is required`);
|
||||
}
|
||||
if (
|
||||
entry.interfaceCompatibleBackTo !== 0 &&
|
||||
entry.interfaceCompatibleBackTo > entry.interfaceVersion
|
||||
) {
|
||||
errors.push(
|
||||
`functionExports[${index}].interfaceCompatibleBackTo must not exceed interfaceVersion`,
|
||||
);
|
||||
}
|
||||
const key = functionKey(fn);
|
||||
if (seenFunctions.has(key)) {
|
||||
errors.push(`duplicate function export: ${key}`);
|
||||
}
|
||||
seenFunctions.add(key);
|
||||
}
|
||||
|
||||
for (const [index, entry] of descriptor.dependencies.entries()) {
|
||||
if (!entry.package) {
|
||||
errors.push(`dependencies[${index}].package is required`);
|
||||
continue;
|
||||
}
|
||||
if (!entry.package.packageNamespace) {
|
||||
errors.push(`dependencies[${index}].package.packageNamespace is required`);
|
||||
}
|
||||
if (!entry.package.packageName) {
|
||||
errors.push(`dependencies[${index}].package.packageName is required`);
|
||||
}
|
||||
if (entry.package.descriptorVersion === 0) {
|
||||
errors.push(`dependencies[${index}].package.descriptorVersion is required`);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
Reference in New Issue
Block a user