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}`); | ||