From c1b69dc550c8e09995f4f28ad0fc81c6b9bf0d96 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Feb 2025 23:51:10 +0100 Subject: [PATCH 01/10] Added the DockerCompose file source to the resources in the library target. --- Library/Resources/Files/Sources/DockerCompose | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Library/Resources/Files/Sources/DockerCompose diff --git a/Library/Resources/Files/Sources/DockerCompose b/Library/Resources/Files/Sources/DockerCompose new file mode 100644 index 0000000..6ea4b20 --- /dev/null +++ b/Library/Resources/Files/Sources/DockerCompose @@ -0,0 +1,7 @@ +services: + app: + build: + context: . + port: + - 3000:8080 + command: ["--hostname", "0.0.0.0", "--port", "8080"] -- 2.47.1 From a6ddfb557fbf8ce35f3db122eb91645b4bf82c58 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 21 Feb 2025 23:51:55 +0100 Subject: [PATCH 02/10] Added the "dockerCompose" case to the File enumeration in the library target. --- Library/Sources/Internal/Enumerations/File.swift | 2 ++ Test/Sources/Cases/Internal/Enumerations/FileTests.swift | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Library/Sources/Internal/Enumerations/File.swift b/Library/Sources/Internal/Enumerations/File.swift index 50e68f0..402b534 100644 --- a/Library/Sources/Internal/Enumerations/File.swift +++ b/Library/Sources/Internal/Enumerations/File.swift @@ -2,6 +2,7 @@ enum File: String { case appArguments = "AppArguments" case appBuilder = "AppBuilder" case appOptions = "AppOptions" + case dockerCompose = "DockerCompose" case dockerFile = "DockerFile" case dockerIgnore = "DockerIgnore" case environment = "Environment" @@ -24,6 +25,7 @@ extension File { case .appArguments: "AppArguments.swift" case .appBuilder: "AppBuilder.swift" case .appOptions: "AppOptions.swift" + case .dockerCompose: "docker-compose.yml" case .dockerFile: "Dockerfile" case .dockerIgnore: ".dockerignore" case .environment: "Environment+Properties.swift" diff --git a/Test/Sources/Cases/Internal/Enumerations/FileTests.swift b/Test/Sources/Cases/Internal/Enumerations/FileTests.swift index 0de6680..800d822 100644 --- a/Test/Sources/Cases/Internal/Enumerations/FileTests.swift +++ b/Test/Sources/Cases/Internal/Enumerations/FileTests.swift @@ -56,6 +56,7 @@ private extension FileTests { "AppArguments.swift", "AppBuilder.swift", "AppOptions.swift", + "docker-compose.yml", "Dockerfile", ".dockerignore", "Environment+Properties.swift", @@ -70,6 +71,7 @@ private extension FileTests { "Library/Sources/Public/AppArguments.swift", "Library/Sources/Public/AppBuilder.swift", "App/Sources/AppOptions.swift", + "docker-compose.yml", "Dockerfile", ".dockerignore", "Library/Sources/Internal/Environment+Properties.swift", @@ -86,6 +88,7 @@ private extension FileTests { .app, .root, .root, + .root, .libraryInternal, .root, .root, @@ -100,6 +103,7 @@ private extension FileTests { "Resources/Files/Sources/App", "Resources/Files/Sources", "Resources/Files/Sources", + "Resources/Files/Sources", "Resources/Files/Sources/Library", "Resources/Files/Sources", "Resources/Files/Sources", -- 2.47.1 From 17426f264ac0dd5d8b149cb3a55d7a3fc8d17212 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 00:17:10 +0100 Subject: [PATCH 03/10] Fixed some issues found in the DockerCompose and DockerFile file resources in the library target. --- Library/Resources/Files/Sources/DockerCompose | 6 +++--- Library/Resources/Files/Sources/DockerFile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Library/Resources/Files/Sources/DockerCompose b/Library/Resources/Files/Sources/DockerCompose index 6ea4b20..8994020 100644 --- a/Library/Resources/Files/Sources/DockerCompose +++ b/Library/Resources/Files/Sources/DockerCompose @@ -2,6 +2,6 @@ services: app: build: context: . - port: - - 3000:8080 - command: ["--hostname", "0.0.0.0", "--port", "8080"] + port: + - 3000:8080 + command: ["--hostname", "0.0.0.0", "--port", "8080"] diff --git a/Library/Resources/Files/Sources/DockerFile b/Library/Resources/Files/Sources/DockerFile index a20ddca..e6cf30a 100644 --- a/Library/Resources/Files/Sources/DockerFile +++ b/Library/Resources/Files/Sources/DockerFile @@ -1,7 +1,7 @@ # ================================ # Build image # ================================ -FROM swift:6.0-noble as build +FROM swift:6.0.3-noble AS build # Install OS updates RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ @@ -83,5 +83,5 @@ USER hummingbird:hummingbird EXPOSE 8080 # Start the Hummingbird service when the image is run, default to listening on 8080 in production environment -ENTRYPOINT ["./App] +ENTRYPOINT ["./App"] CMD ["--hostname", "0.0.0.0", "--port", "8080"] -- 2.47.1 From c5fc608c234932674c3c6ef4d707c8ed9c222cba Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 00:53:22 +0100 Subject: [PATCH 04/10] Defined the Randomable protocol in the library target. --- .../Internal/Protocols/Randomable.swift | 7 ++++ .../Internal/Protocols/RandomableTests.swift | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Library/Sources/Internal/Protocols/Randomable.swift create mode 100644 Test/Sources/Cases/Internal/Protocols/RandomableTests.swift diff --git a/Library/Sources/Internal/Protocols/Randomable.swift b/Library/Sources/Internal/Protocols/Randomable.swift new file mode 100644 index 0000000..5b2f5c5 --- /dev/null +++ b/Library/Sources/Internal/Protocols/Randomable.swift @@ -0,0 +1,7 @@ +protocol Randomable: CaseIterable { + + // MARK: Functions + + static func random() -> Self + +} diff --git a/Test/Sources/Cases/Internal/Protocols/RandomableTests.swift b/Test/Sources/Cases/Internal/Protocols/RandomableTests.swift new file mode 100644 index 0000000..07912c5 --- /dev/null +++ b/Test/Sources/Cases/Internal/Protocols/RandomableTests.swift @@ -0,0 +1,32 @@ +import Testing + +@testable import ColibriLibrary + +struct RandomableTest { + + @Test func random() { + // GIVEN + let allCases = TestRandomable.allCases + + // WHEN + let random = TestRandomable.random() + + // THEN + #expect(allCases.contains(random)) + } + +} + +// MARK: - Enumerations + +enum TestRandomable: Randomable { + case someCase + case someOtherCase + + // MARK: Functions + + static func random() -> TestRandomable { + .allCases.randomElement() ?? .someCase + } + +} -- 2.47.1 From a6b8c0812547a67b52ca9236792609aa2900cb9c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 00:54:33 +0100 Subject: [PATCH 05/10] Conformed the IDE enumeration in the library target to the Randomable protocol. --- Library/Sources/Public/Enumerations/IDE.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Library/Sources/Public/Enumerations/IDE.swift b/Library/Sources/Public/Enumerations/IDE.swift index 2c530ac..75db91e 100644 --- a/Library/Sources/Public/Enumerations/IDE.swift +++ b/Library/Sources/Public/Enumerations/IDE.swift @@ -3,9 +3,9 @@ public enum IDE: String { case xcode } -// MARK: - Extension +// MARK: - Randomable -extension IDE { +extension IDE: Randomable { // MARK: Functions @@ -14,7 +14,3 @@ extension IDE { } } - -// MARK: - CaseIterable - -extension IDE: CaseIterable {} -- 2.47.1 From 5676f91bcbb2d421e78fbb3268dfde911dd67947 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 00:55:40 +0100 Subject: [PATCH 06/10] Defined the Artifact enumeration in the library target and conformed it to the Randomable protocol. --- .../Sources/Public/Enumerations/Artifact.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Library/Sources/Public/Enumerations/Artifact.swift diff --git a/Library/Sources/Public/Enumerations/Artifact.swift b/Library/Sources/Public/Enumerations/Artifact.swift new file mode 100644 index 0000000..cdffbd2 --- /dev/null +++ b/Library/Sources/Public/Enumerations/Artifact.swift @@ -0,0 +1,16 @@ +public enum Artifact: String { + case executable + case image +} + +// MARK: - Randomable + +extension Artifact: Randomable { + + // MARK: Functions + + static func random() -> Artifact { + .allCases.randomElement() ?? .executable + } + +} -- 2.47.1 From 771f150f98e238fd13aa9235b1e28a26d01ddcd1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 00:58:18 +0100 Subject: [PATCH 07/10] Conformed the Artifact enumeration to the ExpressibleByArgument protocol in the executable target. --- .../IDE+Conformances.swift => Definitions/Conformances.swift} | 1 + 1 file changed, 1 insertion(+) rename Executable/Sources/{Extensions/IDE+Conformances.swift => Definitions/Conformances.swift} (72%) diff --git a/Executable/Sources/Extensions/IDE+Conformances.swift b/Executable/Sources/Definitions/Conformances.swift similarity index 72% rename from Executable/Sources/Extensions/IDE+Conformances.swift rename to Executable/Sources/Definitions/Conformances.swift index 413ea49..e9651c0 100644 --- a/Executable/Sources/Extensions/IDE+Conformances.swift +++ b/Executable/Sources/Definitions/Conformances.swift @@ -3,4 +3,5 @@ import ColibriLibrary // MARK: - ExpressibleByArgument +extension Artifact: ExpressibleByArgument {} extension IDE: ExpressibleByArgument {} -- 2.47.1 From 613444ccde623dd562d5b43b2255cc498cc8c526 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 01:00:18 +0100 Subject: [PATCH 08/10] Added the "artifact" property to the BuildOptions options in the executable target. --- Executable/Sources/Options/BuildOptions.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Executable/Sources/Options/BuildOptions.swift b/Executable/Sources/Options/BuildOptions.swift index 9396639..009afdc 100644 --- a/Executable/Sources/Options/BuildOptions.swift +++ b/Executable/Sources/Options/BuildOptions.swift @@ -6,6 +6,9 @@ extension Colibri.Build { // MARK: Properties + @Option(name: .shortAndLong) + var artifact: Artifact = .executable + @Option(name: .shortAndLong) var location: String? -- 2.47.1 From 40d5451447e6f292f48b7f03791602b7a976b088 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 01:23:15 +0100 Subject: [PATCH 09/10] Renamed the BuildProjectTask in the library target as BuildArtifactTask and added support for building Swift executables and Docker images. --- .../Public/Tasks/BuildArtifactTask.swift | 40 ++++++++++++ .../Public/Tasks/BuildProjectTask.swift | 29 --------- .../Public/Tasks/BuildArtifactTaskTests.swift | 63 +++++++++++++++++++ .../Public/Tasks/BuildProjectTaskTests.swift | 42 ------------- 4 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 Library/Sources/Public/Tasks/BuildArtifactTask.swift delete mode 100644 Library/Sources/Public/Tasks/BuildProjectTask.swift create mode 100644 Test/Sources/Cases/Public/Tasks/BuildArtifactTaskTests.swift delete mode 100644 Test/Sources/Cases/Public/Tasks/BuildProjectTaskTests.swift diff --git a/Library/Sources/Public/Tasks/BuildArtifactTask.swift b/Library/Sources/Public/Tasks/BuildArtifactTask.swift new file mode 100644 index 0000000..c10969e --- /dev/null +++ b/Library/Sources/Public/Tasks/BuildArtifactTask.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct BuildArtifactTask { + + // MARK: Properties + + private let terminalService: TerminalServicing + + // MARK: Initialisers + + public init(terminalService: TerminalServicing) { + self.terminalService = terminalService + } + + // MARK: Functions + + public func callAsFunction(_ artifact: Artifact, at location: URL? = nil) async throws (TerminalServiceError) { + let executableURL: URL = switch artifact { + case .executable: .init(at: "/usr/bin/swift") + case .image: .init(at: "/usr/local/bin/docker") + } + + var arguments: [String] = switch artifact { + case .executable: ["build"] + case .image: ["compose", "build"] + } + + if let location { + if case .executable = artifact { + arguments.append(contentsOf: ["--package-path", location.pathString]) + } else if case .image = artifact { + arguments.insert(contentsOf: ["--project-directory", location.pathString], at: 1) + } + } + + try await terminalService.run(executableURL, arguments: arguments) + } + +} + diff --git a/Library/Sources/Public/Tasks/BuildProjectTask.swift b/Library/Sources/Public/Tasks/BuildProjectTask.swift deleted file mode 100644 index 282c7cb..0000000 --- a/Library/Sources/Public/Tasks/BuildProjectTask.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -public struct BuildProjectTask { - - // MARK: Properties - - private let terminalService: TerminalServicing - - // MARK: Initialisers - - public init(terminalService: TerminalServicing) { - self.terminalService = terminalService - } - - // MARK: Functions - - public func callAsFunction(at location: URL? = nil) async throws (TerminalServiceError) { - let executableURL = URL(at: "/usr/bin/swift") - - var arguments: [String] = ["build"] - - if let location { - arguments.append(contentsOf: ["--package-path", location.pathString]) - } - - try await terminalService.run(executableURL, arguments: arguments) - } - -} diff --git a/Test/Sources/Cases/Public/Tasks/BuildArtifactTaskTests.swift b/Test/Sources/Cases/Public/Tasks/BuildArtifactTaskTests.swift new file mode 100644 index 0000000..a8e2752 --- /dev/null +++ b/Test/Sources/Cases/Public/Tasks/BuildArtifactTaskTests.swift @@ -0,0 +1,63 @@ +import Foundation +import Testing + +@testable import ColibriLibrary + +struct BuildArtifactTaskTests { + + @Test(arguments: [nil, URL.someCurrentFolder]) + func taskForExecutable(at location: URL?) async throws { + // GIVEN + let terminalService = TerminalServiceSpy() + let task = BuildArtifactTask(terminalService: terminalService) + + // WHEN + try await task(.executable, at: location) + + // THEN + let executableURL = URL(at: "/usr/bin/swift") + let arguments = if let location { + ["build", "--package-path", location.pathString] + } else { + ["build"] + } + + #expect(terminalService.actions.count == 1) + #expect(terminalService.actions[0] == .ran(executableURL, arguments)) + } + + @Test(arguments: [nil, URL.someCurrentFolder]) + func taskForImage(at location: URL?) async throws { + // GIVEN + let terminalService = TerminalServiceSpy() + let task = BuildArtifactTask(terminalService: terminalService) + + // WHEN + try await task(.image, at: location) + + // THEN + let executableURL = URL(at: "/usr/local/bin/docker") + let arguments = if let location { + ["compose", "--project-directory", location.pathString, "build"] + } else { + ["compose", "build"] + } + + #expect(terminalService.actions.count == 1) + #expect(terminalService.actions[0] == .ran(executableURL, arguments)) + } + + @Test(arguments: [nil, URL.someCurrentFolder], [TerminalServiceError.unexpected, .output(""), .captured("")]) + func taskForArtifact(at location: URL?, throws error: TerminalServiceError) async throws { + // GIVEN + let terminalService = TerminalServiceMock(action: .error(error)) + let task = BuildArtifactTask(terminalService: terminalService) + + // WHEN + // THEN + await #expect(throws: error) { + try await task(.random(), at: location) + } + } + +} diff --git a/Test/Sources/Cases/Public/Tasks/BuildProjectTaskTests.swift b/Test/Sources/Cases/Public/Tasks/BuildProjectTaskTests.swift deleted file mode 100644 index 0c8049f..0000000 --- a/Test/Sources/Cases/Public/Tasks/BuildProjectTaskTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import Testing - -@testable import ColibriLibrary - -struct BuildProjectTaskTests { - - @Test(arguments: [nil, URL.someCurrentFolder]) - func task(at location: URL?) async throws { - // GIVEN - let terminalService = TerminalServiceSpy() - let task = BuildProjectTask(terminalService: terminalService) - - // WHEN - try await task(at: location) - - // THEN - let executableURL = URL(at: "/usr/bin/swift") - let arguments = if let location { - ["build", "--package-path", location.pathString] - } else { - ["build"] - } - - #expect(terminalService.actions.count == 1) - #expect(terminalService.actions[0] == .ran(executableURL, arguments)) - } - - @Test(arguments: [nil, URL.someCurrentFolder], [TerminalServiceError.unexpected, .output(""), .captured("")]) - func task(at location: URL?, throws error: TerminalServiceError) async throws { - // GIVEN - let terminalService = TerminalServiceMock(action: .error(error)) - let task = BuildProjectTask(terminalService: terminalService) - - // WHEN - // THEN - await #expect(throws: error) { - try await task(at: location) - } - } - -} -- 2.47.1 From e3b3b7c1a6ea9c31b85f12c89b23d3e263171971 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 22 Feb 2025 01:23:49 +0100 Subject: [PATCH 10/10] Integrated the BuildArtifactTask task into the BuildCommand command in the executable target. --- Executable/Sources/Commands/BuildCommand.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Executable/Sources/Commands/BuildCommand.swift b/Executable/Sources/Commands/BuildCommand.swift index be1fc08..64cf792 100644 --- a/Executable/Sources/Commands/BuildCommand.swift +++ b/Executable/Sources/Commands/BuildCommand.swift @@ -20,9 +20,9 @@ extension Colibri { mutating func run() async throws { let terminalService = TerminalService() - let buildProject = BuildProjectTask(terminalService: terminalService) + let buildArtifact = BuildArtifactTask(terminalService: terminalService) - try await buildProject(at: options.locationURL) + try await buildArtifact(options.artifact, at: options.locationURL) } } -- 2.47.1