chore: implement semantic versioning with docker image tagging
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: euchre-camp/euchre-camp:latest
|
image: dhg.lol:5000/euchre-camp:0.1.0.dev
|
||||||
container_name: euchre-camp
|
container_name: euchre-camp
|
||||||
ports:
|
ports:
|
||||||
- "51193:3000"
|
- "51193:3000"
|
||||||
@@ -27,6 +27,9 @@ services:
|
|||||||
# Trusted Origins for Better Auth
|
# Trusted Origins for Better Auth
|
||||||
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
|
# Version tracking
|
||||||
|
- IMAGE_VERSION=0.1.0.dev
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# Persist uploaded files and static content
|
# Persist uploaded files and static content
|
||||||
- /DATA/AppData/euchre-camp/config:/app/public/uploads
|
- /DATA/AppData/euchre-camp/config:/app/public/uploads
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Development Docker Compose Configuration
|
||||||
|
# Usage: docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
args:
|
||||||
|
GIT_COMMIT: ${GIT_COMMIT:-dev}
|
||||||
|
container_name: euchre-camp-dev
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
# Database Configuration
|
||||||
|
- DATABASE_PROVIDER=postgresql
|
||||||
|
- DATABASE_URL=postgresql://euchre_camp:password@db:5432/euchre_camp
|
||||||
|
- DATABASE_SHADOW_URL=postgresql://euchre_camp:password@db:5432/euchre_camp_shadow
|
||||||
|
|
||||||
|
# Better Auth Configuration
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-dev-secret-change-in-production}
|
||||||
|
- BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000}
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
- NODE_ENV=development
|
||||||
|
|
||||||
|
# Trusted Origins for Better Auth
|
||||||
|
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
|
# Version tracking
|
||||||
|
- IMAGE_VERSION=0.1.0.dev-dev
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Mount source code for hot reload
|
||||||
|
- .:/app
|
||||||
|
- /app/node_modules
|
||||||
|
- /app/.next
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: euchre-camp-db-dev
|
||||||
|
ports:
|
||||||
|
- "5433:5432" # Different port to avoid conflicts
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=euchre_camp
|
||||||
|
- POSTGRES_USER=euchre_camp
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
volumes:
|
||||||
|
- euchre_camp_db_data_dev:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
euchre_camp_db_data_dev:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
euchre-network:
|
||||||
|
driver: bridge
|
||||||
+19
-28
@@ -1,66 +1,57 @@
|
|||||||
|
# Production Docker Compose Configuration
|
||||||
|
# Usage: docker-compose up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
image: dhg.lol:5000/euchre-camp:0.1.0.dev
|
||||||
context: .
|
|
||||||
target: runner
|
|
||||||
container_name: euchre-camp-app
|
container_name: euchre-camp-app
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
- DATABASE_PROVIDER=postgresql
|
- DATABASE_PROVIDER=postgresql
|
||||||
- DATABASE_URL=postgresql://euchre_camp:euchrepassword@postgresql:5432/euchre_camp
|
- DATABASE_URL=postgresql://euchre_camp:password@db:5432/euchre_camp
|
||||||
- DATABASE_SHADOW_URL=postgresql://euchre_camp:euchrepassword@postgresql:5432/euchre_camp_shadow
|
- DATABASE_SHADOW_URL=postgresql://euchre_camp:password@db:5432/euchre_camp_shadow
|
||||||
|
|
||||||
# Better Auth Configuration
|
# Better Auth Configuration
|
||||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-in-production}
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-change-this-secret-in-production}
|
||||||
- BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000}
|
- BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000}
|
||||||
|
|
||||||
# Application Configuration
|
# Application Configuration
|
||||||
- NODE_ENV=${NODE_ENV:-production}
|
- NODE_ENV=production
|
||||||
|
|
||||||
# Trusted Origins for Better Auth
|
# Trusted Origins for Better Auth
|
||||||
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
depends_on:
|
# Version tracking
|
||||||
postgresql:
|
- IMAGE_VERSION=0.1.0.dev
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
volumes:
|
depends_on:
|
||||||
# Persist uploaded files and static content
|
- db
|
||||||
- app_data:/app/public/uploads
|
|
||||||
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- euchre-network
|
- euchre-network
|
||||||
|
|
||||||
postgresql:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: euchre-camp-postgres
|
container_name: euchre-camp-db
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=euchre_camp
|
- POSTGRES_DB=euchre_camp
|
||||||
- POSTGRES_USER=euchre_camp
|
- POSTGRES_USER=euchre_camp
|
||||||
- POSTGRES_PASSWORD=euchrepassword
|
- POSTGRES_PASSWORD=password
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- euchre_camp_db_data:/var/lib/postgresql/data
|
||||||
- ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
|
- ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U euchre_camp -d euchre_camp"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 30s
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- euchre-network
|
- euchre-network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
euchre_camp_db_data:
|
||||||
driver: local
|
|
||||||
app_data:
|
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
+8
-1
@@ -18,7 +18,14 @@
|
|||||||
"docker:down": "docker-compose down",
|
"docker:down": "docker-compose down",
|
||||||
"docker:logs": "docker-compose logs -f",
|
"docker:logs": "docker-compose logs -f",
|
||||||
"docker:build": "docker-compose build",
|
"docker:build": "docker-compose build",
|
||||||
"docker:shell": "docker exec -it euchre-camp-app sh"
|
"docker:shell": "docker exec -it euchre-camp-app sh",
|
||||||
|
"version": "node scripts/bump-version.js",
|
||||||
|
"version:patch": "node scripts/bump-version.js patch",
|
||||||
|
"version:minor": "node scripts/bump-version.js minor",
|
||||||
|
"version:major": "node scripts/bump-version.js major",
|
||||||
|
"docker:build:push": "node scripts/build-and-push-docker.js",
|
||||||
|
"docker:compose:generate": "node scripts/generate-docker-compose.js",
|
||||||
|
"release": "npm run version:patch && npm run docker:compose:generate && npm run docker:build:push"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
|||||||
Executable
+176
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker Build and Push Script
|
||||||
|
*
|
||||||
|
* Builds Docker images and pushes them to the local registry.
|
||||||
|
* Tags images based on git state:
|
||||||
|
* - Tagged commit: versioned tags + latest
|
||||||
|
* - Untagged commit: .dev suffix + commit hash
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync, spawn } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const REGISTRY = 'dhg.lol:5000';
|
||||||
|
const IMAGE_NAME = 'euchre-camp';
|
||||||
|
const FULL_IMAGE_NAME = `${REGISTRY}/${IMAGE_NAME}`;
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const forceVersion = args[0]; // Optional: force a specific version
|
||||||
|
|
||||||
|
// Get current version from package.json
|
||||||
|
function getCurrentVersion() {
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
return packageJson.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get git commit hash
|
||||||
|
function getGitCommitHash(short = true) {
|
||||||
|
const flag = short ? '--short' : '';
|
||||||
|
return execSync(`git rev-parse ${flag} HEAD`, { encoding: 'utf8' }).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get git tag (if any)
|
||||||
|
function getGitTag() {
|
||||||
|
try {
|
||||||
|
const tag = execSync('git describe --tags --exact-match 2>/dev/null', { encoding: 'utf8' }).trim();
|
||||||
|
return tag;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if working directory is clean
|
||||||
|
function isWorkingDirectoryClean() {
|
||||||
|
try {
|
||||||
|
const status = execSync('git status --porcelain', { encoding: 'utf8' }).trim();
|
||||||
|
return status === '';
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Docker image
|
||||||
|
async function buildDockerImage(tags, gitCommit) {
|
||||||
|
console.log(`🔨 Building Docker image...\n`);
|
||||||
|
|
||||||
|
const buildArgs = [
|
||||||
|
'build',
|
||||||
|
'--build-arg', `GIT_COMMIT=${gitCommit}`,
|
||||||
|
'-t', `${FULL_IMAGE_NAME}:${tags[0]}`, // Primary tag
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add additional tags
|
||||||
|
tags.forEach(tag => {
|
||||||
|
buildArgs.push('-t', `${FULL_IMAGE_NAME}:${tag}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
buildArgs.push('.');
|
||||||
|
|
||||||
|
console.log(`Build command: docker ${buildArgs.join(' ')}\n`);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const docker = spawn('docker', buildArgs, {
|
||||||
|
cwd: path.join(__dirname, '..'),
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
|
||||||
|
docker.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
console.log('✅ Docker image built successfully\n');
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Docker build failed with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push Docker image
|
||||||
|
async function pushDockerImage(tags) {
|
||||||
|
console.log(`🚀 Pushing Docker images to ${REGISTRY}...\n`);
|
||||||
|
|
||||||
|
for (const tag of tags) {
|
||||||
|
const imageName = `${FULL_IMAGE_NAME}:${tag}`;
|
||||||
|
console.log(`Pushing ${imageName}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(`docker push ${imageName}`, { stdio: 'inherit' });
|
||||||
|
console.log(`✅ Pushed ${imageName}\n`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Failed to push ${imageName}:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All images pushed successfully\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
async function main() {
|
||||||
|
console.log('🐳 Docker Build & Push Tool\n');
|
||||||
|
console.log(`Registry: ${REGISTRY}`);
|
||||||
|
console.log(`Image: ${IMAGE_NAME}\n`);
|
||||||
|
|
||||||
|
// Check if working directory is clean
|
||||||
|
if (!isWorkingDirectoryClean()) {
|
||||||
|
console.log('⚠️ Warning: Working directory is not clean');
|
||||||
|
console.log(' Proceeding anyway...\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine image tags
|
||||||
|
const gitCommit = getGitCommitHash();
|
||||||
|
const gitTag = getGitTag();
|
||||||
|
const version = forceVersion || getCurrentVersion();
|
||||||
|
|
||||||
|
let tags = [];
|
||||||
|
|
||||||
|
if (gitTag) {
|
||||||
|
// Tagged commit - use version and 'latest'
|
||||||
|
const tagWithoutV = gitTag.startsWith('v') ? gitTag.slice(1) : gitTag;
|
||||||
|
|
||||||
|
if (tagWithoutV !== version) {
|
||||||
|
console.log(`⚠️ Warning: Git tag (${gitTag}) doesn't match package.json version (${version})`);
|
||||||
|
console.log(' Using package.json version for Docker tags\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
version, // e.g., 0.1.0
|
||||||
|
'latest', // Latest stable
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Untagged commit - use version with .dev suffix and commit hash
|
||||||
|
tags = [
|
||||||
|
`${version}.dev`, // e.g., 0.1.0.dev
|
||||||
|
`${version}-sha-${gitCommit}`, // e.g., 0.1.0-sha-abc123
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Git commit: ${gitCommit}`);
|
||||||
|
console.log(`Git tag: ${gitTag || '(none)'}`);
|
||||||
|
console.log(`Package version: ${version}`);
|
||||||
|
console.log(`Docker tags: ${tags.join(', ')}\n`);
|
||||||
|
|
||||||
|
// Build the image
|
||||||
|
await buildDockerImage(tags, gitCommit);
|
||||||
|
|
||||||
|
// Push the images
|
||||||
|
await pushDockerImage(tags);
|
||||||
|
|
||||||
|
console.log('🎉 Docker build and push complete!\n');
|
||||||
|
console.log('Images available at:');
|
||||||
|
tags.forEach(tag => {
|
||||||
|
console.log(` - ${FULL_IMAGE_NAME}:${tag}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run main function
|
||||||
|
main().catch(error => {
|
||||||
|
console.error('❌ Error:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Executable
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version Bumping Script
|
||||||
|
*
|
||||||
|
* Bumps the version based on conventional commits since last release.
|
||||||
|
* Supports:
|
||||||
|
* - major: Breaking changes (! or BREAKING CHANGE)
|
||||||
|
* - minor: Features (feat:)
|
||||||
|
* - patch: Fixes (fix:) and other changes
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const forcedVersion = args[0]; // e.g., "0.2.0" or "patch/minor/major"
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
|
||||||
|
|
||||||
|
// Load current version from package.json
|
||||||
|
function getCurrentVersion() {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
return packageJson.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse version string into components
|
||||||
|
function parseVersion(version) {
|
||||||
|
const [major, minor, patch] = version.split('.').map(Number);
|
||||||
|
return { major, minor, patch };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format version string
|
||||||
|
function formatVersion(major, minor, patch) {
|
||||||
|
return `${major}.${minor}.${patch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get commits since last tag
|
||||||
|
function getCommitsSinceLastTag() {
|
||||||
|
try {
|
||||||
|
const tags = execSync('git tag --sort=-version:refname', { encoding: 'utf8' }).trim();
|
||||||
|
const latestTag = tags.split('\n')[0];
|
||||||
|
|
||||||
|
if (!latestTag) {
|
||||||
|
// No tags yet, get all commits
|
||||||
|
return execSync('git log --oneline --format=%s', { encoding: 'utf8' }).trim().split('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const commits = execSync(`git log ${latestTag}..HEAD --oneline --format=%s`, { encoding: 'utf8' }).trim();
|
||||||
|
return commits ? commits.split('\n') : [];
|
||||||
|
} catch (error) {
|
||||||
|
// If no commits since tag or other error, get recent commits
|
||||||
|
return execSync('git log --oneline --format=%s -n 20', { encoding: 'utf8' }).trim().split('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze commits to determine version bump type
|
||||||
|
function analyzeCommits(commits) {
|
||||||
|
let bumpType = 'patch'; // default
|
||||||
|
|
||||||
|
for (const commit of commits) {
|
||||||
|
if (!commit) continue;
|
||||||
|
|
||||||
|
// Breaking change indicators
|
||||||
|
if (commit.includes('!') || commit.includes('BREAKING CHANGE')) {
|
||||||
|
bumpType = 'major';
|
||||||
|
break; // Major takes precedence
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feature indicators
|
||||||
|
if (commit.startsWith('feat:') || commit.startsWith('feat(')) {
|
||||||
|
if (bumpType === 'patch') {
|
||||||
|
bumpType = 'minor';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix indicators (already patch by default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bumpType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new version based on bump type
|
||||||
|
function generateNewVersion(currentVersion, bumpType) {
|
||||||
|
const { major, minor, patch } = parseVersion(currentVersion);
|
||||||
|
|
||||||
|
switch (bumpType) {
|
||||||
|
case 'major':
|
||||||
|
return formatVersion(major + 1, 0, 0);
|
||||||
|
case 'minor':
|
||||||
|
return formatVersion(major, minor + 1, 0);
|
||||||
|
case 'patch':
|
||||||
|
default:
|
||||||
|
return formatVersion(major, minor, patch + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update package.json version
|
||||||
|
function updatePackageJson(newVersion) {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
packageJson.version = newVersion;
|
||||||
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||||
|
console.log(`✓ Updated package.json to version ${newVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate changelog entry
|
||||||
|
function generateChangelogEntry(newVersion, commits, bumpType) {
|
||||||
|
const date = new Date().toISOString().split('T')[0];
|
||||||
|
const relevantCommits = commits.filter(commit => {
|
||||||
|
if (!commit) return false;
|
||||||
|
// Filter out non-user-facing commits
|
||||||
|
if (commit.startsWith('chore:') && !commit.includes('BREAKING')) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const changelogEntry = `## [${newVersion}] - ${date}\n\n` +
|
||||||
|
`### ${bumpType.charAt(0).toUpperCase() + bumpType.slice(1)} Changes\n\n` +
|
||||||
|
relevantCommits.map(commit => `- ${commit}`).join('\n') +
|
||||||
|
'\n\n';
|
||||||
|
|
||||||
|
return changelogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update CHANGELOG.md
|
||||||
|
function updateChangelog(newVersion, commits, bumpType) {
|
||||||
|
const changelogEntry = generateChangelogEntry(newVersion, commits, bumpType);
|
||||||
|
|
||||||
|
if (fs.existsSync(changelogPath)) {
|
||||||
|
const content = fs.readFileSync(changelogPath, 'utf8');
|
||||||
|
const newContent = changelogEntry + content;
|
||||||
|
fs.writeFileSync(changelogPath, newContent);
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(changelogPath, `# Changelog\n\n${changelogEntry}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✓ Updated CHANGELOG.md`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
function main() {
|
||||||
|
console.log('🔄 Version Bumping Tool\n');
|
||||||
|
|
||||||
|
const currentVersion = getCurrentVersion();
|
||||||
|
console.log(`Current version: ${currentVersion}`);
|
||||||
|
|
||||||
|
let newVersion;
|
||||||
|
let bumpType;
|
||||||
|
|
||||||
|
if (forcedVersion) {
|
||||||
|
// Check if it's a bump type or explicit version
|
||||||
|
if (['patch', 'minor', 'major'].includes(forcedVersion.toLowerCase())) {
|
||||||
|
bumpType = forcedVersion.toLowerCase();
|
||||||
|
newVersion = generateNewVersion(currentVersion, bumpType);
|
||||||
|
console.log(`Forced bump type: ${bumpType}`);
|
||||||
|
} else {
|
||||||
|
// Assume it's an explicit version
|
||||||
|
newVersion = forcedVersion;
|
||||||
|
bumpType = 'custom';
|
||||||
|
console.log(`Forced version: ${newVersion}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Analyze commits to determine bump type
|
||||||
|
const commits = getCommitsSinceLastTag();
|
||||||
|
console.log(`\nAnalyzing ${commits.length} commits since last release...`);
|
||||||
|
|
||||||
|
bumpType = analyzeCommits(commits);
|
||||||
|
newVersion = generateNewVersion(currentVersion, bumpType);
|
||||||
|
|
||||||
|
console.log(`\nDetected bump type: ${bumpType}`);
|
||||||
|
console.log(`New version: ${currentVersion} → ${newVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm with user
|
||||||
|
const readline = require('readline');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
rl.question(`\nApply version ${newVersion}? (y/N) `, (answer) => {
|
||||||
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
||||||
|
// Update package.json
|
||||||
|
updatePackageJson(newVersion);
|
||||||
|
|
||||||
|
// Update changelog
|
||||||
|
if (bumpType !== 'custom') {
|
||||||
|
const commits = getCommitsSinceLastTag();
|
||||||
|
updateChangelog(newVersion, commits, bumpType);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ Version bumped to ${newVersion}`);
|
||||||
|
console.log(`\nNext steps:`);
|
||||||
|
console.log(` 1. Review changes: git diff`);
|
||||||
|
console.log(` 2. Commit changes: git commit -am "chore: bump version to v${newVersion}"`);
|
||||||
|
console.log(` 3. Create tag: git tag -a v${newVersion} -m "Release v${newVersion}"`);
|
||||||
|
console.log(` 4. Push changes: git push origin main`);
|
||||||
|
console.log(` 5. Push tag: git push origin v${newVersion}`);
|
||||||
|
} else {
|
||||||
|
console.log('❌ Version bump cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run main function
|
||||||
|
main();
|
||||||
Executable
+282
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Docker Compose Files with Versioned Images
|
||||||
|
*
|
||||||
|
* This script generates docker-compose files with the appropriate image tags
|
||||||
|
* based on the current version and git state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const REGISTRY = 'dhg.lol:5000';
|
||||||
|
const IMAGE_NAME = 'euchre-camp';
|
||||||
|
|
||||||
|
// Get current version from package.json
|
||||||
|
function getCurrentVersion() {
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
return packageJson.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get git tag (if any)
|
||||||
|
function getGitTag() {
|
||||||
|
try {
|
||||||
|
const tag = execSync('git describe --tags --exact-match 2>/dev/null', { encoding: 'utf8' }).trim();
|
||||||
|
return tag;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image tag based on git state
|
||||||
|
function getImageTag() {
|
||||||
|
const version = getCurrentVersion();
|
||||||
|
const gitTag = getGitTag();
|
||||||
|
|
||||||
|
if (gitTag) {
|
||||||
|
// Tagged commit - use version tag
|
||||||
|
return version;
|
||||||
|
} else {
|
||||||
|
// Untagged commit - use .dev suffix
|
||||||
|
return `${version}.dev`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate docker-compose.casaos.yml
|
||||||
|
function generateCasaosYml(imageTag) {
|
||||||
|
return `# CasaOS Optimized Docker Compose Configuration
|
||||||
|
# This file is optimized for deployment on CasaOS
|
||||||
|
# Usage: docker-compose -f docker-compose.casaos.yml up -d
|
||||||
|
#
|
||||||
|
# IMPORTANT: This configuration requires an external PostgreSQL database.
|
||||||
|
# Set DATABASE_URL environment variable to your PostgreSQL connection string.
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ${REGISTRY}/${IMAGE_NAME}:${imageTag}
|
||||||
|
container_name: euchre-camp
|
||||||
|
ports:
|
||||||
|
- "51193:3000"
|
||||||
|
environment:
|
||||||
|
# Database Configuration (REQUIRED: Set via CasaOS environment variables)
|
||||||
|
- DATABASE_PROVIDER=postgresql
|
||||||
|
- DATABASE_URL="postgresql://euchre_camp:LINGO5row_hiding@dhg.lol:5432/euchre_camp"
|
||||||
|
- DATABASE_SHADOW_URL="postgresql://euchre_camp:LINGO5row_hiding@dhg.lol:5432/euchre_camp_shadow"
|
||||||
|
|
||||||
|
# Better Auth Configuration
|
||||||
|
- BETTER_AUTH_SECRET=\${BETTER_AUTH_SECRET:-change-this-secret-in-production}
|
||||||
|
- BETTER_AUTH_URL=\${BETTER_AUTH_URL:-http://localhost:3000}
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
- NODE_ENV=production
|
||||||
|
|
||||||
|
# Trusted Origins for Better Auth
|
||||||
|
- TRUSTED_ORIGINS=\${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
|
# Version tracking
|
||||||
|
- IMAGE_VERSION=${imageTag}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Persist uploaded files and static content
|
||||||
|
- /DATA/AppData/euchre-camp/config:/app/public/uploads
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
euchre_camp_app_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
euchre-network:
|
||||||
|
driver: bridge
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate docker-compose.yml (production with PostgreSQL)
|
||||||
|
function generateDockerComposeYml(imageTag) {
|
||||||
|
return `# Production Docker Compose Configuration
|
||||||
|
# Usage: docker-compose up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ${REGISTRY}/${IMAGE_NAME}:${imageTag}
|
||||||
|
container_name: euchre-camp-app
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
# Database Configuration
|
||||||
|
- DATABASE_PROVIDER=postgresql
|
||||||
|
- DATABASE_URL=postgresql://euchre_camp:password@db:5432/euchre_camp
|
||||||
|
- DATABASE_SHADOW_URL=postgresql://euchre_camp:password@db:5432/euchre_camp_shadow
|
||||||
|
|
||||||
|
# Better Auth Configuration
|
||||||
|
- BETTER_AUTH_SECRET=\${BETTER_AUTH_SECRET:-change-this-secret-in-production}
|
||||||
|
- BETTER_AUTH_URL=\${BETTER_AUTH_URL:-http://localhost:3000}
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
- NODE_ENV=production
|
||||||
|
|
||||||
|
# Trusted Origins for Better Auth
|
||||||
|
- TRUSTED_ORIGINS=\${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
|
# Version tracking
|
||||||
|
- IMAGE_VERSION=${imageTag}
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: euchre-camp-db
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=euchre_camp
|
||||||
|
- POSTGRES_USER=euchre_camp
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
volumes:
|
||||||
|
- euchre_camp_db_data:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
euchre_camp_db_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
euchre-network:
|
||||||
|
driver: bridge
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate docker-compose.dev.yml (development with hot reload)
|
||||||
|
function generateDockerComposeDevYml(imageTag) {
|
||||||
|
return `# Development Docker Compose Configuration
|
||||||
|
# Usage: docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
args:
|
||||||
|
GIT_COMMIT: \${GIT_COMMIT:-dev}
|
||||||
|
container_name: euchre-camp-dev
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
# Database Configuration
|
||||||
|
- DATABASE_PROVIDER=postgresql
|
||||||
|
- DATABASE_URL=postgresql://euchre_camp:password@db:5432/euchre_camp
|
||||||
|
- DATABASE_SHADOW_URL=postgresql://euchre_camp:password@db:5432/euchre_camp_shadow
|
||||||
|
|
||||||
|
# Better Auth Configuration
|
||||||
|
- BETTER_AUTH_SECRET=\${BETTER_AUTH_SECRET:-dev-secret-change-in-production}
|
||||||
|
- BETTER_AUTH_URL=\${BETTER_AUTH_URL:-http://localhost:3000}
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
- NODE_ENV=development
|
||||||
|
|
||||||
|
# Trusted Origins for Better Auth
|
||||||
|
- TRUSTED_ORIGINS=\${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
|
||||||
|
|
||||||
|
# Version tracking
|
||||||
|
- IMAGE_VERSION=${imageTag}-dev
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Mount source code for hot reload
|
||||||
|
- .:/app
|
||||||
|
- /app/node_modules
|
||||||
|
- /app/.next
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: euchre-camp-db-dev
|
||||||
|
ports:
|
||||||
|
- "5433:5432" # Different port to avoid conflicts
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=euchre_camp
|
||||||
|
- POSTGRES_USER=euchre_camp
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
volumes:
|
||||||
|
- euchre_camp_db_data_dev:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- euchre-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
euchre_camp_db_data_dev:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
euchre-network:
|
||||||
|
driver: bridge
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
function main() {
|
||||||
|
const imageTag = getImageTag();
|
||||||
|
|
||||||
|
console.log('🔧 Generating Docker Compose files...\n');
|
||||||
|
console.log(`Registry: ${REGISTRY}`);
|
||||||
|
console.log(`Image: ${IMAGE_NAME}`);
|
||||||
|
console.log(`Tag: ${imageTag}\n`);
|
||||||
|
|
||||||
|
// Generate casaos configuration
|
||||||
|
const casaosContent = generateCasaosYml(imageTag);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, '..', 'docker-compose.casaos.yml'),
|
||||||
|
casaosContent
|
||||||
|
);
|
||||||
|
console.log('✓ Generated docker-compose.casaos.yml');
|
||||||
|
|
||||||
|
// Generate production configuration
|
||||||
|
const prodContent = generateDockerComposeYml(imageTag);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, '..', 'docker-compose.yml'),
|
||||||
|
prodContent
|
||||||
|
);
|
||||||
|
console.log('✓ Generated docker-compose.yml');
|
||||||
|
|
||||||
|
// Generate development configuration
|
||||||
|
const devContent = generateDockerComposeDevYml(imageTag);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, '..', 'docker-compose.dev.yml'),
|
||||||
|
devContent
|
||||||
|
);
|
||||||
|
console.log('✓ Generated docker-compose.dev.yml');
|
||||||
|
|
||||||
|
console.log('\n✅ Docker Compose files generated successfully');
|
||||||
|
console.log('\nNext steps:');
|
||||||
|
console.log(' 1. Review the generated files');
|
||||||
|
console.log(' 2. Commit changes to git');
|
||||||
|
console.log(' 3. Run: docker-compose build');
|
||||||
|
console.log(' 4. Run: docker-compose push');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run main function
|
||||||
|
main();
|
||||||
Reference in New Issue
Block a user