test(gateway): cover published protocol artifacts (#118799)

* test(gateway): cover published protocol artifacts

* test(gateway): isolate Swift artifact proof

* test(gateway): compile Swift models portably

* test(gateway): align protocol metadata fixture

* test(gateway): install packed protocol as consumer
This commit is contained in:
Vincent Koc
2026-08-04 03:26:29 +08:00
committed by GitHub
parent ded1ec0c28
commit 7741cbdaa7
4 changed files with 809 additions and 0 deletions
@@ -0,0 +1,71 @@
import Foundation
import OpenClawProtocol
import Testing
struct GatewayProtocolGeneratedModelsTests {
@Test
func `generated frames decode legacy minimums and additive fields`() throws {
let request = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(#"{"type":"req","id":"old-req","method":"health"}"#.utf8))
let additiveRequest = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(
#"{"type":"req","id":"new-req","method":"health","traceparent":"00-0123456789abcdef0123456789abcdef-0123456789abcdef-01","futureField":true}"#
.utf8))
let response = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(#"{"type":"res","id":"old-req","ok":true}"#.utf8))
let additiveResponse = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(
#"{"type":"res","id":"new-req","ok":true,"payload":{"healthy":true},"futureField":true}"#.utf8))
let event = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(#"{"type":"event","event":"tick"}"#.utf8))
let additiveEvent = try JSONDecoder().decode(
GatewayFrame.self,
from: Data(
#"{"type":"event","event":"tick","payload":{"ts":1},"seq":2,"futureField":true}"#.utf8))
guard case let .req(oldRequest) = request,
case let .req(newRequest) = additiveRequest,
case let .res(oldResponse) = response,
case let .res(newResponse) = additiveResponse,
case let .event(oldEvent) = event,
case let .event(newEvent) = additiveEvent
else {
Issue.record("Expected generated request, response, and event frame cases")
return
}
#expect(oldRequest.params == nil)
#expect(oldRequest.traceparent == nil)
#expect(newRequest.traceparent == "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01")
#expect(oldResponse.payload == nil)
#expect(newResponse.payload != nil)
#expect(oldEvent.seq == nil)
#expect(newEvent.seq == 2)
}
@Test
func `generated connect model decodes old and additive handshakes`() throws {
let legacy = try JSONDecoder().decode(
ConnectParams.self,
from: Data(
#"{"minProtocol":4,"maxProtocol":4,"client":{"id":"test","version":"old","platform":"ios","mode":"test"}}"#
.utf8))
let additive = try JSONDecoder().decode(
ConnectParams.self,
from: Data(
#"{"minProtocol":4,"maxProtocol":4,"client":{"id":"test","version":"new","platform":"ios","mode":"test","futureClientField":true},"role":"operator","scopes":["operator.read"],"locale":"en-US","futureField":true}"#
.utf8))
#expect(legacy.role == nil)
#expect(legacy.scopes == nil)
#expect(legacy.locale == nil)
#expect(additive.role == "operator")
#expect(additive.scopes == ["operator.read"])
#expect(additive.locale == "en-US")
}
}
@@ -0,0 +1,38 @@
title: Gateway published protocol artifacts
scenario:
id: gateway-protocol-artifacts
surface: runtime
category: gateway.protocol-compatibility
coverage:
primary:
- gateway.published-protocol-schema
- gateway.json-schema-export
- gateway.swift-client-models
- gateway.backward-compatible-evolution
objective: Verify the published Gateway protocol package and generated Swift models stay aligned with the canonical protocol registry and additive compatibility contract.
successCriteria:
- The real `@openclaw/gateway-protocol` package build is packed and unpacked before inspection.
- The unpacked package imports its built validators and schema registry successfully.
- The shipped `protocol.schema.json` exactly matches the canonical TypeBox registry and includes connect, request, response, and event definitions.
- The canonical protocol drift checks, including generated Swift model drift, pass.
- Generated Swift models compile and decode legacy-minimal frames plus additive optional and unknown fields.
docsRefs:
- docs/gateway/protocol.md
- docs/concepts/qa-e2e-automation.md
- docs/reference/test.md
codeRefs:
- packages/gateway-protocol/package.json
- packages/gateway-protocol/src/schema/protocol-schemas.ts
- scripts/protocol-gen.ts
- scripts/protocol-gen-swift.ts
- test/e2e/qa-lab/runtime/gateway-protocol-artifacts.ts
- apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayProtocolGeneratedModelsTests.swift
execution:
kind: script
path: test/e2e/qa-lab/runtime/gateway-protocol-artifacts.ts
summary: Packs and imports the real Gateway protocol npm artifact, checks canonical JSON Schema and generated-model drift, then runs targeted Swift compatibility decoding.
timeoutMs: 1800000
args:
- --artifact-base
- ${outputDir}
@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import {
assertPublishedProtocolSchema,
buildCanonicalProtocolSchema,
buildInstalledProtocolInspectionScript,
buildPortableSwiftAnyCodableSource,
buildSwiftProtocolCompatibilityHarness,
parseGatewayProtocolArtifactOptions,
} from "./gateway-protocol-artifacts.js";
describe("Gateway protocol artifact producer", () => {
it("parses one artifact directory", () => {
expect(
parseGatewayProtocolArtifactOptions(["--artifact-base", ".artifacts/protocol"], "/repo"),
).toEqual({
artifactBase: "/repo/.artifacts/protocol",
repoRoot: "/repo",
});
expect(() => parseGatewayProtocolArtifactOptions([])).toThrow("--artifact-base is required");
expect(() =>
parseGatewayProtocolArtifactOptions(["--artifact-base", "one", "--artifact-base", "two"]),
).toThrow("--artifact-base was provided more than once");
});
it("builds the same schema envelope as the protocol generator", () => {
const request = { type: "object", properties: { type: { const: "req" } } };
const canonical = buildCanonicalProtocolSchema(
{
ConnectParams: { type: "object" },
EventFrame: { type: "object" },
RequestFrame: request,
ResponseFrame: { type: "object" },
},
[{ name: "health", scope: "operator.read", since: "2026.7" }],
);
expect(canonical.definitions.RequestFrame).toEqual(request);
expect(canonical.methods.health).toEqual({ scope: "operator.read", since: "2026.7" });
expect(canonical.discriminator.mapping.req).toBe("#/definitions/RequestFrame");
});
it("requires the published schema and built registry to match canonical definitions", () => {
const definitions = {
ConnectParams: { type: "object" },
EventFrame: { type: "object" },
RequestFrame: { type: "object" },
ResponseFrame: { type: "object" },
};
const canonical = buildCanonicalProtocolSchema(definitions, []);
expect(() =>
assertPublishedProtocolSchema({
builtSchemas: definitions,
canonical,
published: canonical,
}),
).not.toThrow();
expect(() =>
assertPublishedProtocolSchema({
builtSchemas: definitions,
canonical,
published: {
...canonical,
definitions: {
...definitions,
EventFrame: { type: "string" },
},
},
}),
).toThrow("published protocol.schema.json differs");
});
it("loads installed artifacts through their public package specifiers", () => {
const script = buildInstalledProtocolInspectionScript();
expect(script).toContain('from "@openclaw/gateway-protocol"');
expect(script).toContain('from "@openclaw/gateway-protocol/schema"');
expect(script).not.toContain("dist/index.mjs");
expect(script).not.toContain("dist/schema.mjs");
});
it("builds a standalone Swift compatibility harness for the generated model artifact", () => {
const harness = buildSwiftProtocolCompatibilityHarness();
expect(harness).toContain("@main");
expect(harness).toContain("GatewayFrame.self");
expect(harness).toContain("ConnectParams.self");
expect(harness).toContain('"futureField":true');
});
it("makes the AnyCodable dependency portable for standalone Linux compilation", () => {
expect(buildPortableSwiftAnyCodableSource("import Foundation\n")).toBe(
"import CoreFoundation\nimport Foundation\n",
);
expect(buildPortableSwiftAnyCodableSource("import CoreFoundation\n")).toBe(
"import CoreFoundation\n",
);
});
});
@@ -0,0 +1,601 @@
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { x as extractTar } from "tar";
import {
QA_EVIDENCE_FILENAME,
type QaEvidenceSummaryJson,
} from "../../../../extensions/qa-lab/api.js";
import { ProtocolSchemas } from "../../../../packages/gateway-protocol/src/schema/protocol-schemas.js";
import { listCoreGatewayMethodMetadata } from "../../../../src/gateway/methods/core-descriptors.js";
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
const SOURCE_PATH = "test/e2e/qa-lab/runtime/gateway-protocol-artifacts.ts";
const REQUIRED_DEFINITIONS = [
"ConnectParams",
"RequestFrame",
"ResponseFrame",
"EventFrame",
] as const;
type ProducerOptions = {
artifactBase: string;
repoRoot: string;
};
type ProtocolSchemaDocument = {
definitions: Record<string, unknown>;
discriminator: {
mapping: Record<string, string>;
propertyName: string;
};
methods: Record<string, { scope: string; since: number }>;
oneOf: Array<{ $ref: string }>;
};
type ProtocolArtifactSummary = {
consumer: {
installed: boolean;
packageSpecifier: string;
schemaSpecifier: string;
};
definitions: string[];
package: {
name: string;
sha256: string;
tarball: string;
version: string;
};
validators: {
connect: boolean;
request: boolean;
};
};
type InstalledProtocolInspection = {
schemas: Record<string, unknown>;
validators: {
connect: {
errors: unknown[] | null;
valid: boolean;
};
request: {
errors: unknown[] | null;
valid: boolean;
};
};
};
export function buildInstalledProtocolInspectionScript() {
return `import fs from "node:fs/promises";
import {
validateConnectParams,
validateRequestFrame,
} from "@openclaw/gateway-protocol";
import { ProtocolSchemas } from "@openclaw/gateway-protocol/schema";
const requestValid = validateRequestFrame({
type: "req",
id: "artifact-request",
method: "health",
});
const connectValid = validateConnectParams({
minProtocol: 4,
maxProtocol: 4,
client: {
id: "test",
version: "artifact-proof",
platform: process.platform,
mode: "test",
},
});
await fs.writeFile(
process.argv[2],
JSON.stringify({
schemas: ProtocolSchemas,
validators: {
connect: {
errors: validateConnectParams.errors,
valid: connectValid,
},
request: {
errors: validateRequestFrame.errors,
valid: requestValid,
},
},
}),
"utf8",
);
`;
}
export function buildSwiftProtocolCompatibilityHarness() {
return `import Foundation
enum GatewayProtocolArtifactError: Error {
case unexpected(String)
}
@main
struct GatewayProtocolArtifactHarness {
static func decode<T: Decodable>(_ type: T.Type, _ json: String) throws -> T {
try JSONDecoder().decode(type, from: Data(json.utf8))
}
static func main() throws {
let request = try decode(
GatewayFrame.self,
#"{"type":"req","id":"old-req","method":"health"}"#)
let additiveRequest = try decode(
GatewayFrame.self,
#"{"type":"req","id":"new-req","method":"health","traceparent":"00-0123456789abcdef0123456789abcdef-0123456789abcdef-01","futureField":true}"#)
let response = try decode(
GatewayFrame.self,
#"{"type":"res","id":"old-req","ok":true}"#)
let additiveResponse = try decode(
GatewayFrame.self,
#"{"type":"res","id":"new-req","ok":true,"payload":{"healthy":true},"futureField":true}"#)
let event = try decode(
GatewayFrame.self,
#"{"type":"event","event":"tick"}"#)
let additiveEvent = try decode(
GatewayFrame.self,
#"{"type":"event","event":"tick","payload":{"ts":1},"seq":2,"futureField":true}"#)
let legacyConnect = try decode(
ConnectParams.self,
#"{"minProtocol":4,"maxProtocol":4,"client":{"id":"test","version":"old","platform":"ios","mode":"test"}}"#)
let additiveConnect = try decode(
ConnectParams.self,
#"{"minProtocol":4,"maxProtocol":4,"client":{"id":"test","version":"new","platform":"ios","mode":"test","futureClientField":true},"role":"operator","scopes":["operator.read"],"locale":"en-US","futureField":true}"#)
guard case let .req(oldRequest) = request,
case let .req(newRequest) = additiveRequest,
case let .res(oldResponse) = response,
case let .res(newResponse) = additiveResponse,
case let .event(oldEvent) = event,
case let .event(newEvent) = additiveEvent
else {
throw GatewayProtocolArtifactError.unexpected("frame discriminator")
}
guard oldRequest.params == nil,
oldRequest.traceparent == nil,
newRequest.traceparent == "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01",
oldResponse.payload == nil,
newResponse.payload != nil,
oldEvent.seq == nil,
newEvent.seq == 2,
legacyConnect.role == nil,
legacyConnect.scopes == nil,
legacyConnect.locale == nil,
additiveConnect.role == "operator",
additiveConnect.scopes == ["operator.read"],
additiveConnect.locale == "en-US"
else {
throw GatewayProtocolArtifactError.unexpected("legacy or additive decode")
}
print("gateway Swift generated-model compatibility passed")
}
}
`;
}
export function buildPortableSwiftAnyCodableSource(source: string) {
return source.includes("import CoreFoundation") ? source : `import CoreFoundation\n${source}`;
}
function formatErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
export function parseGatewayProtocolArtifactOptions(
args: readonly string[],
cwd = process.cwd(),
): ProducerOptions {
let artifactBase: string | undefined;
for (let index = 0; index < args.length; index += 1) {
const option = args[index];
const value = args[index + 1];
if (option !== "--artifact-base") {
throw new Error(`unknown argument: ${option}`);
}
if (!value || value.startsWith("--")) {
throw new Error("--artifact-base requires a value");
}
if (artifactBase) {
throw new Error("--artifact-base was provided more than once");
}
artifactBase = value;
index += 1;
}
if (!artifactBase) {
throw new Error("--artifact-base is required");
}
return {
artifactBase: path.resolve(cwd, artifactBase),
repoRoot: path.resolve(cwd),
};
}
export function buildCanonicalProtocolSchema(
schemas: Record<string, unknown> = ProtocolSchemas,
methodMetadata = listCoreGatewayMethodMetadata(),
): ProtocolSchemaDocument {
return JSON.parse(
JSON.stringify({
$schema: "http://json-schema.org/draft-07/schema#",
$id: "https://openclaw.ai/protocol.schema.json",
title: "OpenClaw Gateway Protocol",
description: "Handshake, request/response, and event frames for the Gateway WebSocket.",
oneOf: [
{ $ref: "#/definitions/RequestFrame" },
{ $ref: "#/definitions/ResponseFrame" },
{ $ref: "#/definitions/EventFrame" },
],
discriminator: {
propertyName: "type",
mapping: {
req: "#/definitions/RequestFrame",
res: "#/definitions/ResponseFrame",
event: "#/definitions/EventFrame",
},
},
methods: Object.fromEntries(
methodMetadata.map(({ name, scope, since }) => [name, { since, scope }]),
),
definitions: schemas,
}),
) as ProtocolSchemaDocument;
}
export function assertPublishedProtocolSchema(params: {
builtSchemas: Record<string, unknown>;
canonical: ProtocolSchemaDocument;
published: ProtocolSchemaDocument;
}) {
assert.deepEqual(
JSON.parse(JSON.stringify(params.builtSchemas)),
params.canonical.definitions,
"built package schema registry differs from the canonical TypeBox registry",
);
assert.deepEqual(
params.published,
params.canonical,
"published protocol.schema.json differs from the canonical TypeBox registry",
);
for (const definition of REQUIRED_DEFINITIONS) {
assert.ok(
Object.hasOwn(params.published.definitions, definition),
`published protocol schema is missing ${definition}`,
);
}
assert.deepEqual(
params.published.oneOf.map((entry) => entry.$ref),
["#/definitions/RequestFrame", "#/definitions/ResponseFrame", "#/definitions/EventFrame"],
);
}
async function runCommand(params: {
args: string[];
appendLog: (chunk: unknown) => void;
command: string;
cwd: string;
}) {
await new Promise<void>((resolve, reject) => {
const child = spawn(params.command, params.args, {
cwd: params.cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
child.on("error", reject);
child.stdout.on("data", (chunk: Buffer) => {
process.stdout.write(chunk);
params.appendLog(chunk);
});
child.stderr.on("data", (chunk: Buffer) => {
process.stderr.write(chunk);
params.appendLog(chunk);
});
child.on("exit", (code, signal) => {
if (code === 0 && !signal) {
resolve();
return;
}
reject(
new Error(
`${params.command} ${params.args.join(" ")} failed: code=${String(code)} signal=${String(signal)}`,
),
);
});
});
}
async function withPreservedPackageOutputs<T>(repoRoot: string, run: () => Promise<T>): Promise<T> {
const packageRoot = path.join(repoRoot, "packages", "gateway-protocol");
const targets = [path.join(packageRoot, "dist"), path.join(packageRoot, "protocol.schema.json")];
const backupRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gateway-protocol-pack-"));
const backups: Array<{ backup: string; target: string }> = [];
try {
for (const [index, target] of targets.entries()) {
try {
await fs.access(target);
} catch {
continue;
}
const backup = path.join(backupRoot, String(index));
await fs.rename(target, backup);
backups.push({ backup, target });
}
return await run();
} finally {
for (const target of targets) {
await fs.rm(target, { force: true, recursive: true });
}
for (const { backup, target } of backups) {
await fs.rename(backup, target);
}
await fs.rm(backupRoot, { force: true, recursive: true });
}
}
async function sha256(filePath: string) {
const contents = await fs.readFile(filePath);
return createHash("sha256").update(contents).digest("hex");
}
async function packAndInspectProtocol(params: {
appendLog: (chunk: unknown) => void;
artifactBase: string;
repoRoot: string;
}): Promise<ProtocolArtifactSummary> {
const packageRoot = path.join(params.repoRoot, "packages", "gateway-protocol");
const packDir = path.join(params.artifactBase, "package");
const extractDir = path.join(params.artifactBase, "unpacked");
await fs.rm(packDir, { force: true, recursive: true });
await fs.rm(extractDir, { force: true, recursive: true });
await fs.mkdir(packDir, { recursive: true });
await fs.mkdir(extractDir, { recursive: true });
await withPreservedPackageOutputs(params.repoRoot, async () => {
await runCommand({
args: ["--dir", packageRoot, "pack", "--pack-destination", packDir],
appendLog: params.appendLog,
command: "pnpm",
cwd: params.repoRoot,
});
});
const tarballs = (await fs.readdir(packDir)).filter((entry) => entry.endsWith(".tgz"));
assert.equal(
tarballs.length,
1,
`expected one packed protocol tarball, found ${tarballs.length}`,
);
const tarball = tarballs[0]!;
const tarballPath = path.join(packDir, tarball);
await extractTar({ cwd: extractDir, file: tarballPath });
const unpackedRoot = path.join(extractDir, "package");
const packageJson = JSON.parse(
await fs.readFile(path.join(unpackedRoot, "package.json"), "utf8"),
) as { name?: string; version?: string };
assert.equal(packageJson.name, "@openclaw/gateway-protocol");
assert.ok(packageJson.version, "packed protocol package version is missing");
const published = JSON.parse(
await fs.readFile(path.join(unpackedRoot, "protocol.schema.json"), "utf8"),
) as ProtocolSchemaDocument;
const consumerRoot = await fs.mkdtemp(
path.join(os.tmpdir(), "openclaw-gateway-protocol-consumer-"),
);
const consumerInspectionPath = path.join(consumerRoot, "inspection.json");
const inspection = await (async () => {
try {
await fs.writeFile(
path.join(consumerRoot, "package.json"),
`${JSON.stringify({
name: "openclaw-gateway-protocol-artifact-consumer",
private: true,
type: "module",
})}\n`,
"utf8",
);
await fs.writeFile(
path.join(consumerRoot, "inspect.mjs"),
buildInstalledProtocolInspectionScript(),
"utf8",
);
await runCommand({
args: ["--dir", consumerRoot, "add", "--ignore-scripts", "--lockfile=false", tarballPath],
appendLog: params.appendLog,
command: "pnpm",
cwd: params.repoRoot,
});
await runCommand({
args: ["inspect.mjs", consumerInspectionPath],
appendLog: params.appendLog,
command: "node",
cwd: consumerRoot,
});
return JSON.parse(
await fs.readFile(consumerInspectionPath, "utf8"),
) as InstalledProtocolInspection;
} finally {
await fs.rm(consumerRoot, { force: true, recursive: true });
}
})();
const canonical = buildCanonicalProtocolSchema();
assertPublishedProtocolSchema({
builtSchemas: inspection.schemas,
canonical,
published,
});
assert.ok(
inspection.validators.request.valid,
`installed request validator rejected a minimal frame: ${JSON.stringify(inspection.validators.request.errors)}`,
);
assert.ok(
inspection.validators.connect.valid,
`installed connect validator rejected a minimal payload: ${JSON.stringify(inspection.validators.connect.errors)}`,
);
return {
consumer: {
installed: true,
packageSpecifier: "@openclaw/gateway-protocol",
schemaSpecifier: "@openclaw/gateway-protocol/schema",
},
definitions: REQUIRED_DEFINITIONS.filter((definition) =>
Object.hasOwn(published.definitions, definition),
),
package: {
name: packageJson.name,
sha256: await sha256(tarballPath),
tarball,
version: packageJson.version,
},
validators: {
connect: inspection.validators.connect.valid,
request: inspection.validators.request.valid,
},
};
}
async function compileAndRunSwiftProtocolModels(params: {
appendLog: (chunk: unknown) => void;
artifactBase: string;
repoRoot: string;
}) {
const swiftArtifactDir = path.join(params.artifactBase, "swift");
const anyCodablePath = path.join(swiftArtifactDir, "AnyCodable.swift");
const harnessPath = path.join(swiftArtifactDir, "GatewayProtocolArtifactHarness.swift");
const executablePath = path.join(swiftArtifactDir, "gateway-protocol-artifact-harness");
await fs.rm(swiftArtifactDir, { force: true, recursive: true });
await fs.mkdir(swiftArtifactDir, { recursive: true });
const anyCodableSource = await fs.readFile(
path.join(params.repoRoot, "apps/shared/OpenClawKit/Sources/OpenClawProtocol/AnyCodable.swift"),
"utf8",
);
// Linux Foundation does not re-export the CoreFoundation type-ID helpers.
await fs.writeFile(anyCodablePath, buildPortableSwiftAnyCodableSource(anyCodableSource), "utf8");
await fs.writeFile(harnessPath, buildSwiftProtocolCompatibilityHarness(), "utf8");
await runCommand({
args: [
anyCodablePath,
path.join(
params.repoRoot,
"apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift",
),
harnessPath,
"-o",
executablePath,
],
appendLog: params.appendLog,
command: "swiftc",
cwd: params.repoRoot,
});
await runCommand({
args: [],
appendLog: params.appendLog,
command: executablePath,
cwd: params.repoRoot,
});
}
async function runGatewayProtocolArtifactsProducer(
options: ProducerOptions,
): Promise<QaEvidenceSummaryJson> {
const writer = createQaScriptEvidenceWriter({
artifactBase: options.artifactBase,
logFileName: "gateway-protocol-artifacts.log",
primaryModel: "gateway/protocol-artifacts",
providerMode: "mock-openai",
repoRoot: options.repoRoot,
target: {
codeRefs: [
SOURCE_PATH,
"packages/gateway-protocol/package.json",
"packages/gateway-protocol/src/schema/protocol-schemas.ts",
"scripts/protocol-gen.ts",
"scripts/protocol-gen-swift.ts",
"apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayProtocolGeneratedModelsTests.swift",
],
docsRefs: ["docs/gateway/protocol.md", "docs/reference/test.md"],
id: "gateway-protocol-artifacts",
sourcePath: SOURCE_PATH,
title: "Gateway published protocol artifacts",
},
});
const startedAt = Date.now();
try {
await runCommand({
args: ["protocol:check"],
appendLog: writer.appendLog,
command: "pnpm",
cwd: options.repoRoot,
});
const summary = await packAndInspectProtocol({
appendLog: writer.appendLog,
artifactBase: options.artifactBase,
repoRoot: options.repoRoot,
});
await compileAndRunSwiftProtocolModels({
appendLog: writer.appendLog,
artifactBase: options.artifactBase,
repoRoot: options.repoRoot,
});
const summaryPath = path.join(options.artifactBase, "protocol-artifact-summary.json");
await fs.writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
return await writer.write({
artifacts: [
{ kind: "package", filePath: path.join("package", summary.package.tarball) },
{ kind: "schema", filePath: path.join("unpacked", "package", "protocol.schema.json") },
{ kind: "summary", filePath: "protocol-artifact-summary.json" },
],
details: [
`package=${summary.package.name}@${summary.package.version}`,
`sha256=${summary.package.sha256}`,
`consumer-install=${summary.consumer.installed ? "pass" : "fail"}`,
`definitions=${summary.definitions.join(",")}`,
"protocol-check=pass",
"swift-generated-model-compile=pass",
"swift-generated-model-decode=pass",
].join("; "),
durationMs: Math.max(1, Date.now() - startedAt),
status: "pass",
});
} catch (error) {
const details = formatErrorMessage(error);
writer.appendLog(`\nfail: ${details}\n`);
return await writer.write({
details,
durationMs: Math.max(1, Date.now() - startedAt),
status: "fail",
});
}
}
async function main(argv: readonly string[]) {
const evidence = await runGatewayProtocolArtifactsProducer(
parseGatewayProtocolArtifactOptions(argv),
);
const status = evidence.entries[0]?.result.status;
console.log(`Gateway protocol artifact evidence: ${QA_EVIDENCE_FILENAME}`);
console.log(`Gateway protocol artifact status: ${status}`);
return status === "pass" ? 0 : 1;
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
main(process.argv.slice(2))
.then((exitCode) => {
process.exitCode = exitCode;
})
.catch((error: unknown) => {
console.error(formatErrorMessage(error));
process.exitCode = 1;
});
}