native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #173 from supabitapp/sbertix/env-scripts

authored by

khoi and committed by
GitHub
d731fe65 098acee2

+123 -9
+18
supacode/Domain/Worktree.swift
··· 24 24 self.createdAt = createdAt 25 25 } 26 26 } 27 + 28 + extension Worktree { 29 + /// Environment variables exposed to all Supacode scripts. 30 + var scriptEnvironment: [String: String] { 31 + [ 32 + "SUPACODE_WORKTREE_PATH": workingDirectory.path(percentEncoded: false), 33 + "SUPACODE_ROOT_PATH": repositoryRootURL.path(percentEncoded: false), 34 + ] 35 + } 36 + 37 + /// Shell export statements for prepending to scripts. 38 + var scriptEnvironmentExportPrefix: String { 39 + scriptEnvironment 40 + .sorted(by: { $0.key < $1.key }) 41 + .map { "export \($0.key)='\($0.value.replacing("'", with: "'\"'\"'"))'" } 42 + .joined(separator: "\n") + "\n" 43 + } 44 + }
+2 -1
supacode/Features/Repositories/Reducer/RepositoriesFeature.swift
··· 1285 1285 commandText: commandText 1286 1286 ) 1287 1287 let shellClient = shellClient 1288 + let scriptWithEnv = worktree.scriptEnvironmentExportPrefix + script 1288 1289 return .run { send in 1289 1290 let envURL = URL(fileURLWithPath: "/usr/bin/env") 1290 1291 var progress = ArchiveScriptProgress( ··· 1295 1296 do { 1296 1297 for try await event in shellClient.runLoginStream( 1297 1298 envURL, 1298 - ["bash", "-lc", script], 1299 + ["bash", "-lc", scriptWithEnv], 1299 1300 worktree.workingDirectory, 1300 1301 log: false 1301 1302 ) {
+37
supacode/Features/Settings/Views/RepositorySettingsView.swift
··· 111 111 } 112 112 } 113 113 Section { 114 + ScriptEnvironmentRow( 115 + name: "SUPACODE_WORKTREE_PATH", 116 + description: "Path to the active worktree." 117 + ) 118 + ScriptEnvironmentRow( 119 + name: "SUPACODE_ROOT_PATH", 120 + value: store.rootURL.path(percentEncoded: false), 121 + description: "Path to the repository root." 122 + ) 123 + } header: { 124 + VStack(alignment: .leading, spacing: 4) { 125 + Text("Environment Variables") 126 + Text("Exported in all scripts below") 127 + .foregroundStyle(.secondary) 128 + } 129 + } 130 + Section { 114 131 ZStack(alignment: .topLeading) { 115 132 PlainTextEditor( 116 133 text: settings.setupScript ··· 244 261 .onAppear { isSearchFocused = true } 245 262 } 246 263 } 264 + 265 + private struct ScriptEnvironmentRow: View { 266 + let name: String 267 + var value: String? 268 + let description: String 269 + 270 + var body: some View { 271 + VStack(alignment: .leading, spacing: 2) { 272 + Text(name) 273 + .monospaced() 274 + if let value { 275 + Text(value) 276 + .foregroundStyle(.secondary) 277 + .monospaced() 278 + } 279 + Text(description) 280 + .foregroundStyle(.tertiary) 281 + } 282 + } 283 + }
+2 -8
supacode/Features/Terminal/Models/WorktreeTerminalState.swift
··· 542 542 if trimmed.isEmpty { 543 543 return nil 544 544 } 545 - if script.hasSuffix("\n") { 546 - return script 547 - } 548 - return "\(script)\n" 545 + return worktree.scriptEnvironmentExportPrefix + trimmed + "\n" 549 546 } 550 547 551 548 private func runScriptInput(_ script: String) -> String? { ··· 553 550 if trimmed.isEmpty { 554 551 return nil 555 552 } 556 - if script.hasSuffix("\n") { 557 - return script 558 - } 559 - return "\(script)\n" 553 + return worktree.scriptEnvironmentExportPrefix + trimmed + "\n" 560 554 } 561 555 562 556 private func setRunScriptTabId(_ tabId: TerminalTabID?) {
+64
supacodeTests/WorktreeEnvironmentTests.swift
··· 1 + import Foundation 2 + import Testing 3 + 4 + @testable import supacode 5 + 6 + @MainActor 7 + struct WorktreeEnvironmentTests { 8 + @Test func scriptEnvironmentContainsExpectedKeys() { 9 + let worktree = Worktree( 10 + id: "/tmp/repo/wt-1", 11 + name: "feature-branch", 12 + detail: "detail", 13 + workingDirectory: URL(fileURLWithPath: "/tmp/repo/wt-1"), 14 + repositoryRootURL: URL(fileURLWithPath: "/tmp/repo"), 15 + ) 16 + let env = worktree.scriptEnvironment 17 + #expect(env["SUPACODE_WORKTREE_PATH"] == "/tmp/repo/wt-1") 18 + #expect(env["SUPACODE_ROOT_PATH"] == "/tmp/repo") 19 + #expect(env.count == 2) 20 + } 21 + 22 + @Test func exportPrefixFormatsCorrectly() { 23 + let worktree = Worktree( 24 + id: "/tmp/repo/wt-1", 25 + name: "feature-branch", 26 + detail: "detail", 27 + workingDirectory: URL(fileURLWithPath: "/tmp/repo/wt-1"), 28 + repositoryRootURL: URL(fileURLWithPath: "/tmp/repo/.bare"), 29 + ) 30 + let exports = worktree.scriptEnvironmentExportPrefix 31 + #expect(exports.contains("export SUPACODE_WORKTREE_PATH='/tmp/repo/wt-1'")) 32 + #expect(exports.contains("export SUPACODE_ROOT_PATH='/tmp/repo/.bare'")) 33 + #expect(exports.hasSuffix("\n")) 34 + } 35 + 36 + @Test func exportPrefixIsSortedByKey() { 37 + let worktree = Worktree( 38 + id: "/tmp/repo/wt-1", 39 + name: "feature-branch", 40 + detail: "detail", 41 + workingDirectory: URL(fileURLWithPath: "/tmp/repo/wt-1"), 42 + repositoryRootURL: URL(fileURLWithPath: "/tmp/repo/.bare"), 43 + ) 44 + let lines = worktree.scriptEnvironmentExportPrefix 45 + .trimmingCharacters(in: .newlines) 46 + .components(separatedBy: "\n") 47 + #expect(lines.count == 2) 48 + #expect(lines[0].contains("SUPACODE_ROOT_PATH")) 49 + #expect(lines[1].contains("SUPACODE_WORKTREE_PATH")) 50 + } 51 + 52 + @Test func exportPrefixQuotesPathsWithSpaces() { 53 + let worktree = Worktree( 54 + id: "/tmp/my repo/wt 1", 55 + name: "feature-branch", 56 + detail: "detail", 57 + workingDirectory: URL(fileURLWithPath: "/tmp/my repo/wt 1"), 58 + repositoryRootURL: URL(fileURLWithPath: "/tmp/my repo/.bare"), 59 + ) 60 + let exports = worktree.scriptEnvironmentExportPrefix 61 + #expect(exports.contains("export SUPACODE_WORKTREE_PATH='/tmp/my repo/wt 1'")) 62 + #expect(exports.contains("export SUPACODE_ROOT_PATH='/tmp/my repo/.bare'")) 63 + } 64 + }