Files
tkmind_go/documentation/automation/recipe-schema-tracking/scripts/extract-schema.sh
T

162 lines
4.6 KiB
Bash
Executable File

#!/bin/bash
# Extract and resolve Recipe schema from OpenAPI spec at a specific git version
# Usage: ./extract-schema.sh <version>
# Example: ./extract-schema.sh v1.15.0
set -e
VERSION=${1:-"main"}
GOOSE_REPO=${GOOSE_REPO:-"$HOME/Development/goose"}
if [ ! -d "$GOOSE_REPO" ]; then
echo "Error: GOOSE_REPO directory not found: $GOOSE_REPO" >&2
exit 1
fi
cd "$GOOSE_REPO"
# Verify version exists (for non-main versions)
if [ "$VERSION" != "main" ]; then
if ! git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Error: Version $VERSION not found in git history" >&2
exit 1
fi
fi
# Extract OpenAPI spec from git
if [ "$VERSION" = "main" ]; then
if [ ! -f ui/desktop/openapi.json ]; then
echo "Error: ui/desktop/openapi.json not found in working directory" >&2
exit 1
fi
OPENAPI_JSON=$(cat ui/desktop/openapi.json)
else
OPENAPI_JSON=$(git show "$VERSION:ui/desktop/openapi.json" 2>/dev/null || {
echo "Error: Could not find ui/desktop/openapi.json at version $VERSION" >&2
exit 1
})
fi
# Use Node.js to extract and resolve Recipe schema
echo "$OPENAPI_JSON" | node -e "
const openApiSpec = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
/**
* Resolves \$ref references in OpenAPI schemas by expanding them with the actual schema definitions
* Ported from ui/desktop/src/recipe/validation.ts
*/
function resolveRefs(schema, openApiSpec) {
if (!schema || typeof schema !== 'object') {
return schema;
}
// Handle \$ref
if (typeof schema.\$ref === 'string') {
const refPath = schema.\$ref.replace('#/', '').split('/');
let resolved = openApiSpec;
for (const segment of refPath) {
if (resolved && typeof resolved === 'object' && segment in resolved) {
resolved = resolved[segment];
} else {
console.warn(\`Could not resolve \$ref: \${schema.\$ref}\`);
return schema; // Return original if can't resolve
}
}
if (resolved && typeof resolved === 'object') {
// Recursively resolve refs in the resolved schema
return resolveRefs(resolved, openApiSpec);
}
return schema;
}
// Handle allOf (merge schemas)
if (Array.isArray(schema.allOf)) {
const merged = {};
for (const subSchema of schema.allOf) {
if (typeof subSchema === 'object' && subSchema !== null) {
const resolved = resolveRefs(subSchema, openApiSpec);
Object.assign(merged, resolved);
}
}
// Keep other properties from the original schema
const { allOf, ...rest } = schema;
return { ...merged, ...rest };
}
// Handle oneOf/anyOf (keep as union)
if (Array.isArray(schema.oneOf)) {
return {
...schema,
oneOf: schema.oneOf.map((subSchema) =>
typeof subSchema === 'object' && subSchema !== null
? resolveRefs(subSchema, openApiSpec)
: subSchema
),
};
}
if (Array.isArray(schema.anyOf)) {
return {
...schema,
anyOf: schema.anyOf.map((subSchema) =>
typeof subSchema === 'object' && subSchema !== null
? resolveRefs(subSchema, openApiSpec)
: subSchema
),
};
}
// Handle object properties
if (schema.type === 'object' && schema.properties && typeof schema.properties === 'object') {
const resolvedProperties = {};
for (const [key, value] of Object.entries(schema.properties)) {
if (typeof value === 'object' && value !== null) {
resolvedProperties[key] = resolveRefs(value, openApiSpec);
} else {
resolvedProperties[key] = value;
}
}
return {
...schema,
properties: resolvedProperties,
};
}
// Handle array items
if (schema.type === 'array' && schema.items && typeof schema.items === 'object') {
return {
...schema,
items: resolveRefs(schema.items, openApiSpec),
};
}
// Return schema as-is if no refs to resolve
return schema;
}
// Extract Recipe schema
const recipeSchema = openApiSpec.components?.schemas?.Recipe;
if (!recipeSchema) {
console.error('Error: Recipe schema not found in OpenAPI specification');
process.exit(1);
}
// Resolve all \$refs in the schema
const resolvedSchema = resolveRefs(recipeSchema, openApiSpec);
// Convert OpenAPI schema to JSON Schema format
const jsonSchema = {
'\$schema': 'http://json-schema.org/draft-07/schema#',
...resolvedSchema,
title: resolvedSchema.title || 'Recipe',
description: resolvedSchema.description || 'A Recipe represents a personalized, user-generated agent configuration that defines specific behaviors and capabilities within the Goose system.',
};
// Output the resolved schema
console.log(JSON.stringify(jsonSchema, null, 2));
"