Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 84 additions & 60 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,67 +69,78 @@ async function checkRepos() {

let reposUpdated = false;

for (const repo of repos) {
try {
const git = simpleGit(repo.path)

// Auto-detect remote URL if missing
if (!repo.githubUrl) {
const remotes = await git.getRemotes(true);
const origin = remotes.find(r => r.name === 'origin') || remotes[0];
if (origin) {
repo.githubUrl = convertGitUrlToHttps(origin.refs.fetch);
reposUpdated = true;
// Prevent concurrent checks to avoid git lock issues
if ((global as any).isCheckingRepos) {
console.log('Skipping checkRepos: previous check still in progress');
return;
}
(global as any).isCheckingRepos = true;

try {
for (const repo of repos) {
try {
const git = simpleGit(repo.path)

// Auto-detect remote URL if missing
if (!repo.githubUrl) {
const remotes = await git.getRemotes(true);
const origin = remotes.find(r => r.name === 'origin') || remotes[0];
if (origin) {
repo.githubUrl = convertGitUrlToHttps(origin.refs.fetch);
reposUpdated = true;
}
}
}

await git.fetch()
const status = await git.status()

const updateData: any = {
id: repo.id,
status: 'clean',
commitsBehind: 0,
commitsAhead: 0,
githubUrl: repo.githubUrl
};

if (status.isClean() === false) {
updateData.status = 'dirty';
} else if (status.ahead > 0 && status.behind > 0) {
updateData.status = 'diverged';
updateData.commitsBehind = status.behind;
updateData.commitsAhead = status.ahead;
} else if (status.behind > 0) {
updateData.status = 'behind';
updateData.commitsBehind = status.behind;

if (repo.autoPull) {
await git.pull()
notifyUser(repo.name, `Pulled ${status.behind} new commits.`)
updateData.status = 'clean';
updateData.commitsBehind = 0;
} else {
notifyUser(repo.name, `${status.behind} commits waiting.`, true)
await git.fetch()
const status = await git.status()

const updateData: any = {
id: repo.id,
status: 'clean',
commitsBehind: 0,
commitsAhead: 0,
githubUrl: repo.githubUrl
};

if (status.isClean() === false) {
updateData.status = 'dirty';
} else if (status.ahead > 0 && status.behind > 0) {
updateData.status = 'diverged';
updateData.commitsBehind = status.behind;
updateData.commitsAhead = status.ahead;
} else if (status.behind > 0) {
updateData.status = 'behind';
updateData.commitsBehind = status.behind;

if (repo.autoPull) {
await git.pull()
notifyUser(repo.name, `Pulled ${status.behind} new commits.`)
updateData.status = 'clean';
updateData.commitsBehind = 0;
} else {
notifyUser(repo.name, `${status.behind} commits waiting.`, true)
}
} else if (status.ahead > 0) {
updateData.status = 'ahead';
updateData.commitsAhead = status.ahead;
}

if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('repo-update', updateData)

} catch (err: any) {
console.error(`Error checking ${repo.name}:`, err)
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('repo-error', { id: repo.id, message: err.message || 'Unknown error' })
mainWindow.webContents.send('repo-update', { id: repo.id, status: 'error' })
}
} else if (status.ahead > 0) {
updateData.status = 'ahead';
updateData.commitsAhead = status.ahead;
}

if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('repo-update', updateData)

} catch (err: any) {
console.error(`Error checking ${repo.name}:`, err)
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('repo-error', { id: repo.id, message: err.message || 'Unknown error' })
mainWindow.webContents.send('repo-update', { id: repo.id, status: 'error' })
}
}
}

if (reposUpdated) {
store.set('repos', repos);
if (reposUpdated) {
store.set('repos', repos);
}
} finally {
(global as any).isCheckingRepos = false;
}
}

Expand Down Expand Up @@ -218,15 +229,18 @@ function createWindow(): void {
sandbox: false,
nodeIntegration: false,
contextIsolation: true,
devTools: true
devTools: true,
webSecurity: app.isPackaged ? false : true, // Relax security for portable builds to avoid "Not allowed to load local resource"
allowRunningInsecureContent: true
}
})

// LOGGING DIAGNOSTICS
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.error('FAILED TO LOAD:', errorCode, errorDescription);
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
const msg = `FAILED TO LOAD: ${errorCode} - ${errorDescription} - URL: ${validatedURL}`;
console.error(msg);
try {
writeFileSync(join(app.getPath('userData'), 'load-error.log'), `Error: ${errorCode} - ${errorDescription}`);
writeFileSync(join(app.getPath('userData'), 'load-error.log'), msg);
} catch (e) {}
});

Expand Down Expand Up @@ -255,7 +269,17 @@ function createWindow(): void {
if (process.env.VITE_DEV_SERVER_URL) {
mainWindow.loadurl(process.env.VITE_DEV_SERVER_URL)
} else {
mainWindow.loadFile(join(DIST_PATH, 'index.html'))
// For portable builds, we need to be careful with paths.
// Explicitly resolve the index.html path.
const indexPath = join(DIST_PATH, 'index.html');

// Log for debugging
console.log('Loading index.html from:', indexPath);

mainWindow.loadFile(indexPath).catch(e => {
console.error('Failed to load file:', e);
try { writeFileSync(join(app.getPath('userData'), 'load-exception.log'), String(e)); } catch {}
});
}

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
Expand Down
29 changes: 18 additions & 11 deletions src/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,21 +213,28 @@ export const Settings = () => {
<div className="space-y-0.5">
<Label htmlFor="launchAtStartup" className="text-base font-medium flex items-center gap-2">
<Power className="w-4 h-4 text-muted-foreground" />
Auto-Launch
Start on System Login
</Label>
<p className="text-sm text-muted-foreground">
Start automatically on login.
Automatically launch Git Watcher Pro when you sign in.
</p>
</div>
<Checkbox
id="launchAtStartup"
className="h-6 w-6 border-2 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground shadow-sm hover:shadow-md transition-all"
checked={launchAtStartup}
onCheckedChange={(checked) => {
setLaunchAtStartup(checked === true);
handleSave({ launchAtStartup: checked === true });
}}
/>
<div className="flex items-center gap-2">
{launchAtStartup && (
<span className="text-xs font-medium text-green-600 bg-green-500/10 px-2 py-0.5 rounded animate-in fade-in">
Enabled
</span>
)}
<Checkbox
id="launchAtStartup"
className="h-6 w-6 border-2 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground shadow-sm hover:shadow-md transition-all"
checked={launchAtStartup}
onCheckedChange={(checked) => {
setLaunchAtStartup(checked === true);
handleSave({ launchAtStartup: checked === true });
}}
/>
</div>
</div>

{/* Row 3: Minimize */}
Expand Down
Binary file added verification/settings_page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions verification/verify_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from playwright.sync_api import sync_playwright, expect
import time

def run():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
# Use the preview port
page = browser.new_page()
try:
page.goto("http://localhost:4173")
# Wait for content to load
page.wait_for_selector("text=Dashboard", state="visible")

# Click settings
page.click("text=Settings")

# Verify the new "Start on System Login" text is present
expect(page.get_by_text("Start on System Login")).to_be_visible()
expect(page.get_by_text("Automatically launch Git Watcher Pro when you sign in.")).to_be_visible()

# Take screenshot
page.screenshot(path="verification/settings_page.png")
print("Screenshot taken")
except Exception as e:
print(f"Error: {e}")
page.screenshot(path="verification/error.png")
finally:
browser.close()

if __name__ == "__main__":
run()