From 7660f61777e9fa4b445665e5a7e4071ee2a7f8c4 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Sat, 6 Dec 2025 22:07:34 +0000 Subject: [PATCH] Prune unused definitions from workflow schema Remove 10 unreachable definitions from workflow-v1.0.json: - workflow-root, on, on-mapping (non-strict variants) - job-if-result, step-if-result - boolean-needs-context, number-needs-context, string-needs-context - boolean-steps-context, number-steps-context Saves 2,039 bytes minified (2.9%), 146 bytes gzipped (1.2%). Also adds script/prune-schema.cjs for future maintenance. --- .gitignore | 3 + workflow-parser/package.json | 3 +- .../script/optimize-workflow-schema.js | 114 ++++++++++++++++++ .../src/workflows/workflow-schema.test.ts | 85 +++++++++++++ .../src/workflows/workflow-schema.ts | 2 +- 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 workflow-parser/script/optimize-workflow-schema.js create mode 100644 workflow-parser/src/workflows/workflow-schema.test.ts diff --git a/.gitignore b/.gitignore index cfe37b7..fd16698 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ node_modules # Minified JSON (generated at build time) *.min.json +# Optimized workflow schema (generated by optimize-workflow-schema.js) +*.optimized.json + # Full webhooks source (generated by update-webhooks, used for validation tests) *.full.json diff --git a/workflow-parser/package.json b/workflow-parser/package.json index b5e9451..f102150 100644 --- a/workflow-parser/package.json +++ b/workflow-parser/package.json @@ -38,7 +38,8 @@ "format-check": "prettier --check '**/*.ts'", "lint": "eslint 'src/**/*.ts'", "lint-fix": "eslint --fix 'src/**/*.ts'", - "minify-json": "node ../script/minify-json.js src/workflow-v1.0.json", + "optimize-schema": "node script/optimize-workflow-schema.js", + "minify-json": "npm run optimize-schema && node ../script/minify-json.js src/workflow-v1.0.optimized.json", "prebuild": "npm run minify-json", "prepublishOnly": "npm run build && npm run test", "pretest": "npm run minify-json", diff --git a/workflow-parser/script/optimize-workflow-schema.js b/workflow-parser/script/optimize-workflow-schema.js new file mode 100644 index 0000000..4a8157d --- /dev/null +++ b/workflow-parser/script/optimize-workflow-schema.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node +/** + * Optimizes workflow-v1.0.json by pruning unused definitions. + * + * Removes definitions not reachable from the entry point (workflow-root-strict). + * Output is then minified by minify-json.js to produce the final .min.json file. + * + * Usage: node script/optimize-workflow-schema.js + */ + +import {promises as fs} from "fs"; +import path from "path"; +import {fileURLToPath} from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ENTRY_POINT = "workflow-root-strict"; + +const inputPath = path.join(__dirname, "..", "src", "workflow-v1.0.json"); +const outputPath = path.join(__dirname, "..", "src", "workflow-v1.0.optimized.json"); + +/** + * Find all type references in a definition. + */ +function findRefs(obj) { + const refs = []; + + function visit(node) { + if (!node || typeof node !== "object") return; + + if (Array.isArray(node)) { + for (const item of node) { + if (typeof item === "string") refs.push(item); + else visit(item); + } + return; + } + + for (const [key, value] of Object.entries(node)) { + if (["type", "item-type", "loose-key-type", "loose-value-type"].includes(key)) { + if (typeof value === "string") refs.push(value); + } else if (key === "one-of") { + visit(value); + } else if (key === "properties") { + for (const propValue of Object.values(value)) { + if (typeof propValue === "string") refs.push(propValue); + else if (propValue && typeof propValue === "object") visit(propValue); + } + } else if (["mapping", "sequence", "string"].includes(key)) { + visit(value); + } + } + } + + visit(obj); + return refs; +} + +/** + * Find all definitions reachable from entry point. + */ +function findReachable(schema, entryPoint) { + const reachable = new Set(); + const queue = [entryPoint]; + + while (queue.length > 0) { + const name = queue.shift(); + if (reachable.has(name) || !schema.definitions[name]) continue; + reachable.add(name); + + const refs = findRefs(schema.definitions[name]); + for (const ref of refs) { + if (!reachable.has(ref) && schema.definitions[ref]) { + queue.push(ref); + } + } + } + + return reachable; +} + +async function main() { + const content = await fs.readFile(inputPath, "utf8"); + const schema = JSON.parse(content); + + const reachable = findReachable(schema, ENTRY_POINT); + const allDefs = Object.keys(schema.definitions); + const unused = allDefs.filter((name) => !reachable.has(name)); + + console.log(`Entry point: ${ENTRY_POINT}`); + console.log(`Definitions: ${allDefs.length} -> ${reachable.size} (${unused.length} pruned)`); + + if (unused.length > 0) { + console.log("\nPruned:"); + unused.forEach((name) => console.log(` - ${name}`)); + } + + // Create pruned schema preserving definition order + const pruned = {version: schema.version, definitions: {}}; + for (const name of allDefs) { + if (reachable.has(name)) { + pruned.definitions[name] = schema.definitions[name]; + } + } + + // Write output (will be minified by minify-json.js) + await fs.writeFile(outputPath, JSON.stringify(pruned)); + + console.log(`\nOutput: ${outputPath}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/workflow-parser/src/workflows/workflow-schema.test.ts b/workflow-parser/src/workflows/workflow-schema.test.ts new file mode 100644 index 0000000..783dff8 --- /dev/null +++ b/workflow-parser/src/workflows/workflow-schema.test.ts @@ -0,0 +1,85 @@ +import {getWorkflowSchema} from "./workflow-schema"; + +describe("workflow-schema", () => { + it("loads successfully from workflow-root-strict entry point", () => { + const schema = getWorkflowSchema(); + expect(schema).toBeDefined(); + + // Verify entry point exists + const rootDef = schema.getDefinition("workflow-root-strict"); + expect(rootDef).toBeDefined(); + }); + + it("has all referenced definitions reachable from workflow-root-strict", () => { + const schema = getWorkflowSchema(); + const definitions = schema.definitions; + + // Collect all type references from all definitions + const referencedTypes = new Set(); + const definedTypes = new Set(); + + for (const name of Object.keys(definitions)) { + definedTypes.add(name); + collectReferences(definitions[name], referencedTypes); + } + + // Every referenced type should be defined + const missingDefinitions: string[] = []; + for (const ref of referencedTypes) { + // Skip built-in types + if (isBuiltInType(ref)) continue; + + if (!definedTypes.has(ref)) { + missingDefinitions.push(ref); + } + } + + expect(missingDefinitions).toEqual([]); + }); + + it("can resolve key workflow definitions", () => { + const schema = getWorkflowSchema(); + + // These are critical definitions that must exist + const criticalDefinitions = [ + "workflow-root-strict", + "jobs", + "steps", + "runs-on", + "step-uses", + "job-env", + "step-env", + ]; + + for (const defName of criticalDefinitions) { + const def = schema.getDefinition(defName); + expect(def).toBeDefined(); + } + }); +}); + +function collectReferences(obj: unknown, refs: Set): void { + if (!obj || typeof obj !== "object") return; + + if (Array.isArray(obj)) { + for (const item of obj) { + if (typeof item === "string") refs.add(item); + else collectReferences(item, refs); + } + return; + } + + const record = obj as Record; + for (const [key, value] of Object.entries(record)) { + if (["type", "item-type", "loose-key-type", "loose-value-type"].includes(key)) { + if (typeof value === "string") refs.add(value); + } else if (key === "one-of" || key === "properties" || key === "mapping" || key === "sequence") { + collectReferences(value, refs); + } + } +} + +function isBuiltInType(typeName: string): boolean { + const builtIns = ["null", "boolean", "number", "string", "sequence", "mapping", "any"]; + return builtIns.includes(typeName); +} diff --git a/workflow-parser/src/workflows/workflow-schema.ts b/workflow-parser/src/workflows/workflow-schema.ts index 5ffe5b0..4981a84 100644 --- a/workflow-parser/src/workflows/workflow-schema.ts +++ b/workflow-parser/src/workflows/workflow-schema.ts @@ -1,6 +1,6 @@ import {JSONObjectReader} from "../templates/json-object-reader"; import {TemplateSchema} from "../templates/schema"; -import WorkflowSchema from "../workflow-v1.0.min.json"; +import WorkflowSchema from "../workflow-v1.0.optimized.min.json"; let schema: TemplateSchema;