diff --git a/setup/update/update.mjs b/setup/update/update.mjs index 6b56a7c..4380ee2 100644 --- a/setup/update/update.mjs +++ b/setup/update/update.mjs @@ -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') diff --git a/src/index.ts b/src/index.ts index 1e493f8..32e441b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -946,79 +946,49 @@ async function main(): Promise { 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()