diff --git a/Plugins/CartonDevPlugin/CartonDevPluginCommand.swift b/Plugins/CartonDevPlugin/CartonDevPluginCommand.swift index 8194f9a..b21bca6 100644 --- a/Plugins/CartonDevPlugin/CartonDevPluginCommand.swift +++ b/Plugins/CartonDevPlugin/CartonDevPluginCommand.swift @@ -109,13 +109,16 @@ struct CartonDevPluginCommand: CommandPlugin { while let _ = try buildRequestFileHandle.read(upToCount: 1) { Diagnostics.remark("[Plugin] Received build request") let buildResult = try self.packageManager.build(buildSubset, parameters: parameters) + let responseMessage: Data if !buildResult.succeeded { Diagnostics.remark("[Plugin] **Build Failed**") - print(buildResult.logText) + print(buildResult.logText, terminator: "") + responseMessage = Data([0]) } else { Diagnostics.remark("[Plugin] **Build Succeeded**") + responseMessage = Data([1]) } - try buildResponseFileHandle.write(contentsOf: Data([1])) + try buildResponseFileHandle.write(contentsOf: responseMessage) } frontend.waitUntilExit() diff --git a/Sources/CartonFrontend/Commands/CartonFrontendDevCommand.swift b/Sources/CartonFrontend/Commands/CartonFrontendDevCommand.swift index 86b6fb6..1744ae9 100644 --- a/Sources/CartonFrontend/Commands/CartonFrontendDevCommand.swift +++ b/Sources/CartonFrontend/Commands/CartonFrontendDevCommand.swift @@ -17,23 +17,27 @@ import CartonHelpers import CartonKit import Foundation -struct CartonFrontendDevCommand: AsyncParsableCommand { - enum Error: Swift.Error & CustomStringConvertible { - case noBuildRequestOption - case noBuildResponseOption - case failedToOpenBuildRequestPipe - case failedToOpenBuildResponsePipe +enum DevCommandError: Error & CustomStringConvertible { + case noBuildRequestOption + case noBuildResponseOption + case failedToOpenBuildRequestPipe + case failedToOpenBuildResponsePipe + case pluginConnectionClosed + case brokenPluginResponse - var description: String { - switch self { - case .noBuildRequestOption: "--build-request option is necessary if you want to watch, but has not been specified." - case .noBuildResponseOption: "--build-response option is necessary if you want to watch, but has not been specified." - case .failedToOpenBuildRequestPipe: "failed to open build request pipe" - case .failedToOpenBuildResponsePipe: "failed to open build response pipe" - } + var description: String { + switch self { + case .noBuildRequestOption: "--build-request option is necessary if you want to watch, but has not been specified." + case .noBuildResponseOption: "--build-response option is necessary if you want to watch, but has not been specified." + case .failedToOpenBuildRequestPipe: "failed to open build request pipe." + case .failedToOpenBuildResponsePipe: "failed to open build response pipe." + case .pluginConnectionClosed: "connection with the plugin has been closed." + case .brokenPluginResponse: "response from the plugin was broken." } } +} +struct CartonFrontendDevCommand: AsyncParsableCommand { static let entrypoint = Entrypoint(fileName: "dev.js", content: StaticResource.dev) @Option(help: "Specify name of an executable product in development.") @@ -126,10 +130,10 @@ struct CartonFrontendDevCommand: AsyncParsableCommand { } guard let buildRequest else { - throw Error.noBuildRequestOption + throw DevCommandError.noBuildRequestOption } guard let buildResponse else { - throw Error.noBuildResponseOption + throw DevCommandError.noBuildResponseOption } let pathsToWatch = try watchPaths.map { @@ -137,10 +141,10 @@ struct CartonFrontendDevCommand: AsyncParsableCommand { } guard let buildRequest = FileHandle(forWritingAtPath: buildRequest) else { - throw Error.failedToOpenBuildRequestPipe + throw DevCommandError.failedToOpenBuildRequestPipe } guard let buildResponse = FileHandle(forReadingAtPath: buildResponse) else { - throw Error.failedToOpenBuildResponsePipe + throw DevCommandError.failedToOpenBuildResponsePipe } return SwiftPMPluginBuilder( @@ -202,6 +206,20 @@ struct SwiftPMPluginBuilder: BuilderProtocol { func run() async throws { // We expect single response per request try buildRequest.write(contentsOf: Data([1])) - _ = try buildResponse.read(upToCount: 1) + guard let responseMessage = try buildResponse.read(upToCount: 1) else { + throw DevCommandError.pluginConnectionClosed + } + if responseMessage.count < 1 { + throw DevCommandError.brokenPluginResponse + } + switch responseMessage[0] { + case 0: + throw BuilderProtocolSimpleBuildFailedError() + case 1: + // build succeeded + return + default: + throw DevCommandError.brokenPluginResponse + } } } diff --git a/Sources/CartonKit/Server/Server.swift b/Sources/CartonKit/Server/Server.swift index d6469b7..17c8ee7 100644 --- a/Sources/CartonKit/Server/Server.swift +++ b/Sources/CartonKit/Server/Server.swift @@ -63,6 +63,10 @@ extension Event: Decodable { } } +public struct BuilderProtocolSimpleBuildFailedError: Error { + public init() {} +} + /// A protocol for a builder that can be used to build the app. public protocol BuilderProtocol { var pathsToWatch: [AbsolutePath] { get } @@ -392,9 +396,19 @@ public actor Server { _ builder: any BuilderProtocol, _ terminal: InteractiveWriter ) async throws { - try await builder.run() + do { + try await builder.run() + } catch { + terminal.write("Build failed\n", inColor: .red) + switch error { + case is BuilderProtocolSimpleBuildFailedError: break + default: + terminal.write("\(error)\n", inColor: .red) + } + return + } - terminal.write("\nBuild completed successfully\n", inColor: .green, bold: false) + terminal.write("Build completed successfully\n", inColor: .green) terminal.logLookup("The app is currently hosted at ", localURL) connections.forEach { $0.reload() } }