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
29 changes: 29 additions & 0 deletions Sources/NnexKit/Formula/FormulaManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FormulaManager.swift
// nnex
//
// Created by Nikolai Nobadi on 12/14/25.
//

public struct FormulaManager {
private let fileSystem: any FileSystem

public init(fileSystem: any FileSystem) {
self.fileSystem = fileSystem
}
}


// MARK: - Actions
public extension FormulaManager {
func resolveFormulaFile(formula: HomebrewFormula, tapFolder: any Directory, contents: String) throws -> String {
let fileName = "\(formula.name).rb"
let formulaFolder = try tapFolder.createSubfolderIfNeeded(named: "Formula")

if formulaFolder.containsFile(named: fileName) {
try formulaFolder.deleteFile(named: fileName)
}

return try formulaFolder.createFile(named: fileName, contents: contents)
}
}
30 changes: 25 additions & 5 deletions Sources/nnex/Commands/Brew/Publish.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ extension Nnex.Brew {
@Flag(name: .customLong("skip-tests"), help: "Skips running tests before publishing.")
var skipTests = false

@Flag(name: .long, help: "Simulates the publish process without making any changes to files or remote repositories.")
var dryRun = false

func run() throws {
let shell = Nnex.makeShell()
let context = try Nnex.makeContext()
let gitHandler = Nnex.makeGitHandler()
let fileSystem = Nnex.makeFileSystem()
let gitHandler = makePublishGitHandler(dryRun: dryRun)
let resolvedBuildType = buildType ?? context.loadDefaultBuildType()
let delegate = makePublishDelegate(shell: shell, gitHandler: gitHandler, fileSystem: fileSystem, context: context)
let formulaFileService = makeFormulaFileService(dryRun: dryRun, fileSystem: fileSystem)
let delegate = makePublishDelegate(shell: shell, gitHandler: gitHandler, fileSystem: fileSystem, formulaFileService: formulaFileService, context: context)
let coordinator = PublishCoordinator(shell: shell, gitHandler: gitHandler, fileSystem: fileSystem, delegate: delegate)

try coordinator.publish(projectPath: path, buildType: resolvedBuildType, notes: notes, notesFilePath: notesFile, commitMessage: message, skipTests: skipTests, versionInfo: version)
Expand All @@ -63,25 +67,41 @@ private extension Nnex.Brew.Publish {
return .init(shell: shell, picker: picker, fileSystem: fileSystem, delegate: artifactDelegate)
}

func makePublishDelegate(shell: any NnexShell, gitHandler: any GitHandler, fileSystem: any FileSystem, context: NnexContext) -> any PublishDelegate {
func makePublishGitHandler(dryRun: Bool) -> any GitHandler {
let gitHandler = Nnex.makeGitHandler()

return dryRun ? DryRunPublishGitHandler(gitHandler: gitHandler) : gitHandler
}

func makeFormulaFileService(dryRun: Bool, fileSystem: any FileSystem) -> any FormulaFileService {
if dryRun {
return DryRunFormulaFileService()
}

return FormulaManager(fileSystem: fileSystem)
}

func makePublishDelegate(shell: any NnexShell, gitHandler: any GitHandler, fileSystem: any FileSystem, formulaFileService: any FormulaFileService, context: NnexContext) -> PublishDelegateAdapter {
let picker = Nnex.makePicker()
let folderBrowser = Nnex.makeFolderBrowser(picker: picker, fileSystem: fileSystem)
let dateProvider = DefaultDateProvider()
let loader = PublishInfoStoreAdapter(context: context)

let versionService = AutoVersionHandler(shell: shell, fileSystem: fileSystem)
let versionController = VersionNumberController(shell: shell, picker: picker, gitHandler: gitHandler, fileSystem: fileSystem, versionService: versionService)

let buildController = makeBuildController(shell: shell, picker: picker, fileSystem: fileSystem, folderBrowser: folderBrowser)
let artifactController = makeArtifactController(shell: shell, picker: picker, fileSystem: fileSystem, loader: loader, buildController: buildController)
let releaseController = GithubReleaseController(picker: picker, gitHandler: gitHandler, fileSystem: fileSystem, dateProvider: dateProvider, folderBrowser: folderBrowser)
let publishController = FormulaPublishController(picker: picker, gitHandler: gitHandler, fileSystem: fileSystem, store: loader)
let publishController = FormulaPublishController(picker: picker, gitHandler: gitHandler, fileSystem: fileSystem, store: loader, formulaFileService: formulaFileService)

return PublishDelegateAdapter(versionController: versionController, artifactController: artifactController, releaseController: releaseController, publishController: publishController)
return .init(versionController: versionController, artifactController: artifactController, releaseController: releaseController, publishController: publishController)
}
}


// MARK: - Extension Dependencies
extension FormulaManager: FormulaFileService { }
extension AutoVersionHandler: VersionNumberService { }
extension ReleaseVersionInfo: ExpressibleByArgument { }
extension ReleaseVersionInfo.VersionPart: ExpressibleByArgument { }
Expand Down
27 changes: 27 additions & 0 deletions Sources/nnex/Publish/Adapters/DryRunFormulaFileService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// DryRunFormulaFileService.swift
// nnex
//
// Created by Nikolai Nobadi on 12/14/25.
//

import NnexKit

struct DryRunFormulaFileService: FormulaFileService {
func resolveFormulaFile(formula: HomebrewFormula, tapFolder: any Directory, contents: String) throws -> FilePath {
let fileName = "\(formula.name).rb"
print(
"""
\("Formula File Details".underline)
fileName: \(fileName)
filePath (if created): \(tapFolder.path.appendingPathComponent("Formula/\(fileName)"))

\("Formula Contents".underline)
\(contents)

"""
)

return "No formula file was created, and any previous formula file for \(formula.name) remains unmodified."
}
}
72 changes: 72 additions & 0 deletions Sources/nnex/Publish/Adapters/DryRunPublishGitHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// DryRunPublishGitHandler.swift
// nnex
//
// Created by Nikolai Nobadi on 12/14/25.
//

import NnexKit
import GitShellKit

struct DryRunPublishGitHandler {
private let gitHandler: any GitHandler

init(gitHandler: any GitHandler) {
self.gitHandler = gitHandler
}
}


// MARK: - GitHandler
extension DryRunPublishGitHandler: GitHandler {
func ghVerification() throws {
return try gitHandler.ghVerification()
}

func gitInit(path: String) throws {
return try gitHandler.gitInit(path: path)
}

func getRemoteURL(path: String) throws -> String {
return try gitHandler.getRemoteURL(path: path)
}

func commitAndPush(message: String, path: String) throws {
print("prepering to commit at \(path.underline), message: \(message.yellow)")
}

func getPreviousReleaseVersion(path: String) throws -> String {
return try gitHandler.getPreviousReleaseVersion(path: path)
}

func remoteRepoInit(tapName: String, path: String, projectDetails: String, visibility: RepoVisibility) throws -> String {
return try gitHandler.remoteRepoInit(tapName: tapName, path: path, projectDetails: projectDetails, visibility: visibility)
}

func createNewRelease(version: String, archivedBinaries: [ArchivedBinary], releaseNoteInfo: ReleaseNoteInfo, path: String) throws -> [String] {
print(
"""

\("New Release Details".underline)
version: \(version)
archivedBinaryCount: \(archivedBinaries.count)
projectFolderPath: \(path)
\(releaseNoteInfo.detailText)

"""
)
return []
}
}


// MARK: - Extension Dependencies
private extension ReleaseNoteInfo {
var detailText: String {
if isFromFile {
return "releaseNotesFilePath: \(content)"
}

return "releaseNotes: \(content)"
}
}
46 changes: 22 additions & 24 deletions Sources/nnex/Publish/Controllers/FormulaPublishController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ struct FormulaPublishController {
private let gitHandler: any GitHandler
private let fileSystem: any FileSystem
private let store: any PublishInfoStore
private let formulaFileService: any FormulaFileService

init(picker: any NnexPicker, gitHandler: any GitHandler, fileSystem: any FileSystem, store: any PublishInfoStore) {
init(picker: any NnexPicker, gitHandler: any GitHandler, fileSystem: any FileSystem, store: any PublishInfoStore, formulaFileService: any FormulaFileService) {
self.store = store
self.picker = picker
self.gitHandler = gitHandler
self.fileSystem = fileSystem
self.formulaFileService = formulaFileService
}
}

Expand All @@ -26,14 +28,9 @@ struct FormulaPublishController {
extension FormulaPublishController {
func publishFormula(projectFolder: any Directory, info: FormulaPublishInfo, commitMessage: String?) throws {
let formula = try getFormula(projectFolder: projectFolder, skipTests: true)
let content = try makeFormulaContent(formula: formula, info: info)
let contents = try makeFormulaContent(formula: formula, info: info)
let tapFolder = try fileSystem.directory(at: formula.tapLocalPath)
let fileName = "\(formula.name).rb"
let formulaFolder = try tapFolder.createSubfolderIfNeeded(named: "Formula")

try deleteOldFormula(named: fileName, from: formulaFolder)

let filePath = try formulaFolder.createFile(named: fileName, contents: content)
let filePath = try formulaFileService.resolveFormulaFile(formula: formula, tapFolder: tapFolder, contents: contents)

print("New formula created at \(filePath)")

Expand All @@ -46,6 +43,18 @@ extension FormulaPublishController {

// MARK: - Private Methods
private extension FormulaPublishController {
func getMessage(message: String?) throws -> String? {
if let message {
return message
}

guard picker.getPermission(prompt: "\nWould you like to commit and push the tap to \("GitHub".green)?") else {
return nil
}

return try picker.getRequiredInput(prompt: "Enter your commit message.")
}

func getFormula(projectFolder: any Directory, skipTests: Bool) throws -> HomebrewFormula {
let allTaps = try store.loadTaps()
let tap = try getTap(allTaps: allTaps, projectName: projectFolder.name)
Expand Down Expand Up @@ -177,22 +186,11 @@ private extension FormulaPublishController {
)
}
}

func deleteOldFormula(named name: String, from folder: any Directory) throws {
if folder.containsFile(named: name) {
try folder.deleteFile(named: name)
}
}

func getMessage(message: String?) throws -> String? {
if let message {
return message
}
}

guard picker.getPermission(prompt: "\nWould you like to commit and push the tap to \("GitHub".green)?") else {
return nil
}

return try picker.getRequiredInput(prompt: "Enter your commit message.")
}
// MARK: - Dependencies
protocol FormulaFileService {
typealias FilePath = String
func resolveFormulaFile(formula: HomebrewFormula, tapFolder: any Directory, contents: String) throws -> FilePath
}
Loading