Showing the changes to packages/plugins/openapi/scripts (1 of 10 files). Show all
Unified · Split
| @@ -0,0 +1,109 @@ | ||
| 1 | +/** | |
| 2 | + * Generate the Microsoft Graph slice release assets. | |
| 3 | + * | |
| 4 | + * bun scripts/generate-graph-slices.ts [--source <path-or-url>] [--out <dir>] | |
| 5 | + * | |
| 6 | + * Fetches (or reads) the Graph OpenAPI monolith, builds one slice per catalog | |
| 7 | + * preset plus the default bundle via `slice-build.ts`, validates every slice | |
| 8 | + * against the runtime's streamable profile, and writes `<asset>.yaml` files | |
| 9 | + * plus `manifest.json` to the output directory. The graph-slices workflow runs | |
| 10 | + * this and uploads the output to the `graph-slices` release tag; runtime | |
| 11 | + * resolution lives in `src/providers/microsoft/slices.ts`. | |
| 12 | + * | |
| 13 | + * Offline-only: this whole-parses the 43MB source, which only works where | |
| 14 | + * memory is free (CI runner / dev machine), never in a Workers isolate. | |
| 15 | + */ | |
| 16 | +import { createHash } from "node:crypto"; | |
| 17 | +import { mkdir, readFile, writeFile } from "node:fs/promises"; | |
| 18 | +import { join } from "node:path"; | |
| 19 | + | |
| 20 | +import { structuralSplit } from "../src/sdk/split"; | |
| 21 | +import { | |
| 22 | + MICROSOFT_GRAPH_DEFAULT_PRESET_IDS, | |
| 23 | + MICROSOFT_GRAPH_OPENAPI_URL, | |
| 24 | + microsoftGraphScopePresets, | |
| 25 | +} from "../src/providers/microsoft/presets"; | |
| 26 | +import { MICROSOFT_GRAPH_DEFAULT_SLICE_ASSET } from "../src/providers/microsoft/slices"; | |
| 27 | +import { | |
| 28 | + buildGraphSliceDocument, | |
| 29 | + parseGraphSourceDocument, | |
| 30 | +} from "../src/providers/microsoft/slice-build"; | |
| 31 | + | |
| 32 | +const argValue = (flag: string): string | undefined => { | |
| 33 | + const index = process.argv.indexOf(flag); | |
| 34 | + return index !== -1 ? process.argv[index + 1] : undefined; | |
| 35 | +}; | |
| 36 | + | |
| 37 | +const source = argValue("--source") ?? MICROSOFT_GRAPH_OPENAPI_URL; | |
| 38 | +const outDir = argValue("--out") ?? "graph-slices-out"; | |
| 39 | + | |
| 40 | +const readSource = async (): Promise<string> => { | |
| 41 | + if (source.startsWith("http://") || source.startsWith("https://")) { | |
| 42 | + const response = await fetch(source); | |
| 43 | + if (!response.ok) { | |
| 44 | + throw new Error(`Failed to fetch Graph source: HTTP ${response.status}`); | |
| 45 | + } | |
| 46 | + return response.text(); | |
| 47 | + } | |
| 48 | + return readFile(source, "utf8"); | |
| 49 | +}; | |
| 50 | + | |
| 51 | +const sourceText = await readSource(); | |
| 52 | +const sourceSha256 = createHash("sha256").update(sourceText).digest("hex"); | |
| 53 | +const doc = parseGraphSourceDocument(sourceText); | |
| 54 | +if (!doc) { | |
| 55 | + throw new Error("Microsoft Graph source did not parse to an object"); | |
| 56 | +} | |
| 57 | + | |
| 58 | +const selections: readonly { readonly asset: string; readonly presetIds: readonly string[] }[] = [ | |
| 59 | + ...microsoftGraphScopePresets.map((preset) => ({ asset: preset.id, presetIds: [preset.id] })), | |
| 60 | + { | |
| 61 | + asset: MICROSOFT_GRAPH_DEFAULT_SLICE_ASSET, | |
| 62 | + presetIds: MICROSOFT_GRAPH_DEFAULT_PRESET_IDS, | |
| 63 | + }, | |
| 64 | +]; | |
| 65 | + | |
| 66 | +await mkdir(outDir, { recursive: true }); | |
| 67 | + | |
| 68 | +const manifestAssets: Record< | |
| 69 | + string, | |
| 70 | + { | |
| 71 | + readonly bytes: number; | |
| 72 | + readonly paths: number; | |
| 73 | + readonly operations: number; | |
| 74 | + readonly schemas: number; | |
| 75 | + } | |
| 76 | +> = {}; | |
| 77 | + | |
| 78 | +for (const { asset, presetIds } of selections) { | |
| 79 | + const slice = buildGraphSliceDocument(doc, presetIds); | |
| 80 | + if (slice.operationCount === 0) { | |
| 81 | + throw new Error(`Slice "${asset}" kept zero operations — preset filter or source drifted`); | |
| 82 | + } | |
| 83 | + const structure = structuralSplit(slice.specText); | |
| 84 | + if (!structure) { | |
| 85 | + throw new Error(`Slice "${asset}" is not in the streamable block-YAML profile`); | |
| 86 | + } | |
| 87 | + if (structure.pathItems.length !== slice.pathCount) { | |
| 88 | + throw new Error( | |
| 89 | + `Slice "${asset}" splitter sees ${structure.pathItems.length} path-items, expected ${slice.pathCount}`, | |
| 90 | + ); | |
| 91 | + } | |
| 92 | + await writeFile(join(outDir, `${asset}.yaml`), slice.specText); | |
| 93 | + manifestAssets[asset] = { | |
| 94 | + bytes: Buffer.byteLength(slice.specText), | |
| 95 | + paths: slice.pathCount, | |
| 96 | + operations: slice.operationCount, | |
| 97 | + schemas: slice.schemaCount, | |
| 98 | + }; | |
| 99 | + console.log( | |
| 100 | + `${asset}: ${(Buffer.byteLength(slice.specText) / 1024 / 1024).toFixed(2)}MB, ` + | |
| 101 | + `${slice.pathCount} paths, ${slice.operationCount} operations, ${slice.schemaCount} schemas`, | |
| 102 | + ); | |
| 103 | +} | |
| 104 | + | |
| 105 | +await writeFile( | |
| 106 | + join(outDir, "manifest.json"), | |
| 107 | + `${JSON.stringify({ source, sourceSha256, generatedAt: new Date().toISOString(), assets: manifestAssets }, null, 2)}\n`, | |
| 108 | +); | |
| 109 | +console.log(`wrote ${selections.length} slices + manifest.json to ${outDir}`); | |