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
21 changes: 20 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for gitversion

- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v4
with:
versionSpec: '6.x.x'

- name: Determine Version
id: gitversion
uses: gittools/actions/gitversion/execute@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
Expand All @@ -31,6 +42,14 @@ jobs:

- name: Build project
run: npm run build
env:
GITVERSION_SEMVER: ${{ steps.gitversion.outputs.semVer }}
GITVERSION_FULLSEMVER: ${{ steps.gitversion.outputs.fullSemVer }}
GITVERSION_INFORMATIONALVERSION: ${{ steps.gitversion.outputs.informationalVersion }}

- name: Run tests (includes linting, formatting, and type checking)
run: npm test
run: npm test
env:
GITVERSION_SEMVER: ${{ steps.gitversion.outputs.semVer }}
GITVERSION_FULLSEMVER: ${{ steps.gitversion.outputs.fullSemVer }}
GITVERSION_INFORMATIONALVERSION: ${{ steps.gitversion.outputs.informationalVersion }}
134 changes: 26 additions & 108 deletions src/build/inject-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,116 +3,33 @@ import { join } from 'path';
import { execSync } from 'child_process';

function getGitVersion(): string {
// First, check if GitVersion environment variables are available (from CI)
// Check GitVersion environment variables (from CI)
const envSemVer = process.env.GITVERSION_SEMVER;
const envFullSemVer = process.env.GITVERSION_FULLSEMVER;

if (envSemVer) {
console.log(`Using GitVersion from environment: ${envSemVer}`);
return envSemVer;
// Check if environment variables are defined (even if empty)
if (envSemVer !== undefined || envFullSemVer !== undefined) {
const version = envSemVer || envFullSemVer;
if (!version || version.trim() === '') {
throw new Error('GitVersion environment variables are present but empty. Expected valid version string.');
}
console.log(`Using GitVersion from environment: ${version}`);
return version;
}

// Try GitVersion CLI
try {
// Try GitVersion CLI tool (if available locally)
const gitVersionOutput = execSync('gitversion', { encoding: 'utf-8', cwd: process.cwd() });
const gitVersionData = JSON.parse(gitVersionOutput);
console.log(`Using GitVersion from CLI: ${gitVersionData.SemVer || gitVersionData.FullSemVer}`);
return gitVersionData.SemVer || gitVersionData.FullSemVer;
} catch {
// Fallback to simple git tag approach
try {
// 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(`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);

// Get current branch
const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
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 {
patch += 1;
const version = `${major}.${minor}.${patch}-alpha.${commits}`;
console.log(`Using fallback git logic for feature branch: ${version}`);
return version;
}
} 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';
}
}
const version = gitVersionData.SemVer || gitVersionData.FullSemVer;
console.log(`Using GitVersion from CLI: ${version}`);
return version;
} catch (gitVersionError) {
throw new Error(
`GitVersion is required but was not available. ` +
`Expected GITVERSION_SEMVER environment variable or working 'gitversion' CLI tool. ` +
`Error: ${gitVersionError}`,
);
}
}

Expand All @@ -139,18 +56,19 @@ function injectVersionIntoHtml(): void {
htmlContent = htmlContent.replace(metaTagRegex, `$1\n <meta name="app-version" content="${version}" />`);
}

// Inject version into footer
const footerRegex = /(<footer class="footer">[\s\S]*?<div class="container">[\s\S]*?<p>)/;
// Inject version into footer by replacing entire paragraph content
const footerRegex = /(<footer class="footer">[\s\S]*?<div class="container">[\s\S]*?<p>)[\s\S]*?(<\/p>)/;
if (footerRegex.test(htmlContent)) {
const testSitePath = process.env.TEST_SITE_PATH;
let versionHtml = `\n <span class="version">v${version}</span>\n `;
let footerContent = `\n <span class="version">v${version}</span>\n <a href="https://github.com/tsmarvin/EveryTimeZone" target="_blank" rel="noopener noreferrer">View on GitHub</a>`;

// 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 `;
footerContent += `,\n <a href="https://www.everytimezone.net/${testSitePath}/" target="_blank" rel="noopener noreferrer">Test the development site</a>`;
}
footerContent += '\n ';

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

writeFileSync(distIndexPath, htmlContent, 'utf-8');
Expand Down
46 changes: 8 additions & 38 deletions test/deploy-version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,25 @@ import { readFileSync, existsSync } from 'fs';
import { join } from 'path';

describe('Deploy Version Behavior', () => {
it('should use different versions for main and develop builds', () => {
// This test validates that when GitVersion env vars are not set,
// the inject-version script falls back to git-based version calculation
// which will produce different versions for different branches
it('should fail when GitVersion is not available', () => {
// This test validates that the inject-version script requires GitVersion
// and fails immediately when GitVersion is not available

// Store original values
const originalSemVer = process.env.GITVERSION_SEMVER;
const originalFullSemVer = process.env.GITVERSION_FULLSEMVER;
const originalInformationalVersion = process.env.GITVERSION_INFORMATIONALVERSION;

try {
// Clear GitVersion environment variables to simulate main branch build
// Clear GitVersion environment variables to simulate no GitVersion scenario
delete process.env.GITVERSION_SEMVER;
delete process.env.GITVERSION_FULLSEMVER;
delete process.env.GITVERSION_INFORMATIONALVERSION;

// Build without GitVersion env vars (simulating main branch build scenario)
execSync('npm run build', { cwd: process.cwd(), stdio: 'pipe' });

// Check that version was injected
const distIndexPath = join(process.cwd(), 'dist', 'index.html');
expect(existsSync(distIndexPath)).toBe(true);

const htmlContent = readFileSync(distIndexPath, 'utf-8');

// Should have version meta tag
expect(htmlContent).toMatch(/<meta name="app-version" content="[^"]+"/);

// Should have version in footer
expect(htmlContent).toMatch(/<span class="version">v[^<]+<\/span>/);

// Extract the version from the footer
const versionMatch = htmlContent.match(/<span class="version">v([^<]+)<\/span>/);
expect(versionMatch).toBeTruthy();

const injectedVersion = versionMatch![1];
const currentBranch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();

console.log(`Injected version: ${injectedVersion}`);
console.log(`Current branch: ${currentBranch}`);

// Should be a valid semver-like version
expect(injectedVersion).toMatch(/^\d+\.\d+\.\d+/);

// Since we're not on main branch and have no GitVersion env vars,
// it should use git fallback which produces alpha versions for non-main branches
if (currentBranch !== 'main') {
expect(injectedVersion).toMatch(/alpha/);
}
// Build without GitVersion should fail
expect(() => {
execSync('npm run build', { cwd: process.cwd(), stdio: 'pipe' });
}).toThrow();

} finally {
// Restore environment variables
Expand Down
Loading