@jetstart/core 2.3.1 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,14 +52,21 @@ class DexGenerator {
52
52
  async findD8() {
53
53
  if (this.d8Path)
54
54
  return this.d8Path;
55
- // Check multiple locations for Android SDK
56
- let androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
57
- // Fallback to common Windows locations
55
+ // Check environment variables first, but validate the path actually exists
56
+ const envAndroidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
57
+ let androidHome;
58
+ if (envAndroidHome && fs.existsSync(path.join(envAndroidHome, 'build-tools'))) {
59
+ androidHome = envAndroidHome;
60
+ }
61
+ // Fallback: probe common install locations
58
62
  if (!androidHome) {
59
63
  const commonLocations = [
60
- 'C:\\Android',
61
64
  path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
65
+ 'C:\\Android',
62
66
  'C:\\Users\\Public\\Android\\Sdk',
67
+ path.join(os.homedir(), 'Android', 'Sdk'),
68
+ path.join(os.homedir(), 'Library', 'Android', 'sdk'),
69
+ '/opt/android-sdk',
63
70
  ];
64
71
  for (const loc of commonLocations) {
65
72
  if (fs.existsSync(path.join(loc, 'build-tools'))) {
@@ -70,7 +77,7 @@ class DexGenerator {
70
77
  }
71
78
  }
72
79
  if (!androidHome) {
73
- (0, logger_1.error)('ANDROID_HOME or ANDROID_SDK_ROOT not set');
80
+ (0, logger_1.error)('Android build-tools not found');
74
81
  return null;
75
82
  }
76
83
  // d8 is in build-tools
@@ -42,6 +42,7 @@ const child_process_1 = require("child_process");
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const os = __importStar(require("os"));
45
+ const shared_1 = require("@jetstart/shared");
45
46
  const parser_1 = require("./parser");
46
47
  /**
47
48
  * ADB Helper for auto-installing APKs
@@ -288,16 +289,18 @@ class GradleExecutor {
288
289
  async execute(config) {
289
290
  const startTime = Date.now();
290
291
  const gradlePath = this.findGradle(config.projectPath);
291
- // If Gradle not found, return mock build for testing
292
+ // If Gradle not found, fail build with clear error
292
293
  if (!gradlePath) {
293
- console.log('[Gradle] No Gradle found, returning mock successful build for testing');
294
- // Simulate build delay
295
- await new Promise(resolve => setTimeout(resolve, 2000));
296
294
  return {
297
- success: true,
295
+ success: false,
298
296
  buildTime: Date.now() - startTime,
299
- apkPath: path.join(config.projectPath, 'build/outputs/apk/debug/app-debug.apk'),
300
- apkSize: 5242880, // Mock size: 5MB
297
+ errors: [{
298
+ file: 'gradle',
299
+ line: 0,
300
+ column: 0,
301
+ message: 'Gradle not found. Please install Gradle 8.0+ or ensure the Gradle wrapper (gradlew) exists in your project project.',
302
+ severity: shared_1.ErrorSeverity.ERROR
303
+ }]
301
304
  };
302
305
  }
303
306
  // Ensure Android SDK is configured
@@ -22,6 +22,13 @@ export declare class KotlinCompiler {
22
22
  findComposeCompiler(): Promise<string | null>;
23
23
  /**
24
24
  * Find kotlinc executable
25
+ *
26
+ * Search order:
27
+ * 1. KOTLIN_HOME env var (set in .env or shell)
28
+ * 2. ANDROID_STUDIO_HOME env var
29
+ * 3. Platform-specific static paths (Scoop, Chocolatey, SDKMAN, snap, …)
30
+ * 4. Versioned IDE directories (IntelliJ IDEA, JetBrains Toolbox, Android Studio)
31
+ * 5. `where` / `which` fallback
25
32
  */
26
33
  findKotlinc(): Promise<string | null>;
27
34
  /**
@@ -102,27 +102,99 @@ class KotlinCompiler {
102
102
  }
103
103
  /**
104
104
  * Find kotlinc executable
105
+ *
106
+ * Search order:
107
+ * 1. KOTLIN_HOME env var (set in .env or shell)
108
+ * 2. ANDROID_STUDIO_HOME env var
109
+ * 3. Platform-specific static paths (Scoop, Chocolatey, SDKMAN, snap, …)
110
+ * 4. Versioned IDE directories (IntelliJ IDEA, JetBrains Toolbox, Android Studio)
111
+ * 5. `where` / `which` fallback
105
112
  */
106
113
  async findKotlinc() {
107
114
  if (this.kotlincPath)
108
115
  return this.kotlincPath;
109
- // Check common locations
116
+ const win = os.platform() === 'win32';
117
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
118
+ const progFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
119
+ /**
120
+ * Scan `parentDir` for subdirectories whose names start with `entryPrefix`
121
+ * (pass '' to accept any entry), then return the first candidate that exists:
122
+ * path.join(parentDir, entry, ...rest)
123
+ * Entries are sorted descending so the latest version is tried first.
124
+ * Returns null (never throws) if the directory is missing or unreadable.
125
+ */
126
+ const findInVersionedDir = (parentDir, entryPrefix, ...rest) => {
127
+ try {
128
+ if (!fs.existsSync(parentDir))
129
+ return null;
130
+ const entries = fs.readdirSync(parentDir)
131
+ .filter(e => !entryPrefix || e.toLowerCase().startsWith(entryPrefix.toLowerCase()))
132
+ .sort()
133
+ .reverse(); // latest version first
134
+ for (const entry of entries) {
135
+ const candidate = path.join(parentDir, entry, ...rest);
136
+ if (fs.existsSync(candidate))
137
+ return candidate;
138
+ }
139
+ }
140
+ catch { /* permission / access errors — silently skip */ }
141
+ return null;
142
+ };
143
+ const homeDir = os.homedir();
144
+ // JetStart installs kotlinc here when the user runs --full-install
145
+ const jetstartKotlinc = win
146
+ ? path.join(homeDir, '.jetstart', 'kotlinc', 'bin', 'kotlinc.bat')
147
+ : path.join(homeDir, '.jetstart', 'kotlinc', 'bin', 'kotlinc');
148
+ // Validate KOTLIN_HOME if set — only use it when it actually points to kotlinc
149
+ const kotlinHomeCandidate = process.env.KOTLIN_HOME
150
+ ? (win
151
+ ? path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc.bat')
152
+ : path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc'))
153
+ : null;
110
154
  const locations = [
111
- // From environment variable
112
- process.env.KOTLIN_HOME ? path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc') : null,
113
- // From Android Studio
114
- process.env.ANDROID_STUDIO_HOME ? path.join(process.env.ANDROID_STUDIO_HOME, 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc') : null,
115
- // System-wide installation (Windows)
116
- 'C:\\Program Files\\kotlinc\\bin\\kotlinc.bat',
117
- 'C:\\kotlinc\\bin\\kotlinc.bat',
118
- // System-wide installation (Unix)
119
- '/usr/local/bin/kotlinc',
120
- '/usr/bin/kotlinc',
121
- // Homebrew (macOS)
122
- '/opt/homebrew/bin/kotlinc',
123
- ].filter(Boolean);
155
+ // JetStart managed install (~/.jetstart/kotlinc) — checked first
156
+ jetstartKotlinc,
157
+ // env overrides (validated)
158
+ kotlinHomeCandidate,
159
+ process.env.ANDROID_STUDIO_HOME
160
+ ? path.join(process.env.ANDROID_STUDIO_HOME, 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc')
161
+ : null,
162
+ // Windows static paths
163
+ win ? path.join(progFiles, 'kotlinc', 'bin', 'kotlinc.bat') : null,
164
+ win ? 'C:\\kotlinc\\bin\\kotlinc.bat' : null,
165
+ // Scoop (~\scoop\apps\kotlin\current)
166
+ win ? path.join(homeDir, 'scoop', 'apps', 'kotlin', 'current', 'bin', 'kotlinc.bat') : null,
167
+ // Chocolatey
168
+ win ? 'C:\\ProgramData\\chocolatey\\bin\\kotlinc.bat' : null,
169
+ // Android Studio — standard Google installer
170
+ win ? path.join(progFiles, 'Android', 'Android Studio', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
171
+ win ? path.join(localAppData, 'Programs', 'Android Studio', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
172
+ // Windows versioned IDE paths (wildcard scan)
173
+ // IntelliJ IDEA — any version under C:\Program Files\JetBrains
174
+ win ? findInVersionedDir(path.join(progFiles, 'JetBrains'), 'IntelliJ IDEA', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
175
+ // JetBrains Toolbox — IDEA Ultimate
176
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'IDEA-U', 'ch-0'), '', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
177
+ // JetBrains Toolbox — IDEA Community
178
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'IDEA-C', 'ch-0'), '', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
179
+ // JetBrains Toolbox — Android Studio (Jellyfish+)
180
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'AndroidStudio', 'ch-0'), '', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
181
+ // Unix / macOS static paths
182
+ !win ? '/usr/local/bin/kotlinc' : null,
183
+ !win ? '/usr/bin/kotlinc' : null,
184
+ // Homebrew (Apple Silicon + Intel)
185
+ !win ? '/opt/homebrew/bin/kotlinc' : null,
186
+ !win ? '/usr/local/opt/kotlin/bin/kotlinc' : null,
187
+ // SDKMAN (~/.sdkman/candidates/kotlin/current)
188
+ !win ? path.join(homeDir, '.sdkman', 'candidates', 'kotlin', 'current', 'bin', 'kotlinc') : null,
189
+ // Snap
190
+ !win ? '/snap/bin/kotlinc' : null,
191
+ // IntelliJ IDEA — Linux Toolbox
192
+ !win ? findInVersionedDir(path.join(homeDir, '.local', 'share', 'JetBrains', 'Toolbox', 'apps', 'IDEA-U', 'ch-0'), '', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc') : null,
193
+ ];
124
194
  for (const loc of locations) {
125
- const execPath = os.platform() === 'win32' && !loc.endsWith('.bat') ? `${loc}.bat` : loc;
195
+ if (!loc)
196
+ continue;
197
+ const execPath = win && !loc.endsWith('.bat') ? `${loc}.bat` : loc;
126
198
  if (fs.existsSync(execPath)) {
127
199
  this.kotlincPath = execPath;
128
200
  (0, logger_1.log)(`Found kotlinc at: ${execPath}`);
@@ -131,7 +203,7 @@ class KotlinCompiler {
131
203
  }
132
204
  // Try to find via 'where' (Windows) or 'which' (Unix)
133
205
  try {
134
- const cmd = os.platform() === 'win32' ? 'where' : 'which';
206
+ const cmd = win ? 'where' : 'which';
135
207
  const result = await this.runCommand(cmd, ['kotlinc']);
136
208
  if (result.success && result.stdout.trim()) {
137
209
  this.kotlincPath = result.stdout.trim().split('\n')[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetstart/core",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "Build server and orchestration for JetStart",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "homepage": "https://github.com/dev-phantom/jetstart#readme",
35
35
  "dependencies": {
36
- "@jetstart/shared": "^2.3.1",
37
- "@jetstart/logs": "^2.3.1",
36
+ "@jetstart/shared": "^2.3.3",
37
+ "@jetstart/logs": "^2.3.3",
38
38
  "express": "^4.18.2",
39
39
  "ws": "^8.14.2",
40
40
  "chokidar": "^3.5.3",
@@ -26,15 +26,22 @@ export class DexGenerator {
26
26
  async findD8(): Promise<string | null> {
27
27
  if (this.d8Path) return this.d8Path;
28
28
 
29
- // Check multiple locations for Android SDK
30
- let androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
29
+ // Check environment variables first, but validate the path actually exists
30
+ const envAndroidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
31
+ let androidHome: string | undefined;
32
+ if (envAndroidHome && fs.existsSync(path.join(envAndroidHome, 'build-tools'))) {
33
+ androidHome = envAndroidHome;
34
+ }
31
35
 
32
- // Fallback to common Windows locations
36
+ // Fallback: probe common install locations
33
37
  if (!androidHome) {
34
38
  const commonLocations = [
35
- 'C:\\Android',
36
39
  path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
40
+ 'C:\\Android',
37
41
  'C:\\Users\\Public\\Android\\Sdk',
42
+ path.join(os.homedir(), 'Android', 'Sdk'),
43
+ path.join(os.homedir(), 'Library', 'Android', 'sdk'),
44
+ '/opt/android-sdk',
38
45
  ];
39
46
  for (const loc of commonLocations) {
40
47
  if (fs.existsSync(path.join(loc, 'build-tools'))) {
@@ -46,7 +53,7 @@ export class DexGenerator {
46
53
  }
47
54
 
48
55
  if (!androidHome) {
49
- logError('ANDROID_HOME or ANDROID_SDK_ROOT not set');
56
+ logError('Android build-tools not found');
50
57
  return null;
51
58
  }
52
59
 
@@ -7,7 +7,7 @@ import { spawn, ChildProcess, execSync } from 'child_process';
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
9
9
  import * as os from 'os';
10
- import { BuildConfig, BuildResult } from '@jetstart/shared';
10
+ import { BuildConfig, BuildResult, ErrorSeverity } from '@jetstart/shared';
11
11
  import { BuildOutputParser } from './parser';
12
12
 
13
13
  /**
@@ -289,18 +289,18 @@ export class GradleExecutor {
289
289
  const startTime = Date.now();
290
290
  const gradlePath = this.findGradle(config.projectPath);
291
291
 
292
- // If Gradle not found, return mock build for testing
292
+ // If Gradle not found, fail build with clear error
293
293
  if (!gradlePath) {
294
- console.log('[Gradle] No Gradle found, returning mock successful build for testing');
295
-
296
- // Simulate build delay
297
- await new Promise(resolve => setTimeout(resolve, 2000));
298
-
299
294
  return {
300
- success: true,
295
+ success: false,
301
296
  buildTime: Date.now() - startTime,
302
- apkPath: path.join(config.projectPath, 'build/outputs/apk/debug/app-debug.apk'),
303
- apkSize: 5242880, // Mock size: 5MB
297
+ errors: [{
298
+ file: 'gradle',
299
+ line: 0,
300
+ column: 0,
301
+ message: 'Gradle not found. Please install Gradle 8.0+ or ensure the Gradle wrapper (gradlew) exists in your project project.',
302
+ severity: ErrorSeverity.ERROR
303
+ }]
304
304
  };
305
305
  }
306
306
 
@@ -82,28 +82,113 @@ export class KotlinCompiler {
82
82
 
83
83
  /**
84
84
  * Find kotlinc executable
85
+ *
86
+ * Search order:
87
+ * 1. KOTLIN_HOME env var (set in .env or shell)
88
+ * 2. ANDROID_STUDIO_HOME env var
89
+ * 3. Platform-specific static paths (Scoop, Chocolatey, SDKMAN, snap, …)
90
+ * 4. Versioned IDE directories (IntelliJ IDEA, JetBrains Toolbox, Android Studio)
91
+ * 5. `where` / `which` fallback
85
92
  */
86
93
  async findKotlinc(): Promise<string | null> {
87
94
  if (this.kotlincPath) return this.kotlincPath;
88
95
 
89
- // Check common locations
90
- const locations = [
91
- // From environment variable
92
- process.env.KOTLIN_HOME ? path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc') : null,
93
- // From Android Studio
94
- process.env.ANDROID_STUDIO_HOME ? path.join(process.env.ANDROID_STUDIO_HOME, 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc') : null,
95
- // System-wide installation (Windows)
96
- 'C:\\Program Files\\kotlinc\\bin\\kotlinc.bat',
97
- 'C:\\kotlinc\\bin\\kotlinc.bat',
98
- // System-wide installation (Unix)
99
- '/usr/local/bin/kotlinc',
100
- '/usr/bin/kotlinc',
101
- // Homebrew (macOS)
102
- '/opt/homebrew/bin/kotlinc',
103
- ].filter(Boolean) as string[];
96
+ const win = os.platform() === 'win32';
97
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
98
+ const progFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
99
+
100
+ /**
101
+ * Scan `parentDir` for subdirectories whose names start with `entryPrefix`
102
+ * (pass '' to accept any entry), then return the first candidate that exists:
103
+ * path.join(parentDir, entry, ...rest)
104
+ * Entries are sorted descending so the latest version is tried first.
105
+ * Returns null (never throws) if the directory is missing or unreadable.
106
+ */
107
+ const findInVersionedDir = (
108
+ parentDir: string,
109
+ entryPrefix: string,
110
+ ...rest: string[]
111
+ ): string | null => {
112
+ try {
113
+ if (!fs.existsSync(parentDir)) return null;
114
+ const entries = fs.readdirSync(parentDir)
115
+ .filter(e => !entryPrefix || e.toLowerCase().startsWith(entryPrefix.toLowerCase()))
116
+ .sort()
117
+ .reverse(); // latest version first
118
+ for (const entry of entries) {
119
+ const candidate = path.join(parentDir, entry, ...rest);
120
+ if (fs.existsSync(candidate)) return candidate;
121
+ }
122
+ } catch { /* permission / access errors — silently skip */ }
123
+ return null;
124
+ };
125
+
126
+ const homeDir = os.homedir();
127
+ // JetStart installs kotlinc here when the user runs --full-install
128
+ const jetstartKotlinc = win
129
+ ? path.join(homeDir, '.jetstart', 'kotlinc', 'bin', 'kotlinc.bat')
130
+ : path.join(homeDir, '.jetstart', 'kotlinc', 'bin', 'kotlinc');
131
+
132
+ // Validate KOTLIN_HOME if set — only use it when it actually points to kotlinc
133
+ const kotlinHomeCandidate = process.env.KOTLIN_HOME
134
+ ? (win
135
+ ? path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc.bat')
136
+ : path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc'))
137
+ : null;
138
+
139
+ const locations: Array<string | null> = [
140
+ // JetStart managed install (~/.jetstart/kotlinc) — checked first
141
+ jetstartKotlinc,
142
+
143
+ // env overrides (validated)
144
+ kotlinHomeCandidate,
145
+ process.env.ANDROID_STUDIO_HOME
146
+ ? path.join(process.env.ANDROID_STUDIO_HOME, 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc')
147
+ : null,
148
+
149
+ // Windows static paths
150
+ win ? path.join(progFiles, 'kotlinc', 'bin', 'kotlinc.bat') : null,
151
+ win ? 'C:\\kotlinc\\bin\\kotlinc.bat' : null,
152
+ // Scoop (~\scoop\apps\kotlin\current)
153
+ win ? path.join(homeDir, 'scoop', 'apps', 'kotlin', 'current', 'bin', 'kotlinc.bat') : null,
154
+ // Chocolatey
155
+ win ? 'C:\\ProgramData\\chocolatey\\bin\\kotlinc.bat' : null,
156
+ // Android Studio — standard Google installer
157
+ win ? path.join(progFiles, 'Android', 'Android Studio', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
158
+ win ? path.join(localAppData,'Programs', 'Android Studio', 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
159
+
160
+ // Windows versioned IDE paths (wildcard scan)
161
+ // IntelliJ IDEA — any version under C:\Program Files\JetBrains
162
+ win ? findInVersionedDir(path.join(progFiles, 'JetBrains'), 'IntelliJ IDEA',
163
+ 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
164
+ // JetBrains Toolbox — IDEA Ultimate
165
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'IDEA-U', 'ch-0'), '',
166
+ 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
167
+ // JetBrains Toolbox — IDEA Community
168
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'IDEA-C', 'ch-0'), '',
169
+ 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
170
+ // JetBrains Toolbox — Android Studio (Jellyfish+)
171
+ win ? findInVersionedDir(path.join(localAppData, 'JetBrains', 'Toolbox', 'apps', 'AndroidStudio', 'ch-0'), '',
172
+ 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc.bat') : null,
173
+
174
+ // Unix / macOS static paths
175
+ !win ? '/usr/local/bin/kotlinc' : null,
176
+ !win ? '/usr/bin/kotlinc' : null,
177
+ // Homebrew (Apple Silicon + Intel)
178
+ !win ? '/opt/homebrew/bin/kotlinc' : null,
179
+ !win ? '/usr/local/opt/kotlin/bin/kotlinc' : null,
180
+ // SDKMAN (~/.sdkman/candidates/kotlin/current)
181
+ !win ? path.join(homeDir, '.sdkman', 'candidates', 'kotlin', 'current', 'bin', 'kotlinc') : null,
182
+ // Snap
183
+ !win ? '/snap/bin/kotlinc' : null,
184
+ // IntelliJ IDEA — Linux Toolbox
185
+ !win ? findInVersionedDir(path.join(homeDir, '.local', 'share', 'JetBrains', 'Toolbox', 'apps', 'IDEA-U', 'ch-0'), '',
186
+ 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc') : null,
187
+ ];
104
188
 
105
189
  for (const loc of locations) {
106
- const execPath = os.platform() === 'win32' && !loc.endsWith('.bat') ? `${loc}.bat` : loc;
190
+ if (!loc) continue;
191
+ const execPath = win && !loc.endsWith('.bat') ? `${loc}.bat` : loc;
107
192
  if (fs.existsSync(execPath)) {
108
193
  this.kotlincPath = execPath;
109
194
  log(`Found kotlinc at: ${execPath}`);
@@ -113,7 +198,7 @@ export class KotlinCompiler {
113
198
 
114
199
  // Try to find via 'where' (Windows) or 'which' (Unix)
115
200
  try {
116
- const cmd = os.platform() === 'win32' ? 'where' : 'which';
201
+ const cmd = win ? 'where' : 'which';
117
202
  const result = await this.runCommand(cmd, ['kotlinc']);
118
203
  if (result.success && result.stdout.trim()) {
119
204
  this.kotlincPath = result.stdout.trim().split('\n')[0];