#!/usr/bin/env bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# Release Documentation Validator
# Run from py/ directory: ./bin/validate_release_docs
#
# This script validates release documentation (CHANGELOG.md, PR descriptions,
# blog articles) to catch common mistakes before publishing.
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "=== Release Documentation Validation ==="
echo ""
ERRORS=0
WARNINGS=0
# 1. Check branding: No "Firebase Genkit" references
echo -n "1. Checking branding (no 'Firebase Genkit')... "
if grep -ri "Firebase Genkit" engdoc/ .github/PR_DESCRIPTION_*.md CHANGELOG.md 2>/dev/null; then
echo -e "${RED}FAIL${NC}: Found 'Firebase Genkit' - use 'Genkit' instead"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# 2. Check for known non-existent plugins
echo -n "2. Checking for non-existent plugin names... "
FAKE_PLUGINS="genkit-plugin-aim|genkit-plugin-firestore"
if grep -rE "$FAKE_PLUGINS" engdoc/ .github/PR_DESCRIPTION_*.md CHANGELOG.md 2>/dev/null; then
echo -e "${RED}FAIL${NC}: Found non-existent plugin names"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# 3. Check for unshipped features (DAP, MCP unless actually shipped)
echo -n "3. Checking for unshipped features... "
UNSHIPPED="Dynamic Action Provider|DAP factory|MCP resource|action_provider"
if grep -rE "$UNSHIPPED" engdoc/ .github/PR_DESCRIPTION_*.md CHANGELOG.md 2>/dev/null; then
echo -e "${YELLOW}WARN${NC}: Found references to features that may not be shipped yet"
WARNINGS=$((WARNINGS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# 4. Check blog code syntax errors
echo -n "4. Checking blog code syntax... "
WRONG_PATTERNS='response\.text\(\)|output_schema=|asyncio\.run\(main|from genkit import Genkit[^.]'
if grep -rE "$WRONG_PATTERNS" engdoc/blog-*.md 2>/dev/null; then
echo -e "${RED}FAIL${NC}: Found incorrect API patterns in blog code examples"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# 5. Verify all mentioned plugins exist
echo -n "5. Verifying plugin references... "
PLUGIN_ERROR=0
# shellcheck disable=SC2013
# Word splitting is intended for plugin names
for plugin in $(grep -rohE 'genkit-plugin-[a-z-]+' engdoc/ .github/PR_DESCRIPTION_*.md CHANGELOG.md 2>/dev/null | sort -u); do
dir_name="${plugin//genkit-plugin-/}"
if [ ! -d "plugins/$dir_name" ]; then
if [ $PLUGIN_ERROR -eq 0 ]; then
echo ""
fi
echo -e " ${RED}FAIL${NC}: Plugin $plugin does not exist (no plugins/$dir_name/)"
PLUGIN_ERROR=1
ERRORS=$((ERRORS + 1))
fi
done
if [ $PLUGIN_ERROR -eq 0 ]; then
echo -e "${GREEN}OK${NC}"
fi
# 6. Check contributor GitHub links are properly formatted
echo -n "6. Checking contributor links format... "
BAD_LINKS=$(grep -E '\[@[a-zA-Z0-9_-]+\]' .github/PR_DESCRIPTION_*.md CHANGELOG.md 2>/dev/null | \
grep -vE '\[@[a-zA-Z0-9_-]+\]\(https://github\.com/[a-zA-Z0-9_-]+\)' 2>/dev/null || true)
if [ -n "$BAD_LINKS" ]; then
echo -e "${YELLOW}WARN${NC}: Some contributor links may not have GitHub URLs"
WARNINGS=$((WARNINGS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# 7. Check blog article exists for version in CHANGELOG
echo -n "7. Checking blog article exists... "
VERSION=$(grep -m1 '## \[' CHANGELOG.md 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
if [ -z "$VERSION" ]; then
echo -e "${YELLOW}WARN${NC}: Could not determine version from CHANGELOG.md"
WARNINGS=$((WARNINGS + 1))
elif [ -f "engdoc/blog-genkit-python-$VERSION.md" ]; then
echo -e "${GREEN}OK${NC} (found blog-genkit-python-$VERSION.md)"
else
echo -e "${RED}FAIL${NC}: Missing engdoc/blog-genkit-python-$VERSION.md"
ERRORS=$((ERRORS + 1))
fi
# 8. Verify imports work (if genkit is installed)
echo -n "8. Checking Python imports... "
if python -c "from genkit.ai import Genkit, Output" 2>/dev/null; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${YELLOW}WARN${NC}: Could not verify imports (genkit may not be installed)"
WARNINGS=$((WARNINGS + 1))
fi
# 9. Check for required sections in blog article
if [ -n "$VERSION" ] && [ -f "engdoc/blog-genkit-python-$VERSION.md" ]; then
echo -n "9. Checking blog article sections... "
BLOG_FILE="engdoc/blog-genkit-python-$VERSION.md"
MISSING_SECTIONS=0
for section in "What's New" "Get Started" "Contributors"; do
if ! grep -q "$section" "$BLOG_FILE" 2>/dev/null; then
if [ $MISSING_SECTIONS -eq 0 ]; then
echo ""
fi
echo -e " ${YELLOW}WARN${NC}: Missing section '$section' in blog article"
MISSING_SECTIONS=1
WARNINGS=$((WARNINGS + 1))
fi
done
if [ $MISSING_SECTIONS -eq 0 ]; then
echo -e "${GREEN}OK${NC}"
fi
else
echo "9. Checking blog article sections... SKIPPED (no blog article found)"
fi
# Summary
echo ""
echo "=== Validation Complete ==="
if [ $ERRORS -gt 0 ]; then
echo -e "${RED}FAILED${NC}: $ERRORS error(s), $WARNINGS warning(s). Fix errors before releasing."
exit 1
elif [ $WARNINGS -gt 0 ]; then
echo -e "${YELLOW}PASSED with warnings${NC}: $WARNINGS warning(s). Review before releasing."
exit 0
else
echo -e "${GREEN}PASSED${NC}: All checks passed!"
exit 0
fi