-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathutils.ts
More file actions
155 lines (129 loc) · 6.15 KB
/
utils.ts
File metadata and controls
155 lines (129 loc) · 6.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
/**
* Shared utilities for code generation - schema loading, file I/O, schema processing.
*/
import { execFile } from "child_process";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { promisify } from "util";
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
export const execFileAsync = promisify(execFile);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/** Root of the copilot-sdk repo */
export const REPO_ROOT = path.resolve(__dirname, "../..");
/** Event types to exclude from generation (internal/legacy types) */
export const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]);
// ── Schema paths ────────────────────────────────────────────────────────────
export async function getSessionEventsSchemaPath(): Promise<string> {
const schemaPath = path.join(
REPO_ROOT,
"nodejs/node_modules/@github/copilot/schemas/session-events.schema.json"
);
await fs.access(schemaPath);
return schemaPath;
}
export async function getApiSchemaPath(cliArg?: string): Promise<string> {
if (cliArg) return cliArg;
const schemaPath = path.join(
REPO_ROOT,
"nodejs/node_modules/@github/copilot/schemas/api.schema.json"
);
await fs.access(schemaPath);
return schemaPath;
}
// ── Schema processing ───────────────────────────────────────────────────────
/**
* Post-process JSON Schema for quicktype compatibility.
* Converts boolean const values to enum, filters excluded event types.
*/
export function postProcessSchema(schema: JSONSchema7): JSONSchema7 {
if (typeof schema !== "object" || schema === null) return schema;
const processed: JSONSchema7 = { ...schema };
if ("const" in processed && typeof processed.const === "boolean") {
processed.enum = [processed.const];
delete processed.const;
}
if (processed.properties) {
const newProps: Record<string, JSONSchema7Definition> = {};
for (const [key, value] of Object.entries(processed.properties)) {
newProps[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value;
}
processed.properties = newProps;
}
if (processed.items) {
if (typeof processed.items === "object" && !Array.isArray(processed.items)) {
processed.items = postProcessSchema(processed.items as JSONSchema7);
} else if (Array.isArray(processed.items)) {
processed.items = processed.items.map((item) =>
typeof item === "object" ? postProcessSchema(item as JSONSchema7) : item
) as JSONSchema7Definition[];
}
}
for (const combiner of ["anyOf", "allOf", "oneOf"] as const) {
if (processed[combiner]) {
processed[combiner] = processed[combiner]!
.filter((item) => {
if (typeof item !== "object") return true;
const typeConst = (item as JSONSchema7).properties?.type;
if (typeof typeConst === "object" && "const" in typeConst) {
return !EXCLUDED_EVENT_TYPES.has(typeConst.const as string);
}
return true;
})
.map((item) =>
typeof item === "object" ? postProcessSchema(item as JSONSchema7) : item
) as JSONSchema7Definition[];
}
}
if (processed.definitions) {
const newDefs: Record<string, JSONSchema7Definition> = {};
for (const [key, value] of Object.entries(processed.definitions)) {
newDefs[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value;
}
processed.definitions = newDefs;
}
if (typeof processed.additionalProperties === "object") {
processed.additionalProperties = postProcessSchema(processed.additionalProperties as JSONSchema7);
}
return processed;
}
// ── File output ─────────────────────────────────────────────────────────────
export async function writeGeneratedFile(relativePath: string, content: string): Promise<string> {
const fullPath = path.join(REPO_ROOT, relativePath);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, content, "utf-8");
return fullPath;
}
// ── RPC schema types ────────────────────────────────────────────────────────
export interface RpcMethod {
rpcMethod: string;
params: JSONSchema7 | null;
result: JSONSchema7 | null;
stability?: string;
}
export interface ApiSchema {
server?: Record<string, unknown>;
session?: Record<string, unknown>;
clientSession?: Record<string, unknown>;
}
export function isRpcMethod(node: unknown): node is RpcMethod {
return typeof node === "object" && node !== null && "rpcMethod" in node;
}
/** Returns true when every leaf RPC method inside `node` is marked experimental. */
export function isNodeFullyExperimental(node: Record<string, unknown>): boolean {
const methods: RpcMethod[] = [];
(function collect(n: Record<string, unknown>) {
for (const value of Object.values(n)) {
if (isRpcMethod(value)) {
methods.push(value);
} else if (typeof value === "object" && value !== null) {
collect(value as Record<string, unknown>);
}
}
})(node);
return methods.length > 0 && methods.every(m => m.stability === "experimental");
}