Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions Sources/Container-Compose/Commands/ComposeDown.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,39 @@ public struct ComposeDown: AsyncParsableCommand {
private var cwd: String { process.cwd ?? FileManager.default.currentDirectoryPath }

@Option(name: [.customShort("f"), .customLong("file")], help: "The path to your Docker Compose file")
var composeFilename: String = "compose.yml"
private var composePath: String { "\(cwd)/\(composeFilename)" } // Path to compose.yml
var composeFilename: String?

private var fileManager: FileManager { FileManager.default }
private var projectName: String?
private static let supportedComposeFilenames = [
"compose.yml",
"compose.yaml",
"docker-compose.yml",
"docker-compose.yaml",
]

public mutating func run() async throws {
private var cwdURL: URL {
URL(fileURLWithPath: cwd)
}

// Check for supported filenames and extensions
let filenames = [
"compose.yml",
"compose.yaml",
"docker-compose.yml",
"docker-compose.yaml",
]
for filename in filenames {
if fileManager.fileExists(atPath: "\(cwd)/\(filename)") {
composeFilename = filename
break
private var composePath: String {
if let composeFilename {
return resolvedPath(for: composeFilename, relativeTo: cwdURL)
}

for filename in Self.supportedComposeFilenames {
let candidate = cwdURL.appending(path: filename).path
if fileManager.fileExists(atPath: candidate) {
return candidate
}
}

return cwdURL.appending(path: Self.supportedComposeFilenames[0]).path
}

private var fileManager: FileManager { FileManager.default }
private var projectName: String?

public mutating func run() async throws {

// Read docker-compose.yml content
guard let yamlData = fileManager.contents(atPath: composePath) else {
let path = URL(fileURLWithPath: composePath)
Expand Down
63 changes: 41 additions & 22 deletions Sources/Container-Compose/Commands/ComposeUp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,42 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable {
var detach: Bool = false

@Option(name: [.customShort("f"), .customLong("file")], help: "The path to your Docker Compose file")
var composeFilename: String = "compose.yml"
private var composePath: String { "\(cwd)/\(composeFilename)" } // Path to compose.yml
var composeFilename: String?

private static let supportedComposeFilenames = [
"compose.yml",
"compose.yaml",
"docker-compose.yml",
"docker-compose.yaml",
]

private var cwdURL: URL {
URL(fileURLWithPath: cwd)
}

private var composePath: String {
if let composeFilename {
return resolvedPath(for: composeFilename, relativeTo: cwdURL)
}

for filename in Self.supportedComposeFilenames {
let candidate = cwdURL.appending(path: filename).path
if fileManager.fileExists(atPath: candidate) {
return candidate
}
}

return cwdURL.appending(path: Self.supportedComposeFilenames[0]).path
}

private var envFilePath: String {
let envFile = process.envFile.first ?? ".env"
return resolvedPath(for: envFile, relativeTo: cwdURL)
}

private var composeDirectory: String {
URL(fileURLWithPath: composePath).deletingLastPathComponent().path
}

@Flag(name: [.customShort("b"), .customLong("build")])
var rebuild: Bool = false
Expand All @@ -63,7 +97,6 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable {
var logging: Flags.Logging

private var cwd: String { process.cwd ?? FileManager.default.currentDirectoryPath }
var envFilePath: String { "\(cwd)/\(process.envFile.first ?? ".env")" } // Path to optional .env file

private var fileManager: FileManager { FileManager.default }
private var projectName: String?
Expand All @@ -76,20 +109,6 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable {
]

public mutating func run() async throws {
// Check for supported filenames and extensions
let filenames = [
"compose.yml",
"compose.yaml",
"docker-compose.yml",
"docker-compose.yaml",
]
for filename in filenames {
if fileManager.fileExists(atPath: "\(cwd)/\(filename)") {
composeFilename = filename
break
}
}

// Read compose.yml content
guard let yamlData = fileManager.contents(atPath: composePath) else {
let path = URL(fileURLWithPath: composePath)
Expand Down Expand Up @@ -406,7 +425,7 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable {

if let envFiles = service.env_file {
for envFile in envFiles {
let additionalEnvVars = loadEnvFile(path: "\(cwd)/\(envFile)")
let additionalEnvVars = loadEnvFile(path: URL(fileURLWithPath: envFile, relativeTo: URL(fileURLWithPath: composeDirectory)).path)
combinedEnv.merge(additionalEnvVars) { (current, _) in current }
}
}
Expand Down Expand Up @@ -610,15 +629,15 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable {
}

// Build command arguments
var commands = ["\(self.cwd)/\(buildConfig.context)"]
var commands = [URL(fileURLWithPath: buildConfig.context, relativeTo: URL(fileURLWithPath: composeDirectory)).path]

// Add build arguments
for (key, value) in buildConfig.args ?? [:] {
commands.append(contentsOf: ["--build-arg", "\(key)=\(resolveVariable(value, with: environmentVariables))"])
}

// Add Dockerfile path
commands.append(contentsOf: ["--file", "\(self.cwd)/\(buildConfig.dockerfile ?? "Dockerfile")"])
commands.append(contentsOf: ["--file", URL(fileURLWithPath: buildConfig.dockerfile ?? "Dockerfile", relativeTo: URL(fileURLWithPath: composeDirectory)).path])

// Add caching options
if noCache {
Expand Down
6 changes: 6 additions & 0 deletions Sources/Container-Compose/Helper Functions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import Yams
import Rainbow
import ContainerCommands

public func resolvedPath(for path: String, relativeTo baseURL: URL) -> String {
let expandedPath = NSString(string: path).expandingTildeInPath
return URL(fileURLWithPath: expandedPath, relativeTo: baseURL).standardizedFileURL.path
}


/// Loads environment variables from a .env file.
/// - Parameter path: The full path to the .env file.
/// - Returns: A dictionary of key-value pairs representing environment variables.
Expand Down
18 changes: 18 additions & 0 deletions Tests/Container-Compose-StaticTests/HelperFunctionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ struct HelperFunctionsTests {
#expect(projectName == "_devcontainers")
}

@Test("Resolve explicit relative paths against base URL")
func testResolvedPathRelativeSegments() throws {
let baseURL = URL(fileURLWithPath: "/tmp/project/compose/compose.yml").deletingLastPathComponent()

#expect(resolvedPath(for: "./file.yaml", relativeTo: baseURL) == "/tmp/project/compose/file.yaml")
#expect(resolvedPath(for: "../shared/file.yaml", relativeTo: baseURL) == "/tmp/project/shared/file.yaml")
#expect(resolvedPath(for: "configs/dev/compose.yaml", relativeTo: baseURL) == "/tmp/project/compose/configs/dev/compose.yaml")
}

@Test("Resolve absolute and tilde paths without rebasing")
func testResolvedPathAbsoluteAndTilde() throws {
let baseURL = URL(fileURLWithPath: "/tmp/project/compose")
let homePath = FileManager.default.homeDirectoryForCurrentUser.path

#expect(resolvedPath(for: "/var/tmp/compose.yaml", relativeTo: baseURL) == "/var/tmp/compose.yaml")
#expect(resolvedPath(for: "~/compose.yaml", relativeTo: baseURL) == "\(homePath)/compose.yaml")
}

@Test("Compose port - simple container port")
func testPortSimple() throws {
let result = composePortToRunArg("3000")
Expand Down
Loading