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