chore(ci): Validate git tags for workflows: - Reusable utility for validating git tags across all
scripts - Supports special case for local development mode - Provides helpful error messages with available tags - Can be used as CLI tool or imported modulepull/6190/head
parent
fa069a77ea
commit
1cb33bfb13
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to audit (use "local" for running containers)'
|
||||
description: 'Version to audit (must exist in git tags, e.g., v3.0.0 or "local" for dev containers)'
|
||||
required: false
|
||||
default: 'local'
|
||||
create_issue:
|
||||
|
|
|
@ -14,11 +14,11 @@ on:
|
|||
- cloud-dedicated
|
||||
- cloud-serverless
|
||||
version:
|
||||
description: 'Version being released (e.g., 3.0.0)'
|
||||
description: 'Release tag name (must exist in git tags, e.g., v3.0.0 or "local" for dev)'
|
||||
required: true
|
||||
type: string
|
||||
previous_version:
|
||||
description: 'Previous version for comparison (e.g., 2.9.0)'
|
||||
description: 'Previous release tag name (must exist in git tags, e.g., v2.9.0)'
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
|
|
|
@ -43,6 +43,30 @@ FROM_VERSION="${1:-v3.1.0}"
|
|||
TO_VERSION="${2:-v3.2.0}"
|
||||
PRIMARY_REPO="${3:-${HOME}/Documents/github/influxdb}"
|
||||
|
||||
# Function to validate git tag
|
||||
validate_git_tag() {
|
||||
local version="$1"
|
||||
local repo_path="$2"
|
||||
|
||||
if [ "$version" = "local" ]; then
|
||||
return 0 # Special case for development
|
||||
fi
|
||||
|
||||
if [ ! -d "$repo_path" ]; then
|
||||
echo -e "${RED}Error: Repository not found: $repo_path${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! git -C "$repo_path" tag --list | grep -q "^${version}$"; then
|
||||
echo -e "${RED}Error: Version tag '$version' does not exist in repository $repo_path${NC}"
|
||||
echo -e "${YELLOW}Available tags (most recent first):${NC}"
|
||||
git -C "$repo_path" tag --list --sort=-version:refname | head -10 | sed 's/^/ /'
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Collect additional repositories (all arguments after the third)
|
||||
ADDITIONAL_REPOS=()
|
||||
shift 3 2>/dev/null || true
|
||||
|
@ -58,6 +82,19 @@ YELLOW='\033[0;33m'
|
|||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Validate version tags
|
||||
echo -e "${YELLOW}Validating version tags...${NC}"
|
||||
if ! validate_git_tag "$FROM_VERSION" "$PRIMARY_REPO"; then
|
||||
echo -e "${RED}From version validation failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! validate_git_tag "$TO_VERSION" "$PRIMARY_REPO"; then
|
||||
echo -e "${RED}To version validation failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓ Version tags validated successfully${NC}\n"
|
||||
|
||||
echo -e "${BLUE}Generating release notes for ${TO_VERSION}${NC}"
|
||||
echo -e "Primary Repository: ${PRIMARY_REPO}"
|
||||
if [ ${#ADDITIONAL_REPOS[@]} -gt 0 ]; then
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Git tag validation utility
|
||||
* Validates that provided version strings are actual git tags in the repository
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Execute a command and return the output
|
||||
* @param {string} command - Command to execute
|
||||
* @param {string[]} args - Command arguments
|
||||
* @param {string} cwd - Working directory
|
||||
* @returns {Promise<string>} Command output
|
||||
*/
|
||||
function execCommand(command, args = [], cwd = process.cwd()) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { cwd, stdio: 'pipe' });
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve(stdout.trim());
|
||||
} else {
|
||||
reject(new Error(`Command failed: ${command} ${args.join(' ')}\n${stderr}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all git tags from the repository
|
||||
* @param {string} repoPath - Path to the git repository
|
||||
* @returns {Promise<string[]>} Array of tag names
|
||||
*/
|
||||
async function getGitTags(repoPath = process.cwd()) {
|
||||
try {
|
||||
const output = await execCommand('git', ['tag', '--list', '--sort=-version:refname'], repoPath);
|
||||
return output ? output.split('\n').filter(tag => tag.trim()) : [];
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get git tags: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a version string is an existing git tag
|
||||
* @param {string} version - Version string to validate
|
||||
* @param {string} repoPath - Path to the git repository
|
||||
* @returns {Promise<boolean>} True if version is a valid tag
|
||||
*/
|
||||
async function isValidTag(version, repoPath = process.cwd()) {
|
||||
if (!version || version === 'local') {
|
||||
return true; // 'local' is a special case for development
|
||||
}
|
||||
|
||||
const tags = await getGitTags(repoPath);
|
||||
return tags.includes(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate multiple version tags
|
||||
* @param {string[]} versions - Array of version strings to validate
|
||||
* @param {string} repoPath - Path to the git repository
|
||||
* @returns {Promise<{valid: boolean, errors: string[], availableTags: string[]}>}
|
||||
*/
|
||||
async function validateTags(versions, repoPath = process.cwd()) {
|
||||
const errors = [];
|
||||
const availableTags = await getGitTags(repoPath);
|
||||
|
||||
for (const version of versions) {
|
||||
if (version && version !== 'local' && !availableTags.includes(version)) {
|
||||
errors.push(`Version '${version}' is not a valid git tag`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
availableTags: availableTags.slice(0, 10) // Return top 10 most recent tags
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate version inputs and exit with error if invalid
|
||||
* @param {string} version - Current version
|
||||
* @param {string} previousVersion - Previous version (optional)
|
||||
* @param {string} repoPath - Path to the git repository
|
||||
*/
|
||||
async function validateVersionInputs(version, previousVersion = null, repoPath = process.cwd()) {
|
||||
const versionsToCheck = [version];
|
||||
if (previousVersion) {
|
||||
versionsToCheck.push(previousVersion);
|
||||
}
|
||||
|
||||
const validation = await validateTags(versionsToCheck, repoPath);
|
||||
|
||||
if (!validation.valid) {
|
||||
console.error('\n❌ Version validation failed:');
|
||||
validation.errors.forEach(error => console.error(` - ${error}`));
|
||||
|
||||
if (validation.availableTags.length > 0) {
|
||||
console.error('\n📋 Available tags (most recent first):');
|
||||
validation.availableTags.forEach(tag => console.error(` - ${tag}`));
|
||||
} else {
|
||||
console.error('\n📋 No git tags found in repository');
|
||||
}
|
||||
|
||||
console.error('\n💡 Tip: Use "local" for development/testing with local containers');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ Version tags validated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repository root path (where .git directory is located)
|
||||
* @param {string} startPath - Starting path to search from
|
||||
* @returns {Promise<string>} Path to repository root
|
||||
*/
|
||||
async function getRepositoryRoot(startPath = process.cwd()) {
|
||||
try {
|
||||
const output = await execCommand('git', ['rev-parse', '--show-toplevel'], startPath);
|
||||
return output;
|
||||
} catch (error) {
|
||||
throw new Error(`Not a git repository or git not available: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getGitTags,
|
||||
isValidTag,
|
||||
validateTags,
|
||||
validateVersionInputs,
|
||||
getRepositoryRoot
|
||||
};
|
||||
|
||||
// CLI usage when run directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log('Usage: node validate-tags.js <version> [previous-version]');
|
||||
console.log('Examples:');
|
||||
console.log(' node validate-tags.js v3.0.0');
|
||||
console.log(' node validate-tags.js v3.0.0 v2.9.0');
|
||||
console.log(' node validate-tags.js local # Special case for development');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [version, previousVersion] = args;
|
||||
|
||||
try {
|
||||
const repoRoot = await getRepositoryRoot();
|
||||
await validateVersionInputs(version, previousVersion, repoRoot);
|
||||
console.log('All versions are valid git tags');
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import { promises as fs } from 'fs';
|
|||
import { homedir } from 'os';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { validateVersionInputs, getRepositoryRoot } from '../common/validate-tags.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
@ -945,6 +946,15 @@ async function main() {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate version tag
|
||||
try {
|
||||
const repoRoot = await getRepositoryRoot();
|
||||
await validateVersionInputs(version, null, repoRoot);
|
||||
} catch (error) {
|
||||
console.error(`Version validation failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const auditor = new CLIDocAuditor(product, version);
|
||||
await auditor.run();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue