Skip to content

Commit e8eb12f

Browse files
refactor: enhance environment checks and path resolution logic; improve reset command confirmation
1 parent d80cded commit e8eb12f

File tree

12 files changed

+168
-78
lines changed

12 files changed

+168
-78
lines changed

README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,24 @@ cloudsqlctl status
9999

100100
Configuration and logs are stored in:
101101

102-
- **User Scope (Default):** `%LOCALAPPDATA%\CloudSQLCTL`
103-
- **Machine Scope (Service):** `%ProgramData%\CloudSQLCTL`
102+
- **User Scope (Default)**:
103+
- Config: `%LOCALAPPDATA%\CloudSQLCTL`
104+
- Logs: `%LOCALAPPDATA%\CloudSQLCTL\logs`
105+
- Binary: `%LOCALAPPDATA%\CloudSQLCTL\bin`
104106

105-
You can view the exact paths used by your current environment:
107+
- **Machine Scope (Service)**:
108+
- Config: `%ProgramData%\CloudSQLCTL`
109+
- Logs: `%ProgramData%\CloudSQLCTL\logs`
110+
- Binary: `%ProgramData%\CloudSQLCTL\bin`
111+
112+
**Path Resolution Logic:**
113+
114+
1. Environment Variables (`CLOUDSQLCTL_HOME`, etc.)
115+
2. Existing System Installation (`%ProgramData%`)
116+
3. Existing User Installation (`%LOCALAPPDATA%`)
117+
4. Default: User Scope (`%LOCALAPPDATA%`)
118+
119+
Verify current paths with:
106120

