feat: Add version check before updates to enhance update process and prevent unnecessary restarts

This commit is contained in:
2025-11-09 22:48:16 +01:00
parent 967801515f
commit 9e3aa9a79f
2 changed files with 115 additions and 81 deletions

View File

@@ -200,6 +200,64 @@ async function extractZip(zipPath, destDir) {
// MAIN UPDATE LOGIC
// =============================================================================
/**
* Check if update is available by comparing versions
*/
async function checkVersion() {
try {
// Read local version
const localPkgPath = join(process.cwd(), 'package.json')
if (!existsSync(localPkgPath)) {
console.log('⚠️ Could not find local package.json')
return { updateAvailable: false, localVersion: 'unknown', remoteVersion: 'unknown' }
}
const localPkg = JSON.parse(readFileSync(localPkgPath, 'utf8'))
const localVersion = localPkg.version
// Fetch remote version from GitHub
const repoOwner = 'Obsidian-wtf'
const repoName = 'Microsoft-Rewards-Bot'
const branch = 'main'
const pkgUrl = `https://raw.githubusercontent.com/${repoOwner}/${repoName}/refs/heads/${branch}/package.json`
console.log('🔍 Checking for updates...')
console.log(` Local version: ${localVersion}`)
return new Promise((resolve) => {
httpsGet(pkgUrl, (res) => {
if (res.statusCode !== 200) {
console.log(` ⚠️ Could not check remote version (HTTP ${res.statusCode})`)
resolve({ updateAvailable: false, localVersion, remoteVersion: 'unknown' })
return
}
let data = ''
res.on('data', chunk => data += chunk)
res.on('end', () => {
try {
const remotePkg = JSON.parse(data)
const remoteVersion = remotePkg.version
console.log(` Remote version: ${remoteVersion}`)
const updateAvailable = localVersion !== remoteVersion
resolve({ updateAvailable, localVersion, remoteVersion })
} catch (err) {
console.log(` ⚠️ Could not parse remote package.json: ${err.message}`)
resolve({ updateAvailable: false, localVersion, remoteVersion: 'unknown' })
}
})
}).on('error', (err) => {
console.log(` ⚠️ Network error: ${err.message}`)
resolve({ updateAvailable: false, localVersion, remoteVersion: 'unknown' })
})
})
} catch (err) {
console.log(`⚠️ Version check failed: ${err.message}`)
return { updateAvailable: false, localVersion: 'unknown', remoteVersion: 'unknown' }
}
}
/**
* Perform update using GitHub API (ZIP download)
*/
@@ -208,6 +266,20 @@ async function performUpdate() {
console.log('🚀 Microsoft Rewards Bot - Automatic Update')
console.log('='.repeat(70))
// Step 0: Check if update is needed by comparing versions
const versionCheck = await checkVersion()
if (!versionCheck.updateAvailable) {
console.log('\n✅ Already up to date!')
console.log(` Current version: ${versionCheck.localVersion}`)
console.log('='.repeat(70) + '\n')
return 0 // Exit without creating update marker
}
console.log('\n📥 New version available!')
console.log(` ${versionCheck.localVersion}${versionCheck.remoteVersion}`)
console.log(' Starting update process...\n')
// Step 1: Read user preferences
console.log('\n📋 Reading configuration...')
const configData = readJsonConfig([
@@ -324,7 +396,6 @@ async function performUpdate() {
'LICENSE'
]
let updatedCount = 0
for (const item of itemsToUpdate) {
const srcPath = join(sourceDir, item)
const destPath = join(process.cwd(), item)
@@ -350,7 +421,6 @@ async function performUpdate() {
cpSync(srcPath, destPath)
console.log(`${item}`)
}
updatedCount++
} catch (err) {
console.log(` ⚠️ Failed to update ${item}: ${err.message}`)
}
@@ -387,24 +457,18 @@ async function performUpdate() {
rmSync(backupDir, { recursive: true, force: true })
console.log(' ✓ Temporary files removed')
// Step 9: Check if anything was actually updated
if (updatedCount === 0) {
console.log('\n✅ Already up to date!')
console.log('='.repeat(70) + '\n')
// No update marker - bot won't restart
return 0
}
// Step 10: Create update marker for bot restart detection
// Step 9: Create update marker for bot restart detection
// Version check already confirmed update is needed, so we always create marker here
const updateMarkerPath = join(process.cwd(), '.update-happened')
writeFileSync(updateMarkerPath, JSON.stringify({
timestamp: new Date().toISOString(),
filesUpdated: updatedCount,
fromVersion: versionCheck.localVersion,
toVersion: versionCheck.remoteVersion,
method: 'github-api'
}, null, 2))
console.log(' ✓ Update marker created')
// Step 11: Install dependencies & rebuild
// Step 10: Install dependencies & rebuild
const hasNpm = await which('npm')
if (!hasNpm) {
console.log('\n⚠ npm not found, skipping dependencies and build')

View File

@@ -946,79 +946,49 @@ async function main(): Promise<void> {
const bootstrap = async () => {
try {
// Check for updates BEFORE initializing and running tasks
// Anti-loop protection: Track restart attempts
const restartCounterPath = path.join(process.cwd(), '.update-restart-count')
let restartCount = 0
if (fs.existsSync(restartCounterPath)) {
try {
const content = fs.readFileSync(restartCounterPath, 'utf8')
restartCount = parseInt(content, 10) || 0
} catch {
restartCount = 0
}
}
const updateMarkerPath = path.join(process.cwd(), '.update-happened')
// If we've restarted too many times (3+), something is wrong - skip update
if (restartCount >= 3) {
log('main', 'UPDATE', '⚠️ Too many restart attempts detected - skipping update to prevent loop', 'warn')
// Clean up counter file
try {
fs.unlinkSync(restartCounterPath)
} catch {
// Ignore
}
} else {
try {
const updateResult = await rewardsBot.runAutoUpdate().catch((e) => {
log('main', 'UPDATE', `Auto-update check failed: ${e instanceof Error ? e.message : String(e)}`, 'warn')
return -1
})
try {
const updateResult = await rewardsBot.runAutoUpdate().catch((e) => {
log('main', 'UPDATE', `Auto-update check failed: ${e instanceof Error ? e.message : String(e)}`, 'warn')
return -1
})
if (updateResult === 0) {
// Check if update marker exists (created by update.mjs when version changed)
const updateHappened = fs.existsSync(updateMarkerPath)
if (updateResult === 0) {
const updateMarkerPath = path.join(process.cwd(), '.update-happened')
const updateHappened = fs.existsSync(updateMarkerPath)
if (updateHappened) {
// Remove marker file
try {
fs.unlinkSync(updateMarkerPath)
} catch {
// Ignore cleanup errors
}
// Increment restart counter
restartCount++
try {
fs.writeFileSync(restartCounterPath, String(restartCount))
} catch {
// Ignore
}
log('main', 'UPDATE', `✅ Update successful - restarting with new version (attempt ${restartCount}/3)...`, 'log', 'green')
// Restart the process with the same arguments
const { spawn } = await import('child_process')
const child = spawn(process.execPath, process.argv.slice(1), {
detached: true,
stdio: 'inherit'
})
child.unref()
process.exit(0)
} else {
log('main', 'UPDATE', 'Already up to date, continuing with bot execution')
// Clean restart counter on successful non-update run
try {
if (fs.existsSync(restartCounterPath)) {
fs.unlinkSync(restartCounterPath)
}
} catch {
// Ignore
}
if (updateHappened) {
// Read marker for logging
try {
const markerContent = fs.readFileSync(updateMarkerPath, 'utf8')
const markerData = JSON.parse(markerContent)
log('main', 'UPDATE', `✅ Updated ${markerData.fromVersion}${markerData.toVersion} - restarting...`, 'log', 'green')
} catch {
log('main', 'UPDATE', '✅ Update successful - restarting...', 'log', 'green')
}
// Remove marker file
try {
fs.unlinkSync(updateMarkerPath)
} catch {
// Ignore cleanup errors
}
// Restart the process with the same arguments
const { spawn } = await import('child_process')
const child = spawn(process.execPath, process.argv.slice(1), {
detached: true,
stdio: 'inherit'
})
child.unref()
process.exit(0)
} else {
log('main', 'UPDATE', 'Already up to date, continuing with bot execution')
}
} catch (updateError) {
log('main', 'UPDATE', `Update check failed (continuing): ${updateError instanceof Error ? updateError.message : String(updateError)}`, 'warn')
}
} catch (updateError) {
log('main', 'UPDATE', `Update check failed (continuing): ${updateError instanceof Error ? updateError.message : String(updateError)}`, 'warn')
}
await rewardsBot.initialize()