Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 94 additions & 26 deletions .github/workflows/deploy-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,39 +82,107 @@ jobs:
- name: Install dependencies
run: npm ci

# First: Build main branch content for the root
- name: Checkout and build main branch
# First: Build develop branch content for the subdirectory
- name: Build develop branch for sub-directory
run: npm run build
env:
GITVERSION_SEMVER: ${{ steps.gitversion.outputs.semVer }}
GITVERSION_FULLSEMVER: ${{ steps.gitversion.outputs.fullSemVer }}
GITVERSION_INFORMATIONALVERSION: ${{ steps.gitversion.outputs.informationalVersion }}

# Save develop build
- name: Save develop build
run: mv dist develop-dist

# Second: Build main branch content for the root
- name: Checkout and build main branch with test site link
run: |
# Save current state
git stash push -m "Save develop changes"
# Save current develop state
git stash push -m "Save develop changes" || echo "No changes to stash"

# Checkout main branch
# Checkout main branch with full history and ensure tags are available
git checkout main
git pull origin main || echo "No changes to pull"

# Build main branch content (without develop GitVersion env vars)
# This allows the inject-version script to use git-based version calculation
# which will correctly determine the main branch version
# Ensure all tags are fetched for version calculation
git fetch --tags || echo "Tags already available"

# Build main branch content with proper main branch GitVersion
npm run build

# Save main build
mv dist main-dist

# Return to develop branch
# Return to develop branch and restore state
git checkout develop
# Only pop the stash if one exists, and fail if pop fails
if git stash list | grep -q .; then
if git stash list | grep -q "Save develop changes"; then
git stash pop
else
echo "No stash to pop"
fi
env:
# Pass test site info for footer link
TEST_SITE_PATH: ${{ steps.pr-info.outputs.deploy_path }}

# Second: Build develop branch content for the subdirectory
- name: Build develop branch for sub-directory
run: npm run build
# Get GitVersion for main branch after checking it out
- name: Get main branch GitVersion
id: gitversion-main
run: |
# Save current develop state
git stash push -m "Save develop changes" || echo "No changes to stash"

# Checkout main branch
git checkout main
git pull origin main || echo "No changes to pull"
git fetch --tags || echo "Tags already available"

