prole/prole-app/Sources/PFScriptBridge.swift
chrisfu 197c72dd7a Refactor prole-app and establish temporary release process
- Moved prole-tools-app to prole-app at the project root to make it self-contained for transition to its own repository.
- Created prole-tools-app/dist/ directory to host build artifacts.
- Generated distribution artifacts (Prole Tools.app and Prole Tools.zip) using prole-app/build.sh package.
- Checked in the generated artifacts to prole-tools-app/dist/ (bypassing .gitignore for temporary release process).
Changes Summary
•
Renamed directory prole-tools-app/ to prole-app/.
•
Populated prole-tools-app/dist/ with the latest build output from prole-app/build.sh.
•
Staged all changes, including the forced addition of ignored artifacts in prole-tools-app/dist/.
2026-01-18 15:04:23 -08:00

127 lines
5.9 KiB
Swift

import Foundation
// Bridge to invoke Prole Tools environment wrapper for status/restart.
// Use the environment defined in env.sh and execute scripts via $PROLE_SERVICE
// e.g. "$PROLE_SERVICE/init_port_forwards.sh <cmd>" so we don't rely on PATH.
enum PFScriptBridge {
// Keep the last command description actually used, for display in the UI.
private static var lastInvocation: String = "$PROLE_SERVICE/init_port_forwards.sh"
/// Public accessor for display purposes. Shows the last invocation we used.
static func invocationDescription() -> String { lastInvocation }
@discardableResult
static func restart() -> (code: Int32, out: String, err: String) { runScript(arg: "restart") }
static func status() -> (code: Int32, out: String, err: String) { runScript(arg: "status") }
static func dbStatus() -> (code: Int32, out: String, err: String) {
runRawCommand(command: "kubectl cnpg status prole-db", description: "kubectl cnpg status prole-db")
}
static func cnpgStatus() -> (code: Int32, out: String, err: String) {
runRawCommand(command: "kubectl cnpg status prole-db | head -10", description: "kubectl cnpg status prole-db | head -10")
}
static func openbaoStatus() -> (code: Int32, out: String, err: String) {
// Check if the openbao pod is Ready in the default namespace (or wherever it's deployed)
// We look for any pod with label app=openbao and check its ready status
let cmd = "kubectl get pods -l app=openbao -o jsonpath='{.items[*].status.containerStatuses[*].ready}'"
return runRawCommand(command: cmd, description: "kubectl check openbao ready")
}
private static func runRawCommand(command: String, description: String) -> (code: Int32, out: String, err: String) {
lastInvocation = description
let p = Process()
p.executableURL = URL(fileURLWithPath: "/bin/bash")
p.arguments = ["-lc", command]
// Use home as default CWD
let env = ProcessInfo.processInfo.environment
let home = env["HOME"] ?? NSHomeDirectory()
let proleHomeEnv = env["PROLE_HOME"]
p.currentDirectoryURL = URL(fileURLWithPath: proleHomeEnv ?? home)
Logger.shared.info("invoking raw: \(description)")
let outPipe = Pipe(); let errPipe = Pipe()
p.standardOutput = outPipe
p.standardError = errPipe
do { try p.run() } catch {
let errStr = String(describing: error)
Logger.shared.error("spawn error: \(errStr)")
return (-1, "", errStr)
}
p.waitUntilExit()
let out = String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let err = String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let code = p.terminationStatus
return (code, out, err)
}
private static func runScript(arg: String) -> (code: Int32, out: String, err: String) {
let fm = FileManager.default
let env = ProcessInfo.processInfo.environment
let home = env["HOME"] ?? NSHomeDirectory()
let proleHomeEnv = env["PROLE_HOME"]
// Candidate locations for env.sh
let envCandidates: [String] = [
(proleHomeEnv != nil ? (proleHomeEnv! + "/env.sh") : nil),
home + "/dev/prole/env.sh",
].compactMap { $0 }
// Candidate locations for the script if we can't use env.sh
let scriptCandidates: [String] = [
(proleHomeEnv != nil ? (proleHomeEnv! + "/etc/init_port_forwards.sh") : nil),
home + "/dev/prole/etc/init_port_forwards.sh",
].compactMap { $0 }
// Build the command using the best available method.
var command: String
var cwd: String = proleHomeEnv ?? home
if let envPath = envCandidates.first(where: { fm.fileExists(atPath: $0) }) {
// Use env.sh to populate PROLE_* and invoke via $PROLE_SERVICE
command = "export PROLE_HOME=\"${PROLE_HOME:-$HOME}\"; . \"\(envPath)\"; \"${PROLE_SERVICE}/init_port_forwards.sh\" \(arg)"
lastInvocation = ". \(envPath) && $PROLE_SERVICE/init_port_forwards.sh \(arg)"
} else if let scriptPath = scriptCandidates.first(where: { fm.fileExists(atPath: $0) }) {
// Fallback: directly execute the repo script
command = "\"\(scriptPath)\" \(arg)"
lastInvocation = scriptPath + " \(arg)"
} else {
// Nothing found; construct a failing but explicit command for diagnostics
command = "echo '[prole-tools] env.sh and init_port_forwards.sh not found' 1>&2; exit 127"
lastInvocation = "<not found> init_port_forwards.sh \(arg)"
}
let p = Process()
p.executableURL = URL(fileURLWithPath: "/bin/bash")
p.arguments = ["-lc", command]
p.currentDirectoryURL = URL(fileURLWithPath: cwd)
Logger.shared.info("invoking: \(lastInvocation)")
let outPipe = Pipe(); let errPipe = Pipe()
p.standardOutput = outPipe
p.standardError = errPipe
do { try p.run() } catch {
let errStr = String(describing: error)
Logger.shared.error("spawn error: \(errStr)")
return (-1, "", errStr)
}
p.waitUntilExit()
let out = String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let err = String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let code = p.terminationStatus
// Combine to match UI exactly
var combined = out
if !err.isEmpty { combined += (combined.isEmpty ? "" : "\n") + err }
let trimmedCombined = combined.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmedCombined.isEmpty {
Logger.shared.info("exit code: \(code); (no output)")
} else {
Logger.shared.info("output (exit=\(code)):\n\(combined)")
}
return (code, out, err)
}
}