M1 Task 2: 配置 graphql-codegen 与 federation schema 预处理 - codegen.yml: schema 从 combined-schema.graphql 读取(federation 已剥离) - scripts/normalize-schema.ts: 把 7 个子图的 extend type Query 合并为 type Query - package.json: 新增 codegen/codegen:watch scripts + 4 个 codegen deps + tsx - .gitignore: 忽略 src/lib/api/__generated__/ - documents 配置暂注释,Task 3 创建 operations 文件后启用
154 lines
4.4 KiB
TypeScript
154 lines
4.4 KiB
TypeScript
// Normalize federation subgraph schemas for graphql-codegen.
|
|
//
|
|
// Problem: services schema.graphql files are federation subgraphs that use
|
|
// `extend type Query` (with no body) and multiple `extend type Query { ... }`
|
|
// blocks. Standard GraphQL parser used by codegen cannot parse these.
|
|
//
|
|
// Solution: read all 7 subgraph schema files, merge all `extend type Query`
|
|
// blocks into a single `type Query { ... }`, output to a combined SDL file.
|
|
//
|
|
// Usage: tsx scripts/normalize-schema.ts
|
|
// Output: src/lib/api/__generated__/combined-schema.graphql
|
|
//
|
|
// Related: spec section 2.4
|
|
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
|
|
interface SubgraphSchema {
|
|
name: string;
|
|
content: string;
|
|
}
|
|
|
|
const SCHEMA_FILES = [
|
|
{
|
|
name: "iam",
|
|
path: "../../services/iam/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "config-service",
|
|
path: "../../services/config-service/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "core-edu",
|
|
path: "../../services/core-edu/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "content",
|
|
path: "../../services/content/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "msg",
|
|
path: "../../services/msg/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "data-ana",
|
|
path: "../../services/data-ana/src/graphql/generated/schema.graphql",
|
|
},
|
|
{
|
|
name: "ai",
|
|
path: "../../services/ai/src/graphql/generated/schema.graphql",
|
|
},
|
|
];
|
|
|
|
function loadSchemas(): SubgraphSchema[] {
|
|
const baseDir = process.cwd();
|
|
return SCHEMA_FILES.map((f) => ({
|
|
name: f.name,
|
|
content: fs.readFileSync(path.resolve(baseDir, f.path), "utf8"),
|
|
}));
|
|
}
|
|
|
|
// Strip federation `extend type Query` syntax:
|
|
// - Remove standalone `extend type Query` (no body)
|
|
// - Convert `extend type Query { ... }` blocks to `type Query { ... }`
|
|
// (we collect fields and merge later)
|
|
function normalizeSchema(content: string): {
|
|
staticDefs: string;
|
|
queryFields: string[];
|
|
} {
|
|
const lines = content.split("\n");
|
|
const staticDefs: string[] = [];
|
|
const queryFields: string[] = [];
|
|
let i = 0;
|
|
while (i < lines.length) {
|
|
const line = lines[i];
|
|
// Match `extend type Query` (no body, single line)
|
|
if (/^\s*extend\s+type\s+Query\s*$/.test(line)) {
|
|
i++;
|
|
continue;
|
|
}
|
|
// Match `extend type Query {` block start
|
|
const blockStart = line.match(/^\s*extend\s+type\s+Query\s*\{/);
|
|
if (blockStart) {
|
|
// Collect fields until matching `}`
|
|
i++;
|
|
while (i < lines.length && !/^\s*\}\s*$/.test(lines[i])) {
|
|
const fieldLine = lines[i];
|
|
if (fieldLine.trim()) {
|
|
queryFields.push(fieldLine);
|
|
}
|
|
i++;
|
|
}
|
|
// Skip closing `}`
|
|
i++;
|
|
continue;
|
|
}
|
|
// Match `type Query {` block (already standard, but merge fields)
|
|
const stdBlockStart = line.match(/^\s*type\s+Query\s*\{/);
|
|
if (stdBlockStart) {
|
|
i++;
|
|
while (i < lines.length && !/^\s*\}\s*$/.test(lines[i])) {
|
|
const fieldLine = lines[i];
|
|
if (fieldLine.trim()) {
|
|
queryFields.push(fieldLine);
|
|
}
|
|
i++;
|
|
}
|
|
i++;
|
|
continue;
|
|
}
|
|
staticDefs.push(line);
|
|
i++;
|
|
}
|
|
return { staticDefs: staticDefs.join("\n"), queryFields };
|
|
}
|
|
|
|
function main(): void {
|
|
const schemas = loadSchemas();
|
|
const allStaticDefs: string[] = [];
|
|
const allQueryFields: string[] = [];
|
|
|
|
for (const s of schemas) {
|
|
const { staticDefs, queryFields } = normalizeSchema(s.content);
|
|
allStaticDefs.push(`# === ${s.name} subgraph ===`);
|
|
allStaticDefs.push(staticDefs.trim());
|
|
allQueryFields.push(...queryFields);
|
|
}
|
|
|
|
// Deduplicate query fields (in case multiple subgraphs define same field)
|
|
const uniqueQueryFields = Array.from(
|
|
new Set(allQueryFields.map((f) => f.trim())),
|
|
);
|
|
|
|
const combined = [
|
|
"# Combined normalized schema for graphql-codegen (federation stripped)",
|
|
"# DO NOT EDIT - generated by scripts/normalize-schema.ts",
|
|
"",
|
|
...allStaticDefs,
|
|
"",
|
|
"type Query {",
|
|
...uniqueQueryFields.map((f) => ` ${f}`),
|
|
"}",
|
|
"",
|
|
].join("\n");
|
|
|
|
const outDir = path.resolve(process.cwd(), "src/lib/api/__generated__");
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
const outPath = path.join(outDir, "combined-schema.graphql");
|
|
fs.writeFileSync(outPath, combined);
|
|
console.log(`Combined schema written to ${outPath}`);
|
|
console.log(` Query fields: ${uniqueQueryFields.length}`);
|
|
}
|
|
|
|
main();
|