107121
```powershell
108122
cloudsqlctl paths
@@ -115,11 +129,6 @@ This project uses GitHub Actions for automated releases.
115129
1. Tag a new version: `git tag v0.3.0`
116130
2. Push the tag: `git push origin v0.3.0`
117131
3. The workflow will build, test, sign (if configured), and publish artifacts to the [Releases](https://github.com/Kinin-Code-Offical/cloudsqlctl/releases) page.
118-
`%LOCALAPPDATA%\CloudSQLCTL`
119-
120-
- `config.json`: User preferences and selected instance.
121-
- `logs/`: Application and proxy logs.
122-
- `bin/`: The `cloud_sql_proxy.exe` binary.
123132

124133
## Troubleshooting
125134

src/commands/check.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
import { Command } from 'commander';
2-
import { checkEnvironment } from '../system/env.js';
2+
import { checkEnvironmentDetailed } from '../system/env.js';
33
import { isServiceInstalled, isServiceRunning } from '../system/service.js';
44
import { PATHS } from '../system/paths.js';
55
import fs from 'fs-extra';
66
import { logger } from '../core/logger.js';
7+
import { isAdmin } from '../system/powershell.js';
78

89
export const checkCommand = new Command('check')
910
.description('Verify full system configuration')
10-
.action(async () => {
11+
.option('--scope <scope>', 'Environment scope (User, Machine, or auto)', 'auto')
12+
.action(async (options) => {
1113
try {
1214
logger.info('Checking system configuration...');
1315

14-
const envOk = await checkEnvironment('Machine');
15-
logger.info(`Environment Variables (Machine): ${envOk ? 'OK' : 'MISSING/INCORRECT'}`);
16+
let scope: 'Machine' | 'User' = 'User';
17+
if (options.scope === 'auto') {
18+
scope = await isAdmin() ? 'Machine' : 'User';
19+
} else {
20+
scope = options.scope;
21+
}
22+
23+
const envCheck = await checkEnvironmentDetailed(scope);
24+
if (envCheck.ok) {
25+
logger.info(`Environment Variables (${scope}): OK`);
26+
} else {
27+
logger.warn(`Environment Variables (${scope}): ISSUES FOUND`);
28+
envCheck.problems.forEach(p => logger.warn(` - ${p}`));
29+
}
1630

1731
const binaryOk = await fs.pathExists(PATHS.PROXY_EXE);
1832
logger.info(`Proxy Binary: ${binaryOk ? 'OK' : 'MISSING'}`);
@@ -25,7 +39,7 @@ export const checkCommand = new Command('check')
2539
logger.info(`Service Running: ${serviceRunning ? 'YES' : 'NO'}`);
2640
}
2741

28-
if (envOk && binaryOk && serviceInstalled) {
42+
if (envCheck.ok && binaryOk) {
2943
logger.info('System check passed.');
3044
} else {
3145
logger.warn('System check failed. Run "cloudsqlctl repair" to fix.');

src/commands/doctor.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Command } from 'commander';
22
import { logger } from '../core/logger.js';
33
import { checkGcloudInstalled, getActiveAccount, checkAdc, listInstances } from '../core/gcloud.js';
4-
import { checkEnvironment } from '../system/env.js';
4+
import { checkEnvironmentDetailed } from '../system/env.js';
55
import { isServiceInstalled } from '../system/service.js';
66
import { getEnvVar } from '../system/powershell.js';
77
import { PATHS, ENV_VARS } from '../system/paths.js';
@@ -46,13 +46,22 @@ export const doctorCommand = new Command('doctor')
4646
}
4747

4848
// Check Environment Variables
49-
const envOk = await checkEnvironment('Machine');
50-
if (envOk) {
51-
logger.info('✅ System Environment Variables are correct');
49+
const envCheck = await checkEnvironmentDetailed('Machine');
50+
if (envCheck.ok) {
51+
logger.info('✅ Environment Variables (Machine) OK');
5252
} else {
53-
logger.warn('⚠️ System Environment Variables are missing or incorrect');
53+
// Try User scope
54+
const userEnvCheck = await checkEnvironmentDetailed('User');
55+
if (userEnvCheck.ok) {
56+
logger.info('✅ Environment Variables (User) OK');
57+
} else {
58+
logger.warn('⚠️ Environment Variables issues found:');
59+
envCheck.problems.forEach(p => logger.warn(` [Machine] ${p}`));
60+
userEnvCheck.problems.forEach(p => logger.warn(` [User] ${p}`));
61+
}
5462
}
5563

64+
5665
// Check Proxy Binary
5766
if (await fs.pathExists(PATHS.PROXY_EXE)) {
5867
logger.info('✅ Cloud SQL Proxy binary found');

src/commands/paths.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Command } from 'commander';
2-
import { PATHS, ENV_VARS } from '../system/paths.js';
2+
import { PATHS, PATHS_SOURCE, PATHS_REASON } from '../system/paths.js';
33
import chalk from 'chalk';
44

55
export const pathsCommand = new Command('paths')
@@ -21,14 +21,8 @@ export const pathsCommand = new Command('paths')
2121
});
2222

2323
console.log(chalk.bold('\nResolution Source:'));
24-
25-
if (process.env[ENV_VARS.PROXY_PATH]) {
26-
console.log(`- ${chalk.green('Environment Variable')} (${ENV_VARS.PROXY_PATH}) is set.`);
27-
} else if (PATHS.HOME.includes('ProgramData')) {
28-
console.log(`- ${chalk.yellow('System Installation')} (ProgramData detected).`);
29-
} else {
30-
console.log(`- ${chalk.blue('User Installation')} (AppData detected).`);
31-
}
24+
console.log(`- Source: ${chalk.green(PATHS_SOURCE)}`);
25+
console.log(`- Reason: ${PATHS_REASON}`);
3226

3327
console.log('');
3428
});

src/commands/reset.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ import fs from 'fs-extra';
55

66
export const resetCommand = new Command('reset')
77
.description('Reset configuration and remove local files')
8-
.action(async () => {
8+
.option('--yes', 'Confirm reset without prompting')
9+
.action(async (options) => {
910
try {
10-
logger.warn('This will remove all configuration and logs.');
11-
// In a real app, ask for confirmation
11+
if (!options.yes) {
12+
logger.warn('This will remove all configuration and logs.');
13+
logger.warn(` - Config: ${PATHS.CONFIG_FILE}`);
14+
logger.warn(` - Logs: ${PATHS.LOGS}`);
15+
logger.error('Operation aborted. Use --yes to confirm.');
16+
process.exit(1);
17+
}
18+
19+
logger.info('Resetting configuration...');
1220
await fs.remove(PATHS.CONFIG_FILE);
1321
await fs.remove(PATHS.LOGS);
1422
// Maybe keep the binary?

src/core/updater.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import axios from 'axios';
22
import fs from 'fs-extra';
33
import crypto from 'crypto';
4+
import path from 'path';
45
import { PATHS } from '../system/paths.js';
56
import { logger } from './logger.js';
67

src/system/env.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { runPs, isAdmin } from './powershell.js';
22
import { SYSTEM_PATHS, USER_PATHS, ENV_VARS } from './paths.js';
33
import { logger } from '../core/logger.js';
4+
import fs from 'fs-extra';
45

56
export async function setEnv(name: string, value: string, scope: 'Machine' | 'User') {
67
if (scope === 'Machine' && !await isAdmin()) {
@@ -10,22 +11,68 @@ export async function setEnv(name: string, value: string, scope: 'Machine' | 'Us
1011
await runPs(`[Environment]::SetEnvironmentVariable("${name}", "${value}", "${scope}")`);
1112
}
1213

13-
export async function setupEnvironment(scope: 'Machine' | 'User' = 'User') {
14+
export async function setupEnvironment(scope: 'Machine' | 'User' = 'User', force: boolean = false) {
1415
logger.info(`Configuring ${scope} environment variables...`);
1516
const paths = scope === 'Machine' ? SYSTEM_PATHS : USER_PATHS;
1617

17-
await setEnv(ENV_VARS.HOME, paths.HOME, scope);
18-
await setEnv(ENV_VARS.LOGS, paths.LOGS, scope);
19-
await setEnv(ENV_VARS.PROXY_PATH, paths.PROXY_EXE, scope);
18+
const currentHome = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.HOME}", "${scope}")`);
19+
if (force || !currentHome) {
20+
await setEnv(ENV_VARS.HOME, paths.HOME, scope);
21+
}
22+
23+
const currentLogs = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.LOGS}", "${scope}")`);
24+
if (force || !currentLogs) {
25+
await setEnv(ENV_VARS.LOGS, paths.LOGS, scope);
26+
}
27+
28+
const currentProxy = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.PROXY_PATH}", "${scope}")`);
29+
if (force || !currentProxy) {
30+
await setEnv(ENV_VARS.PROXY_PATH, paths.PROXY_EXE, scope);
31+
}
2032
}
2133

