Skip to content
Merged
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
20 changes: 20 additions & 0 deletions src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { Command } from 'commander';
import { getLatestVersion, downloadProxy } from '../core/updater.js';
import { logger } from '../core/logger.js';
import { isRunning, stopProxy } from '../core/proxy.js';
import { isServiceInstalled, isServiceRunning, startService, stopService } from '../system/service.js';

export const installCommand = new Command('install')
.description('Download and install Cloud SQL Proxy')
.option('-v, --version <version>', 'Specific version to install')
.action(async (options) => {
const serviceInstalled = await isServiceInstalled();
const serviceWasRunning = serviceInstalled && await isServiceRunning();
let serviceStopped = false;

try {
if (serviceWasRunning) {
logger.info('Stopping Windows Service before installation...');
await stopService();
serviceStopped = true;
}

if (await isRunning()) {
logger.info('Stopping running proxy before installation...');
await stopProxy();
Expand All @@ -25,5 +36,14 @@ export const installCommand = new Command('install')
} catch (error) {
logger.error('Installation failed', error);
process.exit(1);
} finally {
if (serviceStopped) {
try {
logger.info('Restarting Windows Service...');
await startService();
} catch (error) {
logger.warn('Failed to restart Windows Service after installation attempt', error);
}
}
}
});
20 changes: 20 additions & 0 deletions src/commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@ import { Command } from 'commander';
import { getLatestVersion, downloadProxy } from '../core/updater.js';
import { logger } from '../core/logger.js';
import { isRunning, stopProxy } from '../core/proxy.js';
import { isServiceInstalled, isServiceRunning, startService, stopService } from '../system/service.js';

export const updateCommand = new Command('update')
.description('Update Cloud SQL Proxy to the latest version')
.action(async () => {
const serviceInstalled = await isServiceInstalled();
const serviceWasRunning = serviceInstalled && await isServiceRunning();
let serviceStopped = false;

try {
if (serviceWasRunning) {
logger.info('Stopping Windows Service before update...');
await stopService();
serviceStopped = true;
}

if (await isRunning()) {
logger.info('Stopping running proxy before update...');
await stopProxy();
Expand All @@ -20,5 +31,14 @@ export const updateCommand = new Command('update')
} catch (error) {
logger.error('Update failed', error);
process.exit(1);
} finally {
if (serviceStopped) {
try {
logger.info('Restarting Windows Service...');
await startService();
} catch (error) {
logger.warn('Failed to restart Windows Service after update attempt', error);
}
}
}
});
31 changes: 17 additions & 14 deletions src/core/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,34 +58,37 @@ export async function downloadProxy(version: string, targetPath: string = PATHS.
// Ensure directory exists
await fs.ensureDir(path.dirname(targetPath));

const writer = fs.createWriteStream(targetPath);
const tmpPath = `${targetPath}.download`;
await fs.remove(tmpPath);

const writer = fs.createWriteStream(tmpPath);
const responseStream = await axios({
url: downloadUrl,
method: 'GET',
responseType: 'stream'
Comment on lines +64 to 68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Handle errors from the HTTP response stream to avoid hanging on download failures.

The awaited promise only subscribes to writer events, so errors from responseStream.data are ignored. If the readable stream errors, the writable may never emit finish/error, leaving downloadProxy stuck on failed requests.

Add an error listener on responseStream.data that rejects the promise (and/or closes writer), or use stream.pipeline (optionally promisified) to handle backpressure and propagate errors in one place.

});

responseStream.data.pipe(writer);

await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
try {
responseStream.data.pipe(writer);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped.

Copilot uses AI. Check for mistakes.

logger.info('Download complete.');
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});

logger.info('Verifying checksum...');
try {
const isValid = await verifyChecksum(targetPath, expectedChecksum);
logger.info('Download complete.');

logger.info('Verifying checksum...');
const isValid = await verifyChecksum(tmpPath, expectedChecksum);
if (!isValid) {
throw new Error('Checksum verification failed');
}
logger.info('Checksum verified.');

await fs.move(tmpPath, targetPath, { overwrite: true });
} catch (err) {
logger.warn('Failed to verify checksum', err);
// If verification fails, we should probably remove the file
await fs.remove(targetPath);
logger.warn('Failed to download/verify proxy', err);
await fs.remove(tmpPath);
throw err;
}

Expand Down