mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-23 11:46:28 +00:00
Merge pull request #130 from rcourtman/develop
Release v3.25.0 - Automated Stable Release System
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# Development files
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output
|
||||
*.test.js
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
docs/
|
||||
|
||||
# Scripts not needed in production
|
||||
scripts/take-screenshots.js
|
||||
playwright.config.js
|
||||
|
||||
# Temporary files
|
||||
temp-release/
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
# Test files
|
||||
test/
|
||||
tests/
|
||||
__tests__/
|
||||
*.test.js
|
||||
*.spec.js
|
||||
|
||||
# Data directories
|
||||
data/
|
||||
config/
|
||||
|
||||
# Development configs
|
||||
.eslintrc*
|
||||
.prettierrc*
|
||||
tailwind.config.js
|
||||
|
||||
# Package lock files - KEEP package-lock.json for npm ci
|
||||
# package-lock.json
|
||||
@@ -0,0 +1,142 @@
|
||||
name: Release Candidate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
create-rc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get version from package.json
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check if RC release needed
|
||||
id: check
|
||||
run: |
|
||||
# Get base version (remove any existing RC suffix)
|
||||
BASE_VERSION=$(echo "${{ steps.version.outputs.version }}" | sed 's/-rc[0-9]*$//')
|
||||
|
||||
# Get the latest RC tag for this base version
|
||||
LATEST_RC=$(git tag -l "v${BASE_VERSION}-rc*" | sort -V | tail -n1)
|
||||
|
||||
if [ -z "$LATEST_RC" ]; then
|
||||
echo "rc_number=1" >> $GITHUB_OUTPUT
|
||||
echo "create_release=true" >> $GITHUB_OUTPUT
|
||||
echo "base_version=$BASE_VERSION" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# Extract RC number and increment
|
||||
RC_NUM=$(echo $LATEST_RC | grep -o 'rc[0-9]*' | grep -o '[0-9]*')
|
||||
NEXT_RC=$((RC_NUM + 1))
|
||||
|
||||
# Check if there are new commits since last RC
|
||||
COMMITS_SINCE=$(git rev-list --count $LATEST_RC..HEAD)
|
||||
if [ "$COMMITS_SINCE" -gt 0 ]; then
|
||||
echo "rc_number=$NEXT_RC" >> $GITHUB_OUTPUT
|
||||
echo "create_release=true" >> $GITHUB_OUTPUT
|
||||
echo "base_version=$BASE_VERSION" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "create_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Update package.json with RC version
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}"
|
||||
echo "Updating package.json version to: $NEW_VERSION"
|
||||
|
||||
# Update package.json version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.version = '$NEW_VERSION';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
# Configure git
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
# Commit the version change
|
||||
git add package.json
|
||||
git commit -m "chore: bump version to $NEW_VERSION for RC release"
|
||||
git push origin develop
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
rcourtman/pulse:v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}
|
||||
rcourtman/pulse:rc
|
||||
|
||||
- name: Create Release Archive
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
run: |
|
||||
# Create release tarball
|
||||
tar -czf pulse-v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}.tar.gz \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.env \
|
||||
--exclude=data \
|
||||
--exclude=*.log \
|
||||
--exclude=temp-release \
|
||||
server src scripts package.json package-lock.json README.md LICENSE CHANGELOG.md \
|
||||
docker-compose.yml Dockerfile
|
||||
|
||||
- name: Create RC Release
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}
|
||||
name: v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: pulse-v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}.tar.gz
|
||||
body: |
|
||||
## 🧪 Release Candidate ${{ steps.check.outputs.rc_number }}
|
||||
|
||||
This is a release candidate for testing. Please report any issues you find.
|
||||
|
||||
### 📦 Installation Options
|
||||
|
||||
#### Script Install (Recommended)
|
||||
```bash
|
||||
wget -qO- https://github.com/rcourtman/Pulse/releases/download/v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}/install-pulse.sh | bash
|
||||
```
|
||||
|
||||
#### Docker
|
||||
```bash
|
||||
docker pull rcourtman/pulse:v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}
|
||||
# or use the rolling RC tag
|
||||
docker pull rcourtman/pulse:rc
|
||||
```
|
||||
|
||||
### ⚠️ Testing Notes
|
||||
- This is a pre-release for testing only
|
||||
- Not recommended for production use
|
||||
- Please report issues on GitHub
|
||||
- The `:rc` Docker tag always points to the latest RC
|
||||
@@ -0,0 +1,306 @@
|
||||
name: Automated Stable Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
detect-stable-release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should-release: ${{ steps.check.outputs.should-release }}
|
||||
suggested-version: ${{ steps.analyze.outputs.suggested-version }}
|
||||
bump-type: ${{ steps.analyze.outputs.bump-type }}
|
||||
reasoning: ${{ steps.analyze.outputs.reasoning }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Full history for commit analysis
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check if stable release should be triggered
|
||||
id: check
|
||||
run: |
|
||||
# Check if this is a push to main that came from develop
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
# Check if last commit message indicates merge from develop
|
||||
LAST_COMMIT=$(git log -1 --pretty=format:"%s")
|
||||
echo "Last commit: $LAST_COMMIT"
|
||||
|
||||
if echo "$LAST_COMMIT" | grep -E "(Merge.*develop|Merge pull request.*develop)"; then
|
||||
echo "should-release=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Detected merge from develop to main - triggering stable release"
|
||||
else
|
||||
echo "should-release=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ Not a develop merge - skipping stable release"
|
||||
fi
|
||||
elif [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "closed" ] && [ "${{ github.event.pull_request.merged }}" = "true" ]; then
|
||||
# PR was merged - check if it was from develop
|
||||
if [ "${{ github.event.pull_request.head.ref }}" = "develop" ]; then
|
||||
echo "should-release=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ PR from develop was merged - triggering stable release"
|
||||
else
|
||||
echo "should-release=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ PR not from develop - skipping stable release"
|
||||
fi
|
||||
else
|
||||
echo "should-release=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ Event does not trigger stable release"
|
||||
fi
|
||||
|
||||
- name: Analyze commits for version bump
|
||||
id: analyze
|
||||
if: steps.check.outputs.should-release == 'true'
|
||||
run: |
|
||||
# Use our enhanced versionUtils to analyze commits
|
||||
node -e "
|
||||
const { analyzeCommitsForVersionBump } = require('./server/versionUtils');
|
||||
const analysis = analyzeCommitsForVersionBump();
|
||||
|
||||
console.log('📊 Version Analysis:');
|
||||
console.log('Current stable version:', analysis.currentStableVersion);
|
||||
console.log('Suggested version:', analysis.suggestedVersion);
|
||||
console.log('Bump type:', analysis.bumpType);
|
||||
console.log('Reasoning:', analysis.reasoning);
|
||||
console.log('Total commits:', analysis.totalCommits);
|
||||
|
||||
console.log('\\n📝 Commit breakdown:');
|
||||
console.log('Breaking changes:', analysis.analysis.breaking.length);
|
||||
console.log('Features:', analysis.analysis.features.length);
|
||||
console.log('Fixes:', analysis.analysis.fixes.length);
|
||||
console.log('Other:', analysis.analysis.other.length);
|
||||
|
||||
// Set outputs for next job
|
||||
const fs = require('fs');
|
||||
const output = fs.readFileSync(process.env.GITHUB_OUTPUT, 'utf8');
|
||||
fs.writeFileSync(process.env.GITHUB_OUTPUT, output +
|
||||
'suggested-version=' + analysis.suggestedVersion + '\\n' +
|
||||
'bump-type=' + analysis.bumpType + '\\n' +
|
||||
'reasoning=' + analysis.reasoning + '\\n'
|
||||
);
|
||||
"
|
||||
|
||||
create-stable-release:
|
||||
needs: detect-stable-release
|
||||
if: needs.detect-stable-release.outputs.should-release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
- name: Update version and create release
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }}
|
||||
BUMP_TYPE: ${{ needs.detect-stable-release.outputs.bump-type }}
|
||||
REASONING: ${{ needs.detect-stable-release.outputs.reasoning }}
|
||||
run: |
|
||||
echo "🚀 Creating stable release v$NEW_VERSION"
|
||||
echo "📈 Version bump: $BUMP_TYPE"
|
||||
echo "💡 Reasoning: $REASONING"
|
||||
|
||||
# Update package.json to stable version
|
||||
npm version $NEW_VERSION --no-git-tag-version
|
||||
|
||||
# Run tests to ensure everything works
|
||||
echo "🧪 Running tests..."
|
||||
npm test || echo "⚠️ Tests failed but continuing with release"
|
||||
|
||||
# Build CSS
|
||||
echo "🎨 Building CSS..."
|
||||
npm run build:css || echo "⚠️ CSS build failed but continuing"
|
||||
|
||||
# Commit version bump
|
||||
git add package.json package-lock.json
|
||||
git commit -m "chore: release v$NEW_VERSION
|
||||
|
||||
$REASONING
|
||||
|
||||
This stable release includes all changes from the develop branch.
|
||||
|
||||
🤖 Generated by automated stable release workflow"
|
||||
|
||||
# Create and push tag
|
||||
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
|
||||
git push origin main
|
||||
git push origin "v$NEW_VERSION"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }}
|
||||
run: |
|
||||
# Get the previous stable tag
|
||||
PREV_TAG=$(git tag -l "v*" | grep -v "rc\|alpha\|beta" | sort -V | tail -2 | head -1)
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
PREV_TAG="v0.0.0"
|
||||
fi
|
||||
|
||||
echo "📝 Generating changelog from $PREV_TAG to v$NEW_VERSION"
|
||||
|
||||
# Analyze commits for changelog
|
||||
node -e "
|
||||
const { execSync } = require('child_process');
|
||||
const { analyzeCommitsForVersionBump } = require('./server/versionUtils');
|
||||
|
||||
try {
|
||||
const analysis = analyzeCommitsForVersionBump();
|
||||
|
||||
let changelog = '## What\\'s Changed\\n\\n';
|
||||
|
||||
if (analysis.analysis.breaking.length > 0) {
|
||||
changelog += '### 💥 Breaking Changes\\n';
|
||||
analysis.analysis.breaking.forEach(commit => {
|
||||
changelog += '- ' + commit + '\\n';
|
||||
});
|
||||
changelog += '\\n';
|
||||
}
|
||||
|
||||
if (analysis.analysis.features.length > 0) {
|
||||
changelog += '### ✨ New Features\\n';
|
||||
analysis.analysis.features.forEach(commit => {
|
||||
changelog += '- ' + commit + '\\n';
|
||||
});
|
||||
changelog += '\\n';
|
||||
}
|
||||
|
||||
if (analysis.analysis.fixes.length > 0) {
|
||||
changelog += '### 🐛 Bug Fixes\\n';
|
||||
analysis.analysis.fixes.forEach(commit => {
|
||||
changelog += '- ' + commit + '\\n';
|
||||
});
|
||||
changelog += '\\n';
|
||||
}
|
||||
|
||||
if (analysis.analysis.other.length > 0) {
|
||||
changelog += '### 🔧 Other Changes\\n';
|
||||
analysis.analysis.other.forEach(commit => {
|
||||
changelog += '- ' + commit + '\\n';
|
||||
});
|
||||
changelog += '\\n';
|
||||
}
|
||||
|
||||
changelog += '### 📊 Release Statistics\\n';
|
||||
changelog += '- **Version bump**: ' + analysis.bumpType + '\\n';
|
||||
changelog += '- **Total commits**: ' + analysis.totalCommits + '\\n';
|
||||
changelog += '- **Breaking changes**: ' + analysis.analysis.breaking.length + '\\n';
|
||||
changelog += '- **New features**: ' + analysis.analysis.features.length + '\\n';
|
||||
changelog += '- **Bug fixes**: ' + analysis.analysis.fixes.length + '\\n';
|
||||
changelog += '\\n';
|
||||
changelog += '### 🐳 Docker\\n';
|
||||
changelog += '\\`\\`\\`bash\\n';
|
||||
changelog += 'docker pull rcourtman/pulse:v' + analysis.suggestedVersion + '\\n';
|
||||
changelog += 'docker pull rcourtman/pulse:latest\\n';
|
||||
changelog += '\\`\\`\\`\\n';
|
||||
changelog += '\\n';
|
||||
changelog += '🤖 *This release was automatically created from the develop branch*';
|
||||
|
||||
console.log(changelog);
|
||||
|
||||
// Write to file for GitHub release
|
||||
require('fs').writeFileSync('CHANGELOG.md', changelog);
|
||||
} catch (error) {
|
||||
console.error('Error generating changelog:', error);
|
||||
require('fs').writeFileSync('CHANGELOG.md', 'Automated stable release\\n\\nSee commit history for details.');
|
||||
}
|
||||
"
|
||||
|
||||
- name: Build release tarball
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }}
|
||||
run: |
|
||||
echo "📦 Building release tarball..."
|
||||
|
||||
# Create tarball with all necessary files
|
||||
tar -czf "pulse-v$NEW_VERSION.tar.gz" \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.env \
|
||||
--exclude=data \
|
||||
--exclude=*.log \
|
||||
--exclude=temp-release \
|
||||
server src/public package.json package-lock.json README.md LICENSE CHANGELOG.md scripts/install-pulse.sh
|
||||
|
||||
ls -lh "pulse-v$NEW_VERSION.tar.gz"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }}
|
||||
run: |
|
||||
echo "🐳 Building multi-arch Docker images..."
|
||||
|
||||
# Build and push stable version tag and latest tag
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag "rcourtman/pulse:v$NEW_VERSION" \
|
||||
--tag "rcourtman/pulse:latest" \
|
||||
--push .
|
||||
|
||||
echo "✅ Docker images pushed successfully"
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "📝 Creating GitHub release..."
|
||||
|
||||
# Create the release
|
||||
gh release create "v$NEW_VERSION" \
|
||||
--title "Release v$NEW_VERSION" \
|
||||
--notes-file CHANGELOG.md \
|
||||
"pulse-v$NEW_VERSION.tar.gz"
|
||||
|
||||
echo "✅ Release v$NEW_VERSION created successfully!"
|
||||
echo "🔗 https://github.com/${{ github.repository }}/releases/tag/v$NEW_VERSION"
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
rm -f CHANGELOG.md pulse-v*.tar.gz
|
||||
+49
-7
@@ -2,6 +2,14 @@
|
||||
|
||||
Thank you for your interest in contributing to Pulse! We appreciate your help. Here are some guidelines to follow:
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
Pulse uses a two-branch workflow:
|
||||
- **`main`** - Stable releases only (protected)
|
||||
- **`develop`** - Daily development work (default working branch)
|
||||
|
||||
All contributions should target the `develop` branch.
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
- Please ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/rcourtman/Pulse/issues).
|
||||
@@ -16,11 +24,45 @@ Thank you for your interest in contributing to Pulse! We appreciate your help. H
|
||||
|
||||
## Pull Requests
|
||||
|
||||
- Fork the repository and create your branch from `main`.
|
||||
- Ensure your code adheres to the project's existing style.
|
||||
- If you've added code that should be tested, add tests.
|
||||
- Ensure the test suite passes (if applicable).
|
||||
- Make sure your code lints (if linters are set up).
|
||||
- Issue that pull request!
|
||||
### Getting Started
|
||||
1. **Fork the repository** and clone your fork locally
|
||||
2. **Create your branch from `develop`**: `git checkout -b feature/your-feature develop`
|
||||
3. **Set up development environment**:
|
||||
```bash
|
||||
npm install
|
||||
npm run build:css
|
||||
npm run dev # Starts development server with hot reload
|
||||
```
|
||||
|
||||
We will review your pull request and provide feedback. Thank you for your contribution!
|
||||
### Development Workflow
|
||||
- **Local testing**: Your changes will show with dynamic RC versions (e.g., "3.24.0-rc5")
|
||||
- **Version display**: RC versions increment automatically with each commit
|
||||
- **No version management needed**: The system handles versioning automatically
|
||||
|
||||
### Before Submitting
|
||||
- Ensure your code adheres to the project's existing style
|
||||
- If you've added code that should be tested, add tests
|
||||
- Ensure the test suite passes: `npm test`
|
||||
- Make sure your code lints (if linters are set up)
|
||||
- Test your changes thoroughly
|
||||
|
||||
### Submitting Your Pull Request
|
||||
1. **Push to your fork**: `git push origin feature/your-feature`
|
||||
2. **Create a pull request** targeting the `develop` branch
|
||||
3. **Provide a clear description** of what your changes do
|
||||
4. **Reference any related issues** in your PR description
|
||||
|
||||
### After Submission
|
||||
- We will review your pull request and provide feedback
|
||||
- Your changes will automatically get RC releases for testing when merged to `develop`
|
||||
- Once tested and approved, changes will be included in the next stable release
|
||||
|
||||
## Release Candidate Testing
|
||||
|
||||
When your PR is merged to `develop`:
|
||||
- **Automatic RC creation**: A new RC release is created automatically
|
||||
- **Docker images**: Multi-arch Docker images are built and published
|
||||
- **Version tracking**: RC versions increment automatically (rc1, rc2, rc3...)
|
||||
- **Testing opportunity**: Community can test your changes before stable release
|
||||
|
||||
Thank you for your contribution!
|
||||
@@ -0,0 +1,69 @@
|
||||
# Pulse Development Workflow
|
||||
|
||||
## Branch Structure
|
||||
|
||||
- **`main`** - Stable releases only. Users install from here.
|
||||
- **`develop`** - Daily development work. RC releases are created from here.
|
||||
|
||||
## Daily Workflow
|
||||
|
||||
### 1. Always work on develop branch
|
||||
```bash
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
```
|
||||
|
||||
### 2. Make your changes and commit frequently
|
||||
```bash
|
||||
# Edit files...
|
||||
git add .
|
||||
git commit -m "fix: your change description"
|
||||
git push
|
||||
```
|
||||
|
||||
### 3. RC releases are automatic
|
||||
- Every push to `develop` creates a new RC release (if there are changes)
|
||||
- Share RC version with users who need to test: `v3.24.0-rc1`, `v3.24.0-rc2`, etc.
|
||||
|
||||
### 4. Creating a stable release
|
||||
When RC testing is complete and you're ready for a stable release:
|
||||
|
||||
```bash
|
||||
# 1. Ensure develop is up to date
|
||||
git checkout develop
|
||||
git pull
|
||||
|
||||
# 2. Merge to main
|
||||
git checkout main
|
||||
git merge develop
|
||||
|
||||
# 3. Update version in package.json (remove any -rc suffix)
|
||||
# Edit package.json to bump version if needed
|
||||
|
||||
# 4. Commit and tag
|
||||
git add package.json
|
||||
git commit -m "chore: release v3.25.0"
|
||||
git tag v3.25.0
|
||||
git push origin main --tags
|
||||
|
||||
# 5. Go back to develop for daily work
|
||||
git checkout develop
|
||||
git merge main # Keep develop in sync
|
||||
git push
|
||||
```
|
||||
|
||||
## For Users Reporting Issues
|
||||
|
||||
1. User reports issue on v3.24.0
|
||||
2. You fix it on `develop` branch
|
||||
3. Automatic RC is created (e.g., v3.24.1-rc1)
|
||||
4. Ask user to test: "Can you test with v3.24.1-rc1?"
|
||||
5. If good, merge to main for stable v3.24.1
|
||||
|
||||
## Key Benefits
|
||||
|
||||
- ✅ Stable users stay on tested versions
|
||||
- ✅ You can commit frequently without affecting stable users
|
||||
- ✅ Easy testing workflow with automatic RC builds
|
||||
- ✅ Professional release management
|
||||
- ✅ Clear separation between development and production
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# ---- Builder Stage ----
|
||||
FROM node:18-alpine AS builder
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -24,7 +24,7 @@ RUN npm run build:css
|
||||
RUN npm prune --production
|
||||
|
||||
# ---- Runner Stage ----
|
||||
FROM node:18-alpine
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
@@ -709,20 +709,54 @@ To update a tarball installation:
|
||||
|
||||
If running from source code:
|
||||
|
||||
**For stable releases (production):**
|
||||
```bash
|
||||
cd /path/to/pulse
|
||||
git checkout main
|
||||
git pull origin main
|
||||
npm install
|
||||
npm run build:css
|
||||
npm run start # or your preferred restart method
|
||||
```
|
||||
|
||||
**Note:** The development setup only requires npm install in the root directory, not in a separate server directory.
|
||||
**For development/RC versions:**
|
||||
```bash
|
||||
cd /path/to/pulse
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
npm install
|
||||
npm run build:css
|
||||
npm run start # or your preferred restart method
|
||||
```
|
||||
|
||||
**Note:**
|
||||
- The development setup only requires npm install in the root directory, not in a separate server directory.
|
||||
- The `develop` branch shows dynamic RC versions (e.g., "3.24.0-rc5") that auto-increment with each commit.
|
||||
- The `main` branch contains stable releases only.
|
||||
|
||||
## 📝 Contributing
|
||||
|
||||
Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md).
|
||||
|
||||
### Development Workflow
|
||||
|
||||
**Branch Strategy:**
|
||||
- `main` - Stable releases only (protected)
|
||||
- `develop` - Daily development work (default working branch)
|
||||
|
||||
**Release Candidate (RC) Automation:**
|
||||
- Every commit to `develop` automatically creates an RC release
|
||||
- RC versions increment automatically: `v3.24.0-rc1`, `v3.24.0-rc2`, etc.
|
||||
- Docker images are built for both `amd64` and `arm64` architectures
|
||||
- Local development shows dynamic RC versions that update with each commit
|
||||
|
||||
**Making Contributions:**
|
||||
1. Fork the repository
|
||||
2. Create a feature branch from `develop`
|
||||
3. Make your changes
|
||||
4. Test locally (version will show as RC automatically)
|
||||
5. Submit a pull request to `develop`
|
||||
|
||||
## 🔒 Privacy
|
||||
|
||||
* **No Data Collection:** Pulse does not collect or transmit any telemetry or user data externally.
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
# Release Guide for Pulse
|
||||
|
||||
A comprehensive guide for handling commits, releases, and development workflow.
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
**IMPORTANT**: Pulse uses a two-branch workflow:
|
||||
- `main` - Stable releases only (protected)
|
||||
- `develop` - Daily development work (default working branch)
|
||||
|
||||
Always check current branch: `git branch --show-current`
|
||||
|
||||
## Versioning System
|
||||
|
||||
**Dynamic RC Versioning:**
|
||||
- **Develop branch**: Automatically calculates RC versions from git commits (e.g., "3.24.0-rc5")
|
||||
- **Main branch**: Uses stable package.json version (e.g., "3.24.0")
|
||||
- **Package.json**: Always contains base stable version, never RC versions
|
||||
- **Local display**: Shows dynamic RC version when on develop branch
|
||||
- **Automatic sync**: RC versions increment with each commit, no manual management needed
|
||||
|
||||
## Pre-flight Checklist
|
||||
|
||||
Before starting any release process, verify:
|
||||
- [ ] Clean working directory: `git status`
|
||||
- [ ] Docker logged in: `docker login` (check with `docker info | grep Username`)
|
||||
- [ ] GitHub CLI authenticated: `gh auth status`
|
||||
- [ ] Docker buildx available: `docker buildx ls || docker buildx create --name mybuilder --use`
|
||||
- [ ] All tests passing: `npm test`
|
||||
|
||||
## Prerequisites Check
|
||||
|
||||
First run (only if user mentions "clean context" or you see errors):
|
||||
|
||||
```bash
|
||||
# Check required tools
|
||||
which git || echo "ERROR: git not installed"
|
||||
which node || echo "ERROR: Node.js not installed"
|
||||
which npm || echo "ERROR: npm not installed"
|
||||
which gh || echo "WARNING: GitHub CLI not installed (needed for releases)"
|
||||
which docker || echo "WARNING: Docker not installed (needed for Docker releases)"
|
||||
|
||||
# If in a fresh clone, run:
|
||||
npm install
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
When user says:
|
||||
- **"commit this"** → Check branch first! Then go to [Commit Process](#commit-process)
|
||||
- **"create a release"** → Use [Automated Stable Release Process](#automated-stable-release-process-new) via PR
|
||||
- **"create an RC"** or **"release candidate"** → Automatic from `develop` push (see below)
|
||||
- **"what changed?"** → `git status -s` and `git diff --stat`
|
||||
- **"run tests"** → `npm test` (also check for lint/typecheck scripts)
|
||||
- **"merge to main"** → Use [Automated Stable Release Process](#automated-stable-release-process-new)
|
||||
- **"manual release"** → Only for hotfixes → [Manual Release Process](#manual-release-process-legacy)
|
||||
|
||||
## Commit Process
|
||||
|
||||
### 1. Check Branch & Summarize
|
||||
```bash
|
||||
# CRITICAL: Check current branch
|
||||
git branch --show-current
|
||||
# If not on develop: git checkout develop
|
||||
git status -s
|
||||
git diff --stat
|
||||
```
|
||||
Tell user: "You're on [branch]. You've modified X files. Main changes: [brief summary]"
|
||||
|
||||
### 2. Stage & Commit
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "<type>: <description>"
|
||||
```
|
||||
|
||||
**Commit types**: `feat:` (new feature), `fix:` (bug fix), `docs:`, `chore:`, `refactor:`
|
||||
|
||||
### 3. Push
|
||||
```bash
|
||||
# For daily work (most common)
|
||||
git push origin develop # This triggers automatic RC release!
|
||||
|
||||
# For hotfixes on main (rare)
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**If rejected**:
|
||||
```bash
|
||||
git pull --rebase origin develop # or main
|
||||
git push origin develop # or main
|
||||
```
|
||||
|
||||
## Pre-Release Process (Automatic)
|
||||
|
||||
**NEW**: RC releases are now fully automated when you push to `develop` branch!
|
||||
|
||||
### How It Works
|
||||
1. Make changes on `develop` branch
|
||||
2. Commit and push: `git push origin develop`
|
||||
3. GitHub Actions automatically:
|
||||
- Calculates new RC version (increments from last RC)
|
||||
- Updates package.json with new RC version
|
||||
- Commits version bump back to develop
|
||||
- Creates RC release with proper versioning
|
||||
- Builds multi-arch Docker images
|
||||
|
||||
### Dynamic Local Versioning
|
||||
- **Local develop branch**: Shows dynamic RC versions calculated from git commits
|
||||
- **Example**: If you have 5 commits since v3.24.0, version shows as "3.24.0-rc5"
|
||||
- **Auto-increment**: Each new commit increments the RC number instantly
|
||||
- **No manual version management**: Version automatically stays in sync
|
||||
|
||||
### Manual RC Release (if needed)
|
||||
Only use if automatic process fails or for special cases:
|
||||
```bash
|
||||
# On develop branch
|
||||
git tag v3.24.0-rc1
|
||||
git push origin v3.24.0-rc1
|
||||
gh release create v3.24.0-rc1 --title "v3.24.0-rc1" --prerelease --generate-release-notes
|
||||
```
|
||||
|
||||
### Docker Images for RC
|
||||
The GitHub Action handles Docker builds automatically, including:
|
||||
- Multi-arch builds (amd64, arm64)
|
||||
- Tagged with RC version
|
||||
- Does NOT update `:latest` tag
|
||||
- Rolling `:rc` tag always points to latest RC
|
||||
|
||||
## Automated Stable Release Process (NEW!)
|
||||
|
||||
**IMPORTANT**: Stable releases are now fully automated when you merge develop to main!
|
||||
|
||||
### How It Works
|
||||
1. When develop branch is ready for stable release, create a pull request from develop to main
|
||||
2. Once the PR is approved and merged, GitHub Actions automatically:
|
||||
- Analyzes all commits since the last stable release using semantic versioning
|
||||
- Determines appropriate version bump (major/minor/patch)
|
||||
- Updates package.json with the new stable version
|
||||
- Creates a git tag and GitHub release
|
||||
- Builds multi-arch Docker images with both version tag and `:latest`
|
||||
- Generates comprehensive changelog from commit analysis
|
||||
|
||||
### Semantic Commit Analysis
|
||||
The automation analyzes commit messages to determine version bumps:
|
||||
- **Major bump**: Commits with `BREAKING CHANGE` or `!:` in message
|
||||
- **Minor bump**: Commits starting with `feat:` or `feature:`
|
||||
- **Patch bump**: Commits starting with `fix:` or `bugfix:`
|
||||
- **Patch bump**: All other commits (chore, docs, refactor, etc.)
|
||||
|
||||
### Creating a Stable Release
|
||||
```bash
|
||||
# 1. Ensure develop is ready for release
|
||||
git checkout develop
|
||||
git pull
|
||||
|
||||
# 2. Create pull request to main (preferred method)
|
||||
gh pr create --base main --head develop --title "Release v3.25.0" --body "Ready for stable release
|
||||
|
||||
This PR includes:
|
||||
- 8 new features
|
||||
- 5 bug fixes
|
||||
- Various improvements
|
||||
|
||||
The automated workflow will analyze commits and create the appropriate version bump."
|
||||
|
||||
# 3. Get PR approved and merge it
|
||||
# 4. GitHub Actions automatically handles the rest!
|
||||
```
|
||||
|
||||
### Alternative: Direct Merge (if needed)
|
||||
```bash
|
||||
# 1. Ensure develop is up to date
|
||||
git checkout develop
|
||||
git pull
|
||||
|
||||
# 2. Switch to main and merge
|
||||
git checkout main
|
||||
git pull
|
||||
git merge develop
|
||||
|
||||
# 3. Push (this triggers automated stable release)
|
||||
git push origin main
|
||||
|
||||
# GitHub Actions will detect the merge and create the stable release automatically
|
||||
```
|
||||
|
||||
## Manual Release Process (Legacy)
|
||||
|
||||
**NOTE**: Manual releases are now rarely needed since the automated stable release process handles most cases. Use this only for hotfixes or special circumstances.
|
||||
|
||||
**IMPORTANT**: Manual releases must be done from `main` branch only!
|
||||
|
||||
### 1. Pre-release Verification
|
||||
```bash
|
||||
# Verify on main branch
|
||||
git branch --show-current # Must show "main"
|
||||
# If not: echo "ERROR: Must be on main branch for releases!"
|
||||
|
||||
# Verify pre-flight checklist items
|
||||
git status # Should be clean
|
||||
docker info | grep Username || echo "WARNING: Not logged into Docker Hub"
|
||||
gh auth status || echo "ERROR: GitHub CLI not authenticated. Run: gh auth login"
|
||||
docker buildx ls || echo "WARNING: Docker buildx not available"
|
||||
```
|
||||
|
||||
### 2. Analyze Changes
|
||||
```bash
|
||||
# Get current version (should be stable base version like "3.24.0")
|
||||
node -p "require('./package.json').version"
|
||||
|
||||
# Get dynamic version from API if running locally
|
||||
curl -s http://localhost:7655/api/version | grep -o '"version":"[^"]*"' | cut -d'"' -f4
|
||||
|
||||
# Analyze commits since last stable tag
|
||||
git log $(git describe --tags --abbrev=0)..HEAD --oneline
|
||||
```
|
||||
|
||||
### 3. Determine Version
|
||||
- Breaking changes → Major (1.0.0 → 2.0.0)
|
||||
- New features → Minor (1.0.0 → 1.1.0)
|
||||
- Bug fixes → Patch (1.0.0 → 1.0.1)
|
||||
- Testing release → RC: X.Y.Z-rc.N (release candidate)
|
||||
|
||||
Ask user: "Based on changes, I suggest version X.Y.Z. OK?"
|
||||
|
||||
### 4. Update Version & Run Tests
|
||||
|
||||
**IMPORTANT**: For stable releases, package.json should contain the base stable version (e.g., "3.24.0"), not RC versions. The dynamic versioning system handles RC display automatically.
|
||||
|
||||
```bash
|
||||
# Update package.json and package-lock.json to stable version
|
||||
npm version X.Y.Z --no-git-tag-version
|
||||
|
||||
# Alternatively, if npm version fails:
|
||||
node -e "const p=require('./package.json');p.version='X.Y.Z';require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2)+'\n')"
|
||||
npm install # This updates package-lock.json
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Check for deprecation warnings
|
||||
npm audit || echo "Check audit results - warnings don't block release"
|
||||
|
||||
# Build CSS
|
||||
npm run build:css
|
||||
|
||||
# Run linting if available
|
||||
npm run lint || echo "No lint script found"
|
||||
|
||||
# Run type checking if available
|
||||
npm run typecheck || echo "No typecheck script found"
|
||||
```
|
||||
|
||||
**If tests fail**: Try to fix or ask user how to proceed
|
||||
|
||||
### 5. Commit & Tag
|
||||
```bash
|
||||
git add package.json package-lock.json
|
||||
git commit -m "chore: release vX.Y.Z"
|
||||
git push origin main
|
||||
|
||||
git tag -a vX.Y.Z -m "Release vX.Y.Z"
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
### 6. Build Release Tarball & Create Changelog
|
||||
|
||||
**IMPORTANT CHANGELOG RULES**:
|
||||
- Analyze ALL changes since last release: `git diff v[last]..v[current] --stat`
|
||||
- Focus ONLY on user-visible changes that matter to users
|
||||
- DO NOT include: dependency updates, docs changes, gitignore, dev tooling, Docker file restoration
|
||||
- DO NOT repeat version number in changelog (GitHub already shows it)
|
||||
- Use `echo` instead of heredoc to avoid EOF issues
|
||||
- Look for big picture changes: major refactors, feature removals, new functionality
|
||||
|
||||
```bash
|
||||
# First understand the scope of changes
|
||||
git diff $(git describe --tags --abbrev=0)..HEAD --stat
|
||||
echo "Total changes: $(git diff $(git describe --tags --abbrev=0)..HEAD --stat | tail -1)"
|
||||
|
||||
# Create user-focused changelog - use echo, NOT heredoc
|
||||
echo "## Changes
|
||||
- [List only user-visible changes]
|
||||
- [Major feature additions/removals]
|
||||
- [Breaking changes]
|
||||
|
||||
This release [brief summary of main theme]." > CHANGELOG_TEMP.md
|
||||
|
||||
# Build tarball
|
||||
echo "X.Y.Z" | ./scripts/create-release.sh
|
||||
|
||||
# If create-release.sh fails, manual fallback:
|
||||
if [ ! -f pulse-vX.Y.Z.tar.gz ]; then
|
||||
echo "Release script failed, creating tarball manually..."
|
||||
tar -czf pulse-vX.Y.Z.tar.gz \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.env \
|
||||
--exclude=data \
|
||||
--exclude=*.log \
|
||||
server src/public package.json package-lock.json README.md LICENSE CHANGELOG.md
|
||||
fi
|
||||
```
|
||||
|
||||
### 7. Build and Test Docker Images
|
||||
```bash
|
||||
# Ensure buildx is available
|
||||
docker buildx ls || docker buildx create --name mybuilder --use
|
||||
|
||||
# IMPORTANT: First check what port the app uses (usually 7655, not 3000!)
|
||||
grep "const PORT" server/index.js || grep "Server listening on port" server/index.js
|
||||
|
||||
# Build single-platform image for testing
|
||||
docker build -t rcourtman/pulse:vX.Y.Z .
|
||||
|
||||
# Test with real .env file if available (RECOMMENDED)
|
||||
if [ -f .env ]; then
|
||||
echo "Testing with real .env configuration..."
|
||||
docker run --rm -d --name pulse-test --env-file .env -p 7656:7655 rcourtman/pulse:vX.Y.Z
|
||||
else
|
||||
echo "Testing with minimal config (less thorough)..."
|
||||
docker run --rm -d --name pulse-test \
|
||||
-e PROXMOX_HOST=test.example.com \
|
||||
-e PROXMOX_TOKEN_ID=test@pam!test \
|
||||
-e PROXMOX_TOKEN_SECRET=test-secret \
|
||||
-p 7656:7655 rcourtman/pulse:vX.Y.Z
|
||||
fi
|
||||
|
||||
sleep 5 # Give it time to start
|
||||
|
||||
# Check if container is running
|
||||
docker ps | grep pulse-test || (echo "Container failed to start!"; docker logs pulse-test; exit 1)
|
||||
|
||||
# Check container logs for successful startup
|
||||
docker logs pulse-test 2>&1 | grep "Server listening on port" || (echo "Server didn't start!"; docker logs pulse-test; exit 1)
|
||||
|
||||
# If using real config, verify data collection
|
||||
if [ -f .env ]; then
|
||||
docker logs pulse-test 2>&1 | grep -E "(nodes:|VMs:|CTs:)" && echo "✓ Data collection working"
|
||||
fi
|
||||
|
||||
# Try to access the main page
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:7656/ | grep -q "200" && echo "✓ Web interface accessible"
|
||||
|
||||
# Check for API health (if endpoint exists)
|
||||
curl -s http://localhost:7656/api/health 2>/dev/null | grep -q "ok" && echo "✓ API health check passed" || echo "Note: API health endpoint may not exist in this version"
|
||||
|
||||
# Check logs for critical errors (ignore connection errors if using test config)
|
||||
docker logs pulse-test 2>&1 | grep -i error | grep -v "ENOTFOUND\|ECONNREFUSED\|getaddrinfo" | head -5
|
||||
|
||||
# Stop test container
|
||||
docker stop pulse-test
|
||||
echo "✓ Docker test completed successfully"
|
||||
|
||||
# If tests pass, build and push multi-arch images
|
||||
echo "Building and pushing multi-arch images..."
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t rcourtman/pulse:vX.Y.Z -t rcourtman/pulse:latest --push .
|
||||
|
||||
# Verify images were pushed
|
||||
docker manifest inspect rcourtman/pulse:vX.Y.Z || echo "WARNING: Failed to verify Docker push"
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- The app runs on port 7655, not 3000!
|
||||
- Always test with real .env file when available for better validation
|
||||
- Connection errors are expected with test config, but not with real config
|
||||
- Main page (/) is more reliable to test than /api/health endpoint (which may not exist)
|
||||
|
||||
### 8. Create GitHub Release
|
||||
```bash
|
||||
# Verify GitHub CLI is authenticated
|
||||
gh auth status || (echo "ERROR: Must authenticate with GitHub CLI first: gh auth login"; exit 1)
|
||||
|
||||
# Create GitHub release with tarball
|
||||
gh release create vX.Y.Z --title "Release vX.Y.Z" --notes-file CHANGELOG_TEMP.md pulse-vX.Y.Z.tar.gz
|
||||
|
||||
# Clean up temporary files
|
||||
rm -f CHANGELOG_TEMP.md pulse-vX.Y.Z.tar.gz
|
||||
```
|
||||
|
||||
### 9. Post-Release
|
||||
```bash
|
||||
# Verify the release was created
|
||||
gh release view vX.Y.Z
|
||||
|
||||
# Check Docker Hub for new images
|
||||
docker manifest inspect rcourtman/pulse:vX.Y.Z
|
||||
docker manifest inspect rcourtman/pulse:latest
|
||||
|
||||
echo "✅ Release vX.Y.Z completed successfully!"
|
||||
echo "🐳 Docker: docker pull rcourtman/pulse:vX.Y.Z"
|
||||
echo "📦 GitHub: https://github.com/rcourtman/pulse/releases/tag/vX.Y.Z"
|
||||
```
|
||||
|
||||
## Error Recovery
|
||||
|
||||
### If Docker push fails:
|
||||
```bash
|
||||
# Re-authenticate
|
||||
docker login
|
||||
# Retry the build and push
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t rcourtman/pulse:vX.Y.Z -t rcourtman/pulse:latest --push .
|
||||
```
|
||||
|
||||
### If GitHub release fails:
|
||||
```bash
|
||||
# Delete failed release if it was created
|
||||
gh release delete vX.Y.Z --yes
|
||||
# Try again
|
||||
gh release create vX.Y.Z --title "Release vX.Y.Z" --notes-file CHANGELOG_TEMP.md pulse-vX.Y.Z.tar.gz
|
||||
```
|
||||
|
||||
### If git push is rejected:
|
||||
```bash
|
||||
git pull --rebase origin main
|
||||
# Resolve any conflicts, then:
|
||||
git push origin main
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse",
|
||||
"version": "3.24.0",
|
||||
"version": "3.24.0-rc6",
|
||||
"description": "A lightweight monitoring application for Proxmox VE.",
|
||||
"main": "server/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -415,6 +415,17 @@ download_and_extract_tarball() {
|
||||
|
||||
# Get current installed version
|
||||
get_current_version() {
|
||||
# Try to get version from the running service API first
|
||||
if systemctl is-active --quiet pulse.service 2>/dev/null; then
|
||||
local api_version
|
||||
api_version=$(curl -s -m 5 "http://localhost:7655/api/version" 2>/dev/null | grep -o '"version":"[^"]*"' | cut -d'"' -f4 2>/dev/null)
|
||||
if [ -n "$api_version" ] && [ "$api_version" != "null" ]; then
|
||||
echo "$api_version"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback to package.json if API is not available
|
||||
if [ -f "$PULSE_DIR/package.json" ]; then
|
||||
grep '"version"' "$PULSE_DIR/package.json" | head -1 | sed -E 's/.*"version": "([^"]+)".*/\1/'
|
||||
fi
|
||||
|
||||
+11
-3
@@ -729,8 +729,13 @@ app.get('/api/alerts/status', (req, res) => {
|
||||
// Version API endpoint
|
||||
app.get('/api/version', async (req, res) => {
|
||||
try {
|
||||
const packageJson = require('../package.json');
|
||||
const currentVersion = packageJson.version || 'N/A';
|
||||
const { getCurrentVersionInfo } = require('./versionUtils');
|
||||
|
||||
// Get version info using centralized logic
|
||||
const versionInfo = getCurrentVersionInfo();
|
||||
const currentVersion = versionInfo.version;
|
||||
const gitBranch = versionInfo.gitBranch;
|
||||
const isDevelopment = versionInfo.isDevelopment;
|
||||
|
||||
let latestVersion = currentVersion;
|
||||
let updateAvailable = false;
|
||||
@@ -748,7 +753,9 @@ app.get('/api/version', async (req, res) => {
|
||||
res.json({
|
||||
version: currentVersion,
|
||||
latestVersion: latestVersion,
|
||||
updateAvailable: updateAvailable
|
||||
updateAvailable: updateAvailable,
|
||||
gitBranch: gitBranch,
|
||||
isDevelopment: isDevelopment
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[Version API] Error in version endpoint:", error);
|
||||
@@ -1746,6 +1753,7 @@ async function startServer() {
|
||||
path.join(__dirname, '../src/public'), // Frontend files
|
||||
path.join(__dirname, './'), // Server files
|
||||
path.join(__dirname, '../data'), // Config files
|
||||
path.join(__dirname, '../package.json'), // Package.json for auto-restart on version updates
|
||||
];
|
||||
|
||||
devWatcher = chokidar.watch(watchPaths, {
|
||||
|
||||
+76
-18
@@ -5,6 +5,7 @@ const path = require('path');
|
||||
const { exec, spawn } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const { getUpdateChannelPreference } = require('./configLoader');
|
||||
const { getCurrentVersion } = require('./versionUtils');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
class UpdateManager {
|
||||
@@ -61,20 +62,23 @@ class UpdateManager {
|
||||
* @param {string} channelOverride - Optional channel override ('stable' or 'rc')
|
||||
*/
|
||||
async checkForUpdates(channelOverride = null) {
|
||||
// Get the current version using centralized logic (outside try block for error handling)
|
||||
const dynamicCurrentVersion = getCurrentVersion();
|
||||
|
||||
// Use override channel if provided and valid, otherwise use config
|
||||
const configChannel = getUpdateChannelPreference();
|
||||
const updateChannel = (channelOverride && ['stable', 'rc'].includes(channelOverride))
|
||||
? channelOverride
|
||||
: configChannel;
|
||||
let channelDescription = '';
|
||||
|
||||
try {
|
||||
console.log('[UpdateManager] Checking for updates...');
|
||||
|
||||
// Use override channel if provided and valid, otherwise use config
|
||||
const configChannel = getUpdateChannelPreference();
|
||||
const updateChannel = (channelOverride && ['stable', 'rc'].includes(channelOverride))
|
||||
? channelOverride
|
||||
: configChannel;
|
||||
|
||||
if (channelOverride && channelOverride !== configChannel) {
|
||||
console.log(`[UpdateManager] Using channel override: ${channelOverride} (config: ${configChannel})`);
|
||||
}
|
||||
let response;
|
||||
let channelDescription = '';
|
||||
|
||||
if (updateChannel === 'stable') {
|
||||
// Stable channel: only check latest stable release
|
||||
@@ -113,17 +117,20 @@ class UpdateManager {
|
||||
const releaseVersion = release.tag_name.replace('v', '');
|
||||
const releaseIsRC = this.isReleaseCandidate(releaseVersion);
|
||||
|
||||
if (releaseIsRC && semver.gt(releaseVersion, this.currentVersion)) {
|
||||
latestRelease = release;
|
||||
break;
|
||||
if (releaseIsRC) {
|
||||
// For RC channel, show the latest RC regardless of current version
|
||||
// This allows showing RC versions even if current is stable
|
||||
if (!latestRelease || semver.gt(releaseVersion, latestRelease.tag_name.replace('v', ''))) {
|
||||
latestRelease = release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!latestRelease) {
|
||||
// No newer RC version found
|
||||
const updateInfo = {
|
||||
currentVersion: this.currentVersion,
|
||||
latestVersion: this.currentVersion,
|
||||
currentVersion: dynamicCurrentVersion,
|
||||
latestVersion: dynamicCurrentVersion,
|
||||
updateAvailable: false,
|
||||
isDocker: this.isDockerEnvironment(),
|
||||
releaseNotes: 'No newer RC version available',
|
||||
@@ -132,7 +139,7 @@ class UpdateManager {
|
||||
assets: [],
|
||||
updateChannel: channelDescription
|
||||
};
|
||||
console.log(`[UpdateManager] No RC updates available: ${this.currentVersion}`);
|
||||
console.log(`[UpdateManager] No RC updates available: ${dynamicCurrentVersion}`);
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
@@ -142,21 +149,24 @@ class UpdateManager {
|
||||
const latestVersion = response.data.tag_name.replace('v', '');
|
||||
|
||||
// For stable channel, also consider "downgrade" from RC as an update
|
||||
const isCurrentRC = this.isReleaseCandidate();
|
||||
const isCurrentRC = this.isReleaseCandidate(dynamicCurrentVersion);
|
||||
const isStableChannel = updateChannel === 'stable';
|
||||
const isDifferentVersion = latestVersion !== this.currentVersion;
|
||||
const isDifferentVersion = latestVersion !== dynamicCurrentVersion;
|
||||
|
||||
let updateAvailable;
|
||||
if (isStableChannel && isCurrentRC && isDifferentVersion) {
|
||||
// Offer stable version even if it's older than current RC
|
||||
updateAvailable = true;
|
||||
} else if (updateChannel === 'rc') {
|
||||
// For RC channel, show update if versions differ or if latest is newer
|
||||
updateAvailable = isDifferentVersion || semver.gt(latestVersion, dynamicCurrentVersion);
|
||||
} else {
|
||||
// Normal case: only newer versions
|
||||
updateAvailable = semver.gt(latestVersion, this.currentVersion);
|
||||
updateAvailable = semver.gt(latestVersion, dynamicCurrentVersion);
|
||||
}
|
||||
|
||||
const updateInfo = {
|
||||
currentVersion: this.currentVersion,
|
||||
currentVersion: dynamicCurrentVersion,
|
||||
latestVersion,
|
||||
updateAvailable,
|
||||
isDocker: this.isDockerEnvironment(),
|
||||
@@ -171,11 +181,59 @@ class UpdateManager {
|
||||
}))
|
||||
};
|
||||
|
||||
console.log(`[UpdateManager] Current version: ${this.currentVersion}, Latest version: ${latestVersion}, Channel: ${channelDescription}, Docker: ${updateInfo.isDocker}`);
|
||||
console.log(`[UpdateManager] Current version: ${dynamicCurrentVersion}, Latest version: ${latestVersion}, Channel: ${channelDescription}, Docker: ${updateInfo.isDocker}`);
|
||||
return updateInfo;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[UpdateManager] Error checking for updates:', error.message);
|
||||
|
||||
// Handle different types of errors gracefully
|
||||
if (error.response?.status === 403) {
|
||||
// GitHub API rate limit exceeded
|
||||
console.warn('[UpdateManager] GitHub API rate limit exceeded, returning current version info');
|
||||
return {
|
||||
currentVersion: dynamicCurrentVersion,
|
||||
latestVersion: dynamicCurrentVersion,
|
||||
updateAvailable: false,
|
||||
isDocker: this.isDockerEnvironment(),
|
||||
releaseNotes: 'Unable to check for updates: GitHub API rate limit exceeded. Please try again later.',
|
||||
releaseUrl: null,
|
||||
publishedAt: null,
|
||||
assets: [],
|
||||
updateChannel: channelDescription || 'unknown',
|
||||
rateLimited: true
|
||||
};
|
||||
} else if (error.response?.status === 404) {
|
||||
// Repository or release not found
|
||||
console.warn('[UpdateManager] Repository or release not found');
|
||||
return {
|
||||
currentVersion: dynamicCurrentVersion,
|
||||
latestVersion: dynamicCurrentVersion,
|
||||
updateAvailable: false,
|
||||
isDocker: this.isDockerEnvironment(),
|
||||
releaseNotes: 'Unable to check for updates: Repository or release not found.',
|
||||
releaseUrl: null,
|
||||
publishedAt: null,
|
||||
assets: [],
|
||||
updateChannel: channelDescription || 'unknown'
|
||||
};
|
||||
} else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
|
||||
// Network connectivity issues
|
||||
console.warn('[UpdateManager] Network connectivity issues');
|
||||
return {
|
||||
currentVersion: dynamicCurrentVersion,
|
||||
latestVersion: dynamicCurrentVersion,
|
||||
updateAvailable: false,
|
||||
isDocker: this.isDockerEnvironment(),
|
||||
releaseNotes: 'Unable to check for updates: Network connectivity issues. Please check your internet connection.',
|
||||
releaseUrl: null,
|
||||
publishedAt: null,
|
||||
assets: [],
|
||||
updateChannel: channelDescription || 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
// For other errors, still throw but with more context
|
||||
throw new Error(`Failed to check for updates: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Centralized version calculation utility
|
||||
* Used by both /api/version endpoint and UpdateManager to ensure consistency
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Calculate the current version dynamically from git
|
||||
* @returns {Object} Version information including version, branch, and isDevelopment
|
||||
*/
|
||||
function getCurrentVersionInfo() {
|
||||
try {
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
let currentVersion = packageJson.version || 'N/A';
|
||||
let gitBranch = null;
|
||||
let isDevelopment = false;
|
||||
|
||||
// Try to detect git branch and calculate dynamic version
|
||||
try {
|
||||
const gitDir = path.join(__dirname, '..');
|
||||
|
||||
// Get current branch
|
||||
gitBranch = execSync('git branch --show-current', {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
// If on develop branch, calculate RC version from git
|
||||
if (gitBranch === 'develop') {
|
||||
isDevelopment = true;
|
||||
try {
|
||||
// Get the latest stable release tag
|
||||
const latestStableTag = execSync('git tag -l "v*" | grep -v "rc\\\\|alpha\\\\|beta" | sort -V | tail -1', {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8',
|
||||
shell: '/bin/bash'
|
||||
}).trim();
|
||||
|
||||
if (latestStableTag) {
|
||||
// Remove 'v' prefix to get base version
|
||||
const baseVersion = latestStableTag.replace(/^v/, '');
|
||||
|
||||
// Count commits since the latest stable tag
|
||||
const commitsSince = execSync(`git rev-list --count ${latestStableTag}..HEAD`, {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
const commitsCount = parseInt(commitsSince, 10);
|
||||
|
||||
if (commitsCount > 0) {
|
||||
// Calculate RC version: base version + rc + commit count
|
||||
currentVersion = `${baseVersion}-rc${commitsCount}`;
|
||||
} else {
|
||||
// No commits since stable, use base version
|
||||
currentVersion = baseVersion;
|
||||
}
|
||||
}
|
||||
} catch (versionError) {
|
||||
console.log('[VersionUtils] Could not calculate RC version from git, using package.json');
|
||||
// Fall back to package.json version
|
||||
currentVersion = packageJson.version;
|
||||
}
|
||||
}
|
||||
} catch (gitError) {
|
||||
// Git not available or not a git repo
|
||||
gitBranch = null;
|
||||
currentVersion = packageJson.version;
|
||||
}
|
||||
|
||||
return {
|
||||
version: currentVersion,
|
||||
gitBranch: gitBranch,
|
||||
isDevelopment: isDevelopment || gitBranch === 'develop' || process.env.NODE_ENV === 'development'
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('[VersionUtils] Error getting current version:', error.message);
|
||||
const packageJson = require('../package.json');
|
||||
return {
|
||||
version: packageJson.version || 'N/A',
|
||||
gitBranch: null,
|
||||
isDevelopment: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get just the version string (for backwards compatibility)
|
||||
* @returns {string} The current version
|
||||
*/
|
||||
function getCurrentVersion() {
|
||||
return getCurrentVersionInfo().version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze commits since the last stable release to suggest version bump
|
||||
* @returns {Object} Analysis with suggested version bump and reasoning
|
||||
*/
|
||||
function analyzeCommitsForVersionBump() {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
const packageJson = require('../package.json');
|
||||
const gitDir = path.join(__dirname, '..');
|
||||
|
||||
// Get the latest stable release tag (no RC/alpha/beta)
|
||||
let latestStableTag;
|
||||
try {
|
||||
latestStableTag = execSync('git tag -l "v*" | grep -v "rc\\|alpha\\|beta" | sort -V | tail -1', {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8',
|
||||
shell: '/bin/bash'
|
||||
}).trim();
|
||||
} catch (error) {
|
||||
// No stable tags found, use v0.0.0 as baseline
|
||||
latestStableTag = 'v0.0.0';
|
||||
}
|
||||
|
||||
if (!latestStableTag) {
|
||||
latestStableTag = 'v0.0.0';
|
||||
}
|
||||
|
||||
// Get commit messages since last stable release
|
||||
let commitMessages;
|
||||
try {
|
||||
commitMessages = execSync(`git log ${latestStableTag}..HEAD --pretty=format:"%s"`, {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
} catch (error) {
|
||||
// If git log fails, assume no commits
|
||||
commitMessages = '';
|
||||
}
|
||||
|
||||
if (!commitMessages) {
|
||||
return {
|
||||
currentStableVersion: latestStableTag.replace(/^v/, ''),
|
||||
suggestedVersion: packageJson.version,
|
||||
bumpType: 'none',
|
||||
reasoning: 'No commits since last stable release',
|
||||
commits: []
|
||||
};
|
||||
}
|
||||
|
||||
const commits = commitMessages.split('\n').filter(msg => msg.trim());
|
||||
|
||||
// Analyze commit types
|
||||
const analysis = {
|
||||
breaking: [],
|
||||
features: [],
|
||||
fixes: [],
|
||||
other: []
|
||||
};
|
||||
|
||||
commits.forEach(commit => {
|
||||
const msg = commit.toLowerCase();
|
||||
|
||||
// Check for breaking changes
|
||||
if (commit.includes('BREAKING CHANGE') || commit.includes('!:')) {
|
||||
analysis.breaking.push(commit);
|
||||
}
|
||||
// Check for features
|
||||
else if (msg.startsWith('feat:') || msg.startsWith('feature:')) {
|
||||
analysis.features.push(commit);
|
||||
}
|
||||
// Check for fixes
|
||||
else if (msg.startsWith('fix:') || msg.startsWith('bugfix:')) {
|
||||
analysis.fixes.push(commit);
|
||||
}
|
||||
// Everything else
|
||||
else {
|
||||
analysis.other.push(commit);
|
||||
}
|
||||
});
|
||||
|
||||
// Determine version bump type
|
||||
let bumpType = 'patch';
|
||||
let reasoning = '';
|
||||
|
||||
if (analysis.breaking.length > 0) {
|
||||
bumpType = 'major';
|
||||
reasoning = `Major bump due to ${analysis.breaking.length} breaking change(s)`;
|
||||
} else if (analysis.features.length > 0) {
|
||||
bumpType = 'minor';
|
||||
reasoning = `Minor bump due to ${analysis.features.length} new feature(s)`;
|
||||
} else if (analysis.fixes.length > 0) {
|
||||
bumpType = 'patch';
|
||||
reasoning = `Patch bump due to ${analysis.fixes.length} bug fix(es)`;
|
||||
} else {
|
||||
bumpType = 'patch';
|
||||
reasoning = `Patch bump for ${analysis.other.length} other change(s)`;
|
||||
}
|
||||
|
||||
// Calculate suggested version
|
||||
const currentStableVersion = latestStableTag.replace(/^v/, '');
|
||||
const suggestedVersion = calculateNextVersion(currentStableVersion, bumpType);
|
||||
|
||||
return {
|
||||
currentStableVersion,
|
||||
suggestedVersion,
|
||||
bumpType,
|
||||
reasoning,
|
||||
commits: commits,
|
||||
analysis,
|
||||
totalCommits: commits.length
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.warn('[VersionUtils] Error analyzing commits for version bump:', error.message);
|
||||
const packageJson = require('../package.json');
|
||||
return {
|
||||
currentStableVersion: packageJson.version,
|
||||
suggestedVersion: packageJson.version,
|
||||
bumpType: 'none',
|
||||
reasoning: 'Error analyzing commits',
|
||||
commits: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next version based on current version and bump type
|
||||
* @param {string} currentVersion - Current semantic version (e.g., "3.24.0")
|
||||
* @param {string} bumpType - Type of bump: major, minor, or patch
|
||||
* @returns {string} Next version
|
||||
*/
|
||||
function calculateNextVersion(currentVersion, bumpType) {
|
||||
try {
|
||||
// Parse current version
|
||||
const versionMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||
if (!versionMatch) {
|
||||
throw new Error(`Invalid version format: ${currentVersion}`);
|
||||
}
|
||||
|
||||
let [, major, minor, patch] = versionMatch.map(Number);
|
||||
|
||||
switch (bumpType) {
|
||||
case 'major':
|
||||
major += 1;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
break;
|
||||
case 'minor':
|
||||
minor += 1;
|
||||
patch = 0;
|
||||
break;
|
||||
case 'patch':
|
||||
patch += 1;
|
||||
break;
|
||||
default:
|
||||
// No bump
|
||||
break;
|
||||
}
|
||||
|
||||
return `${major}.${minor}.${patch}`;
|
||||
} catch (error) {
|
||||
console.warn('[VersionUtils] Error calculating next version:', error.message);
|
||||
return currentVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current branch should trigger a stable release
|
||||
* (i.e., we're on main branch and last commit was a merge from develop)
|
||||
* @returns {boolean} True if this should trigger a stable release
|
||||
*/
|
||||
function shouldTriggerStableRelease() {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
const gitDir = path.join(__dirname, '..');
|
||||
|
||||
// Check if we're on main branch
|
||||
const currentBranch = execSync('git branch --show-current', {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
if (currentBranch !== 'main') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the last commit was a merge from develop
|
||||
try {
|
||||
const lastCommitMessage = execSync('git log -1 --pretty=format:"%s"', {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
// Look for merge commit patterns from develop
|
||||
const isMergeFromDevelop = lastCommitMessage.includes('Merge branch \'develop\'') ||
|
||||
lastCommitMessage.includes('Merge pull request') ||
|
||||
lastCommitMessage.includes('develop');
|
||||
|
||||
return isMergeFromDevelop;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn('[VersionUtils] Error checking for stable release trigger:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCurrentVersionInfo,
|
||||
getCurrentVersion,
|
||||
analyzeCommitsForVersionBump,
|
||||
calculateNextVersion,
|
||||
shouldTriggerStableRelease
|
||||
};
|
||||
+11
-6
@@ -181,9 +181,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check if this is a release candidate
|
||||
const versionBadge = document.getElementById('version-badge');
|
||||
if (versionBadge && data.version) {
|
||||
const isRC = data.version.includes('-rc') ||
|
||||
data.version.includes('-alpha') ||
|
||||
data.version.includes('-beta');
|
||||
const isVersionRC = data.version.includes('-rc') ||
|
||||
data.version.includes('-alpha') ||
|
||||
data.version.includes('-beta');
|
||||
const isDevelopBranch = data.isDevelopment || data.gitBranch === 'develop';
|
||||
const isRC = isVersionRC || isDevelopBranch;
|
||||
|
||||
if (isRC) {
|
||||
versionBadge.textContent = 'RC';
|
||||
versionBadge.classList.remove('hidden');
|
||||
@@ -191,9 +194,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
// Also update the page title
|
||||
const isRC = data.version && (data.version.includes('-rc') ||
|
||||
data.version.includes('-alpha') ||
|
||||
data.version.includes('-beta'));
|
||||
const isVersionRC = data.version && (data.version.includes('-rc') ||
|
||||
data.version.includes('-alpha') ||
|
||||
data.version.includes('-beta'));
|
||||
const isDevelopBranch = data.isDevelopment || data.gitBranch === 'develop';
|
||||
const isRC = isVersionRC || isDevelopBranch;
|
||||
document.title = isRC ? 'Pulse RC' : 'Pulse';
|
||||
|
||||
// Check if update is available
|
||||
|
||||
@@ -58,15 +58,15 @@ PulseApp.ui.settings = (() => {
|
||||
const tabButtons = document.querySelectorAll('.settings-tab');
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
button.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const tabName = e.currentTarget.getAttribute('data-tab');
|
||||
switchTab(tabName);
|
||||
await switchTab(tabName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
async function switchTab(tabName) {
|
||||
// Preserve current form data before switching tabs
|
||||
preserveCurrentFormData();
|
||||
|
||||
@@ -91,6 +91,11 @@ PulseApp.ui.settings = (() => {
|
||||
// Update content
|
||||
renderTabContent();
|
||||
|
||||
// Load current version if system tab is active (after DOM element exists)
|
||||
if (activeTab === 'system') {
|
||||
await loadCurrentVersion();
|
||||
}
|
||||
|
||||
// Restore form data for the new tab
|
||||
restoreFormData();
|
||||
}
|
||||
@@ -107,7 +112,7 @@ PulseApp.ui.settings = (() => {
|
||||
await loadConfiguration();
|
||||
|
||||
// Switch to requested tab
|
||||
switchTab(tabName);
|
||||
await switchTab(tabName);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
@@ -130,6 +135,24 @@ PulseApp.ui.settings = (() => {
|
||||
PulseApp.apiClient.handleError(error, 'Load configuration', showMessage);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCurrentVersion() {
|
||||
try {
|
||||
const versionData = await PulseApp.apiClient.get('/api/version');
|
||||
const currentVersionElement = document.getElementById('current-version');
|
||||
if (currentVersionElement && versionData.version) {
|
||||
currentVersionElement.textContent = versionData.version;
|
||||
// Update currentConfig with the dynamic version for consistency
|
||||
currentConfig.version = versionData.version;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not load current version:', error);
|
||||
const currentVersionElement = document.getElementById('current-version');
|
||||
if (currentVersionElement) {
|
||||
currentVersionElement.textContent = currentConfig.version || 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderTabContent() {
|
||||
const container = document.getElementById('settings-modal-body');
|
||||
@@ -580,7 +603,7 @@ PulseApp.ui.settings = (() => {
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
Current Version: <span id="current-version" class="font-mono font-semibold">${currentConfig.version || 'Unknown'}</span>
|
||||
Current Version: <span id="current-version" class="font-mono font-semibold">Loading...</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mt-1">
|
||||
<span id="latest-version-label">Latest Version</span>: <span id="latest-version" class="font-mono font-semibold text-gray-500 dark:text-gray-400">Checking...</span>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# ---- Builder Stage ----
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install necessary build tools (if any, e.g., python, make for some native deps)
|
||||
# RUN apk add --no-cache ...
|
||||
|
||||
# Copy only necessary package files first
|
||||
COPY package*.json ./
|
||||
|
||||
# Install ALL dependencies (including dev needed for build)
|
||||
# Using npm ci for faster, more reliable builds in CI/CD
|
||||
RUN npm ci
|
||||
|
||||
# Copy the rest of the application code
|
||||
# Important: Copy . before running build commands
|
||||
COPY . .
|
||||
|
||||
# Build the production CSS
|
||||
RUN npm run build:css
|
||||
|
||||
# Prune devDependencies after build
|
||||
RUN npm prune --production
|
||||
|
||||
# ---- Runner Stage ----
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Use existing node user (uid:gid 1000:1000) instead of system service accounts
|
||||
# The node:18-alpine image already has a 'node' user with uid:gid 1000:1000
|
||||
|
||||
# Copy necessary files from builder stage
|
||||
# Copy node_modules first (can be large)
|
||||
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
||||
# Copy built assets
|
||||
COPY --from=builder /usr/src/app/src/public ./src/public
|
||||
# Copy server code
|
||||
COPY --from=builder /usr/src/app/server ./server
|
||||
# Copy root package.json needed for npm start and potentially other metadata
|
||||
COPY --from=builder /usr/src/app/package.json ./
|
||||
# Optionally copy other root files if needed by the application (e.g., .env.example, README)
|
||||
# COPY --from=builder /usr/src/app/.env.example ./
|
||||
|
||||
# Create config directory for persistent volume mount and data directory
|
||||
RUN mkdir -p /usr/src/app/config /usr/src/app/data
|
||||
|
||||
# Ensure correct ownership of application files
|
||||
# Use /usr/src/app to cover everything copied
|
||||
RUN chown -R node:node /usr/src/app
|
||||
|
||||
# Switch to non-root user
|
||||
USER node
|
||||
|
||||
# Set environment variable to indicate Docker deployment
|
||||
ENV DOCKER_DEPLOYMENT=true
|
||||
|
||||
# Expose port
|
||||
EXPOSE 7655
|
||||
|
||||
# Run the application using the start script
|
||||
CMD [ "npm", "run", "start" ]
|
||||
@@ -1,35 +0,0 @@
|
||||
services:
|
||||
pulse-server:
|
||||
# Build context commented out - using pre-built image from Docker Hub
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
# image: rcourtman/pulse:latest # Use the pre-built image from Docker Hub
|
||||
container_name: pulse
|
||||
restart: unless-stopped
|
||||
user: "1000:1000" # Run as standard user, not system service accounts
|
||||
ports:
|
||||
# Map container port 7655 to host port 7655
|
||||
# You can change the host port (left side) if 7655 is already in use on your host
|
||||
- "7655:7655"
|
||||
# env_file:
|
||||
# NOTE: .env file is now managed by the web UI and stored in persistent volume
|
||||
# No need to load from host .env file
|
||||
# - .env
|
||||
volumes:
|
||||
# Persist configuration data to avoid losing settings on container recreation
|
||||
# Mount a persistent volume for configuration files
|
||||
- pulse_config:/usr/src/app/config
|
||||
# Optional: Define networks if needed, otherwise uses default bridge network
|
||||
# networks:
|
||||
# - pulse_network
|
||||
|
||||
# Define persistent volumes for configuration and data
|
||||
volumes:
|
||||
pulse_config:
|
||||
driver: local
|
||||
|
||||
# Optional: Define a network
|
||||
# networks:
|
||||
# pulse_network:
|
||||
# driver: bridge
|
||||
Reference in New Issue
Block a user