workflow: auto-update recipe-reference on release (#5988)
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
#!/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));
|
||||
"
|
||||
Reference in New Issue
Block a user