34+
/** @deprecated Use checkEnvironmentDetailed instead */
2235
export async function checkEnvironment(scope: 'Machine' | 'User' = 'User'): Promise<boolean> {
23-
const paths = scope === 'Machine' ? SYSTEM_PATHS : USER_PATHS;
36+
const result = await checkEnvironmentDetailed(scope);
37+
return result.ok;
38+
}
39+
40+
export async function checkEnvironmentDetailed(scope: 'Machine' | 'User' = 'User'): Promise<{ ok: boolean, problems: string[], values: { home: string, logs: string, proxy: string } }> {
2441
const home = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.HOME}", "${scope}")`);
2542
const logs = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.LOGS}", "${scope}")`);
2643
const proxy = await runPs(`[Environment]::GetEnvironmentVariable("${ENV_VARS.PROXY_PATH}", "${scope}")`);
2744

28-
return home === paths.HOME &&
29-
logs === paths.LOGS &&
30-
proxy === paths.PROXY_EXE;
45+
const problems: string[] = [];
46+
47+
if (!home) {
48+
problems.push(`${ENV_VARS.HOME} is not set.`);
49+
} else {
50+
try {
51+
await fs.ensureDir(home);
52+
} catch {
53+
problems.push(`${ENV_VARS.HOME} path (${home}) does not exist and cannot be created.`);
54+
}
55+
}
56+
57+
if (!logs) {
58+
problems.push(`${ENV_VARS.LOGS} is not set.`);
59+
} else {
60+
try {
61+
await fs.ensureDir(logs);
62+
} catch {
63+
problems.push(`${ENV_VARS.LOGS} path (${logs}) does not exist and cannot be created.`);
64+
}
65+
}
66+
67+
if (!proxy) {
68+
problems.push(`${ENV_VARS.PROXY_PATH} is not set.`);
69+
} else if (!fs.existsSync(proxy)) {
70+
problems.push(`${ENV_VARS.PROXY_PATH} points to non-existent file (${proxy}).`);
71+
}
72+
73+
return {
74+
ok: problems.length === 0,
75+
problems,
76+
values: { home, logs, proxy }
77+
};
3178
}

src/system/paths.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ export const ENV_VARS = {
3434
GOOGLE_CREDS: 'GOOGLE_APPLICATION_CREDENTIALS'
3535
};
3636

