Skip to content

Commit 1cb33bf

Browse files
committed
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 module
1 parent fa069a7 commit 1cb33bf

File tree

5 files changed

+225
-3
lines changed

5 files changed

+225
-3
lines changed

.github/workflows/audit-documentation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: 'Version to audit (use "local" for running containers)'
7+
description: 'Version to audit (must exist in git tags, e.g., v3.0.0 or "local" for dev containers)'
88
required: false
99
default: 'local'
1010
create_issue:

.github/workflows/influxdb3-release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ on:
1414
- cloud-dedicated
1515
- cloud-serverless
1616
version:
17-
description: 'Version being released (e.g., 3.0.0)'
17+
description: 'Release tag name (must exist in git tags, e.g., v3.0.0 or "local" for dev)'
1818
required: true
1919
type: string
2020
previous_version:
21-
description: 'Previous version for comparison (e.g., 2.9.0)'
21+
description: 'Previous release tag name (must exist in git tags, e.g., v2.9.0)'
2222
required: true
2323
type: string
2424
dry_run:

helper-scripts/common/generate-release-notes.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ FROM_VERSION="${1:-v3.1.0}"
4343
TO_VERSION="${2:-v3.2.0}"
4444
PRIMARY_REPO="${3:-${HOME}/Documents/github/influxdb}"
4545

46+
# Function to validate git tag
47+
validate_git_tag() {
48+
local version="$1"
49+
local repo_path="$2"
50+
51+
if [ "$version" = "local" ]; then
52+
return 0 # Special case for development
53+
fi
54+
55+
if [ ! -d "$repo_path" ]; then
56+
echo -e "${RED}Error: Repository not found: $repo_path${NC}"
57+
return 1
58+
fi
59+
60+
if ! git -C "$repo_path" tag --list | grep -q "^${version}$"; then
61+
echo -e "${RED}Error: Version tag '$version' does not exist in repository $repo_path${NC}"
62+
echo -e "${YELLOW}Available tags (most recent first):${NC}"
63+
git -C "$repo_path" tag --list --sort=-version:refname | head -10 | sed 's/^/ /'
64+
return 1
65+
fi
66+
67+
return 0
68+
}
69+
4670
# Collect additional repositories (all arguments after the third)
4771
ADDITIONAL_REPOS=()
4872
shift 3 2>/dev/null || true
@@ -58,6 +82,19 @@ YELLOW='\033[0;33m'
5882
BLUE='\033[0;34m'
5983
NC='\033[0m' # No Color
6084

