Section 7/181 menit

7. Command Plugins

7. Command Plugins

Command Plugin tidak berjalan saat build — dijalankan secara eksplisit via swift package <plugin-name> atau dari Xcode menu. Cocok untuk code formatting, linting, dokumentasi generation, atau release automation.

Contoh: SwiftFormat Command Plugin

swift
// Package.swift — plugin package
import PackageDescription

let package = Package(
    name: "SwiftFormatPlugin",
    products: [
        .plugin(name: "SwiftFormatPlugin", targets: ["SwiftFormatPlugin"])
    ],
    dependencies: [
        .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.52.0")
    ],
    targets: [
        .plugin(
            name: "SwiftFormatPlugin",
            capability: .command(
                intent: .sourceCodeFormatting(),
                permissions: [
                    // Command plugin butuh izin eksplisit untuk write ke disk
                    .writeToPackageDirectory(reason: "Format Swift source files")
                ]
            ),
            dependencies: [
                .product(name: "swiftformat", package: "SwiftFormat")
            ]
        )
    ]
)
swift
// Sources/SwiftFormatPlugin/SwiftFormatPlugin.swift
import PackagePlugin
import Foundation

@main
struct SwiftFormatPlugin: CommandPlugin {
    func performCommand(context: PluginContext, arguments: [String]) async throws {
        let swiftformat = try context.tool(named: "swiftformat")
        
        // Parse custom arguments
        var argExtractor = ArgumentExtractor(arguments)
        let targetNames = argExtractor.extractOption(named: "target")
        let dryRun = argExtractor.extractFlag(named: "dry-run")
        
        // Pilih targets yang akan diformat
        let targetsToFormat: [Target]
        if targetNames.isEmpty {
            targetsToFormat = context.package.targets
        } else {
            targetsToFormat = try context.package.targets(named: targetNames)
        }
        
        for target in targetsToFormat {
            guard let sourceTarget = target as? SourceModuleTarget else { continue }
            
            let sourcePaths = sourceTarget.sourceFiles(withSuffix: "swift").map(\.path.string)
            guard !sourcePaths.isEmpty else { continue }
            
            var formatArgs = sourcePaths
            if dryRun {
                formatArgs.append("--dryrun")
            }
            
            // Jalankan swiftformat
            let process = Process()
            process.executableURL = URL(fileURLWithPath: swiftformat.path.string)
            process.arguments = formatArgs
            try process.run()
            process.waitUntilExit()
            
            if process.terminationStatus != 0 {
                throw PluginError.formattingFailed(target: target.name)
            }
            
            print("Formatted \(sourcePaths.count) files in target '\(target.name)'")
        }
    }
    
    enum PluginError: Error, LocalizedError {
        case formattingFailed(target: String)
        
        var errorDescription: String? {
            switch self {
            case .formattingFailed(let name):
                return "SwiftFormat failed for target '\(name)'"
            }
        }
    }
}

// Untuk Xcode
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension SwiftFormatPlugin: XcodeCommandPlugin {
    func performCommand(context: XcodePluginContext, arguments: [String]) throws {
        let swiftformat = try context.tool(named: "swiftformat")
        let swiftFiles = context.xcodeProject.filesToProcess
            .filter { $0.path.extension == "swift" }
            .map(\.path.string)
        
        let process = Process()
        process.executableURL = URL(fileURLWithPath: swiftformat.path.string)
        process.arguments = swiftFiles
        try process.run()
        process.waitUntilExit()
    }
}
#endif

Cara menjalankan:

swift
# Format semua target
swift package plugin swiftformat-plugin

# Format target tertentu
swift package plugin swiftformat-plugin --target MyFeature

# Dry run
swift package plugin swiftformat-plugin --dry-run