Skip to content

Commit 89e25c3

Browse files
committed
feat: Add a buildReason property to Saga
This allows you to only do certain work when certain files have changed. For example, only run SwiftTailwind when css or template files were changed.
1 parent b20787c commit 89e25c3

File tree

4 files changed

+55
-4
lines changed

4 files changed

+55
-4
lines changed

Sources/Saga/BuildReason.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import SagaPathKit
2+
3+
/// Describes why the current build was triggered.
4+
public enum BuildReason: Sendable {
5+
/// First build when the process starts (cold start).
6+
case initial
7+
8+
/// A non-Swift content/asset file changed during dev mode.
9+
case fileChange(Path)
10+
11+
/// A Swift source file changed, triggering recompilation and relaunch.
12+
case recompile(Path)
13+
14+
/// Returns the path of the file that triggered this build, if any.
15+
public func changedFile() -> Path? {
16+
switch self {
17+
case .initial:
18+
return nil
19+
case .fileChange(let path), .recompile(let path):
20+
return path
21+
}
22+
}
23+
}

Sources/Saga/Saga+Build.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ extension Saga {
8282

8383
fileIO.log("All done in \(elapsed(from: totalStart))")
8484
}
85+
86+
var recompileReasonPath: Path {
87+
rootPath + ".build/saga-recompile-reason"
88+
}
89+
90+
/// Read `.build/saga-recompile-reason` to determine if this launch was triggered by a Swift file change.
91+
/// If the file exists, sets ``buildReason`` to `.recompile` and deletes it.
92+
func readRecompileReason() {
93+
let path = recompileReasonPath
94+
guard path.exists, let contents: String = try? path.read(), !contents.isEmpty else { return }
95+
buildReason = .recompile(Path(contents))
96+
try? path.delete()
97+
}
98+
99+
/// Write `.build/saga-recompile-reason` so the next launch knows which Swift file triggered it.
100+
func writeRecompileReason(_ changedPath: String) {
101+
try? recompileReasonPath.write(changedPath)
102+
}
85103

86104
/// Watch for file changes and rebuild when content changes.
87105
/// Signals saga-cli via SIGUSR1 if Swift source files change (so it can recompile and relaunch).
@@ -90,15 +108,19 @@ extension Saga {
90108
let monitor = FolderMonitor(paths: watchPaths, ignoredPatterns: ignoredPatterns) { [weak self] changedPaths in
91109
guard let self else { return }
92110

93-
if changedPaths.contains(where: { $0.hasSuffix(".swift") }) {
94-
// Swift source changed — signal saga-cli to recompile and relaunch
111+
if let swiftFile = changedPaths.first(where: { $0.hasSuffix(".swift") }) {
112+
// Swift source changed — write the reason file and signal saga-cli to recompile and relaunch
113+
self.writeRecompileReason(swiftFile)
95114
self.signalParent(SIGUSR1)
96115
return
97116
}
98117

99118
Task {
100119
do {
101120
try self.reset()
121+
if let changedPath = changedPaths.first {
122+
self.buildReason = .fileChange(Path(changedPath))
123+
}
102124
try await self.build()
103125
self.signalParent(SIGUSR2)
104126
} catch {

Sources/Saga/Saga+Pipeline.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ extension Saga {
5454
output: (try? outputPath.relativePath(from: rootPath))?.string ?? "deploy"
5555
)
5656

57-
let configPath = rootPath + ".build/saga-config.json"
5857
if let data = try? JSONEncoder().encode(config) {
59-
try? data.write(to: URL(fileURLWithPath: configPath.string))
58+
let configPath = rootPath + ".build/saga-config.json"
59+
try? configPath.write(data)
6060
}
6161
}
6262

Sources/Saga/Saga.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class Saga: StepBuilder, @unchecked Sendable {
5252
/// Glob patterns to ignore during file watching in dev mode
5353
var ignoredPatterns: [String] = [".DS_Store"]
5454

55+
/// Why the current build was triggered.
56+
public internal(set) var buildReason: BuildReason = .initial
57+
5558
public init(input: Path, output: Path = "deploy", fileIO: FileIO = .diskAccess, originFilePath: StaticString = #filePath) throws {
5659
let originFile = Path("\(originFilePath)")
5760
let rootPath = try fileIO.resolveSwiftPackageFolder(originFile)
@@ -199,6 +202,9 @@ public class Saga: StepBuilder, @unchecked Sendable {
199202
// Write config file so saga-cli can detect output path for serving
200203
writeConfigFile()
201204

205+
// Check if this launch was triggered by a recompile
206+
readRecompileReason()
207+
202208
// Run the pipeline
203209
try await build()
204210

0 commit comments

Comments
 (0)