37+
export let PATHS_SOURCE: 'ENV' | 'SYSTEM_EXISTING' | 'USER_EXISTING' | 'DEFAULT_USER' = 'DEFAULT_USER';
38+
export let PATHS_REASON: string = 'Defaulting to User scope';
39+
3740
function resolvePaths() {
3841
// 1. Priority: Environment Variables
3942
const envProxyPath = process.env[ENV_VARS.PROXY_PATH];
4043
if (envProxyPath) {
44+
PATHS_SOURCE = 'ENV';
45+
PATHS_REASON = `Environment variable ${ENV_VARS.PROXY_PATH} is set`;
4146
const proxyPath = envProxyPath;
4247
const home = process.env[ENV_VARS.HOME] || path.dirname(path.dirname(proxyPath)); // Guess home from proxy
4348
return {
@@ -56,6 +61,8 @@ function resolvePaths() {
5661

5762
// 2. Fallback: Check for existing proxy file (System then User)
5863
if (fs.existsSync(SYSTEM_PATHS.PROXY_EXE)) {
64+
PATHS_SOURCE = 'SYSTEM_EXISTING';
65+
PATHS_REASON = `Found existing proxy at ${SYSTEM_PATHS.PROXY_EXE}`;
5966
return {
6067
...SYSTEM_PATHS,
6168
CONFIG_DIR: SYSTEM_PATHS.HOME,
@@ -67,18 +74,15 @@ function resolvePaths() {
6774
}
6875

6976
if (fs.existsSync(USER_PATHS.PROXY_EXE)) {
77+
PATHS_SOURCE = 'USER_EXISTING';
78+
PATHS_REASON = `Found existing proxy at ${USER_PATHS.PROXY_EXE}`;
7079
return USER_PATHS;
7180
}
7281

73-
// 3. Default: System Paths (Target for new installation)
74-
return {
75-
...SYSTEM_PATHS,
76-
CONFIG_DIR: SYSTEM_PATHS.HOME,
77-
CONFIG_FILE: path.join(SYSTEM_PATHS.HOME, 'config.json'),
78-
TEMP: path.join(SYSTEM_PATHS.HOME, 'temp'),
79-
GCLOUD_DIR: path.join(SYSTEM_PATHS.HOME, 'gcloud'),
80-
PID_FILE: path.join(SYSTEM_PATHS.HOME, 'proxy.pid'),
81-
};
82+
// 3. Default: User Paths (Target for new installation)
83+
PATHS_SOURCE = 'DEFAULT_USER';
84+
PATHS_REASON = 'Defaulting to User scope (no existing installation found)';
85+
return USER_PATHS;
8286
}
8387

8488
// Default to resolved paths

src/system/ps1.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,15 @@ const SCRIPTS = {
1111
Write-Host "Environment variables set."
1212
`,
1313
'install-proxy.ps1': `
14-
param([string]$ProxyPath = "${SYSTEM_PATHS.PROXY_EXE}")
15-
16-
if (!(Test-Path $ProxyPath)) {
17-
Write-Error "Proxy binary not found at $ProxyPath"
18-
exit 1
19-
}
20-
21-
$service = Get-Service -Name "${SERVICE_NAME}" -ErrorAction SilentlyContinue
22-
if ($service) {
23-
Write-Host "Service ${SERVICE_NAME} already exists."
24-
} else {
25-
New-Service -Name "${SERVICE_NAME}" -BinaryPathName $ProxyPath -StartupType Automatic
26-
Write-Host "Service ${SERVICE_NAME} installed."
27-
}
14+
Write-Warning "DEPRECATED: Please use 'cloudsqlctl service install' instead."
15+
exit 1
2816
`,
2917
'uninstall-proxy.ps1': `
18+
Write-Warning "DEPRECATED: Please use 'cloudsqlctl service remove' instead."
3019
$service = Get-Service -Name "${SERVICE_NAME}" -ErrorAction SilentlyContinue
3120
if ($service) {
3221
Stop-Service -Name "${SERVICE_NAME}" -Force -ErrorAction SilentlyContinue
33-
Remove-Service -Name "${SERVICE_NAME}" -ErrorAction SilentlyContinue
22+
sc.exe delete "${SERVICE_NAME}"
3423
Write-Host "Service ${SERVICE_NAME} removed."
3524
}
3625
`,
@@ -49,3 +38,4 @@ export async function generateScripts() {
4938
logger.info(`Generated script: ${filePath}`);
5039
}
5140
}
41+

src/system/selfheal.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'fs-extra';
22
import { PATHS } from './paths.js';
3-
import { checkEnvironment, setupEnvironment } from './env.js';
3+
import { checkEnvironmentDetailed, setupEnvironment } from './env.js';
44
import { isServiceInstalled, installService } from './service.js';
55
import { getLatestVersion, downloadProxy } from '../core/updater.js';
66
import { generateScripts } from './ps1.js';
@@ -15,16 +15,19 @@ export async function selfHeal() {
1515
const scope = admin ? 'Machine' : 'User';
1616

1717
// 1. Check Environment Variables
18-
if (!await checkEnvironment(scope)) {
18+
const envCheck = await checkEnvironmentDetailed(scope);
19+
if (!envCheck.ok) {
1920
if (admin) {
2021
logger.warn(`Environment variables (${scope}) missing or incorrect. Fixing...`);
22+
envCheck.problems.forEach(p => logger.debug(` - ${p}`));
2123
try {
2224
await setupEnvironment(scope);
2325
} catch (e) {
2426
logger.error('Failed to fix environment variables', e);
2527
}
2628
} else {
2729
logger.warn('Environment variables missing or incorrect. Run as Administrator to fix Machine scope, or we will fix User scope now.');
30+
envCheck.problems.forEach(p => logger.debug(` - ${p}`));
2831
try {
2932
await setupEnvironment(scope);
3033
} catch (e) {

0 commit comments

Comments
 (0)