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
49 changes: 49 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Test

on:
push:
branches:
- main
pull_request:
branches:
- "*"

jobs:
test-xcode:
runs-on: macos-latest

steps:
- uses: actions/checkout@v6
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Build
run: swift build -v
- name: Build release
run: swift build -c release -v

test-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build
run: swift build -v
- name: Build release
run: swift build -c release -v

test-template:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Build saga
run: swift build -c release
- name: Scaffold a site and build it
run: |
mkdir -p "$RUNNER_TEMP/smoke"
cd "$RUNNER_TEMP/smoke"
"$GITHUB_WORKSPACE/.build/release/saga" init testsite
cd testsite
swift build
3 changes: 2 additions & 1 deletion .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
--indentcase true
--patternlet inline
--disable unusedArguments
--disable redundantReturn
--disable redundantReturn
--exclude .build,IntegrationTests/Fixture/Sources/Fixture/Generated.swift
4 changes: 2 additions & 2 deletions IntegrationTests/Fixture/Package.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// swift-tools-version:6.0
import PackageDescription

// Stands in for a Saga site so the shutdown tests don't need the real Saga
// dependency graph. Built and driven by ../run-shutdown-tests.sh.
/// Stands in for a Saga site so the shutdown tests don't need the real Saga
/// dependency graph. Built and driven by ../run-shutdown-tests.sh.
let package = Package(
name: "fixture",
products: [.executable(name: "Fixture", targets: ["Fixture"])],
Expand Down
7 changes: 3 additions & 4 deletions Sources/SagaCLI/DevCommand.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ArgumentParser
import Foundation
import SagaPathKit
import os

struct Dev: ParsableCommand {
static let configuration = CommandConfiguration(
Expand Down Expand Up @@ -40,7 +39,7 @@ private final class DevCoordinator: @unchecked Sendable {
var shuttingDown = false
}

private let lifecycle = OSAllocatedUnfairLock(initialState: Lifecycle())
private let lifecycle = Locked(initialState: Lifecycle())

/// Serializes recompiles. Shutdown never uses this, so Ctrl-C doesn't wait on
/// an in-flight build.
Expand All @@ -60,7 +59,7 @@ private final class DevCoordinator: @unchecked Sendable {
sigintSrc.setEventHandler { [weak self] in
print("\nShutting down...")
guard let self else { Foundation.exit(0) }
self.shutdown()
shutdown()
}
sigintSrc.resume()

Expand Down Expand Up @@ -97,7 +96,7 @@ private final class DevCoordinator: @unchecked Sendable {
return (process, false)
}
if alreadyShuttingDown {
dispatchMain() // shutdown() is mid-flight and exits the process
dispatchMain() // shutdown() is mid-flight and exits the process
}
guard let siteProcess else {
log("Failed to launch site process.")
Expand Down
25 changes: 11 additions & 14 deletions Sources/SagaCLI/DevServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ final class DevServer: @unchecked Sendable {
}

func start() throws {
let outputPath = self.outputPath
let sseConnections = self.sseConnections
let outputPath = outputPath
let sseConnections = sseConnections
let baseDir = FileManager.default.currentDirectoryPath

let bootstrap = ServerBootstrap(group: group)
Expand Down Expand Up @@ -111,7 +111,7 @@ private final class HTTPHandler: ChannelInboundHandler, @unchecked Sendable {
// Static file serving
let filePath = resolveFilePath(uri: uri)

guard let filePath = filePath,
guard let filePath,
FileManager.default.fileExists(atPath: filePath),
let data = FileManager.default.contents(atPath: filePath)
else {
Expand All @@ -122,11 +122,10 @@ private final class HTTPHandler: ChannelInboundHandler, @unchecked Sendable {
let contentType = mimeType(for: filePath)
let isHTML = contentType == "text/html"

var responseData: Data
if isHTML, let html = String(data: data, encoding: .utf8) {
responseData = Data(injectReloadScript(into: html).utf8)
var responseData: Data = if isHTML, let html = String(data: data, encoding: .utf8) {
Data(injectReloadScript(into: html).utf8)
} else {
responseData = data
data
}

var headers = HTTPHeaders()
Expand Down Expand Up @@ -166,9 +165,8 @@ private final class HTTPHandler: ChannelInboundHandler, @unchecked Sendable {

// Direct file match
let directPath = outputPath + path
if fileManager.fileExists(atPath: directPath) {
var isDir: ObjCBool = false
fileManager.fileExists(atPath: directPath, isDirectory: &isDir)
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: directPath, isDirectory: &isDir) {
if !isDir.boolValue {
return directPath
}
Expand Down Expand Up @@ -220,11 +218,10 @@ private final class HTTPHandler: ChannelInboundHandler, @unchecked Sendable {
}

private func mimeType(for path: String) -> String {
let ext: String
if let dotIndex = path.lastIndex(of: ".") {
ext = String(path[path.index(after: dotIndex)...]).lowercased()
let ext = if let dotIndex = path.lastIndex(of: ".") {
String(path[path.index(after: dotIndex)...]).lowercased()
} else {
ext = ""
""
}
switch ext {
case "html", "htm": return "text/html"
Expand Down
17 changes: 17 additions & 0 deletions Sources/SagaCLI/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,20 @@ func openBrowser(url: String) {
try? process.run()
#endif
}

/// State that can only be reached while its lock is held. Used instead of
/// `OSAllocatedUnfairLock`, which doesn't exist on Linux.
final class Locked<State>: @unchecked Sendable {
private var state: State
private let lock = NSLock()

init(initialState: State) {
state = initialState
}

func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R {
lock.lock()
defer { lock.unlock() }
return try body(&state)
}
}
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
build:
swift build

build-swift510:
docker run --rm -v "$PWD":/src -w /src --tmpfs /src/.build:exec swift:5.10 swift build
build-linux:
docker run --rm -v "$PWD":/src -w /src --tmpfs /src/.build:exec swift:6.2 swift build

format:
swiftformat -swift-version 5 .
swiftformat -swift-version 6 .