diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f7aa32..9fb6a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ ### Changed +- **All commands now use project Ruby SDK** — `bundle exec docscribe` (via `DocscribeRunner` + and `DocscribeDaemon.fallback()`) and `bundle exec docscribe --version` (via + `DocscribeDaemon.performGemCheck()`) now prepend the Ruby SDK's `bin/` directory to `PATH` + and set `BUNDLE_GEMFILE`. This ensures the SDK's Ruby and Bundler are used instead of system + defaults. Extracted shared `buildSdkEnvironment()` helper, also reused in server startup. - **`com.intellij.modules.ruby` made optional** — changed from hard `` to ``. Ruby-specific extensions (`ExternalAnnotator`, `FoldingBuilder`, `DependencySupport`, `IntentionAction`) moved to a new diff --git a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt index f34a06e..aa8a21b 100644 --- a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt +++ b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt @@ -307,15 +307,7 @@ class DocscribeDaemon( val pb = ProcessBuilder(ruby, "-e", script).directory(File(gemRoot)) val env = pb.environment() - val sdk = ProjectRootManager.getInstance(project).projectSdk - if (sdk?.homePath != null) { - val sdkBin = File(ruby).parentFile?.absolutePath - if (sdkBin != null) { - val currentPath = env["PATH"] ?: "" - env["PATH"] = "$sdkBin${File.pathSeparator}$currentPath" - } - env["BUNDLE_GEMFILE"] = File(gemRoot, "Gemfile").absolutePath - } + env.putAll(buildSdkEnvironment()) val localGemPath = System.getProperty("docscribe.local.gem.path") if (localGemPath != null) { @@ -395,6 +387,7 @@ class DocscribeDaemon( val pb = ProcessBuilder("bundle", "exec", "docscribe", "--version") .directory(File(gemRoot)) + pb.environment().putAll(buildSdkEnvironment()) pb.redirectErrorStream(true) val proc = pb.start() val exited = proc.waitFor(GEM_CHECK_TIMEOUT_SECONDS, TimeUnit.SECONDS) @@ -504,6 +497,24 @@ class DocscribeDaemon( return if (path != null && path.isNotBlank() && File(path).canExecute()) path else null } + /** + * Build environment variables that point `bundle` and `ruby` to the project's Ruby SDK. + * + * Prepends the SDK's `bin/` directory to `PATH` and sets `BUNDLE_GEMFILE`. + * Returns an empty map when no SDK is configured or the SDK binary is not found. + */ + private fun buildSdkEnvironment(): Map { + val ruby = rubyCommand() ?: return emptyMap() + val sdkBin = File(ruby).parentFile?.absolutePath ?: return emptyMap() + val currentPath = System.getenv("PATH") ?: "" + val env = mutableMapOf("PATH" to "$sdkBin${File.pathSeparator}$currentPath") + val gemRoot = DocscribeRunner.findProjectRoot(project.basePath ?: "") + if (gemRoot != null) { + env["BUNDLE_GEMFILE"] = File(gemRoot, "Gemfile").absolutePath + } + return env + } + /** * Perform a single JSON-RPC 2.0 call over a Unix domain socket. * @@ -582,7 +593,9 @@ class DocscribeDaemon( strategy = strategy, formatJson = formatJson, ) - return DocscribeRunner.runDocscribe(options, DefaultCommandExecutor()) + val sdkEnv = buildSdkEnvironment() + val executor = if (sdkEnv.isEmpty()) DefaultCommandExecutor() else DefaultCommandExecutor(sdkEnv) + return DocscribeRunner.runDocscribe(options, executor) } /** diff --git a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeRunner.kt b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeRunner.kt index b756cd0..3a65291 100644 --- a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeRunner.kt +++ b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeRunner.kt @@ -83,7 +83,9 @@ interface CommandExecutor { * * Applies a 120-second timeout. Exit code 2 is treated as a failure (indistinguishable from timeout). */ -class DefaultCommandExecutor : CommandExecutor { +class DefaultCommandExecutor( + private val environment: Map = emptyMap(), +) : CommandExecutor { override fun execute( cmd: String, args: List, @@ -93,6 +95,7 @@ class DefaultCommandExecutor : CommandExecutor { GeneralCommandLine(cmd) .withParameters(args) .withWorkDirectory(cwd) + .withEnvironment(environment) val handler = CapturingProcessHandler(commandLine) val output = try {