85+
# Validate version tags
86+
echo -e "${YELLOW}Validating version tags...${NC}"
87+
if ! validate_git_tag "$FROM_VERSION" "$PRIMARY_REPO"; then
88+
echo -e "${RED}From version validation failed${NC}"
89+
exit 1
90+
fi
91+
92+
if ! validate_git_tag "$TO_VERSION" "$PRIMARY_REPO"; then
93+
echo -e "${RED}To version validation failed${NC}"
94+
exit 1
95+
fi
96+
echo -e "${GREEN}✓ Version tags validated successfully${NC}\n"
97+
6198
echo -e "${BLUE}Generating release notes for ${TO_VERSION}${NC}"
6299
echo -e "Primary Repository: ${PRIMARY_REPO}"
63100
if [ ${#ADDITIONAL_REPOS[@]} -gt 0 ]; then
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Git tag validation utility
5+
* Validates that provided version strings are actual git tags in the repository
6+
*/
7+
8+
import { spawn } from 'child_process';
9+
import { dirname, join } from 'path';
10+
import { fileURLToPath } from 'url';
11+
12+
const __filename = fileURLToPath(import.meta.url);
13+
const __dirname = dirname(__filename);
14+
15+
/**
16+
* Execute a command and return the output
17+
* @param {string} command - Command to execute
18+
* @param {string[]} args - Command arguments
19+
* @param {string} cwd - Working directory
20+
* @returns {Promise<string>} Command output
21+
*/
22+
function execCommand(command, args = [], cwd = process.cwd()) {
23+
return new Promise((resolve, reject) => {
24+
const child = spawn(command, args, { cwd, stdio: 'pipe' });
25+
let stdout = '';
26+
let stderr = '';
27+
28+
child.stdout.on('data', (data) => {
29+
stdout += data.toString();
30+
});
31+
32+
child.stderr.on('data', (data) => {
33+
stderr += data.toString();
34+
});
35+
36+
child.on('close', (code) => {
37+
if (code === 0) {
38+
resolve(stdout.trim());
39+
} else {
40+
reject(new Error(`Command failed: ${command} ${args.join(' ')}\n${stderr}`));
41+
}
42+
});
43+
});
44+
}
45+
46+
/**
47+
* Get all git tags from the repository
48+
* @param {string} repoPath - Path to the git repository
49+
* @returns {Promise<string[]>} Array of tag names
50+
*/
51+
async function getGitTags(repoPath = process.cwd()) {
52+
try {
53+
const output = await execCommand('git', ['tag', '--list', '--sort=-version:refname'], repoPath);
54+
return output ? output.split('\n').filter(tag => tag.trim()) : [];
55+
} catch (error) {
56+
throw new Error(`Failed to get git tags: ${error.message}`);
57+
}
58+
}
59+
60+
/**
61+
* Validate that a version string is an existing git tag
62+
* @param {string} version - Version string to validate
63+
* @param {string} repoPath - Path to the git repository
64+
* @returns {Promise<boolean>} True if version is a valid tag
65+
*/
66+
async function isValidTag(version, repoPath = process.cwd()) {
67+
if (!version || version === 'local') {
68+
return true; // 'local' is a special case for development
69+
}
70+
71+
const tags = await getGitTags(repoPath);
72+
return tags.includes(version);
73+
}
74+
75+
/**
76+
* Validate multiple version tags
77+
* @param {string[]} versions - Array of version strings to validate
78+
* @param {string} repoPath - Path to the git repository
79+
* @returns {Promise<{valid: boolean, errors: string[], availableTags: string[]}>}
80+
*/
81+
async function validateTags(versions, repoPath = process.cwd()) {
82+
const errors = [];
83+
const availableTags = await getGitTags(repoPath);
84+
85+
for (const version of versions) {
86+
if (version && version !== 'local' && !availableTags.includes(version)) {
87+
errors.push(`Version '${version}' is not a valid git tag`);
88+
}
89+
}
90+
91+
return {
92+
valid: errors.length === 0,
93+
errors,
94+
availableTags: availableTags.slice(0, 10) // Return top 10 most recent tags
95+
};
96+
}
97+
98+
/**
99+
* Validate version inputs and exit with error if invalid
100+
* @param {string} version - Current version
101+
* @param {string} previousVersion - Previous version (optional)
102+
* @param {string} repoPath - Path to the git repository
103+
*/
104+
async function validateVersionInputs(version, previousVersion = null, repoPath = process.cwd()) {
105+
const versionsToCheck = [version];
106+
if (previousVersion) {
107+
versionsToCheck.push(previousVersion);
108+
}
109+
110+
const validation = await validateTags(versionsToCheck, repoPath);
111+
112+
if (!validation.valid) {
113+
console.error('\n❌ Version validation failed:');
114+
validation.errors.forEach(error => console.error(` - ${error}`));
115+
116+
if (validation.availableTags.length > 0) {
117+
console.error('\n📋 Available tags (most recent first):');
118+
validation.availableTags.forEach(tag => console.error(` - ${tag}`));
119+
} else {
120+
console.error('\n📋 No git tags found in repository');
121+
}
122+
123+
console.error('\n💡 Tip: Use "local" for development/testing with local containers');
124+
process.exit(1);
125+
}
126+
127+
console.log('✅ Version tags validated successfully');
128+
}
129+
130+
/**
131+
* Get the repository root path (where .git directory is located)
132+
* @param {string} startPath - Starting path to search from
133+
* @returns {Promise<string>} Path to repository root
134+
*/
135+
async function getRepositoryRoot(startPath = process.cwd()) {
136+
try {
137+
const output = await execCommand('git', ['rev-parse', '--show-toplevel'], startPath);
138+
return output;
139+
} catch (error) {
140+
throw new Error(`Not a git repository or git not available: ${error.message}`);
141+
}
142+
}
143+
144+
export {
145+
getGitTags,
146+
isValidTag,
147+
validateTags,
148+
validateVersionInputs,
149+
getRepositoryRoot
150+
};
151+
152+
// CLI usage when run directly
153+
if (import.meta.url === `file://${process.argv[1]}`) {
154+
const args = process.argv.slice(2);
155+
156+
if (args.length === 0) {
157+
console.log('Usage: node validate-tags.js <version> [previous-version]');
158+
console.log('Examples:');
159+
console.log(' node validate-tags.js v3.0.0');
160+
console.log(' node validate-tags.js v3.0.0 v2.9.0');
161+
console.log(' node validate-tags.js local # Special case for development');
162+
process.exit(1);
163+
}
164+
165+
const [version, previousVersion] = args;
166+
167+
try {
168+
const repoRoot = await getRepositoryRoot();
169+
await validateVersionInputs(version, previousVersion, repoRoot);
170+
console.log('All versions are valid git tags');
171+
} catch (error) {
172+
console.error(`Error: ${error.message}`);
173+
process.exit(1);
174+
}
175+
}

helper-scripts/influxdb3-monolith/audit-cli-documentation.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { promises as fs } from 'fs';
1111
import { homedir } from 'os';
1212
import { join, dirname } from 'path';
1313
import { fileURLToPath } from 'url';
14+
import { validateVersionInputs, getRepositoryRoot } from '../common/validate-tags.js';
1415

1516
const __filename = fileURLToPath(import.meta.url);
1617
const __dirname = dirname(__filename);
@@ -945,6 +946,15 @@ async function main() {
945946
process.exit(1);
946947
}
947948

949+
// Validate version tag
950+
try {
951+
const repoRoot = await getRepositoryRoot();
952+
await validateVersionInputs(version, null, repoRoot);
953+
} catch (error) {
954+
console.error(`Version validation failed: ${error.message}`);
955+
process.exit(1);
956+
}
957+
948958
const auditor = new CLIDocAuditor(product, version);
949959
await auditor.run();
950960
}

0 commit comments

Comments
 (0)