# Get GitVersion for main branch
gitversion /output json | tee gitversion-main.json
MAIN_SEMVER=$(cat gitversion-main.json | grep -o '"SemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_FULLSEMVER=$(cat gitversion-main.json | grep -o '"FullSemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_INFORMATIONAL=$(cat gitversion-main.json | grep -o '"InformationalVersion":"[^"]*"' | cut -d'"' -f4)
Comment on lines +139 to +141
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using shell text processing with grep and cut to parse JSON is fragile and error-prone. Consider using jq for reliable JSON parsing: MAIN_SEMVER=$(jq -r '.SemVer' gitversion-main.json). This approach is more robust and handles edge cases better.

Suggested change
MAIN_SEMVER=$(cat gitversion-main.json | grep -o '"SemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_FULLSEMVER=$(cat gitversion-main.json | grep -o '"FullSemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_INFORMATIONAL=$(cat gitversion-main.json | grep -o '"InformationalVersion":"[^"]*"' | cut -d'"' -f4)
MAIN_SEMVER=$(jq -r '.SemVer' gitversion-main.json)
MAIN_FULLSEMVER=$(jq -r '.FullSemVer' gitversion-main.json)
MAIN_INFORMATIONAL=$(jq -r '.InformationalVersion' gitversion-main.json)

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +141
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using shell text processing with grep and cut to parse JSON is fragile and error-prone. Consider using jq for reliable JSON parsing: MAIN_FULLSEMVER=$(jq -r '.FullSemVer' gitversion-main.json). This approach is more robust and handles edge cases better.

Suggested change
MAIN_SEMVER=$(cat gitversion-main.json | grep -o '"SemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_FULLSEMVER=$(cat gitversion-main.json | grep -o '"FullSemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_INFORMATIONAL=$(cat gitversion-main.json | grep -o '"InformationalVersion":"[^"]*"' | cut -d'"' -f4)
MAIN_SEMVER=$(jq -r '.SemVer' gitversion-main.json)
MAIN_FULLSEMVER=$(jq -r '.FullSemVer' gitversion-main.json)
MAIN_INFORMATIONAL=$(jq -r '.InformationalVersion' gitversion-main.json)

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +141
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using shell text processing with grep and cut to parse JSON is fragile and error-prone. Consider using jq for reliable JSON parsing: MAIN_INFORMATIONAL=$(jq -r '.InformationalVersion' gitversion-main.json). This approach is more robust and handles edge cases better.

Suggested change
MAIN_SEMVER=$(cat gitversion-main.json | grep -o '"SemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_FULLSEMVER=$(cat gitversion-main.json | grep -o '"FullSemVer":"[^"]*"' | cut -d'"' -f4)
MAIN_INFORMATIONAL=$(cat gitversion-main.json | grep -o '"InformationalVersion":"[^"]*"' | cut -d'"' -f4)
MAIN_SEMVER=$(jq -r '.SemVer' gitversion-main.json)
MAIN_FULLSEMVER=$(jq -r '.FullSemVer' gitversion-main.json)
MAIN_INFORMATIONAL=$(jq -r '.InformationalVersion' gitversion-main.json)

Copilot uses AI. Check for mistakes.

echo "main_semver=$MAIN_SEMVER" >> $GITHUB_OUTPUT
echo "main_fullsemver=$MAIN_FULLSEMVER" >> $GITHUB_OUTPUT
echo "main_informational=$MAIN_INFORMATIONAL" >> $GITHUB_OUTPUT

echo "::notice::Main branch GitVersion - SemVer: $MAIN_SEMVER"
echo "::notice::Main branch GitVersion - FullSemVer: $MAIN_FULLSEMVER"

# Return to develop branch and restore state
git checkout develop
if git stash list | grep -q "Save develop changes"; then
git stash pop
fi

# Rebuild main with proper GitVersion values
- name: Rebuild main branch with correct GitVersion
run: |
# Save current develop state
git stash push -m "Save develop changes" || echo "No changes to stash"

# Checkout main branch
git checkout main
git pull origin main || echo "No changes to pull"
git fetch --tags || echo "Tags already available"

# Build main branch content with proper main branch GitVersion
npm run build

# Save main build
rm -rf main-dist
mv dist main-dist

# Return to develop branch and restore state
git checkout develop
if git stash list | grep -q "Save develop changes"; then
git stash pop
fi
env:
GITVERSION_SEMVER: ${{ steps.gitversion.outputs.semVer }}
GITVERSION_FULLSEMVER: ${{ steps.gitversion.outputs.fullSemVer }}
GITVERSION_INFORMATIONALVERSION: ${{ steps.gitversion.outputs.informationalVersion }}
# Use proper GitVersion values for main branch
GITVERSION_SEMVER: ${{ steps.gitversion-main.outputs.main_semver }}
GITVERSION_FULLSEMVER: ${{ steps.gitversion-main.outputs.main_fullsemver }}
GITVERSION_INFORMATIONALVERSION: ${{ steps.gitversion-main.outputs.main_informational }}
# Pass test site info for footer link
TEST_SITE_PATH: ${{ steps.pr-info.outputs.deploy_path }}

# Third: Combine both builds into a single directory structure
- name: Combine builds for deployment
Expand All @@ -129,21 +197,21 @@ jobs:
mkdir -p "combined-dist/$DEPLOY_PATH"

# Modify develop build for sub-directory deployment
if [ -f dist/index.html ]; then
if [ -f develop-dist/index.html ]; then
# Add base tag for sub-directory if not present
if ! grep -q "<base" dist/index.html; then
sed -E '0,/<head[[:space:]]*>/s|<head[[:space:]]*>|<head>\n <base href="/'$DEPLOY_PATH'/">|' dist/index.html > "combined-dist/$DEPLOY_PATH/index.html"
if ! grep -q "<base" develop-dist/index.html; then
sed -E '0,/<head[[:space:]]*>/s|<head[[:space:]]*>|<head>\n <base href="/'$DEPLOY_PATH'/">|' develop-dist/index.html > "combined-dist/$DEPLOY_PATH/index.html"
echo "::notice::Added base tag to index.html for sub-directory support"
else
cp dist/index.html "combined-dist/$DEPLOY_PATH/"
cp develop-dist/index.html "combined-dist/$DEPLOY_PATH/"
echo "::notice::Base tag already exists in index.html"
fi

# Copy other files, preserving directory structure
rsync -av --exclude=index.html dist/ "combined-dist/$DEPLOY_PATH/"
rsync -av --exclude=index.html develop-dist/ "combined-dist/$DEPLOY_PATH/"
else
echo "::warning::dist/index.html not found, copying all files as-is"
cp -r dist/* "combined-dist/$DEPLOY_PATH/"
echo "::warning::develop-dist/index.html not found, copying all files as-is"
cp -r develop-dist/* "combined-dist/$DEPLOY_PATH/"
fi

# Replace dist with combined structure
Expand Down
108 changes: 83 additions & 25 deletions src/build/inject-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,98 @@ function getGitVersion(): string {
} catch {
// Fallback to simple git tag approach
try {
const lastTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf-8', cwd: process.cwd() }).trim();
const commitsSince = execSync(`git rev-list ${lastTag}..HEAD --count`, {
encoding: 'utf-8',
cwd: process.cwd(),
}).trim();
const commits = parseInt(commitsSince, 10);

// Parse the tag version
// Ensure tags are fetched (helpful in CI environments)
try {
execSync('git fetch --tags', { stdio: 'pipe', cwd: process.cwd() });
console.log('Tags fetched successfully');
} catch {
console.log('Failed to fetch tags, continuing anyway');
}

// Try to get the last tag - if no tags exist, handle gracefully
let lastTag: string;
let commits: number;

try {
lastTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf-8', cwd: process.cwd() }).trim();
const commitsSince = execSync(`git rev-list ${lastTag}..HEAD --count`, {
encoding: 'utf-8',
cwd: process.cwd(),
}).trim();
commits = parseInt(commitsSince, 10);
console.log(`Last tag: ${lastTag}, commits since: ${commits}`);
} catch (tagError) {
console.log(`No tags found: ${tagError}, generating version from scratch`);
// No tags exist, generate a proper version based on branch
const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
const shortHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8', cwd: process.cwd() }).trim();
if (branch === 'main') {
console.log(`No tags available, using default main version: 1.0.0+${shortHash}`);
return `1.0.0+${shortHash}`;
} else {
console.log(`No tags available, using default feature branch version: 0.0.1-alpha.${shortHash}`);
return `0.0.1-alpha.${shortHash}`;
}
}

// Parse the tag version - handle both normal and pre-release tags
const match = lastTag.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
if (!match || !match[1] || !match[2] || !match[3]) {
console.log('Using default fallback version: 1.0.0');
return '1.0.0';
console.log(`Tag format not recognized: ${lastTag}, falling back to generated version`);
// Generate a proper version format instead of using git describe
const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
const shortHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8', cwd: process.cwd() }).trim();
if (branch === 'main') {
return `1.0.0+${shortHash}`;
} else {
return `0.0.1-alpha.${shortHash}`;
}
}

const major = parseInt(match[1], 10);
const minor = parseInt(match[2], 10);
let patch = parseInt(match[3], 10);

// Simple branch-based logic
// Get current branch
const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
if (branch === 'main' && commits > 0) {
console.log(`Current branch: ${branch}`);

// If we're exactly on a tag, use the tag directly
if (commits === 0) {
const cleanTag = lastTag.replace(/^v/, '');
console.log(`Using exact tag version: ${cleanTag}`);
return cleanTag;
}

// For commits beyond the tag, increment appropriately based on branch
if (branch === 'main') {
patch += 1;
const version = `${major}.${minor}.${patch}`;
console.log(`Using fallback git logic for main branch: ${version}`);
return version;
} else if (commits > 0) {
} else {
patch += 1;
const version = `${major}.${minor}.${patch}-alpha.${commits}`;
console.log(`Using fallback git logic for feature branch: ${version}`);
return version;
}

const version = `${major}.${minor}.${patch}`;
console.log(`Using fallback git logic for tagged commit: ${version}`);
return version;
} catch {
// No git or tags available, use default
console.log('No git available, using default version: 1.0.0');
return '1.0.0';
} catch (gitError) {
console.log(`Git operations failed: ${gitError}`);
// Last resort - generate a proper version format
try {
const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
const shortHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8', cwd: process.cwd() }).trim();
if (branch === 'main') {
console.log(`Using basic fallback for main: 1.0.0+${shortHash}`);
return `1.0.0+${shortHash}`;
} else {
console.log(`Using basic fallback for feature branch: 0.0.1-alpha.${shortHash}`);
return `0.0.1-alpha.${shortHash}`;
}
} catch {
console.log('All git operations failed, using minimal fallback');
return '0.0.1-unknown';
}
}
}
}
Expand Down Expand Up @@ -89,10 +142,15 @@ function injectVersionIntoHtml(): void {
// Inject version into footer
const footerRegex = /(<footer class="footer">[\s\S]*?<div class="container">[\s\S]*?<p>)/;
if (footerRegex.test(htmlContent)) {
htmlContent = htmlContent.replace(
footerRegex,
`$1\n <span class="version">v${version}</span>\n `,
);
const testSitePath = process.env.TEST_SITE_PATH;
let versionHtml = `\n <span class="version">v${version}</span>\n `;

// Add test site link if this is the main site build (has TEST_SITE_PATH env var)
if (testSitePath) {
versionHtml += `\n - preview the <a href="https://www.everytimezone.net/${testSitePath}/" target="_blank" rel="noopener noreferrer">test site</a>\n `;
Comment on lines +149 to +150
Copy link

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testSitePath variable is directly interpolated into the URL without validation or sanitization. This could potentially lead to URL injection if the environment variable contains malicious content. Consider validating that testSitePath contains only expected characters (e.g., alphanumeric, hyphens, slashes).

Suggested change
if (testSitePath) {
versionHtml += `\n - preview the <a href="https://www.everytimezone.net/${testSitePath}/" target="_blank" rel="noopener noreferrer">test site</a>\n `;
// Only allow alphanumeric, hyphens, underscores, and slashes in testSitePath
if (testSitePath && /^[a-zA-Z0-9_\-\/]+$/.test(testSitePath)) {
versionHtml += `\n - preview the <a href="https://www.everytimezone.net/${testSitePath}/" target="_blank" rel="noopener noreferrer">test site</a>\n `;
} else if (testSitePath) {
console.warn('TEST_SITE_PATH contains invalid characters and will not be included in the HTML.');

Copilot uses AI. Check for mistakes.
}

htmlContent = htmlContent.replace(footerRegex, `$1${versionHtml}`);
}

writeFileSync(distIndexPath, htmlContent, 'utf-8');
Expand Down
Loading