From 49523ba90e8414422fceca25093a1558d31158d9 Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 00:40:26 +0000 Subject: [PATCH 01/13] track running builds and completed builds separately --- .../Symbols/Identifiers/Symbol.Edition.swift | 41 --- .../Identifiers/Symbol.PackageAtRef.swift | 23 +- Sources/UnidocClient/Unidoc.Client.swift | 10 +- .../Building/Unidoc.BuildIdentifier.swift | 49 ++++ .../Building/Unidoc.BuildLogPath.swift | 17 +- .../Building/Unidoc.CompleteBuild.swift | 96 +++++++ .../Building/Unidoc.DB.CompleteBuilds.swift | 58 ++++ .../Building/Unidoc.DB.PendingBuilds.swift | 264 ++++++++++++++++++ .../Building/Unidoc.PendingBuild.swift | 89 ++++++ .../Building/Unidoc.PendingBuildDelta.swift | 34 +++ Sources/UnidocDB/Unidoc.DB.swift | 21 +- .../Mongo.PipelineEncoder (ext).swift | 70 ----- .../Unidoc.CompleteBuildsPageSegment.swift | 42 +++ .../Versions/Unidoc.CompleteBuildsQuery.swift | 5 + .../Unidoc.ConsumersPageSegment.swift | 82 ++++++ .../Unidoc.ConsumersQuery.Output.swift | 49 ---- .../Versions/Unidoc.ConsumersQuery.swift | 58 +--- .../Versions/Unidoc.PackagePageOutput.swift | 49 ++++ .../Versions/Unidoc.PackagePageQuery.swift | 66 +++++ .../Versions/Unidoc.PackagePageSegment.swift | 18 ++ .../Unidoc.VersionsQuery.Output.swift | 16 +- .../Versions/Unidoc.VersionsQuery.swift | 27 +- .../Building/Unidoc.BuildArtifact.swift | 24 +- .../Building/Unidoc.BuildLogType.swift | 12 +- .../Building/Unidoc.BuildReport.swift | 14 +- .../Symbol.PackageAtRef (ext).swift | 6 + Sources/UnidocRender/Unidoc.ServerRoot.swift | 1 + ...Unidoc.BuildCoordinator.Notification.swift | 32 +-- .../Building/Unidoc.BuildCoordinator.swift | 6 +- .../Unidoc.BuilderPollOperation.swift | 2 +- .../Unidoc.PackageBuildOperation.Action.swift | 12 - ...ckageBuildOperation.DirectParameters.swift | 68 ----- .../Unidoc.PackageBuildOperation.swift | 91 ++---- .../Unidoc.BuildArtifact (ext).swift | 9 +- .../Unidoc.BuilderUploadOperation.swift | 61 ++-- .../Requests/Unidoc.BuildRequestPage.swift | 68 +---- .../UnidocServer/Requests/Unidoc.Router.swift | 15 +- .../Endpoints/Tags/Unidoc.BuildButton.swift | 94 ------- .../Tags/Unidoc.BuildForm.Action.swift | 34 +++ .../Endpoints/Tags/Unidoc.BuildForm.swift | 42 +++ .../Tags/Unidoc.BuildFormTool.Inhibitor.swift | 11 + .../Endpoints/Tags/Unidoc.BuildFormTool.swift | 142 ++++++++++ .../Endpoints/Tags/Unidoc.BuildTools.swift | 88 ++++++ .../Tags/Unidoc.CompleteBuildsEndpoint.swift | 72 +++++ .../Tags/Unidoc.CompleteBuildsTable.swift | 143 ++++++++++ .../Tags/Unidoc.ConsumersEndpoint.swift | 17 +- .../Tags/Unidoc.ConsumersTable.Row.swift | 43 --- .../Tags/Unidoc.ConsumersTable.swift | 47 +++- ...e.swift => Unidoc.PackageCursorPage.swift} | 23 +- .../Endpoints/Tags/Unidoc.RefsEndpoint.swift | 79 +++++- .../Tags/Unidoc.RefsPage.BuildTools.swift | 231 --------------- .../Tags/Unidoc.RefsPage.Heading.swift | 6 + .../Endpoints/Tags/Unidoc.RefsPage.swift | 62 ++-- .../Tags/Unidoc.RefsTable.Row.Graph.swift | 10 +- .../Collections/Mongo.CollectionModel.swift | 53 +++- 55 files changed, 1768 insertions(+), 1034 deletions(-) delete mode 100644 Sources/Symbols/Identifiers/Symbol.Edition.swift create mode 100644 Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift rename Sources/{UnidocRecords => UnidocDB}/Building/Unidoc.BuildLogPath.swift (60%) create mode 100644 Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift create mode 100644 Sources/UnidocDB/Building/Unidoc.DB.CompleteBuilds.swift create mode 100644 Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift create mode 100644 Sources/UnidocDB/Building/Unidoc.PendingBuild.swift create mode 100644 Sources/UnidocDB/Building/Unidoc.PendingBuildDelta.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsPageSegment.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsQuery.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.ConsumersPageSegment.swift delete mode 100644 Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.Output.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.PackagePageOutput.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.PackagePageQuery.swift create mode 100644 Sources/UnidocQueries/Versions/Unidoc.PackagePageSegment.swift create mode 100644 Sources/UnidocRecords/Symbol.PackageAtRef (ext).swift delete mode 100644 Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.Action.swift delete mode 100644 Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.DirectParameters.swift delete mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.Action.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.Inhibitor.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift delete mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.Row.swift rename Sources/UnidocUI/Endpoints/Tags/{Unidoc.ConsumersPage.swift => Unidoc.PackageCursorPage.swift} (66%) delete mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.BuildTools.swift diff --git a/Sources/Symbols/Identifiers/Symbol.Edition.swift b/Sources/Symbols/Identifiers/Symbol.Edition.swift deleted file mode 100644 index 2d6755ef9..000000000 --- a/Sources/Symbols/Identifiers/Symbol.Edition.swift +++ /dev/null @@ -1,41 +0,0 @@ -extension Symbol -{ - @frozen public - struct Edition:Equatable, Hashable, Sendable - { - public - var package:Package - /// A name corresponding to a git ref. - public - var ref:String - - @inlinable public - init(package:Package, ref:String) - { - self.package = package - self.ref = ref - } - } -} -extension Symbol.Edition:CustomStringConvertible -{ - @inlinable public - var description:String { "\(self.package)/\(self.ref)" } -} -extension Symbol.Edition:LosslessStringConvertible -{ - @inlinable public - init?(_ description:some StringProtocol) - { - if let slash:String.Index = description.firstIndex(of: "/") - { - self.init( - package: .init(description[.. /// Listens for SSGC updates over the provided pipe, uploading any intermediate reports to /// Unidoc server and returning the final report, without uploading it. private - func stream(from pipe:FilePath, package:Unidoc.Package) async throws -> Unidoc.BuildFailure? + func stream(from pipe:FilePath, edition:Unidoc.Edition) async throws -> Unidoc.BuildFailure? { try await SSGC.StatusStream.read(from: pipe) { // Acknowledge the build request. try? await self.connect { - try await $0.upload(.init(package: package, entered: .cloningRepository)) + try await $0.upload(.init(edition: edition, entered: .cloningRepository)) } while let update:SSGC.StatusUpdate = try $0.next() @@ -119,7 +119,7 @@ extension Unidoc.Client try await self.connect { - try await $0.upload(.init(package: package, entered: stage)) + try await $0.upload(.init(edition: edition, entered: stage)) } } @@ -237,9 +237,9 @@ extension Unidoc.Client } let failure:Unidoc.BuildFailure? = try await self.stream(from: status, - package: labels.coordinate.package) + edition: labels.coordinate) - var artifact:Unidoc.BuildArtifact = .init(package: labels.coordinate.package, + var artifact:Unidoc.BuildArtifact = .init(edition: labels.coordinate, outcome: .failure(failure ?? .failedForUnknownReason)) // Check the exit status of the child process. diff --git a/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift new file mode 100644 index 000000000..41fa67012 --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift @@ -0,0 +1,49 @@ +import BSON +import MongoQL +import UnidocRecords +import UnixTime + +extension Unidoc +{ + @frozen public + struct BuildIdentifier:Equatable, Hashable, Sendable + { + public + let edition:Unidoc.Edition + public + let instant:UnixMillisecond + + @inlinable public + init(edition:Unidoc.Edition, instant:UnixMillisecond) + { + self.edition = edition + self.instant = instant + } + } +} +extension Unidoc.BuildIdentifier:Mongo.MasterCodingModel +{ + @frozen public + enum CodingKey:String, Sendable, BSONDecodable + { + case edition = "e" + case instant = "T" + } +} +extension Unidoc.BuildIdentifier:BSONDocumentEncodable +{ + public + func encode(to bson:inout BSON.DocumentEncoder) + { + bson[.edition] = self.edition + bson[.instant] = self.instant + } +} +extension Unidoc.BuildIdentifier:BSONDocumentDecodable +{ + @inlinable public + init(bson:BSON.DocumentDecoder) throws + { + self.init(edition: try bson[.edition].decode(), instant: try bson[.instant].decode()) + } +} diff --git a/Sources/UnidocRecords/Building/Unidoc.BuildLogPath.swift b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift similarity index 60% rename from Sources/UnidocRecords/Building/Unidoc.BuildLogPath.swift rename to Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift index b97571681..792afdd50 100644 --- a/Sources/UnidocRecords/Building/Unidoc.BuildLogPath.swift +++ b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift @@ -1,17 +1,21 @@ +import UnidocRecords +import UnixCalendar +import UnixTime + extension Unidoc { @frozen public struct BuildLogPath { public - let package:Package + let id:BuildIdentifier public let type:BuildLogType @inlinable public - init(package:Package, type:BuildLogType) + init(id:BuildIdentifier, type:BuildLogType) { - self.package = package + self.id = id self.type = type } } @@ -23,7 +27,12 @@ extension Unidoc.BuildLogPath var prefix:String { // As this is public-facing, we want it to be at least somewhat human-readable. - "builds/\(self.package)/\(self.type.name).log" + """ + logs/\ + \(self.id.instant.timestamp?.date.description ?? "0000-00-00")/\ + \(self.id.edition.package)/\ + \(self.id.edition.version).\(self.type.name).log + """ } } extension Unidoc.BuildLogPath:CustomStringConvertible diff --git a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift new file mode 100644 index 000000000..43e48f591 --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift @@ -0,0 +1,96 @@ +import BSON +import MongoQL +import UnidocAPI +import UnidocRecords +import UnixTime + +extension Unidoc +{ + @frozen public + struct CompleteBuild:Sendable + { + public + let edition:Edition + + public + let launched:UnixMillisecond + public + let finished:UnixMillisecond + + public + var failure:BuildFailure? + public + var logs:[BuildLogType] + + @inlinable public + init(edition:Edition, + launched:UnixMillisecond, + finished:UnixMillisecond, + failure:BuildFailure?, + logs:[BuildLogType]) + { + self.edition = edition + self.launched = launched + self.finished = finished + self.failure = failure + self.logs = logs + } + } +} +extension Unidoc.CompleteBuild +{ + @inlinable public + init(id:Unidoc.BuildIdentifier, + finished:UnixMillisecond, + failure:Unidoc.BuildFailure?, + logs:[Unidoc.BuildLogType]) + { + self.init(edition: id.edition, + launched: id.instant, + finished: finished, + failure: failure, + logs: logs) + } +} +extension Unidoc.CompleteBuild:Identifiable +{ + @inlinable public + var id:Unidoc.BuildIdentifier { .init(edition: self.edition, instant: self.launched) } +} +extension Unidoc.CompleteBuild:Mongo.MasterCodingModel +{ + @frozen public + enum CodingKey:String, Sendable, BSONDecodable + { + case id = "_id" + case finished = "F" + case failure = "E" + case logs = "O" + + case package = "p" + } +} +extension Unidoc.CompleteBuild:BSONDocumentEncodable +{ + public + func encode(to bson:inout BSON.DocumentEncoder) + { + bson[.id] = self.id + bson[.finished] = self.finished + bson[.failure] = self.failure + bson[.logs] = self.logs.isEmpty ? nil : self.logs + + bson[.package] = self.id.edition.package + } +} +extension Unidoc.CompleteBuild:BSONDocumentDecodable +{ + @inlinable public + init(bson:BSON.DocumentDecoder) throws + { + self.init(id: try bson[.id].decode(), + finished: try bson[.finished].decode(), + failure: try bson[.failure]?.decode(), + logs: try bson[.logs]?.decode() ?? []) + } +} diff --git a/Sources/UnidocDB/Building/Unidoc.DB.CompleteBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.CompleteBuilds.swift new file mode 100644 index 000000000..1fbaab30c --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.DB.CompleteBuilds.swift @@ -0,0 +1,58 @@ +import BSON +import MongoDB +import UnidocAPI +import UnidocRecords +import UnixTime + +extension Unidoc.DB +{ + @frozen public + struct CompleteBuilds + { + public + let database:Mongo.Database + public + let session:Mongo.Session + + @inlinable + init(database:Mongo.Database, session:Mongo.Session) + { + self.database = database + self.session = session + } + } +} +extension Unidoc.DB.CompleteBuilds +{ + /// It’s really unlikely that we’ll have duplicate timestamps, especially for the same + /// package, but it’s not impossible. + public static + let indexFinished:Mongo.CollectionIndex = .init("Finished") + { + $0[Unidoc.CompleteBuild[.finished]] = (-) + } + + public static + let indexFinishedByPackage:Mongo.CollectionIndex = .init("FinishedByPackage") + { + $0[Unidoc.CompleteBuild[.package]] = (+) + $0[Unidoc.CompleteBuild[.finished]] = (-) + } +} +extension Unidoc.DB.CompleteBuilds:Mongo.CollectionModel +{ + public + typealias Element = Unidoc.CompleteBuild + + @inlinable public static + var name:Mongo.Collection { "CompleteBuilds" } + + @inlinable public static + var indexes:[Mongo.CollectionIndex] + { + [ + Self.indexFinished, + Self.indexFinishedByPackage + ] + } +} diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift new file mode 100644 index 000000000..675cab67c --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift @@ -0,0 +1,264 @@ +import BSON +import MongoDB +import Symbols +import UnidocAPI +import UnidocRecords +import UnixTime + +extension Unidoc.DB +{ + @frozen public + struct PendingBuilds + { + public + let database:Mongo.Database + public + let session:Mongo.Session + + @inlinable + init(database:Mongo.Database, session:Mongo.Session) + { + self.database = database + self.session = session + } + } +} +extension Unidoc.DB.PendingBuilds +{ + public static + let indexEnqueued:Mongo.CollectionIndex = .init("Enqueued", unique: false) + { + $0[Unidoc.PendingBuild[.enqueued]] = (+) + } + where: + { + $0[Unidoc.PendingBuild[.enqueued]] { $0[.exists] = true } + } + + public static + let indexLaunched:Mongo.CollectionIndex = .init("Launched", unique: false) + { + $0[Unidoc.PendingBuild[.launched]] = (+) + } + where: + { + $0[Unidoc.PendingBuild[.launched]] { $0[.exists] = true } + } + + public static + let indexPackage:Mongo.CollectionIndex = .init("Package", unique: false) + { + $0[Unidoc.PendingBuild[.package]] = (+) + } +} +extension Unidoc.DB.PendingBuilds:Mongo.CollectionModel +{ + public + typealias Element = Unidoc.PendingBuild + + @inlinable public static + var name:Mongo.Collection { "PendingBuilds" } + + @inlinable public static + var indexes:[Mongo.CollectionIndex] + { + [ + Self.indexEnqueued, + Self.indexLaunched, + Self.indexPackage + ] + } +} +extension Unidoc.DB.PendingBuilds +{ + public + func selectBuild(await awaits:Bool) async throws -> Unidoc.PendingBuild? + { + // Find a build, any build... + if let pendingBuild:Unidoc.PendingBuild = try await session.run( + command: Mongo.Find>.init(Self.name, limit: 1) + { + $0[.filter] + { + $0[Unidoc.PendingBuild[.enqueued]] { $0[.exists] = true } + } + $0[.sort] + { + $0[Unidoc.PendingBuild[.enqueued]] = (+) + } + $0[.hint] = Self.indexEnqueued.id + }, + against: self.database) + { + return pendingBuild + } + + guard awaits + else + { + return nil + } + + let startTime:BSON.Timestamp? = session.preconditionTime + + // Open a change stream and wait for a build to be enqueued... + return try await session.run( + command: Mongo.Aggregate>>.init(Self.name, + tailing: .init(timeout: .milliseconds(30_000), awaits: true)) + { + $0[stage: .changeStream] + { + // This prevents us from missing any builds enqueued between when we ran + // the find query and when we open the change stream. + $0[.startAtOperationTime] = startTime + } + // TODO: filter change events + }, + against: self.database) + { + for try await events:[Mongo.ChangeEvent] in $0 + { + for event:Mongo.ChangeEvent in events + { + switch event.change + { + case .replace(_, before: _, after: let build): return build + case .insert(let build): return build + default: continue + } + } + } + + return nil + } + } + + /// Adds a build to the queue, if it is not already queued, or returns the existing build. + public + func submitBuild(id:Unidoc.Edition, + name:Symbol.PackageAtRef) async throws -> Unidoc.PendingBuild + { + let (pendingBuild, _):(Unidoc.PendingBuild, Bool) = try await self.modify( + upserting: id, + returning: .new) + { + $0[.setOnInsert] + { + $0[Unidoc.PendingBuild[.id]] = id + $0[Unidoc.PendingBuild[.enqueued]] = UnixMillisecond.now() + $0[Unidoc.PendingBuild[.name]] = name + } + } + return pendingBuild + } + + /// Cancels a build, if it has not yet been launched. + public + func cancelBuild(id:Unidoc.Edition) async throws -> Bool + { + try await self.delete + { + $0[Unidoc.PendingBuild[.id]] = id + $0[Unidoc.PendingBuild[.launched]] { $0[.exists] = false } + } + } + + public + func assignBuild(id:Unidoc.Edition, to assignee:Unidoc.Account) async throws -> Bool + { + let (update, _):(Unidoc.PendingBuild?, Never?) = try await session.run( + command: Mongo.FindAndModify>.init(Self.name, + returning: .new) + { + $0[.query] + { + $0[Unidoc.PendingBuild[.id]] = id + $0[Unidoc.PendingBuild[.assignee]] { $0[.exists] = false } + } + $0[.update] + { + $0[.unset] + { + $0[Unidoc.PendingBuild[.enqueued]] = () + } + $0[.set] + { + $0[Unidoc.PendingBuild[.assignee]] = assignee + $0[Unidoc.PendingBuild[.launched]] = UnixMillisecond.now() + $0[Unidoc.PendingBuild[.stage]] = Unidoc.BuildStage.initializing + } + } + }, + against: self.database) + + return update != nil + } + + @discardableResult + public + func updateBuild(id:Unidoc.Edition, + entered stage:Unidoc.BuildStage) async throws -> Unidoc.PendingBuild? + { + try await self.modify(existing: id, returning: .new) + { + $0[.set] { $0[Unidoc.PendingBuild[.stage]] = stage } + } + } + + func finishBuild(id:Unidoc.Edition) async throws -> Unidoc.PendingBuild? + { + try await self.remove + { + $0[Unidoc.PendingBuild[.id]] = id + $0[Unidoc.PendingBuild[.launched]] { $0[.exists] = true } + } + } + + public + func lintBuilds(startedBefore:UnixMillisecond) async throws -> Int + { + try await self.deleteAll + { + $0[Unidoc.PendingBuild[.launched]] { $0[.exists] = true } + $0[Unidoc.PendingBuild[.launched]] { $0[.lt] = startedBefore } + } + } + + public + func killBuilds(by assignee:Unidoc.Account) async throws -> Int + { + try await self.deleteAll + { + $0[Unidoc.PendingBuild[.launched]] { $0[.exists] = true } + $0[Unidoc.PendingBuild[.assignee]] = assignee + } + } +} +extension Unidoc.DB.PendingBuilds +{ + public + func completeBuild(id:Unidoc.Edition, + duration:Seconds) async throws -> (launched:UnixMillisecond, finished:UnixMillisecond) + { + let launched:UnixMillisecond + let finished:UnixMillisecond + + if let finishedBuild:Unidoc.PendingBuild = try await self.finishBuild(id: id), + let instant:UnixMillisecond = finishedBuild.launched + { + launched = instant + finished = launched.advanced(by: .init(duration)) + } + else + { + // Build was timed out on the server side, but still delivered to the server. + // In this situation, we use the current time as the finish time, and work out + // launch time by subtracting the duration from the finish time. + finished = .now() + launched = finished.regressed(by: .init(duration)) + } + + return (launched, finished) + } +} diff --git a/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift new file mode 100644 index 000000000..c8e7c5acb --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift @@ -0,0 +1,89 @@ +import BSON +import MongoQL +import Symbols +import UnidocAPI +import UnidocRecords +import UnixTime + +extension Unidoc +{ + @frozen public + struct PendingBuild:Identifiable, Sendable + { + public + let id:Edition + + public + let enqueued:UnixMillisecond? + public + let launched:UnixMillisecond? + + public + let assignee:Account? + public + var stage:BuildStage? + + /// Used for display purposes only. + public + let name:Symbol.PackageAtRef + + @inlinable public + init(id:Edition, + enqueued:UnixMillisecond?, + launched:UnixMillisecond?, + assignee:Account?, + stage:BuildStage?, + name:Symbol.PackageAtRef) + { + self.id = id + self.enqueued = enqueued + self.launched = launched + self.assignee = assignee + self.stage = stage + self.name = name + } + } +} +extension Unidoc.PendingBuild:Mongo.MasterCodingModel +{ + @frozen public + enum CodingKey:String, Sendable, BSONDecodable + { + case id = "_id" + case enqueued = "Q" + case launched = "L" + case assignee = "A" + case stage = "S" + case name = "N" + + case package = "p" + } +} +extension Unidoc.PendingBuild:BSONDocumentEncodable +{ + public + func encode(to bson:inout BSON.DocumentEncoder) + { + bson[.id] = self.id + bson[.enqueued] = self.enqueued + bson[.launched] = self.launched + bson[.assignee] = self.assignee + bson[.stage] = self.stage + bson[.name] = self.name + + bson[.package] = self.id.package + } +} +extension Unidoc.PendingBuild:BSONDocumentDecodable +{ + @inlinable public + init(bson:BSON.DocumentDecoder) throws + { + self.init(id: try bson[.id].decode(), + enqueued: try bson[.enqueued]?.decode(), + launched: try bson[.launched]?.decode(), + assignee: try bson[.assignee]?.decode(), + stage: try bson[.stage]?.decode(), + name: try bson[.name].decode()) + } +} diff --git a/Sources/UnidocDB/Building/Unidoc.PendingBuildDelta.swift b/Sources/UnidocDB/Building/Unidoc.PendingBuildDelta.swift new file mode 100644 index 000000000..9fa125235 --- /dev/null +++ b/Sources/UnidocDB/Building/Unidoc.PendingBuildDelta.swift @@ -0,0 +1,34 @@ +import BSON +import MongoQL +import UnixTime + +extension Unidoc +{ + struct PendingBuildDelta:Sendable + { + var enqueued:UnixMillisecond? + var launched:UnixMillisecond? + var stage:BuildStage? + + init(enqueued:UnixMillisecond?, launched:UnixMillisecond?, stage:BuildStage?) + { + self.enqueued = enqueued + self.launched = launched + self.stage = stage + } + } +} +extension Unidoc.PendingBuildDelta:Mongo.MasterCodingDelta +{ + typealias Model = Unidoc.PendingBuild +} +extension Unidoc.PendingBuildDelta:BSONDocumentDecodable +{ + init(bson:BSON.DocumentDecoder) throws + { + self.init( + enqueued: try bson[.enqueued]?.decode(), + launched: try bson[.launched]?.decode(), + stage: try bson[.stage]?.decode()) + } +} diff --git a/Sources/UnidocDB/Unidoc.DB.swift b/Sources/UnidocDB/Unidoc.DB.swift index 05f340532..f60f86933 100644 --- a/Sources/UnidocDB/Unidoc.DB.swift +++ b/Sources/UnidocDB/Unidoc.DB.swift @@ -54,37 +54,43 @@ extension Unidoc.DB } @inlinable public - var repoFeed:RepoFeed + var pendingBuilds:PendingBuilds { .init(database: self.id, session: self.session) } @inlinable public - var docsFeed:DocsFeed + var completeBuilds:CompleteBuilds { .init(database: self.id, session: self.session) } @inlinable public - var realmAliases:RealmAliases + var repoFeed:RepoFeed { .init(database: self.id, session: self.session) } @inlinable public - var realms:Realms + var docsFeed:DocsFeed { .init(database: self.id, session: self.session) } @inlinable public - var packageAliases:PackageAliases + var realmAliases:RealmAliases { .init(database: self.id, session: self.session) } @inlinable public - var packageBuilds:PackageBuilds + var realms:Realms + { + .init(database: self.id, session: self.session) + } + + @inlinable public + var packageAliases:PackageAliases { .init(database: self.id, session: self.session) } @@ -176,11 +182,12 @@ extension Unidoc.DB:Mongo.DatabaseModel try await self.crawlingWindows.setup() try await self.repoFeed.setup() try await self.docsFeed.setup() + try await self.pendingBuilds.setup() + try await self.completeBuilds.setup() try await self.realmAliases.setup() try await self.realms.setup() try await self.packageAliases.setup() - try await self.packageBuilds.setup() try await self.packages.setup() try await self.editions.setup() try await self.snapshots.setup() diff --git a/Sources/UnidocQueries/Mongo.PipelineEncoder (ext).swift b/Sources/UnidocQueries/Mongo.PipelineEncoder (ext).swift index 7884ae9fb..d39a864e0 100644 --- a/Sources/UnidocQueries/Mongo.PipelineEncoder (ext).swift +++ b/Sources/UnidocQueries/Mongo.PipelineEncoder (ext).swift @@ -66,76 +66,6 @@ extension Mongo.PipelineEncoder } } extension Mongo.PipelineEncoder -{ - mutating - func loadDependents( - limit:Int, - skip:Int = 0, - from package:Mongo.AnyKeyPath, - into output:Mongo.AnyKeyPath) - { - self[stage: .lookup] - { - $0[.from] = Unidoc.DB.PackageDependencies.name - $0[.localField] = package / Unidoc.PackageMetadata[.id] - $0[.foreignField] = Unidoc.PackageDependency[.id] - / Unidoc.Edge[.target] - - $0[.pipeline] - { - $0[stage: .skip] = skip == 0 ? nil : skip - $0[stage: .limit] = limit - - $0[stage: .replaceWith] = .init - { - $0[Unidoc.PackageDependent[.package]] = Unidoc.PackageDependency[.id] - / Unidoc.Edge[.source] - $0[Unidoc.PackageDependent[.edition]] = Unidoc.PackageDependency[.source] - } - - // Look up volume metadata, if it exists. - $0[stage: .lookup] - { - $0[.from] = Unidoc.DB.Volumes.name - $0[.localField] = Unidoc.PackageDependent[.edition] - $0[.foreignField] = Unidoc.VolumeMetadata[.id] - $0[.as] = Unidoc.PackageDependent[.volume] - } - - // Unbox single- or zero-element array. - $0[stage: .set, using: Unidoc.PackageDependent.CodingKey.self] - { - $0[.volume] { $0[.first] = Unidoc.PackageDependent[.volume] } - } - - // Look up edition metadata - $0[stage: .lookup] - { - $0[.from] = Unidoc.DB.Editions.name - $0[.localField] = Unidoc.PackageDependent[.edition] - $0[.foreignField] = Unidoc.EditionMetadata[.id] - $0[.as] = Unidoc.PackageDependent[.edition] - } - // The edition metadata is mandatory. - $0[stage: .unwind] = Unidoc.PackageDependent[.edition] - - // Look up package metadata - $0[stage: .lookup] - { - $0[.from] = Unidoc.DB.Packages.name - $0[.localField] = Unidoc.PackageDependent[.package] - $0[.foreignField] = Unidoc.PackageMetadata[.id] - $0[.as] = Unidoc.PackageDependent[.package] - } - // The package metadata is mandatory. - $0[stage: .unwind] = Unidoc.PackageDependent[.package] - } - - $0[.as] = output - } - } -} -extension Mongo.PipelineEncoder { mutating func loadBranches( diff --git a/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsPageSegment.swift b/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsPageSegment.swift new file mode 100644 index 000000000..2da2070e2 --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsPageSegment.swift @@ -0,0 +1,42 @@ +import MongoQL +import SymbolGraphs +import Symbols +import UnidocDB +import UnidocRecords + +extension Unidoc +{ + public + enum CompleteBuildsPageSegment:PackagePageSegment + { + public + typealias Item = CompleteBuild + + public static + func bridge(pipeline self:inout Mongo.PipelineEncoder, + limit:Int, + skip:Int = 0, + from package:Mongo.AnyKeyPath, + into output:Mongo.AnyKeyPath) + { + self[stage: .lookup] + { + $0[.from] = DB.CompleteBuilds.name + $0[.localField] = package / PackageMetadata[.id] + $0[.foreignField] = CompleteBuild[.package] + + $0[.pipeline] + { + $0[stage: .sort, using: CompleteBuild.CodingKey.self] + { + $0[.finished] = (-) + } + $0[stage: .skip] = skip == 0 ? nil : skip + $0[stage: .limit] = limit + } + + $0[.as] = output + } + } + } +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsQuery.swift new file mode 100644 index 000000000..be1104b74 --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.CompleteBuildsQuery.swift @@ -0,0 +1,5 @@ +extension Unidoc +{ + public + typealias CompleteBuildsQuery = PackagePageQuery +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.ConsumersPageSegment.swift b/Sources/UnidocQueries/Versions/Unidoc.ConsumersPageSegment.swift new file mode 100644 index 000000000..8c1a7dc4e --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.ConsumersPageSegment.swift @@ -0,0 +1,82 @@ +import MongoQL +import SymbolGraphs +import Symbols +import UnidocDB +import UnidocRecords + +extension Unidoc +{ + public + enum ConsumersPageSegment:PackagePageSegment + { + public + typealias Item = PackageDependent + + public static + func bridge(pipeline self:inout Mongo.PipelineEncoder, + limit:Int, + skip:Int = 0, + from package:Mongo.AnyKeyPath, + into output:Mongo.AnyKeyPath) + { + self[stage: .lookup] + { + $0[.from] = DB.PackageDependencies.name + $0[.localField] = package / PackageMetadata[.id] + $0[.foreignField] = PackageDependency[.id] / Edge[.target] + + $0[.pipeline] + { + $0[stage: .skip] = skip == 0 ? nil : skip + $0[stage: .limit] = limit + + $0[stage: .replaceWith] = .init + { + $0[PackageDependent[.package]] = PackageDependency[.id] + / Edge[.source] + $0[PackageDependent[.edition]] = PackageDependency[.source] + } + + // Look up volume metadata, if it exists. + $0[stage: .lookup] + { + $0[.from] = DB.Volumes.name + $0[.localField] = PackageDependent[.edition] + $0[.foreignField] = VolumeMetadata[.id] + $0[.as] = PackageDependent[.volume] + } + + // Unbox single- or zero-element array. + $0[stage: .set, using: PackageDependent.CodingKey.self] + { + $0[.volume] { $0[.first] = PackageDependent[.volume] } + } + + // Look up edition metadata + $0[stage: .lookup] + { + $0[.from] = DB.Editions.name + $0[.localField] = PackageDependent[.edition] + $0[.foreignField] = EditionMetadata[.id] + $0[.as] = PackageDependent[.edition] + } + // The edition metadata is mandatory. + $0[stage: .unwind] = PackageDependent[.edition] + + // Look up package metadata + $0[stage: .lookup] + { + $0[.from] = DB.Packages.name + $0[.localField] = PackageDependent[.package] + $0[.foreignField] = PackageMetadata[.id] + $0[.as] = PackageDependent[.package] + } + // The package metadata is mandatory. + $0[stage: .unwind] = PackageDependent[.package] + } + + $0[.as] = output + } + } + } +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.Output.swift b/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.Output.swift deleted file mode 100644 index b1b5606b4..000000000 --- a/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.Output.swift +++ /dev/null @@ -1,49 +0,0 @@ -import BSON -import MongoQL -import UnidocDB -import UnidocRecords - -extension Unidoc.ConsumersQuery -{ - @frozen public - struct Output - { - public - let dependency:Unidoc.PackageMetadata - public - let dependents:[Unidoc.PackageDependent] - public - let user:Unidoc.User? - - @inlinable public - init(dependency:Unidoc.PackageMetadata, - dependents:[Unidoc.PackageDependent], - user:Unidoc.User?) - { - self.dependency = dependency - self.dependents = dependents - self.user = user - } - } -} -extension Unidoc.ConsumersQuery.Output:Mongo.MasterCodingModel -{ - @frozen public - enum CodingKey:String, Sendable - { - case dependency - case dependents - case user - } -} -extension Unidoc.ConsumersQuery.Output:BSONDocumentDecodable -{ - public - init(bson:BSON.DocumentDecoder) throws - { - self.init( - dependency: try bson[.dependency].decode(), - dependents: try bson[.dependents].decode(), - user: try bson[.user]?.decode()) - } -} diff --git a/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.swift index d3712501f..0a8521be9 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.ConsumersQuery.swift @@ -1,61 +1,5 @@ -import MongoDB -import SymbolGraphs -import Symbols -import UnidocDB -import UnidocRecords - extension Unidoc { - @frozen public - struct ConsumersQuery:Equatable, Sendable - { - public - let symbol:Symbol.Package - public - let limit:Int - public - let page:Int - - /// We don’t use this yet, but it’s here for future expansion. - public - let user:Account? - - @inlinable public - init(symbol:Symbol.Package, limit:Int, page:Int, as user:Account? = nil) - { - self.symbol = symbol - self.limit = limit - self.page = page - self.user = user - } - } -} -extension Unidoc.ConsumersQuery:Mongo.PipelineQuery -{ - public - typealias Iteration = Mongo.Single -} -extension Unidoc.ConsumersQuery:Unidoc.AliasingQuery -{ - public - typealias CollectionOrigin = Unidoc.DB.PackageAliases - public - typealias CollectionTarget = Unidoc.DB.Packages - - @inlinable public static - var target:Mongo.AnyKeyPath { Output[.dependency] } - public - func extend(pipeline:inout Mongo.PipelineEncoder) - { - pipeline.loadDependents(limit: self.limit, - skip: self.limit * self.page, - from: Self.target, - into: Output[.dependents]) - - if let id:Unidoc.Account = self.user - { - pipeline.loadUser(matching: id, as: Output[.user]) - } - } + typealias ConsumersQuery = PackagePageQuery } diff --git a/Sources/UnidocQueries/Versions/Unidoc.PackagePageOutput.swift b/Sources/UnidocQueries/Versions/Unidoc.PackagePageOutput.swift new file mode 100644 index 000000000..0f60b42d0 --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.PackagePageOutput.swift @@ -0,0 +1,49 @@ +import BSON +import MongoQL +import UnidocDB +import UnidocRecords + +extension Unidoc +{ + @frozen public + struct PackagePageOutput where Item:BSONDecodable + { + public + let package:PackageMetadata + public + let list:[Item] + public + let user:Unidoc.User? + + @inlinable public + init(package:PackageMetadata, list:[Item], user:User?) + { + self.package = package + self.list = list + self.user = user + } + } +} +extension Unidoc.PackagePageOutput:Sendable where Item:Sendable +{ +} +extension Unidoc.PackagePageOutput:Mongo.MasterCodingModel +{ + @frozen public + enum CodingKey:String, Sendable + { + case package + case list + case user + } +} +extension Unidoc.PackagePageOutput:BSONDocumentDecodable, BSONDecodable +{ + public + init(bson:BSON.DocumentDecoder) throws + { + self.init(package: try bson[.package].decode(), + list: try bson[.list].decode(), + user: try bson[.user]?.decode()) + } +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.PackagePageQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.PackagePageQuery.swift new file mode 100644 index 000000000..744fee1d8 --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.PackagePageQuery.swift @@ -0,0 +1,66 @@ +import MongoQL +import SymbolGraphs +import Symbols +import UnidocDB +import UnidocRecords + +extension Unidoc +{ + @frozen public + struct PackagePageQuery:Equatable, Sendable + where PageSegment:PackagePageSegment + { + public + let symbol:Symbol.Package + public + let limit:Int + public + let page:Int + + /// We don’t use this yet, but it’s here for future expansion. + public + let user:Account? + + @inlinable public + init(symbol:Symbol.Package, limit:Int, page:Int, as user:Account? = nil) + { + self.symbol = symbol + self.limit = limit + self.page = page + self.user = user + } + } +} +extension Unidoc.PackagePageQuery:Mongo.PipelineQuery +{ + public + typealias Iteration = Mongo.Single + + public + typealias Output = Unidoc.PackagePageOutput +} +extension Unidoc.PackagePageQuery:Unidoc.AliasingQuery +{ + public + typealias CollectionOrigin = Unidoc.DB.PackageAliases + public + typealias CollectionTarget = Unidoc.DB.Packages + + @inlinable public static + var target:Mongo.AnyKeyPath { Output[.package] } + + public + func extend(pipeline:inout Mongo.PipelineEncoder) + { + PageSegment.bridge(pipeline: &pipeline, + limit: self.limit, + skip: self.limit * self.page, + from: Self.target, + into: Output[.list]) + + if let id:Unidoc.Account = self.user + { + pipeline.loadUser(matching: id, as: Output[.user]) + } + } +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.PackagePageSegment.swift b/Sources/UnidocQueries/Versions/Unidoc.PackagePageSegment.swift new file mode 100644 index 000000000..c4e35860a --- /dev/null +++ b/Sources/UnidocQueries/Versions/Unidoc.PackagePageSegment.swift @@ -0,0 +1,18 @@ +import BSON +import MongoQL + +extension Unidoc +{ + public + protocol PackagePageSegment + { + associatedtype Item:BSONDecodable & Sendable + + static + func bridge(pipeline:inout Mongo.PipelineEncoder, + limit:Int, + skip:Int, + from package:Mongo.AnyKeyPath, + into output:Mongo.AnyKeyPath) + } +} diff --git a/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.Output.swift b/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.Output.swift index 2250c6eef..39e74cb75 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.Output.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.Output.swift @@ -21,7 +21,9 @@ extension Unidoc.VersionsQuery public var aliases:[Symbol.Package] public - var build:Unidoc.BuildMetadata? + var pendingBuilds:[Unidoc.PendingBuild] + public + var recentBuilds:[Unidoc.CompleteBuild] public var realm:Unidoc.RealmMetadata? @@ -37,7 +39,8 @@ extension Unidoc.VersionsQuery versions:[Unidoc.VersionState], branches:[Unidoc.VersionState], aliases:[Symbol.Package], - build:Unidoc.BuildMetadata?, + pendingBuilds:[Unidoc.PendingBuild], + recentBuilds:[Unidoc.CompleteBuild], realm:Unidoc.RealmMetadata?, ticket:Unidoc.CrawlingTicket?, user:Unidoc.User?) @@ -48,7 +51,8 @@ extension Unidoc.VersionsQuery self.branches = branches self.aliases = aliases self.ticket = ticket - self.build = build + self.pendingBuilds = pendingBuilds + self.recentBuilds = recentBuilds self.realm = realm self.user = user } @@ -64,7 +68,8 @@ extension Unidoc.VersionsQuery.Output:Mongo.MasterCodingModel case branches case aliases case package - case build + case pendingBuilds + case recentBuilds case realm case ticket case user @@ -80,7 +85,8 @@ extension Unidoc.VersionsQuery.Output:BSONDocumentDecodable versions: try bson[.versions].decode(), branches: try bson[.branches].decode(), aliases: try bson[.aliases].decode(), - build: try bson[.build]?.decode(), + pendingBuilds: try bson[.pendingBuilds]?.decode() ?? [], + recentBuilds: try bson[.recentBuilds]?.decode() ?? [], realm: try bson[.realm]?.decode(), ticket: try bson[.ticket]?.decode(), user: try bson[.user]?.decode()) diff --git a/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.swift index e97f03821..b58ffc153 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.VersionsQuery.swift @@ -19,6 +19,8 @@ extension Unidoc public let limitDependents:Int public + let limitBuilds:Int + public let user:Account? @inlinable public @@ -26,12 +28,14 @@ extension Unidoc limitTags:Int, limitBranches:Int = 32, limitDependents:Int = 32, + limitBuilds:Int = 8, as user:Account? = nil) { self.symbol = symbol self.limitTags = limitTags self.limitBranches = limitBranches self.limitDependents = limitDependents + self.limitBuilds = limitBuilds self.user = user } } @@ -71,10 +75,6 @@ extension Unidoc.VersionsQuery:Unidoc.AliasingQuery from: Self.target, into: Output[.branches]) - pipeline.loadDependents(limit: self.limitDependents, - from: Self.target, - into: Output[.dependents]) - // Concatenate the two lists. pipeline[stage: .set, using: Output.CodingKey.self] { @@ -99,13 +99,23 @@ extension Unidoc.VersionsQuery:Unidoc.AliasingQuery $0[.aliases] { $0[.map] = aliases.map { $0[.id] } } } - // Lookup the associated build. + Unidoc.ConsumersPageSegment.bridge(pipeline: &pipeline, + limit: self.limitDependents, + from: Self.target, + into: Output[.dependents]) + + Unidoc.CompleteBuildsPageSegment.bridge(pipeline: &pipeline, + limit: self.limitBuilds, + from: Self.target, + into: Output[.recentBuilds]) + + // Lookup any queued or in-progress builds. pipeline[stage: .lookup] { - $0[.from] = Unidoc.DB.PackageBuilds.name + $0[.from] = Unidoc.DB.PendingBuilds.name $0[.localField] = Self.target / Unidoc.PackageMetadata[.id] - $0[.foreignField] = Unidoc.BuildMetadata[.id] - $0[.as] = Output[.build] + $0[.foreignField] = Unidoc.PendingBuild[.package] + $0[.as] = Output[.pendingBuilds] } // Lookup the associated realm. pipeline[stage: .lookup] @@ -127,7 +137,6 @@ extension Unidoc.VersionsQuery:Unidoc.AliasingQuery // Unbox single-element arrays. pipeline[stage: .set, using: Output.CodingKey.self] { - $0[.build] { $0[.first] = Output[.build] } $0[.realm] { $0[.first] = Output[.realm] } $0[.ticket] { $0[.first] = Output[.ticket] } } diff --git a/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift b/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift index f939ee299..292899191 100644 --- a/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift +++ b/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift @@ -7,7 +7,7 @@ extension Unidoc struct BuildArtifact:Sendable { public - let package:Package + let edition:Edition public var outcome:Result public @@ -16,12 +16,12 @@ extension Unidoc var logs:[BuildLog] @inlinable public - init(package:Package, + init(edition:Edition, outcome:Result, seconds:Int64 = 0, logs:[BuildLog] = []) { - self.package = package + self.edition = edition self.seconds = seconds self.outcome = outcome self.logs = logs @@ -29,11 +29,23 @@ extension Unidoc } } extension Unidoc.BuildArtifact +{ + @inlinable public + var failure:Unidoc.BuildFailure? + { + switch self.outcome + { + case .success: nil + case .failure(let failure): failure + } + } +} +extension Unidoc.BuildArtifact { @frozen public enum CodingKey:String, Sendable { - case package = "P" + case edition = "e" case payload = "S" case failure = "F" case seconds = "D" @@ -45,7 +57,7 @@ extension Unidoc.BuildArtifact:BSONDocumentEncodable public func encode(to bson:inout BSON.DocumentEncoder) { - bson[.package] = self.package + bson[.edition] = self.edition switch self.outcome { @@ -74,7 +86,7 @@ extension Unidoc.BuildArtifact:BSONDocumentDecodable } self.init( - package: try bson[.package].decode(), + edition: try bson[.edition].decode(), outcome: outcome, seconds: try bson[.seconds].decode(), logs: try bson[.logs]?.decode() ?? []) diff --git a/Sources/UnidocRecords/Building/Unidoc.BuildLogType.swift b/Sources/UnidocRecords/Building/Unidoc.BuildLogType.swift index 7eeb59907..12ccb2d42 100644 --- a/Sources/UnidocRecords/Building/Unidoc.BuildLogType.swift +++ b/Sources/UnidocRecords/Building/Unidoc.BuildLogType.swift @@ -5,11 +5,6 @@ extension Unidoc @frozen public enum BuildLogType:String, BSONDecodable, BSONEncodable, Equatable, Sendable { - // Deprecated. - case _swiftPackageResolution = "R" - case _swiftPackageBuild = "B" - case _swiftSymbolGraphExtract = "E" - case ssgc = "C" case ssgcDiagnostics = "D" } @@ -21,11 +16,8 @@ extension Unidoc.BuildLogType { switch self { - case ._swiftPackageResolution: "swift-package-resolution" - case ._swiftPackageBuild: "swift-package-build" - case ._swiftSymbolGraphExtract: "swift-symbolgraph-extract" - case .ssgc: "ssgc" - case .ssgcDiagnostics: "ssgc-diagnostics" + case .ssgc: "build" + case .ssgcDiagnostics: "documentation" } } } diff --git a/Sources/UnidocRecords/Building/Unidoc.BuildReport.swift b/Sources/UnidocRecords/Building/Unidoc.BuildReport.swift index 749b10ebc..71bf0f8f5 100644 --- a/Sources/UnidocRecords/Building/Unidoc.BuildReport.swift +++ b/Sources/UnidocRecords/Building/Unidoc.BuildReport.swift @@ -7,14 +7,14 @@ extension Unidoc struct BuildReport:Equatable, Sendable { public - let package:Package + let edition:Edition public var entered:BuildStage @inlinable public - init(package:Package, entered:BuildStage) + init(edition:Edition, entered:BuildStage) { - self.package = package + self.edition = edition self.entered = entered } } @@ -24,7 +24,7 @@ extension Unidoc.BuildReport @frozen public enum CodingKey:String, Sendable { - case package = "P" + case edition = "e" case entered = "E" } } @@ -33,7 +33,7 @@ extension Unidoc.BuildReport:BSONDocumentEncodable public func encode(to bson:inout BSON.DocumentEncoder) { - bson[.package] = self.package + bson[.edition] = self.edition bson[.entered] = self.entered } } @@ -42,8 +42,6 @@ extension Unidoc.BuildReport:BSONDocumentDecodable public init(bson:BSON.DocumentDecoder) throws { - self.init( - package: try bson[.package].decode(), - entered: try bson[.entered].decode()) + self.init(edition: try bson[.edition].decode(), entered: try bson[.entered].decode()) } } diff --git a/Sources/UnidocRecords/Symbol.PackageAtRef (ext).swift b/Sources/UnidocRecords/Symbol.PackageAtRef (ext).swift new file mode 100644 index 000000000..7b8824749 --- /dev/null +++ b/Sources/UnidocRecords/Symbol.PackageAtRef (ext).swift @@ -0,0 +1,6 @@ +import BSON +import Symbols + +extension Symbol.PackageAtRef:BSONStringEncodable, BSONStringDecodable +{ +} diff --git a/Sources/UnidocRender/Unidoc.ServerRoot.swift b/Sources/UnidocRender/Unidoc.ServerRoot.swift index 864ee80ae..359a0560e 100644 --- a/Sources/UnidocRender/Unidoc.ServerRoot.swift +++ b/Sources/UnidocRender/Unidoc.ServerRoot.swift @@ -29,6 +29,7 @@ extension Unidoc case render case robots_txt = "robots.txt" case rules + case runs case sitemap_xml = "sitemap.xml" case stats case tags diff --git a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift index 203838305..3176387f7 100644 --- a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift +++ b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift @@ -10,9 +10,7 @@ extension Unidoc.BuildCoordinator private let appeared:BSON.Timestamp private - let package:Unidoc.Package - private - let request:Unidoc.BuildRequest + let request:Unidoc.Edition private let producer:CheckedContinuation @@ -20,12 +18,10 @@ extension Unidoc.BuildCoordinator let producerAwaiting:ManagedAtomic init(appeared:BSON.Timestamp, - package:Unidoc.Package, - request:Unidoc.BuildRequest, + request:Unidoc.Edition, producer:CheckedContinuation) { self.appeared = appeared - self.package = package self.request = request self.producer = producer self.producerAwaiting = .init(true) @@ -109,10 +105,7 @@ extension Unidoc.BuildCoordinator.Notification // original notification, so that we do not experience reversed ordering. db.session.synchronize(to: self.appeared) - guard try await db.packageBuilds.assignBuild( - request: self.request.behavior, - package: self.package, - builder: assignee) + guard try await db.pendingBuilds.assignBuild(id: self.request, to: assignee) else { // The build request no longer exists in the database, perhaps because it was @@ -121,26 +114,15 @@ extension Unidoc.BuildCoordinator.Notification return nil } - let version:Unidoc.BuildSelector - - switch self.request.version - { - case .latest(let series, of: ()): version = .latest(series, of: self.package) - case .id(let id): version = .id(id) - } - - if let edition:Unidoc.EditionState = try await db.editionState(of: version), - let labels:Unidoc.BuildLabels = try await registrar.resolve(edition, - rebuild: self.request.rebuild) + if let edition:Unidoc.EditionState = try await db.editionState(of: .id(self.request)), + let labels:Unidoc.BuildLabels = try await registrar.resolve(edition, rebuild: true) { return labels } else { - /// We don’t really care if something else raced us here. - let _:Unidoc.BuildMetadata? = try await db.packageBuilds.finishBuild( - package: self.package, - failure: .noValidVersion) + /// Either the edition or the package, or both, no longer exist. + let _:Bool = try await db.pendingBuilds.cancelBuild(id: self.request) return nil } } diff --git a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.swift b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.swift index 8b64b12a4..96f5d7ec4 100644 --- a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.swift +++ b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.swift @@ -88,8 +88,7 @@ extension Unidoc.BuildCoordinator let db:Unidoc.DB = try await db.session() guard - let metadata:Unidoc.BuildMetadata = try await db.packageBuilds.selectBuild(await: true), - let request:Unidoc.BuildRequest = metadata.request + let pending:Unidoc.PendingBuild = try await db.pendingBuilds.selectBuild(await: true) else { throw Unidoc.BuildCoordinatorAssertionError.invalidChangeStreamElement @@ -105,8 +104,7 @@ extension Unidoc.BuildCoordinator try await withCheckedThrowingContinuation { let notification:Notification = .init(appeared: clusterTime, - package: metadata.id, - request: request, + request: pending.id, producer: $0) self.eventQueue.yield(.notify(notification)) diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderPollOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderPollOperation.swift index 99cd6a32f..89b5078fa 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderPollOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderPollOperation.swift @@ -30,7 +30,7 @@ extension Unidoc.BuilderPollOperation:Unidoc.MachineOperation /// If the builder is recovering from a crash, kill any builds that had previously been /// assigned to it. - let _:Int = try await db.packageBuilds.killBuilds(builder: self.id) + let _:Int = try await db.pendingBuilds.killBuilds(by: self.id) let labels:Unidoc.BuildLabels? = try await withThrowingTaskGroup( of: Unidoc.BuildLabels?.self) diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.Action.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.Action.swift deleted file mode 100644 index 5f4ed5d0a..000000000 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.Action.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Symbols - -extension Unidoc.PackageBuildOperation -{ - enum Action - { - case submitSymbolic(Symbol.Edition) - case submit(Unidoc.Package, Unidoc.BuildRequest) - case cancel(Unidoc.Package) - case cancelSymbolic(Symbol.Package) - } -} diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.DirectParameters.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.DirectParameters.swift deleted file mode 100644 index 3df0ca1db..000000000 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.DirectParameters.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Symbols - -extension Unidoc.PackageBuildOperation -{ - struct DirectParameters - { - let symbols:Symbol.PackageAtRef - let package:Unidoc.Package - let request:Unidoc.BuildRequest? - - private - init(symbols:Symbol.PackageAtRef, - package:Unidoc.Package, - request:Unidoc.BuildRequest?) - { - self.symbols = symbols - self.package = package - self.request = request - } - } -} -extension Unidoc.PackageBuildOperation.DirectParameters -{ - init?(from form:borrowing [String: String]) - { - guard - let symbols:String = form["selector"], - let package:String = form["package"], - let package:Unidoc.Package = .init(package) - else - { - return nil - } - - let request:Unidoc.BuildRequest? - - switch form["build"] - { - case "request"?: - let selector:Unidoc.BuildSelector - - if let version:String = form["version"], - let version:Unidoc.Version = .init(version) - { - selector = .id(.init(package: package, version: version)) - } - else if - case "prerelease"? = form["series"] - { - selector = .latest(.prerelease) - } - else - { - selector = .latest(.release) - } - - request = .init(version: selector, rebuild: form["force"] == "true") - - case "cancel"?: - request = nil - - default: - return nil - } - - self.init(symbols: .init(symbols), package: package, request: request) - } -} diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift index 6021fc69a..9a796af99 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift @@ -9,16 +9,16 @@ extension Unidoc struct PackageBuildOperation:MeteredOperation { let account:Account - let action:Action - let redirect:Symbol.Package + let symbol:Symbol.PackageAtRef + let action:Unidoc.BuildForm.Action var rights:Unidoc.UserRights - init(account:Account, action:Action, redirect:Symbol.Package) + init(account:Account, symbol:Symbol.PackageAtRef, action:Unidoc.BuildForm.Action) { self.account = account + self.symbol = symbol self.action = action - self.redirect = redirect self.rights = .init() } @@ -26,11 +26,9 @@ extension Unidoc } extension Unidoc.PackageBuildOperation { - init(account:Unidoc.Account, build:DirectParameters) + init(account:Unidoc.Account, form:Unidoc.BuildForm) { - self.init(account: account, - action: build.request.map { .submit(build.package, $0) } ?? .cancel(build.package), - redirect: build.symbols.package) + self.init(account: account, symbol: form.symbol, action: form.action) } } extension Unidoc.PackageBuildOperation:Unidoc.RestrictedOperation @@ -39,72 +37,41 @@ extension Unidoc.PackageBuildOperation:Unidoc.RestrictedOperation db:Unidoc.DB, as _:Unidoc.RenderFormat) async throws -> HTTP.ServerResponse? { - let metadata:Unidoc.PackageMetadata? - let package:Unidoc.Package - let request:Unidoc.BuildRequest? - - switch self.action + guard + let outputs:Unidoc.EditionOutput = try await db.edition( + package: self.symbol.package, + version: .name(self.symbol.ref)), + let edition:Unidoc.EditionMetadata = outputs.edition + else { - case .submit(let id, let build): - metadata = nil - package = id - request = build - - case .cancel(let id): - metadata = nil - package = id - request = nil - - case .cancelSymbolic(let symbol): - metadata = try await db.package(named: symbol) - - guard - let metadata:Unidoc.PackageMetadata - else - { - return .notFound("No such package") - } - - package = metadata.id - request = nil - - case .submitSymbolic(let symbol): - guard - let outputs:Unidoc.EditionOutput = try await db.edition( - package: symbol.package, - version: .name(symbol.ref)), - let edition:Unidoc.Edition = outputs.edition?.id - else - { - return .notFound("No such edition") - } - - metadata = outputs.package - package = outputs.package.id - request = .init(version: .id(edition), rebuild: true) + return .notFound("No such edition") } if let rejection:HTTP.ServerResponse = try await db.authorize( - package: metadata, - loading: package, + package: outputs.package, + loading: edition.id.package, account: self.account, rights: self.rights) { return rejection } - if let request:Unidoc.BuildRequest - { - _ = try await db.packageBuilds.submitBuild(request: request, package: package) - } - else if try await db.packageBuilds.cancelBuild(package: package) - { - } - else + // From now on, use canonical names + let name:Symbol.PackageAtRef = .init(package: outputs.package.symbol, ref: edition.name) + + switch self.action { - return .resource("Cannot cancel a build that has already started", status: 409) + case .submit: + _ = try await db.pendingBuilds.submitBuild(id: edition.id, name: name) + + case .cancel: + guard try await db.pendingBuilds.cancelBuild(id: edition.id) + else + { + return .resource("Cannot cancel a build that has already started", status: 409) + } } - return .redirect(.seeOther("\(Unidoc.RefsEndpoint[self.redirect])")) + return .redirect(.seeOther("\(Unidoc.RefsEndpoint[name.package])")) } } diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuildArtifact (ext).swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuildArtifact (ext).swift index b4f14ca3e..ddf17637f 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuildArtifact (ext).swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuildArtifact (ext).swift @@ -6,13 +6,13 @@ import S3Client extension Unidoc.BuildArtifact { mutating - func export(from server:Unidoc.Server, - _logsIncluded:Bool = true) async throws -> [Unidoc.BuildLogType] + func export(as id:Unidoc.BuildIdentifier, + from server:Unidoc.Server) async throws -> [Unidoc.BuildLogType] { var logs:[Unidoc.BuildLogType] = [] let logsToExport:Int = self.logs.count - if logsToExport > 0 && _logsIncluded + if logsToExport > 0 { if let bucket:AWS.S3.Bucket = server.bucket.assets { @@ -26,8 +26,7 @@ extension Unidoc.BuildArtifact { for log:Unidoc.BuildLog in self.logs { - let path:Unidoc.BuildLogPath = .init(package: self.package, - type: log.type) + let path:Unidoc.BuildLogPath = .init(id: id, type: log.type) try await $0.put(object: .init( body: .binary(log.text.bytes), diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift index 8791f69a6..bbdc101c3 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift @@ -8,6 +8,7 @@ import S3Client import SymbolGraphs import UnidocDB import UnidocRecords +import UnixTime extension Unidoc { @@ -33,51 +34,36 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation { case .report: let report:Unidoc.BuildReport = try .init(bson: bson) - try await db.packageBuilds.updateBuild( - package: report.package, - entered: report.entered) + try await db.pendingBuilds.updateBuild(id: report.edition, entered: report.entered) case .labeled: - var artifact:Unidoc.BuildArtifact = try .init(bson: bson) - let logs:[Unidoc.BuildLogType] + var build:Unidoc.BuildArtifact = try .init(bson: bson) - switch artifact.outcome + let launched:UnixMillisecond + let finished:UnixMillisecond + + (launched, finished) = try await db.pendingBuilds.completeBuild(id: build.edition, + duration: .seconds(build.seconds)) + + var complete:Unidoc.CompleteBuild = .init(edition: build.edition, + launched: launched, + finished: finished, + failure: build.failure, + logs: []) + + complete.logs = try await build.export(as: complete.id, from: server) + + try await db.completeBuilds.upsert(complete) + + if case .success(let snapshot) = build.outcome { - case .failure(let reason): - logs = try await artifact.export(from: server) - try await db.packageBuilds.finishBuild( - package: artifact.package, - failure: reason, - logs: logs) - - case .success(let snapshot): + try await db.snapshots.upsert(snapshot) + /// A successful (labeled) build also sets the platform preference, since we now /// know that the package can be built on that platform. - let _metadata:Unidoc.PackageMetadata? = try await db.packages.reset( + let _:Unidoc.PackageMetadata? = try await db.packages.reset( platformPreference: snapshot.metadata.triple, of: snapshot.id.package) - - /// Right now, exporting build logs for private repositories is a security - /// hazard, because the logs contain secrets, and the log URLs are easily - /// predicted. For now, we just discard the logs for private repositories. - let _logsIncluded:Bool - if case true? = _metadata?.repo?.private - { - _logsIncluded = false - } - else - { - _logsIncluded = true - } - - logs = try await artifact.export(from: server, _logsIncluded: _logsIncluded) - - try await db.packageBuilds.finishBuild( - package: artifact.package, - failure: nil, - logs: logs) - - try await db.snapshots.upsert(snapshot) } case .labeling: @@ -98,7 +84,6 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation try await snapshot.moveSymbolGraph(to: s3) } - try await db.packageBuilds.finishBuild(package: snapshot.id.package) try await db.snapshots.upsert(snapshot) } diff --git a/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift b/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift index 0ff92565f..d7e6c93f5 100644 --- a/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift +++ b/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift @@ -7,55 +7,43 @@ extension Unidoc { struct BuildRequestPage { - let symbols:Symbol.PackageAtRef - let cancel:Bool + let form:BuildForm let action:URI - init(symbols:Symbol.PackageAtRef, cancel:Bool, action:URI) + init(form:BuildForm, action:URI) { - self.symbols = symbols - self.cancel = cancel + self.form = form self.action = action } } } extension Unidoc.BuildRequestPage:Unidoc.ConfirmationPage { - var button:String { self.cancel ? "Cancel build" : "Build package" } - var title:String { self.cancel ? "Cancel build?" : "Build package?" } + var button:String { self.form.action == .cancel ? "Cancel build" : "Build package" } + var title:String { self.form.action == .cancel ? "Cancel build?" : "Build package?" } func form(_ form:inout HTML.ContentEncoder, format:Unidoc.RenderFormat) { form[.p] { - let package:URI = Unidoc.RefsEndpoint[self.symbols.package] - if self.cancel + let package:URI = Unidoc.RefsEndpoint[self.form.symbol.package] + switch self.form.action { + case .cancel: $0 += "You can cancel the build for " - $0[.a] { $0.href = "\(package)" } = "\(self.symbols.package)" + $0[.a] { $0.href = "\(package)" } = "\(self.form.symbol.package)" $0 += " if it has not started yet." - } - else if - let ref:Substring = self.symbols.ref - { + + case .submit: $0 += "A builder will build the package " - $0[.a] { $0.href = "\(package)" } = "\(self.symbols.package)" + $0[.a] { $0.href = "\(package)" } = "\(self.form.symbol.package)" $0 += " at " - $0[.code] = "\(ref)" + $0[.code] = "\(self.form.symbol.ref)" $0 += """ once one becomes available. If you move the ref in the meantime, it might \ build the new commit instead. """ } - else - { - $0 += "A builder will select a recent version of the package " - $0[.a] { $0.href = "\(package)" } = "\(self.symbols.package)" - $0 += """ - once one becomes available. If you tag a new release in the meantime, \ - it might build that instead. - """ - } } form[.p] @@ -71,40 +59,12 @@ extension Unidoc.BuildRequestPage:Unidoc.ConfirmationPage { $0.type = "checkbox" $0.name = "force" + $0.checked = true $0.value = "true" } $0[.span] = "Force rebuild of existing documentation" } } - - if self.cancel - { - return - } - if case _? = self.symbols.ref - { - return - } - - form[.p] - { - $0[.label] - { - $0.class = "checkbox" - $0.title = "Build the latest prerelease instead of the latest release." - } - content: - { - $0[.input] - { - $0.type = "checkbox" - $0.name = "series" - $0.value = "prerelease" - } - - $0[.span] = "Build prereleases" - } - } } } diff --git a/Sources/UnidocServer/Requests/Unidoc.Router.swift b/Sources/UnidocServer/Requests/Unidoc.Router.swift index 5b6d2cd4f..ad90b4dce 100644 --- a/Sources/UnidocServer/Requests/Unidoc.Router.swift +++ b/Sources/UnidocServer/Requests/Unidoc.Router.swift @@ -236,6 +236,7 @@ extension Unidoc.Router case .render: return self.render() case .robots_txt: return self.robots() case .rules: return self.rules() + case .runs: return nil // Unimplemented. case .sitemap_xml: return self.sitemap() case .sitemaps: return self.sitemaps() case .stats: return self.stats() @@ -453,11 +454,11 @@ extension Unidoc.Router { case .build: if let account:Unidoc.Account = self.authorization.account, - let build:Unidoc.PackageBuildOperation.DirectParameters = .init(from: form) + let build:Unidoc.BuildForm = .init(from: form) { return .unordered(Unidoc.PackageBuildOperation.init( account: account, - build: build)) + form: build)) } case .packageAlias: @@ -864,8 +865,8 @@ extension Unidoc.Router } return .unordered(Unidoc.PackageBuildOperation.init(account: account, - action: .submitSymbolic(.init(package: symbol, ref: name)), - redirect: symbol)) + symbol: .init(package: symbol, ref: name), + action: .submit)) case "state": return .unordered(Unidoc.LoadEditionStateOperation.init( @@ -917,15 +918,13 @@ extension Unidoc.Router { case .build: guard - let build:Unidoc.PackageBuildOperation.DirectParameters = .init(from: table) + let form:Unidoc.BuildForm = .init(from: table) else { return nil } - return .syncHTML(Unidoc.BuildRequestPage.init(symbols: build.symbols, - cancel: build.request == nil, - action: uri)) + return .syncHTML(Unidoc.BuildRequestPage.init(form: form, action: uri)) case .unlink: really = .unlink(uri) diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift deleted file mode 100644 index db589cb19..000000000 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift +++ /dev/null @@ -1,94 +0,0 @@ -import HTML -import Symbols -import UnidocRecords - -extension Unidoc -{ - struct BuildButton - { - let symbols:Symbol.PackageAtRef - let package:Package - let version:Version? - let cancel:Bool - let area:Bool - - private - init(symbols:Symbol.PackageAtRef, - package:Package, - version:Version?, - cancel:Bool, - area:Bool) - { - self.symbols = symbols - self.package = package - self.version = version - self.cancel = cancel - self.area = area - } - } -} -extension Unidoc.BuildButton -{ - static - func edition(id:Unidoc.Edition, package:Symbol.Package, ref:String) -> Self - { - .init(symbols: .init(package: package, ref: ref[...]), - package: id.package, - version: id.version, - cancel: false, - area: false) - } - - static - func latest(of package:Unidoc.PackageMetadata, cancel:Bool = false) -> Self - { - .init(symbols: .init(package: package.symbol, ref: nil), - package: package.id, - version: nil, - cancel: cancel, - area: true) - } -} -extension Unidoc.BuildButton:HTML.OutputStreamable -{ - static - func += (form:inout HTML.ContentEncoder, self:Self) - { - form[.input] - { - $0.type = "hidden" - $0.name = "selector" - $0.value = "\(self.symbols)" - } - - form[.input] - { - $0.type = "hidden" - $0.name = "build" - $0.value = self.cancel ? "cancel" : "request" - } - - form[.input] - { - $0.type = "hidden" - $0.name = "package" - $0.value = "\(self.package)" - } - - if let version:Unidoc.Version = self.version - { - form[.input] - { - $0.type = "hidden" - $0.name = "version" - $0.value = "\(version)" - } - } - - form[.button] - { - $0.class = self.area ? "area" : "text" - $0.type = "submit" - } = self.cancel ? "Cancel build" : "Request build" - } -} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.Action.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.Action.swift new file mode 100644 index 000000000..31e320171 --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.Action.swift @@ -0,0 +1,34 @@ +extension Unidoc.BuildForm +{ + @frozen public + enum Action:Equatable, Sendable + { + case submit + case cancel + } +} +extension Unidoc.BuildForm.Action:CustomStringConvertible +{ + @inlinable public + var description:String + { + switch self + { + case .submit: "submit" + case .cancel: "cancel" + } + } +} +extension Unidoc.BuildForm.Action:LosslessStringConvertible +{ + @inlinable public + init?(_ description:String) + { + switch description + { + case "submit": self = .submit + case "cancel": self = .cancel + default: return nil + } + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.swift new file mode 100644 index 000000000..4b8482de7 --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildForm.swift @@ -0,0 +1,42 @@ +import Symbols + +extension Unidoc +{ + @frozen public + struct BuildForm + { + public + let symbol:Symbol.PackageAtRef + public + let action:Action + + init(symbol:Symbol.PackageAtRef, action:Action) + { + self.symbol = symbol + self.action = action + } + } +} +extension Unidoc.BuildForm +{ + static var symbol:String { "symbol" } + static var action:String { "action" } +} +extension Unidoc.BuildForm +{ + public + init?(from parameters:borrowing [String: String]) + { + guard + let symbol:String = parameters["symbol"], + let symbol:Symbol.PackageAtRef = .init(symbol), + let action:String = parameters["action"], + let action:Action = .init(action) + else + { + return nil + } + + self.init(symbol: symbol, action: action) + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.Inhibitor.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.Inhibitor.swift new file mode 100644 index 000000000..4aab173af --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.Inhibitor.swift @@ -0,0 +1,11 @@ +extension Unidoc.BuildFormTool +{ + enum Inhibitor + { + case alreadyStarted + case alreadySubmitted + case unauthenticated + case unauthorized + case unavailable + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift new file mode 100644 index 000000000..c17363a80 --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift @@ -0,0 +1,142 @@ +import HTML +import Symbols +import UnidocRecords + +extension Unidoc +{ + struct BuildFormTool + { + let form:BuildForm + let area:Bool + let disabled:Inhibitor? + + init(form:BuildForm, area:Bool, disabled:Inhibitor? = nil) + { + self.form = form + self.area = area + self.disabled = disabled + } + } +} +extension Unidoc.BuildFormTool +{ + static + func shortcut(buildable:String?, + submitted:Bool, + package:Symbol.Package, + view:Unidoc.Permissions) -> Self + { + guard + let buildable:String + else + { + // Use the empty string for the ref, as the form should not be submittable at all. + return .init(form: .init( + symbol: .init(package: package, ref: ""), + action: .submit), + area: true, + disabled: .unavailable) + } + + let form:Unidoc.BuildForm = .init( + symbol: .init(package: package, ref: buildable), + action: .submit) + + if submitted + { + return .init(form: form, area: true, disabled: .alreadySubmitted) + } + + guard case _? = view.global + else + { + return .init(form: form, area: true, disabled: .unauthenticated) + } + + guard view.editor + else + { + return .init(form: form, area: true, disabled: .unauthorized) + } + + return .init(form: form, area: true) + } + + static + func control(pending build:Unidoc.PendingBuild, view:Unidoc.Permissions) -> Self + { + let form:Unidoc.BuildForm = .init(symbol: build.name, action: .cancel) + + guard case nil = build.launched + else + { + return .init(form: form, area: true, disabled: .alreadyStarted) + } + + guard case _? = view.global + else + { + return .init(form: form, area: true, disabled: .unauthenticated) + } + + guard view.editor + else + { + return .init(form: form, area: true, disabled: .unauthorized) + } + + return .init(form: form, area: true) + } +} +extension Unidoc.BuildFormTool:HTML.OutputStreamable +{ + static + func += (form:inout HTML.ContentEncoder, self:Self) + { + form[.input] + { + $0.type = "hidden" + $0.name = Unidoc.BuildForm.symbol + $0.value = "\(self.form.symbol)" + } + + form[.input] + { + $0.type = "hidden" + $0.name = Unidoc.BuildForm.action + $0.value = "\(self.form.action)" + } + + let label:String + + switch self.form.action + { + case .submit: label = "Request build" + case .cancel: label = "Cancel build" + } + + form[.button] + { + $0.class = self.area ? "area" : "text" + $0.type = "submit" + + guard + let inhibitor:Inhibitor = self.disabled + else + { + return + } + + $0.disabled = true + + switch inhibitor + { + case .alreadyStarted: $0.title = "This build has already started!" + case .alreadySubmitted: $0.title = "This build has already been queued!" + case .unauthenticated: $0.title = "You are not logged in!" + case .unauthorized: $0.title = "You are not an editor of this package!" + case .unavailable: $0.title = "This repository does not have such a tag yet!" + } + } = label + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift new file mode 100644 index 000000000..6cfdd388b --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift @@ -0,0 +1,88 @@ +import HTML +import Media +import Symbols +import URI + +extension Unidoc +{ + struct BuildTools + { + let prerelease:BuildFormTool + let release:BuildFormTool + let running:[Unidoc.PendingBuild] + let view:Unidoc.Permissions + let back:URI + } +} +extension Unidoc.BuildTools:HTML.OutputStreamable +{ + static + func += (section:inout HTML.ContentEncoder, self:Self) + { + section[.div] + { + for shortcut:Unidoc.BuildFormTool in [self.prerelease, self.release] + { + $0[.form] + { + $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" + $0.action = "\(Unidoc.Post[.build, confirm: true])" + $0.method = "post" + } = shortcut + } + } + + section[.ol] + { + for build:Unidoc.PendingBuild in self.running + { + let tooltip:String + let label:String + + switch build.stage + { + case nil: + tooltip = "The build has not yet started." + label = "Queued" + + case .initializing?: + tooltip = "The builder is initializing." + label = "Git" + + case .cloningRepository?: + tooltip = "The builder is cloning the package’s repository." + label = "Git" + + case .resolvingDependencies?: + tooltip = "The builder is resolving the package’s dependencies." + label = "SwiftPM" + + case .compilingCode?: + tooltip = "The builder is compiling the package’s source code." + label = "Swift" + } + + $0[.li] + { + $0[.form] + { + $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" + $0.action = "\(Unidoc.Post[.build, confirm: true])" + $0.method = "post" + } = Unidoc.BuildFormTool.control(pending: build, view: self.view) + + $0[.div] + { + $0.class = build.stage == nil ? "phase queued" : "phase started" + $0.title = tooltip + } = label + + $0[.div] + { + $0.class = "ref" + } = build.name.ref + } + } + } + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift new file mode 100644 index 000000000..1da06815e --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift @@ -0,0 +1,72 @@ +import HTTP +import MongoDB +import Symbols +import UnidocDB +import UnidocQueries +import UnidocRender +import URI + +extension Unidoc +{ + @frozen public + struct CompleteBuildsEndpoint + { + public + let query:CompleteBuildsQuery + public + var value:CompleteBuildsQuery.Output? + + @inlinable public + init(query:CompleteBuildsQuery) + { + self.query = query + self.value = nil + } + } +} +extension Unidoc.CompleteBuildsEndpoint +{ + static + subscript(package:Symbol.Package, page index:Int) -> URI + { + var uri:URI = Unidoc.ServerRoot.runs / "\(package)" + uri["page"] = "\(index)" + return uri + } +} +extension Unidoc.CompleteBuildsEndpoint:Mongo.PipelineEndpoint, Mongo.SingleOutputEndpoint +{ + @inlinable public static + var replica:Mongo.ReadPreference { .nearest } +} +extension Unidoc.CompleteBuildsEndpoint:HTTP.ServerEndpoint +{ + public consuming + func response(as format:Unidoc.RenderFormat) -> HTTP.ServerResponse + { + guard + let output:Unidoc.CompleteBuildsQuery.Output = self.value + else + { + return .error("Query for endpoint '\(Self.self)' returned no outputs!") + } + + let view:Unidoc.Permissions = format.security.permissions(package: output.package, + user: output.user) + + let content:Unidoc.Paginated = .init( + table: .init( + package: output.package.symbol, + rows: output.list, + view: view), + index: self.query.page, + truncated: output.list.count >= self.query.limit) + + let completeBuildsPage:Unidoc.PackageCursorPage = .init( + location: Self[content.table.package, page: content.index], + package: output.package, + content: content) + + return .ok(completeBuildsPage.resource(format: format)) + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift new file mode 100644 index 000000000..aa59c387d --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift @@ -0,0 +1,143 @@ +import HTML +import Symbols +import URI +import UnixTime + +extension Unidoc +{ + struct CompleteBuildsTable + { + let package:Symbol.Package + let rows:[CompleteBuild] + + let view:Permissions + + init(package:Symbol.Package, rows:[CompleteBuild], view:Permissions) + { + self.package = package + self.rows = rows + self.view = view + } + } +} +extension Unidoc.CompleteBuildsTable:Unidoc.IterableTable +{ + func more(page index:Int) -> URI + { + Unidoc.CompleteBuildsEndpoint[self.package, page: index] + } +} +extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable +{ + static + func |= (table:inout HTML.AttributeEncoder, self:Self) + { + table[data: "type"] = "complete-builds" + } + + static + func += (table:inout HTML.ContentEncoder, self:Self) + { + table[.thead] + { + $0[.tr] + { + $0[.th] = "Run time" + $0[.th] = "Status" + $0[.th] = "Logs" + } + } + + table[.tbody] + { + for row:Unidoc.CompleteBuild in self.rows + { + $0[.tr] + { + $0[.td] + { + let duration:DurationFormat = .init(row.finished - row.launched) + + $0[.span] + { + $0.class = row.failure == nil ? "success" : "failure" + } = duration.short + } + + switch row.failure + { + case nil: + $0[.td] + + case .killed?: + $0[.td] = "Killed" + + case .noValidVersion?: + $0[.td] = "No Valid Version" + + case .failedToCloneRepository?: + $0[.td] = "Failed to Clone Repo" + + case .failedToReadManifest?: + $0[.td] = "Failed to Read Root Manifest" + + case .failedToReadManifestForDependency?: + $0[.td] = "Failed to Read Dependency Manifest" + + case .failedToResolveDependencies?: + $0[.td] = "Failed to Resolve Dependencies" + + case .failedToBuild?: + $0[.td] = "Failed to Build Package" + + case .failedToExtractSymbolGraph?: + $0[.td] = "Failed to Extract Symbol Graph" + + case .failedToLoadSymbolGraph?: + $0[.td] = "Failed to Load Symbol Graph" + + case .failedToLinkSymbolGraph?: + $0[.td] = "Failed to Link Symbol Graph" + + case .failedForUnknownReason?: + $0[.td] = "Failed for Unknown Reason" + } + + $0[.td] + { + if row.logs.isEmpty + { + return + } + + $0[.div, { $0.class = "menu" }] + { + $0[.button] = "•••" + $0[.ul] + { + for log:Unidoc.BuildLogType in row.logs + { + $0[.li] + { + // We never persist logs anywhere except in S3, where + // they are served through CloudFront. Therefore, we + // can safely hardcode the URL here. + let path:Unidoc.BuildLogPath = .init(id: row.id, + type: log) + + $0[.a] + { + $0.target = "_blank" + $0.href = "https://static.swiftinit.org\(path)" + $0.rel = .external + } = log.name + } + } + } + } + } + } + } + } + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift index 79ce9d233..e9403096d 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift @@ -51,15 +51,16 @@ extension Unidoc.ConsumersEndpoint:HTTP.ServerEndpoint return .error("Query for endpoint '\(Self.self)' returned no outputs!") } - //let view:Unidoc.Permissions = format.security.permissions(package: output.dependency, - // user: output.user) - - let table:Unidoc.Paginated = .init( - table: .init(dependency: output.dependency.symbol, rows: output.dependents), + let content:Unidoc.Paginated = .init( + table: .init(package: output.package.symbol, rows: output.list), index: self.query.page, - truncated: output.dependents.count >= self.query.limit) + truncated: output.list.count >= self.query.limit) + + let consumersPage:Unidoc.PackageCursorPage = .init( + location: Self[content.table.package, page: content.index], + package: output.package, + content: content) - let page:Unidoc.ConsumersPage = .init(package: output.dependency, table: table) - return .ok(page.resource(format: format)) + return .ok(consumersPage.resource(format: format)) } } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.Row.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.Row.swift deleted file mode 100644 index 87d8b2150..000000000 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.Row.swift +++ /dev/null @@ -1,43 +0,0 @@ -import HTML - -extension Unidoc.ConsumersTable -{ - struct Row - { - private - let dependent:Unidoc.PackageDependent - - init(dependent:Unidoc.PackageDependent) - { - self.dependent = dependent - } - } -} -extension Unidoc.ConsumersTable.Row:HTML.OutputStreamable -{ - static - func += (tr:inout HTML.ContentEncoder, self:Self) - { - tr[.td] - { - $0[.a] - { - $0.href = "\(Unidoc.RefsEndpoint[self.dependent.package.symbol])" - } = "\(self.dependent.package.symbol)" - } - - tr[.td] { $0.class = "ref" } = self.dependent.edition.name - - tr[.td, { $0.class = "version" }] - { - guard - let volume:Unidoc.VolumeMetadata = self.dependent.volume - else - { - return - } - - $0[.a] { $0.href = "\(Unidoc.DocsEndpoint[volume])" } = volume.symbol.version - } - } -} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.swift index c4664f409..d33e53aa1 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersTable.swift @@ -6,30 +6,21 @@ extension Unidoc { struct ConsumersTable { - private - let dependency:Symbol.Package - private + let package:Symbol.Package let rows:[PackageDependent] - init(dependency:Symbol.Package, rows:[PackageDependent]) + init(package:Symbol.Package, rows:[PackageDependent]) { - self.dependency = dependency + self.package = package self.rows = rows } } } -extension Unidoc.ConsumersTable:RandomAccessCollection -{ - var startIndex:Int { self.rows.startIndex } - var endIndex:Int { self.rows.endIndex } - - subscript(index:Int) -> Row { .init(dependent: self.rows[index]) } -} extension Unidoc.ConsumersTable:Unidoc.IterableTable { func more(page index:Int) -> URI { - Unidoc.ConsumersEndpoint[self.dependency, page: index] + Unidoc.ConsumersEndpoint[self.package, page: index] } } extension Unidoc.ConsumersTable:HTML.OutputStreamable @@ -55,9 +46,35 @@ extension Unidoc.ConsumersTable:HTML.OutputStreamable table[.tbody] { - for row:Row in self + for row:Unidoc.PackageDependent in self.rows { - $0[.tr] = row + $0[.tr] + { + $0[.td] + { + $0[.a] + { + $0.href = "\(Unidoc.RefsEndpoint[row.package.symbol])" + } = "\(row.package.symbol)" + } + + $0[.td] { $0.class = "ref" } = row.edition.name + + $0[.td, { $0.class = "version" }] + { + guard + let volume:Unidoc.VolumeMetadata = row.volume + else + { + return + } + + $0[.a] + { + $0.href = "\(Unidoc.DocsEndpoint[volume])" + } = volume.symbol.version + } + } } } } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersPage.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift similarity index 66% rename from Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersPage.swift rename to Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift index 2be103327..9a4017abd 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersPage.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift @@ -5,32 +5,31 @@ import URI extension Unidoc { - struct ConsumersPage + struct PackageCursorPage where Table:IterableTable & HTML.OutputStreamable { + let location:URI + private let package:PackageMetadata private - let page:Paginated + let content:Paginated
- init(package:PackageMetadata, table page:Paginated) + init(location:URI, package:PackageMetadata, content:Paginated
) { + self.location = location self.package = package - self.page = page + self.content = content } } } -extension Unidoc.ConsumersPage:Unidoc.RenderablePage +extension Unidoc.PackageCursorPage:Unidoc.RenderablePage { var title:String { "Consumers · \(self.package.symbol)" } } -extension Unidoc.ConsumersPage:Unidoc.StaticPage +extension Unidoc.PackageCursorPage:Unidoc.StaticPage { - var location:URI - { - Unidoc.ConsumersEndpoint[self.package.symbol, page: self.page.index] - } } -extension Unidoc.ConsumersPage:Unidoc.ApplicationPage +extension Unidoc.PackageCursorPage:Unidoc.ApplicationPage { func main(_ main:inout HTML.ContentEncoder, format:Unidoc.RenderFormat) { @@ -51,7 +50,7 @@ extension Unidoc.ConsumersPage:Unidoc.ApplicationPage main[.section, { $0.class = "details" }] { - $0 += self.page + $0 += self.content $0[.a] { diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift index 1de16b51f..d6ff6f22a 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift @@ -49,7 +49,52 @@ extension Unidoc.RefsEndpoint:HTTP.ServerEndpoint let view:Unidoc.Permissions = format.security.permissions(package: output.package, user: output.user) - let releases:Int = output.versions.reduce(into: 0) + // Reverse order, because we want the latest versions to come first. + let versions:[Unidoc.VersionState] = output.versions.sorted + { + $0.edition.ordering > $1.edition.ordering + } + // Find the most recent prerelease and release tags. + let prerelease:Unidoc.VersionState? = versions.first + { + $0.edition.semver != nil && !$0.edition.release + } + let release:Unidoc.VersionState? = versions.first + { + $0.edition.release + } + + // Determine if we are already building the prerelease and release tags. + let submitted:(prerelease:Bool, release:Bool) = output.pendingBuilds.reduce( + into: (false, false)) + { + if case $1.name.ref? = prerelease?.edition.name + { + $0.prerelease = true + } + if case $1.name.ref? = release?.edition.name + { + $0.release = true + } + } + + let prereleaseTool:Unidoc.BuildFormTool = .shortcut(buildable: prerelease?.edition.name, + submitted: submitted.prerelease, + package: output.package.symbol, + view: view) + let releaseTool:Unidoc.BuildFormTool = .shortcut(buildable: release?.edition.name, + submitted: submitted.release, + package: output.package.symbol, + view: view) + + let buildTools:Unidoc.BuildTools = .init( + prerelease: prereleaseTool, + release: releaseTool, + running: output.pendingBuilds, + view: view, + back: Unidoc.RefsEndpoint[output.package.symbol]) + + let releaseCount:Int = output.versions.reduce(into: 0) { if $1.edition.release { @@ -57,26 +102,32 @@ extension Unidoc.RefsEndpoint:HTTP.ServerEndpoint } } - let dependents:Unidoc.Paginated = .init( - table: .init(dependency: output.package.symbol, rows: output.dependents), + let builds:Unidoc.Paginated = .init( + table: .init( + package: output.package.symbol, + rows: output.recentBuilds, + view: view), index: -1, - truncated: output.dependents.count >= self.query.limitDependents) + truncated: output.recentBuilds.count >= self.query.limitBuilds) - let versions:Unidoc.Paginated = .init( - table: .init(package: output.package.symbol, - // Reverse order, because we want the latest versions to come first. - rows: output.versions.sorted { $0.edition.ordering > $1.edition.ordering }, - view: view, - type: .versions), + let consumers:Unidoc.Paginated = .init( + table: .init(package: output.package.symbol, rows: output.dependents), index: -1, - truncated: releases >= self.query.limitTags) + truncated: output.dependents.count >= self.query.limitDependents) let page:Unidoc.RefsPage = .init(package: output.package, - dependents: dependents, - versions: versions, + consumers: consumers, + versions: .init( + table: .init(package: output.package.symbol, + rows: versions, + view: view, + type: .versions), + index: -1, + truncated: releaseCount >= self.query.limitTags), branches: output.branches, aliases: output.aliases, - build: output.build, + buildTools: buildTools, + builds: builds, realm: output.realm, ticket: output.ticket) diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.BuildTools.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.BuildTools.swift deleted file mode 100644 index 2af05fe94..000000000 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.BuildTools.swift +++ /dev/null @@ -1,231 +0,0 @@ -import HTML -import Media -import URI - -extension Unidoc.RefsPage -{ - struct BuildTools - { - let package:Unidoc.PackageMetadata - let build:Unidoc.BuildMetadata? - let view:Unidoc.Permissions - let back:URI - - init(package:Unidoc.PackageMetadata, - build:Unidoc.BuildMetadata?, - view:Unidoc.Permissions, - back:URI) - { - self.package = package - self.build = build - self.view = view - self.back = back - } - } -} -extension Unidoc.RefsPage.BuildTools:HTML.OutputStreamable -{ - static - func += (section:inout HTML.ContentEncoder, self:Self) - { - if let progress:Unidoc.BuildProgress = self.build?.progress - { - section[.div] - { - $0.title = "You cannot cancel a build that has already started!" - } = "Cancel build" - - switch progress.request - { - case .latest(let series, force: _): - section[.div] = "Queued (\(series))" - - case .id: - section[.div] = "Queued (ref)" - } - - switch progress.stage - { - case .initializing: - section[.div] - { - $0.class = "phase" - $0.title = "The builder is initializing." - } = "Started (git)" - - case .cloningRepository: - section[.div] - { - $0.class = "phase" - $0.title = "The builder is cloning the package’s repository." - } = "Started (git)" - - case .resolvingDependencies: - section[.div] - { - $0.class = "phase" - $0.title = "The builder is resolving the package’s dependencies." - } = "Started (swiftpm)" - - case .compilingCode: - section[.div] - { - $0.class = "phase" - $0.title = "The builder is compiling the package’s source code." - } = "Started (swift)" - } - } - else if - let request:Unidoc.BuildRequest = self.build?.request - { - if self.view.editor - { - section[.form] - { - $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" - $0.action = "\(Unidoc.Post[.build, confirm: true])" - $0.method = "post" - } = Unidoc.BuildButton.latest(of: self.package, cancel: true) - } - else - { - section[.form] = Unidoc.DisabledButton.init( - label: "Cancel build", - view: self.view) - } - - switch request.version - { - case .latest(let series, of: ()): - section[.div] - { - $0.class = "phase" - $0.title = "The builder will build the latest \(series) version." - } = "Queued (\(series))" - - case .id: - section[.div] - { - $0.class = "phase" - $0.title = "The builder will build the specified git ref." - } = "Queued (ref)" - } - - section[.div] - } - else - { - if self.view.editor - { - section[.form] - { - $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" - $0.action = "\(Unidoc.Post[.build, confirm: true])" - $0.method = "post" - } = Unidoc.BuildButton.latest(of: self.package) - } - else - { - section[.form] = Unidoc.DisabledButton.init( - label: "Request build", - view: self.view) - } - - section[.div] - - switch self.build?.failure - { - case .killed?: - section[.div] - { - $0.title = """ - The build was killed due to exceeding resource limits. - """ - } = "Failed (killed)" - - case .noValidVersion?: - section[.div] - { - $0.title = """ - There were no valid versions of this package to build. - """ - } = "Skipped" - - case .failedToCloneRepository?: - section[.div] - { - $0.title = """ - We failed to clone the package’s repository. - """ - } = "Failed (git)" - - case .failedToReadManifest?: - section[.div] - { - $0.title = """ - We failed to detect the package’s manifest. - """ - } = "Failed (swiftpm)" - - case .failedToReadManifestForDependency?: - section[.div] - { - $0.title = """ - We failed to read the manifest of one of the package’s dependencies. - """ - } = "Failed (swiftpm)" - - case .failedToResolveDependencies?: - section[.div] - { - $0.title = """ - We failed to resolve the package’s dependencies. - """ - } = "Failed (swiftpm)" - - case .failedToBuild?: - section[.div] - { - $0.title = """ - We failed to build the package’s source code. - """ - } = "Failed (swift)" - - case .failedToExtractSymbolGraph?: - section[.div] - { - $0.title = """ - We failed to extract the package’s symbol graphs. - """ - } = "Failed (swift)" - - case .failedToLoadSymbolGraph?: - section[.div] - { - $0.title = """ - We failed to parse the package’s symbol graphs. - """ - } = "Failed (ssgc)" - - case .failedToLinkSymbolGraph?: - section[.div] - { - $0.title = """ - We failed to link the package’s documentation. - """ - } = "Failed (ssgc)" - - case .failedForUnknownReason?: - section[.div] - { - $0.title = """ - The builder crashed for an unknown reason. - """ - } = "Failed (ssgc)" - - case nil: - section[.div] - } - } - } -} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift index 5830543ee..a4b506430 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift @@ -10,6 +10,8 @@ extension Unidoc.RefsPage case consumers case settings case settingsAdmin + case buildTools + case builds case importRefs } } @@ -25,6 +27,8 @@ extension Unidoc.RefsPage.Heading:Identifiable case .consumers: "ss:consumers" case .settings: "ss:settings" case .settingsAdmin: "ss:settings-admin" + case .buildTools: "ss:build-tools" + case .builds: "ss:builds" case .importRefs: "ss:import-refs" } } @@ -41,6 +45,8 @@ extension Unidoc.RefsPage.Heading:HTML.OutputStreamableHeading case .consumers: "Consumers" case .settings: "Settings" case .settingsAdmin: "Admin actions" + case .buildTools: "Request builds" + case .builds: "Recent builds" case .importRefs: "Add branches" } } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift index 3eb5ad3b2..e8962ce00 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift @@ -12,7 +12,7 @@ extension Unidoc private let package:PackageMetadata private - let dependents:Paginated + let consumers:Paginated private let versions:Paginated private @@ -20,7 +20,9 @@ extension Unidoc private let aliases:[Symbol.Package] private - let build:BuildMetadata? + let buildTools:BuildTools + private + let builds:Paginated private let realm:RealmMetadata? private @@ -28,20 +30,22 @@ extension Unidoc init( package:PackageMetadata, - dependents:Paginated, + consumers:Paginated, versions:Paginated, branches:[VersionState], aliases:[Symbol.Package] = [], - build:BuildMetadata? = nil, + buildTools:BuildTools, + builds:Paginated, realm:RealmMetadata? = nil, ticket:CrawlingTicket? = nil) { self.package = package - self.dependents = dependents + self.consumers = consumers self.versions = versions self.branches = branches self.aliases = aliases - self.build = build + self.buildTools = buildTools + self.builds = builds self.realm = realm self.ticket = ticket } @@ -159,15 +163,15 @@ extension Unidoc.RefsPage section[.h2] = Heading.consumers - if self.dependents.table.isEmpty + if self.consumers.table.rows.isEmpty { section[.p] { $0.class = "note" } = "This package has no known consumers!" } else { - section[.table] = self.dependents.table + section[.table] = self.consumers.table } - if let more:URI = self.dependents.next + if let more:URI = self.consumers.next { section[.a] { $0.class = "area" ; $0.href = "\(more)" } = "Browse more consumers" } @@ -354,46 +358,16 @@ extension Unidoc.RefsPage section[.form] = Unidoc.DisabledButton.init(label: "Refresh tags", view: self.view) } - section[.div] - { - $0.class = "build-pipeline" - } = BuildTools.init(package: self.package, - build: self.build, - view: self.view, - back: self.location) + section[.h2] = Heading.buildTools + section += self.buildTools // All logged-in users can see the build logs. The only reason they are not totally // public is to prevent crawlers from making dynamic CloudFront requests, because the // CDN firewall is less effective than our apex firewall. - if case _? = self.view.global, - let build:Unidoc.BuildMetadata = self.build, - !build.logs.isEmpty + if !self.builds.table.rows.isEmpty { - section[.h3] = "Build logs" - section[.ol] - { - $0.class = "build-logs" - } - content: - { - for log:Unidoc.BuildLogType in build.logs - { - $0[.li] - { - // We never persist logs anywhere except in S3, where they are - // served through CloudFront. Therefore, we can safely hardcode - // the URL here. - let path:Unidoc.BuildLogPath = .init(package: build.id, type: log) - - $0[.a] - { - $0.target = "_blank" - $0.href = "https://static.swiftinit.org\(path)" - $0.rel = .external - } = log.name - } - } - } + section[.h3] = Heading.builds + section[.table] = self.builds.table } section[.h3] = Heading.importRefs diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift index 5ba3393a3..2c30b9416 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift @@ -7,14 +7,14 @@ extension Unidoc.RefsTable.Row struct Graph { private - let symbol:Symbol.Edition + let symbol:Symbol.PackageAtRef private let state:State private let view:Unidoc.Permissions - init(symbol:Symbol.Edition, + init(symbol:Symbol.PackageAtRef, state:State, view:Unidoc.Permissions) { @@ -107,9 +107,9 @@ extension Unidoc.RefsTable.Row.Graph:HTML.OutputStreamable $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" $0.action = "\(Unidoc.Post[.build, confirm: true])" $0.method = "post" - } = Unidoc.BuildButton.edition(id: self.id, - package: self.symbol.package, - ref: self.symbol.ref) + } = Unidoc.BuildFormTool.init( + form: .init(symbol: self.symbol, action: .submit), + area: false) } guard diff --git a/Sources/_MongoDB/Collections/Mongo.CollectionModel.swift b/Sources/_MongoDB/Collections/Mongo.CollectionModel.swift index f976d5766..74a1e1ebe 100644 --- a/Sources/_MongoDB/Collections/Mongo.CollectionModel.swift +++ b/Sources/_MongoDB/Collections/Mongo.CollectionModel.swift @@ -451,13 +451,12 @@ extension Mongo.CollectionModel where Element:Insertable } } -extension Mongo.CollectionModel +extension Mongo.CollectionModel where Element:BSONDecodable, Element.ID:BSONEncodable { @inlinable package func modify(upserting id:Element.ID, returning phase:Mongo.UpdatePhase = .new, do encode:(inout Mongo.UpdateEncoder) -> ()) async throws -> (state:Element, new:Bool) - where Element:BSONDecodable, Element.ID:BSONEncodable { let (element, upserted):(Element, Element.ID?) = try await session.run( command: Mongo.FindAndModify>.init(Self.name, @@ -474,7 +473,6 @@ extension Mongo.CollectionModel func modify(existing id:Element.ID, returning phase:Mongo.UpdatePhase = .new, do encode:(inout Mongo.UpdateEncoder) -> ()) async throws -> Element? - where Element:BSONDecodable, Element.ID:BSONEncodable { let (element, _):(Element?, Never?) = try await session.run( command: Mongo.FindAndModify>.init(Self.name, @@ -494,7 +492,6 @@ extension Mongo.CollectionModel func modify(existing predicate:some Mongo.PredicateEncodable, returning phase:Mongo.UpdatePhase = .new, do encode:(inout Mongo.UpdateEncoder) -> ()) async throws -> Element? - where Element:BSONDecodable, Element.ID:BSONEncodable { let (element, _):(Element?, Never?) = try await session.run( command: Mongo.FindAndModify>.init(Self.name, @@ -509,8 +506,47 @@ extension Mongo.CollectionModel against: self.database) return element } -} + @inlinable package + func remove(id:Element.ID) async throws -> Element? + + { + let (element, _):(Element?, Never?) = try await session.run( + command: Mongo.FindAndModify>.init(Self.name, + returning: .deleted) + { + $0[.query] { $0["_id"] = id } + }, + against: self.database) + return element + } + + @inlinable package + func remove(matching predicate:some Mongo.PredicateEncodable) async throws -> Element? + { + let (element, _):(Element?, Never?) = try await session.run( + command: Mongo.FindAndModify>.init(Self.name, + returning: .deleted) + { + $0[.query] { predicate.encode(to: &$0) } + }, + against: self.database) + return element + } + + @inlinable package + func remove(where predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Element? + { + let (element, _):(Element?, Never?) = try await session.run( + command: Mongo.FindAndModify>.init(Self.name, + returning: .deleted) + { + $0[.query, predicate] + }, + against: self.database) + return element + } +} extension Mongo.CollectionModel { @inlinable @@ -589,8 +625,8 @@ extension Mongo.CollectionModel try await self.delete { $0["_id"] = id } } - @inlinable - func delete(matching predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Bool + @inlinable public + func delete(where predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Bool { let response:Mongo.DeleteResponse = try await session.run( command: Mongo.Delete.init(Self.name) @@ -607,7 +643,8 @@ extension Mongo.CollectionModel return deletions.deleted != 0 } - func deleteAll(matching predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Int + @inlinable public + func deleteAll(where predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Int { let response:Mongo.DeleteResponse = try await session.run( command: Mongo.Delete.init(Self.name) From 3382a4f9726f185155ffca3d2c2f439fa177e75e Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 19:57:04 +0000 Subject: [PATCH 02/13] remember to encode missing fields in submitBuild(id:name:) --- .../UnidocDB/Building/Unidoc.DB.PendingBuilds.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift index 675cab67c..82777d795 100644 --- a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift +++ b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift @@ -143,12 +143,12 @@ extension Unidoc.DB.PendingBuilds upserting: id, returning: .new) { - $0[.setOnInsert] - { - $0[Unidoc.PendingBuild[.id]] = id - $0[Unidoc.PendingBuild[.enqueued]] = UnixMillisecond.now() - $0[Unidoc.PendingBuild[.name]] = name - } + $0[.setOnInsert] = Unidoc.PendingBuild.init(id: id, + enqueued: UnixMillisecond.now(), + launched: nil, + assignee: nil, + stage: nil, + name: name) } return pendingBuild } From fc80310e5bbb4903f026d3fd82061ba4a7cb9acb Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 22:07:06 +0000 Subject: [PATCH 03/13] add build UI --- Assets/css/Main.css | 2293 ++++++++++++++++- Assets/css/Main.css.map | 2 +- .../Building/Unidoc.CompleteBuild.swift | 34 +- .../Building/Unidoc.DB.PendingBuilds.swift | 28 +- .../Unidoc.BuilderUploadOperation.swift | 15 +- .../Requests/Unidoc.BuildRequestPage.swift | 5 + .../Endpoints/Tags/Unidoc.BuildButton.swift | 14 + .../Tags/Unidoc.BuildButtonType.swift | 8 + .../Endpoints/Tags/Unidoc.BuildFormTool.swift | 37 +- .../Endpoints/Tags/Unidoc.BuildTools.swift | 81 +- .../Tags/Unidoc.CompleteBuildsTable.swift | 5 +- .../Endpoints/Tags/Unidoc.RefsEndpoint.swift | 2 + .../Tags/Unidoc.RefsPage.Heading.swift | 41 +- .../Endpoints/Tags/Unidoc.RefsPage.swift | 114 +- .../Tags/Unidoc.RefsTable.Row.Graph.swift | 2 +- Stylesheets/Main.scss | 3 +- Stylesheets/_button.scss | 7 +- Stylesheets/_div.build-pipeline.scss | 39 - Stylesheets/_div.hstack.scss | 17 + Stylesheets/_ol.builds-pending.scss | 29 + 20 files changed, 2558 insertions(+), 218 deletions(-) create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift create mode 100644 Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButtonType.swift delete mode 100644 Stylesheets/_div.build-pipeline.scss create mode 100644 Stylesheets/_div.hstack.scss create mode 100644 Stylesheets/_ol.builds-pending.scss diff --git a/Assets/css/Main.css b/Assets/css/Main.css index b811ce910..0537bfa38 100644 --- a/Assets/css/Main.css +++ b/Assets/css/Main.css @@ -1 +1,2292 @@ -:root{--color-accent-light: rgb(255, 172, 200);--color-accent-semi: rgb(255, 136, 176);--color-accent: rgb(255, 86, 158);--color-accent-dark: rgb(209, 23, 104);--color-hot: rgb(253, 39, 110);--color-secondary: rgb(189, 108, 255);--color-secondary-dark: rgb(150, 80, 255);--color-tertiary: rgb(0, 193, 207)}:root{--blur-shadow: rgba(0, 0, 0, 0.15);--bg: rgb(250, 250, 250);--bg-translucent: rgba(250, 250, 250, 0.5);--bg-codewell: rgb(240, 240, 240);--bg-codewell-gloss: rgb(236, 236, 236);--bg-elevated: rgb(239, 239, 239);--bg-elevated-shadow: rgb(182, 182, 182);--bg-alert: rgb(255, 103, 103);--bg-alert-shadow: rgb(235, 75, 75);--bg-warn: rgb(255, 163, 58);--bg-warn-shadow: rgb(248, 123, 21);--bg-rose: rgb(255, 86, 158);--bg-rose-shadow: rgb(209, 23, 104);--bg-aqua: rgb(0, 193, 207);--bg-aqua-shadow: rgb(0, 162, 173);--bg-accent: rgb(255, 224, 239);--bg-accent-translucent: rgba(255, 224, 239, 0.5);--bg-accent-shadow: var(--color-accent);--bg-highlight: rgba(255, 238, 0, 0.25);--bg-diff-insert: rgba(0, 255, 255, 0.3);--bg-diff-delete: rgba(255, 72, 0, 0.3);--bg-diff-update: rgba(255, 0, 212, 0.3);--fg: rgb(22, 22, 22);--fg-heavy: rgb(50, 50, 50);--fg-semi: rgb(109, 109, 109);--fg-light: rgb(172, 172, 172);--fg-snippet-line-number: rgb(192, 192, 192);--fg-accent: var(--color-accent);--fg-accent-semi: var(--color-accent-semi);--fg-accent-light: var(--color-accent-light);--fg-warn: rgb(255, 115, 0);--fg-swift: rgb(134, 134, 134);--fg-swift-comment: rgb(109, 109, 109);--fg-swift-keyword: rgb(253, 39, 110);--fg-swift-literal: rgb(141, 61, 231);--fg-swift-type: rgb(255, 123, 0)}@media(prefers-color-scheme: dark){:root{--color-accent-light: rgb(153, 0, 71);--color-accent-semi: rgb(218, 0, 102);--color-accent: rgb(255, 40, 133);--color-accent-dark: rgb(255, 86, 158);--color-hot: rgb(253, 39, 110);--color-secondary: rgb(189, 108, 255);--color-secondary-dark: rgb(150, 80, 255);--color-tertiary: rgb(0, 193, 207)}:root{--blur-shadow: rgba(0, 0, 0, 0.75);--bg: rgb(20, 20, 20);--bg-translucent: rgba(20, 20, 20, 0.3);--bg-codewell: rgb(30, 30, 30);--bg-codewell-gloss: rgb(36, 36, 36);--bg-elevated: rgb(32, 32, 32);--bg-elevated-shadow: rgb(17, 17, 17);--bg-accent: rgb(66, 0, 31);--bg-accent-translucent: rgba(66, 0, 31, 0.5);--bg-accent-shadow: rgb(190, 0, 95);--bg-alert: rgb(211, 29, 29);--bg-alert-shadow: rgb(155, 0, 0);--bg-warn: rgb(233, 129, 10);--bg-warn-shadow: rgb(189, 71, 2);--bg-aqua: rgb(0, 133, 167);--bg-aqua-shadow: rgb(0, 96, 121);--bg-highlight: rgba(255, 184, 31, 0.15);--bg-diff-insert: rgba(0, 183, 255, 0.2);--bg-diff-delete: rgba(255, 72, 0, 0.2);--bg-diff-update: rgba(255, 0, 212, 0.2);--fg: rgb(211, 211, 211);--fg-heavy: rgb(192, 192, 192);--fg-semi: rgb(129, 129, 129);--fg-light: rgb(59, 59, 59);--fg-snippet-line-number: rgb(109, 109, 109);--fg-swift: rgb(117, 117, 117);--fg-swift-comment: rgb(139, 139, 139);--fg-swift-keyword: rgb(255, 53, 97);--fg-swift-literal: rgb(195, 143, 255);--fg-swift-type: rgb(255, 161, 37)}}:root{--github-icon: url('data:image/svg+xml;utf8, ');--leaving-icon: url('data:image/svg+xml;utf8, ');--broken-link-icon: url('data:image/svg+xml;utf8, ')}a{color:var(--fg-accent);text-decoration:none;text-underline-offset:.35em}a:hover,a:focus{text-decoration:underline}a:not([href]){color:var(--fg-semi);text-decoration:none}a:not([href])::after{display:inline-block;content:"";height:12px;width:12px;margin-left:.5em;margin-right:.25em;background:var(--fg-accent);mask-image:var(--broken-link-icon);mask-size:100% 100%;-webkit-mask-image:var(--broken-link-icon);-webkit-mask-size:100% 100%;background-color:var(--fg-semi)}a.area{margin:1.5rem 0;border:2px dashed var(--fg-accent-semi);text-decoration:none;font-family:"DM Sans",sans-serif;font-weight:700;font-size:120%;display:block;padding:.5rem 0;text-align:center}a.area:hover,a.area:focus{border-color:var(--fg-accent)}a[rel~=external]::after{display:inline-block;content:"";height:12px;width:12px;margin-left:.5em;margin-right:.25em;background:var(--fg-accent);mask-image:var(--leaving-icon);mask-size:100% 100%;-webkit-mask-image:var(--leaving-icon);-webkit-mask-size:100% 100%}blockquote{margin:2rem 0;font-style:italic;color:var(--fg-heavy)}blockquote em{font-style:normal}blockquote img{max-width:100%;max-height:40rem}button.area{margin:1.5rem 0;border:2px dashed var(--fg-accent-semi);text-decoration:none;font-family:"DM Sans",sans-serif;font-weight:700;font-size:120%;display:block;padding:.5rem 0;text-align:center;width:100%;background:none;box-shadow:none;border-radius:0;color:var(--fg-accent)}button.area:hover,button.area:focus{border-color:var(--fg-accent);box-shadow:none}button.area:active{box-shadow:none;transform:none}button.area:disabled{color:var(--fg-semi);border-color:var(--fg-light);cursor:not-allowed}button.text,button.text:hover,button.text:focus,button.text:active{display:inline-block;background:none;box-shadow:none;transform:none;padding:0;border-radius:0;font-family:"Literata",serif;font-size:inherit;color:var(--fg-accent);text-underline-offset:.35em}button.text:hover{text-decoration:underline}section.introduction>nav.calendar{margin:2rem 0 1rem 0;display:flex;justify-content:center;align-items:center;height:3rem}section.introduction>nav.calendar>h1{margin:0 .5em}section.introduction>nav.calendar>h1,section.introduction>nav.calendar a{font-size:1.8rem;font-family:"Literata",serif;font-weight:normal;line-height:100%}section.introduction>nav.calendar>a{color:var(--fg-semi)}section.introduction>nav.calendar>a:hover,section.introduction>nav.calendar>a:focus{color:var(--fg-accent);text-decoration:none}section.details>ol.calendar{list-style:none;padding:0;display:grid;grid-gap:.25rem;grid-template-columns:repeat(7, 1fr)}@media only screen and (min-width: 56rem){section.details>ol.calendar{grid-gap:.5rem}}section.details>ol.calendar li>*{min-height:3rem;display:flex;flex-direction:column;justify-content:space-between;background-color:var(--bg-codewell);border-radius:.25rem}@media only screen and (min-width: 56rem){section.details>ol.calendar li>*{padding:.35rem .5rem .5rem .5rem}}section.details>ol.calendar li>* p:first-child{text-align:right}section.details>ol.calendar li>* p{padding:0;margin:0;line-height:1em}section.details>ol.calendar li>div p{color:var(--fg-semi)}section.details>ol.calendar li>a p.crawled{font-weight:bold}section.details>ol.calendar li>* span.m{color:var(--fg-light)}section.details>ol.calendar li>* span.d{color:var(--fg)}section.details>ol.calendar li>a:hover,section.details>ol.calendar li a:focus{background-color:var(--bg-accent);text-decoration:none}section.details>ol.calendar li>a:hover span.m,section.details>ol.calendar li a:focus span.m{color:var(--fg-accent-semi)}section.details>ol.calendar li>a:hover span.d,section.details>ol.calendar li a:focus span.d{color:var(--fg-accent)}section.details>ol.calendar li.first>* span.m{color:var(--fg)}section.details>ol.calendar li.first>a:hover span.m,section.details>ol.calendar li.first a:focus span.m{color:var(--fg-accent)}p.chyron{display:flex;flex-direction:row;justify-content:space-between;color:var(--fg-semi)}p.chyron>span.release{font-weight:bold}p.chyron>span:last-child{display:flex;gap:.5em}p.chyron>span:last-child span.pushed::before{content:"▲ "}p.chyron>span:last-child span.stars::before{content:"★ ";color:var(--fg-swift-type)}ol.packages{padding-left:0;list-style:none;display:flex;flex-direction:column;gap:1rem}ol.packages>li:first-child>p:first-child{border-top:none}ol.packages>li>p:first-child{display:flex;flex-direction:row;justify-content:space-between}ol.packages>li>p{margin:.25rem 0}ol.packages>li>p:first-child{margin-top:.5rem;border-top:2px solid var(--bg-elevated)}ol.packages>li>p:first-child a{font-style:italic}ol.packages>li>p:first-child a.dead{color:var(--fg-semi)}ol.packages>li>p:first-child span.owner{color:var(--fg-semi)}ol.packages>li>p:first-child span.owner::before{content:" by "}ol.packages>li>p.chyron>span.release{font-weight:bold}code{font-family:"IBM Plex Mono",monospace;white-space:pre-wrap;hyphens:none}code.multiline span.xi::before{content:"\a"}code.multiline span.xi::after{content:" ";display:inline-block}code.multiline wbr{display:block}div.build-pipeline{display:flex;gap:1rem}div.build-pipeline button.area{margin:0;height:100%}div.build-pipeline>*{flex:1;height:5rem}div.build-pipeline>div{border:2px dashed var(--fg-light);text-decoration:none;font-family:"DM Sans",sans-serif;font-weight:700;font-size:120%;box-sizing:border-box;display:flex;justify-content:center;text-align:center;align-items:center;color:var(--fg-semi)}div.build-pipeline>div.phase{border-color:var(--fg-semi);color:var(--fg)}@media only screen and (min-width: 56rem){div.columns{display:grid}div.columns>div{overflow:scroll}}div.constraints code,div.constraints code{line-height:1.6rem;color:var(--fg-swift);font-size:inherit;display:block;text-indent:-3.6rem;margin-left:3.6rem}div.constraints code span.xk,div.constraints code span.xk{color:var(--fg-swift-comment)}div.constraints code span.xv,div.constraints code span.xt,div.constraints code span.xu,div.constraints code span.xv,div.constraints code span.xt,div.constraints code span.xu{color:var(--fg-swift-type);font-weight:500}div.constraints code a,div.constraints code a{text-decoration:underline;text-decoration-style:dashed;text-decoration-color:var(--fg-swift-type)}div.constraints code a:hover,div.constraints code a:hover{text-decoration-style:solid}div.menu{display:inline-block;position:relative}div.menu>button{background:none;box-shadow:none;border:none;color:var(--fg-semi);cursor:pointer}div.menu>button:hover{color:var(--fg-dark)}div.menu>ul{position:absolute;display:none;background-color:var(--bg);list-style-type:none;margin:0;padding:.5em 1em .75em 1em;box-shadow:0 1rem 3rem var(--blur-shadow);border-radius:.5rem}@supports(backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)){div.menu>ul{background-color:var(--bg-translucent);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%)}}div.menu>ul>li>a{display:block}div.menu>ul>li>*{width:100%}div.menu>ul>li>form>button{width:100%;text-align:left}div.menu.open>button{color:var(--fg)}div.menu.open>ul{display:flex;flex-direction:column;z-index:1}div.more{margin:1.5rem 0;text-align:center}div.more>div.charts{display:flex;flex-flow:row wrap;justify-content:center}div.more>div.charts h2,div.more>div.charts h3{font-size:100%;margin:0}div.more>div.charts h3{font-weight:500;color:var(--fg-semi)}div.more>div.charts figure{margin:1rem}div.more>div.charts figure div.pie{font-size:10px}div.sidebar ol.table-of-contents{margin-top:10rem;position:sticky;top:3rem;padding:0 1rem 1rem 1rem;list-style:none;font-size:.875rem;max-height:calc(100vh - 3rem - 1rem);overflow-y:auto;overflow-x:hidden}div.sidebar ol.table-of-contents li{position:relative;color:var(--fg-semi)}div.sidebar ol.table-of-contents li a{color:inherit}div.sidebar ol.table-of-contents li::before{position:absolute;content:"•";left:-0.8em;color:var(--fg-light)}div.sidebar ol.table-of-contents li.active::before{content:"•";color:var(--fg-heavy)}div.sidebar ol.table-of-contents li.active+li.active::before{color:var(--fg-light)}div.sidebar ol.table-of-contents li.title{font-style:italic;color:var(--fg);padding-bottom:.4em;margin-bottom:.6em;border-bottom:1px solid var(--fg-light)}div.sidebar ol.table-of-contents li.title::before{content:"";display:none}div.sidebar ol.table-of-contents li.active{color:var(--fg)}div.sidebar ol.table-of-contents li.h2{margin-left:0}div.sidebar ol.table-of-contents li.h3{margin-left:1rem}div.sidebar ol.table-of-contents li.h4{margin-left:2rem}div.sidebar ol.table-of-contents li.group{margin-top:.5rem;display:flex;align-content:center}div.sidebar ol.table-of-contents li.group>a>span{font-weight:700;font-style:italic}div.sidebar ol.table-of-contents li.group>a>span,div.sidebar ol.table-of-contents li.group>a>code{display:block}div.sidebar ol.table-of-contents li.group>a>code{font-style:normal;font-size:.75rem;margin:.2em 0;overflow-wrap:anywhere}div.sidebar div.nountree{margin-left:1rem;margin-top:10rem;font-family:"IBM Plex Mono",monospace;font-size:.8125rem}div.sidebar div.nountree>a::first-letter{color:var(--fg-accent)}div.sidebar div.nountree>a.text{font-family:"Literata",serif;font-style:italic;font-size:.875rem}div.sidebar div.nountree>a.text+a:not(.text){margin-top:.5rem}div.sidebar div.nountree>a.extension.local::first-letter{color:var(--color-secondary)}div.sidebar div.nountree>a.extension.foreign::first-letter{color:var(--color-tertiary)}div.sidebar div.nountree div.indent{padding-left:1rem}div.sidebar div.nountree a,div.sidebar div.nountree span{display:block;overflow:hidden;white-space:nowrap;text-overflow:"...";color:var(--fg-semi)}div.sidebar div.nountree a:hover{text-decoration:none;color:var(--fg-accent)}div.sidebar div.nountree a.extension.local:hover{color:var(--color-secondary)}div.sidebar div.nountree a.extension.foreign:hover{color:var(--color-tertiary)}div.tooltips{position:fixed;z-index:1}div.tooltips>div{position:fixed;display:block;pointer-events:none;opacity:0;margin-top:0;margin-left:-1rem;margin-right:-1rem;transition:all .25s;background-color:var(--bg);padding:.5rem 1rem;box-shadow:0 1rem 3rem var(--blur-shadow);border-radius:.5rem;max-width:30rem}@supports(backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)){div.tooltips>div{background-color:var(--bg-translucent);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%)}}div.tooltips>div pre{margin:0;font-size:90.625%}div.tooltips>div p{font-size:90.625%;line-height:1.4em;display:none}div.tooltips>div.visible{opacity:1;margin-top:.5rem;transition:opacity .25s;transition:margin-top .15s}div.tooltips>div.overview p{display:block}dl{display:grid;grid-template-columns:33% 67%}dl dt{grid-column:1}dl dd{grid-column:2}dl dd>p:first-child{margin-top:0}figure{margin:2rem 0}figure img{max-width:100%;max-height:40rem}figure.chart{display:flex;align-items:center;flex-direction:column}@media only screen and (min-width: 56rem){figure.chart{flex-direction:row}}figure.chart div.pie{margin:0;font-size:20px}figure.chart div.pie>div.pie-color{height:0;width:0;margin:0}figure.chart div.pie>div.pie-color>svg,figure.chart div.pie>div.pie-geometry{width:12em;height:12em;margin:0;position:relative}figure.chart div.pie>div.pie-geometry{transition:box-shadow .1s ease-out;pointer-events:none;border-radius:6em;box-shadow:inset 0 -0.5em .75em -0.75em rgba(255,255,255,.3),inset 0 -0.4em .5px .1em rgba(0,0,0,.24),inset 0 -1.3em 5em .1em rgba(0,0,0,.14)}figure.chart div.pie>div.pie-color:hover+div.pie-geometry{box-shadow:inset 0 -0.5em 1.5em -0.75em rgba(255,255,255,.5),inset 0 -0.4em .5px .1em rgba(0,0,0,.18),inset 0 -1.3em 5em .1em rgba(0,0,0,.11)}@media(prefers-color-scheme: dark){figure.chart div.pie>div.pie-geometry{box-shadow:inset 0 -0.5em .75em -0.75em rgba(255,255,255,.2),inset 0 -0.4em .5px .1em rgba(0,0,0,.34),inset 0 -1.3em 5em .1em rgba(0,0,0,.3)}figure.chart div.pie>div.pie-color:hover+div.pie-geometry{box-shadow:inset 0 -0.5em 1.5em -0.75em rgba(255,255,255,.4),inset 0 -0.4em .5px .1em rgba(0,0,0,.18),inset 0 -1.3em 5em .1em rgba(0,0,0,.16)}}figure.chart div.pie+figcaption{align-self:flex-start}figure.chart div.pie+figcaption dl{margin:0 0 0 1rem;display:grid;grid-template-columns:auto 4rem;direction:rtl}figure.chart div.pie+figcaption dl dd{grid-column:2;margin:0}figure.chart div.pie+figcaption dl dt{direction:ltr;margin-left:1rem}figure.chart.decl svg>g>*{fill:#fff}figure.chart.decl svg>g>*.function{fill:#ff6767}figure.chart.decl svg>g>*.operator{fill:#ff326f}figure.chart.decl svg>g>*.constructor{fill:#ff678d}figure.chart.decl svg>g>*.method{fill:#ffaac4}figure.chart.decl svg>g>*.subscript{fill:#fa68ff}figure.chart.decl svg>g>*.functor{fill:#c670ff}figure.chart.decl svg>g>*.protocol{fill:#52ceff}figure.chart.decl svg>g>*.requirement{fill:#82fff9}figure.chart.decl svg>g>*.witness{fill:#abfff4}figure.chart.decl svg>g>*.macro.attached{fill:#3dff67}figure.chart.decl svg>g>*.macro.freestanding{fill:#84ff9f}figure.chart.decl svg>g>*.structure{fill:#fff45e}figure.chart.decl svg>g>*.class{fill:#ffd448}figure.chart.decl svg>g>*.actor{fill:#ffc527}figure.chart.decl svg>g>*.typealias{fill:#ff9a26}figure.chart.spis svg>g>*.none{fill:#eee}figure.chart.spis svg>g>*.underscored{fill:#b1b1b1}figure.chart.spis svg>g>*.unknown{fill:#ffd255}figure.chart.spis svg>g>*.nominal{fill:#6783ff}figure.chart.coverage svg>g>*.undocumented{fill:#eee}figure.chart.coverage svg>g>*.indirect{fill:#ffaac4}figure.chart.coverage svg>g>*.direct{fill:#ff678d}form button{display:inline-block;background:var(--fg-accent);box-shadow:0 .2rem 0 0 var(--fg-accent-semi);border:none;border-radius:.25rem;color:#fff;font-family:"DM Sans",sans-serif;font-size:87.5%}form button:hover{cursor:pointer}form button:hover,form button:focus{box-shadow:0 .2rem 0 0 var(--fg-accent-light)}form button:active{box-shadow:none;transform:translateY(0.2rem)}form fieldset{border:2px var(--fg-light) solid}form.config input[type=url]{width:100%;box-sizing:border-box}form.config select,form.config input[type=text],form.config input[type=url]{outline:none;background:var(--bg-codewell);margin-bottom:.5em;padding:.1rem .5rem;border-radius:.25rem;border:none;color:var(--fg);font-family:"Literata",serif;font-style:italic;font-size:inherit}form.config select{padding-bottom:.2em}form.config select[disabled],form.config select[readonly],form.config input[type=text][disabled],form.config input[type=text][readonly],form.config input[type=url][disabled],form.config input[type=url][readonly]{background:none;color:var(--fg-semi)}form.config select:not([disabled]):not([readonly]):hover,form.config select:not([disabled]):not([readonly]):focus,form.config input[type=text]:not([disabled]):not([readonly]):hover,form.config input[type=text]:not([disabled]):not([readonly]):focus,form.config input[type=url]:not([disabled]):not([readonly]):hover,form.config input[type=url]:not([disabled]):not([readonly]):focus{background:var(--bg-codewell-gloss)}form.config input[type=text]:invalid,form.config input[type=url]:invalid{color:var(--fg-accent)}form.config input[type=text]::placeholder,form.config input[type=url]::placeholder{color:var(--fg-heavy)}form.sort-controls fieldset{display:flex;gap:1rem}form.sort-controls fieldset>label{display:flex;align-items:center}form.sort-controls fieldset>label>input{margin-right:.5em}header.visual{display:flex;justify-content:space-between;align-content:center}header.visual>h2,header.visual h3{margin-top:auto;margin-bottom:auto}header.visual>div.visibility{margin:1rem 0;filter:grayscale(100%)}header.visual>img.icon{height:5rem;border-radius:50%}h1,h2,h3,h4,h5,h6{font-family:"DM Sans",sans-serif}dt[id]>a,dt[name]>a{color:inherit}h1[id]>a,h1[name]>a,h2[id]>a,h2[name]>a,h3[id]>a,h3[name]>a,h4[id]>a,h4[name]>a,h5[id]>a,h5[name]>a,h6[id]>a,h6[name]>a{position:relative;color:inherit}@media only screen and (min-width: 56rem){h1[id]>a::before,h1[name]>a::before,h2[id]>a::before,h2[name]>a::before,h3[id]>a::before,h3[name]>a::before,h4[id]>a::before,h4[name]>a::before,h5[id]>a::before,h5[name]>a::before,h6[id]>a::before,h6[name]>a::before{content:"$";position:absolute;margin-left:-1.6rem;font-weight:400;font-family:"IBM Plex Mono",monospace;font-size:92.5%;color:var(--fg-semi);opacity:.5}h1[id]>a:hover::before,h1[name]>a:hover::before,h2[id]>a:hover::before,h2[name]>a:hover::before,h3[id]>a:hover::before,h3[name]>a:hover::before,h4[id]>a:hover::before,h4[name]>a:hover::before,h5[id]>a:hover::before,h5[name]>a:hover::before,h6[id]>a:hover::before,h6[name]>a:hover::before{opacity:1}}h1{font-size:200%}h2{font-size:150%}h3{font-size:130%}h4,h5,h6{font-size:110%}kbd{background-color:var(--bg-codewell);padding:.2rem .5rem .25rem;border-radius:.3em;box-shadow:0 .25rem 0 var(--bg-elevated-shadow)}main.home>div.search-tool{height:4rem;position:relative;z-index:1}main.home>div.search-tool div.searchbar{max-width:100%}main.home>nav{margin-bottom:1rem}main.home>nav ul{list-style-type:none;padding:0;margin:0;font-family:"DM Sans",sans-serif;display:flex;flex-direction:column}main.home>nav ul li{padding:.25rem 0}@media only screen and (min-width: 56rem){main.home>nav ul{display:grid;grid-template-columns:1fr 1fr}main.home>nav ul>li{display:flex}main.home>nav ul>li:nth-child(even){justify-content:flex-end}}main.home>div.feeds section{margin-bottom:1rem}main.home>div.feeds section:last-child{text-align:right}@media only screen and (min-width: 56rem){main.home>div.feeds{display:flex;flex-direction:row;justify-content:space-between;min-width:30rem}}main.home>div.feeds h2{margin:0;padding:0;font-size:112.5%;color:var(--fg)}main.home>div.feeds ol{list-style-type:none;padding:0;margin:0}main.home>div.feeds ol li{margin:.5rem 0}main.home>div.feeds ol li>p.edition{margin:0}main.home>div.feeds ol li>p.edition>span:first-child{color:var(--fg-heavy)}main.home>div.feeds ol li>p.edition>*:first-child{font-style:italic}main.home>div.feeds ol li>p.edition>*:last-child{margin-left:.5em}main.home>div.feeds ol li>p.age{margin:0;font-size:87.5%;color:var(--fg-semi)}nav.cornice{font-family:"DM Sans",sans-serif;font-size:12pt}nav.cornice>div:first-child{font-weight:700}nav.paginator{display:flex;justify-content:space-between;margin:1rem 0}nav.paginator>*:first-child::before{content:"◀";padding-right:.5em}nav.paginator>*:last-child::after{content:"▶";padding-left:.5em}section.notice,section.signage{margin:.75rem 0}section.notice p,section.signage p{margin:0}section.notice{font-style:italic;color:var(--fg-accent)}section.notice code{font-style:normal;background-color:rgba(0,0,0,0)}section.notice a{text-decoration:underline;text-decoration-style:dashed}section.notice a:hover{text-decoration-style:solid}section.notice em{font-weight:700}section.signage{display:flex;align-items:stretch}section.signage::before{padding:.25rem 1rem .5rem 1rem;font-family:"Literata",serif;font-weight:400;border-radius:.5rem 0 0 .5rem;color:#fff}section.signage.spi::before{font-size:125%;background-color:var(--bg-warn);box-shadow:0 .25rem 0 0 var(--bg-warn-shadow);content:"@"}section.signage.deprecation::before{font-size:150%;font-family:"IBM Plex Mono",monospace;background-color:var(--bg-alert);box-shadow:0 .25rem 0 0 var(--bg-alert-shadow);content:"!"}section.signage.deprecation.renamed::before{background-color:var(--bg-aqua);box-shadow:0 .25rem 0 0 var(--bg-aqua-shadow);content:"→"}section.signage>p{flex:1 1 auto;padding:.5rem .5rem .5rem 1rem;font-family:"IBM Plex Mono",monospace;background-color:var(--bg-elevated);border-radius:0 .5rem .5rem 0;box-shadow:0 .25rem 0 0 var(--bg-elevated-shadow)}ol.steps{padding-left:0}ol.build-logs{padding-left:1em}ol.build-logs>li::marker{font-variant-numeric:oldstyle-nums}p{line-height:1.8rem;hyphens:auto}p code{background-color:var(--bg-codewell);font-size:90.625%;padding:.2rem .5rem .25rem;line-height:0;border-radius:.4rem}p img{max-width:100%;max-height:40rem}p.note{color:var(--fg-semi);font-style:italic}p.note em{font-style:normal}pre{margin:2rem 0 2rem;line-height:1.5rem}pre code *.xv,pre code *.xo,pre code *.extendee{color:var(--fg)}pre code *.xb{color:var(--fg)}pre code *.xa,pre code *.xr,pre code *.xk,pre code *.xm{color:var(--fg-swift-keyword)}pre code *.xj,pre code *.xp{color:var(--fg-accent)}pre code *.xn,pre code *.xs{color:var(--fg-swift-literal)}pre code *.xy,pre code *.xz,pre code *.xt{color:var(--fg-swift-type)}pre code *.xu{color:var(--fg-accent);font-style:italic}pre code *.xc,pre code *.xd{color:var(--fg-swift-comment);font-style:italic}pre code a{color:inherit}pre code a{text-decoration:underline dashed}pre code a:hover{text-decoration:underline}pre.snippet{overflow:scroll;padding:.5rem 0 .5rem 3rem;background-color:var(--bg-codewell);scrollbar-color:var(--fg-accent) rgba(0,0,0,0);scrollbar-gutter:stable}pre.snippet code{counter-set:line 0;white-space:pre}pre.snippet code span.newline{counter-increment:line}pre.snippet code span.newline:after{color:var(--fg-snippet-line-number);position:relative;display:inline-block;content:counter(line);width:0;right:2rem;direction:rtl;word-wrap:normal}pre.snippet code ins{background-color:var(--bg-diff-insert);text-decoration:inherit}pre.snippet code del{background-color:var(--bg-diff-delete);text-decoration:inherit}pre.snippet code mark{background-color:var(--bg-diff-update);text-decoration:inherit}pre.snippet code.language-swift{color:var(--fg-swift)}div.searchbar-container{padding:.5rem 0}div.searchbar-container label.checkbox{background-color:var(--bg);max-width:20rem;margin-top:.5rem;margin-left:.5rem}@supports(backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)){div.searchbar-container label.checkbox{background-color:var(--bg-translucent);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%)}}@media only screen and (min-width: 56rem){div.searchbar-container label.checkbox{margin-top:0}div.searchbar-container{display:flex;flex-direction:row;justify-content:left;align-items:center}}div.search-results-container{display:flex;flex-direction:row;justify-content:left;align-items:center}div.searchbar{flex:1 1 20rem;max-width:20rem;padding:.25rem .5rem .25rem 1rem;background-color:var(--bg);box-shadow:0 1rem 1.5rem var(--blur-shadow),0 .2rem 0 var(--blur-shadow);border-radius:.5rem}@supports(backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)){div.searchbar{background-color:var(--bg-translucent);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%)}}#search-results li:last-child{box-shadow:0 .25rem 0 var(--bg-accent-shadow)}#search-input::placeholder{color:var(--fg-semi);opacity:1}#search-input{width:100%;border-radius:0;border:none;outline:none;background:none;color:var(--color-hot);font-family:"IBM Plex Mono",monospace;font-size:100%}#search-results{width:100%;padding:0;list-style-type:none;display:flex;flex-flow:column nowrap;border-radius:.5rem}#search-results li a{display:flex;justify-content:space-between;color:var(--fg-accent);-webkit-tap-highlight-color:rgba(0,0,0,0)}#search-results li a>span:first-child{font-family:"IBM Plex Mono",monospace;font-weight:700}#search-results li a>span.module{font-style:italic}#search-results li a>span:last-child{flex-shrink:0;margin-left:1rem}#search-results li>*{margin:0;word-wrap:anywhere;padding:.5rem 1rem}#search-results li.package a>span:first-child{font-family:"Literata",serif;font-style:italic;font-weight:400}#search-results li:first-child{border-top-left-radius:.5rem;border-top-right-radius:.5rem}#search-results li:last-child{border-bottom-left-radius:.5rem;border-bottom-right-radius:.5rem}#search-results li:hover{background-color:var(--fg-accent)}#search-results li:focus,#search-results li.selected{background-color:var(--color-hot)}#search-results li:hover a,#search-results li:focus a,#search-results li.selected a{text-decoration:none;color:#fff}@media(hover: hover)and (pointer: fine){#search-results{display:none}div.search-tool:focus-within #search-results,header:focus-within #search-results{display:flex}}#search-results{background-color:var(--bg-accent)}@supports(backdrop-filter: blur(10px) saturate(180%)){#search-results{background-color:var(--bg-accent-translucent);backdrop-filter:blur(10px) saturate(180%)}}section a.source.github::before{display:inline-block;content:"";height:1.125rem;width:1.125rem;margin-right:.5em;background:var(--fg-accent);mask-image:var(--github-icon);mask-size:100% 100%;-webkit-mask-image:var(--github-icon);-webkit-mask-size:100% 100%}section a.source{display:flex;align-items:center;font-family:"IBM Plex Mono",monospace;font-size:87.5%;color:var(--fg-semi);text-decoration-color:var(--fg-accent)}section a.source span.file{font-weight:500}section a.source span.file,section a.source span.line{color:var(--fg-accent)}section>h2,section>h3{margin-top:1.5em}section.availability{margin:.75rem 0}section.availability dl{display:block;margin:0;font-size:87.5%;font-family:"DM Sans",sans-serif}section.availability dl dt,section.availability dl dd{display:inline-block;background:var(--fg-accent);color:#fff}section.availability dl dt:first-child{margin:0}section.availability dl dt{margin:0 0 0 .5em;padding:.1em .2em .1em .4em;border-radius:.25rem 0 0 .25rem}section.availability dl dd{margin:0;padding:.1em .4em .1em .2em;border-radius:0 .25rem .25rem 0}section.declaration pre{margin:2.5rem 0 2.5rem}section.declaration pre code{display:block;text-indent:-2.5rem;margin-left:2.5rem;font-size:inherit;color:var(--fg-swift)}section.declaration pre code a{text-decoration:underline dashed}section.declaration pre code a:hover{text-decoration:underline}section.declaration pre code a.xv{color:var(--fg-accent)}section.declaration pre code.multiline{text-indent:0;margin-left:0}section.details aside code,section.details table code{background-color:var(--bg-codewell);font-size:90.625%;padding:.2rem .5rem .25rem;line-height:0;border-radius:.4rem}section.details section.parameters>h2{padding-left:0}section.details section.parameters>dl{counter-set:parameter-index -1}section.details section.parameters>dl>dt{counter-increment:parameter-index;font-family:"IBM Plex Mono",monospace}section.details section.parameters>dl>dt:not([id])::before,section.details section.parameters>dl>dt>a::before{content:"$" counter(parameter-index)}section.details section.returns>h2{padding-left:0}section.details section.returns>*{padding-left:3rem;margin-left:0}section.details pre.title{margin-top:1rem;margin-bottom:0;font-family:"IBM Plex Mono",monospace;font-weight:700;color:var(--fg-heavy)}section.details pre.title+pre{margin-top:.5rem}section.details pre{font-size:90.625%}section.details pre+a.source{margin-top:0rem;position:relative;top:-1rem}section.events ol{list-style:none;padding:0;border-top:1px solid var(--fg-semi)}section.events ol>li time{font-weight:700}section.group details summary{cursor:pointer;display:flex;flex-flow:column nowrap;align-items:center;text-align:center}section.group details summary p{margin:0}section.group details summary p.view,section.group details summary p.hide{font-family:"DM Sans",sans-serif;font-weight:700;font-size:125%}section.group details summary p.hide{display:none}section.group details summary p.reason{margin-top:.5rem;font-style:italic;color:var(--fg-semi)}section.group details summary p.reason span.count{font-style:normal}section.group details summary::marker{content:""}section.group details summary::-webkit-details-marker{display:none}section.group details.impl summary p.view,section.group details.impl summary p.hide{color:var(--fg-semi);font-size:1rem}section.group details.impl+details.impl{margin-top:1em;border-top:dashed 1px var(--fg-light);padding-top:1em}section.group details[open] summary p.view,section.group details[open] summary p.reason{display:none}section.group details[open] summary p.hide{display:block}section.group>header>h2>a[href^="#"]{color:inherit}section.group>header,section.group>h2{margin-top:2rem;margin-bottom:1rem}section.group>div.constraints+h3{margin-top:1rem;border-top:solid 1px var(--fg-swift-type)}section.group>header+h3,section.group>h2+h3{margin-top:1rem;border-top:solid 1px var(--fg-light)}section.group>h3,section.group>details>h3{margin-top:1rem;border-top:dashed 1px var(--fg-semi);padding-top:.75rem;font-weight:600;color:var(--fg-semi)}section.introduction{margin-top:0;padding:0 0 .75rem;border-bottom:2px solid var(--fg-accent)}section.introduction div.eyebrows{color:var(--fg-semi);font-style:italic;display:flex;flex-flow:row wrap}section.introduction div.eyebrows .phylum{flex:1}section.introduction div.eyebrows .phylum .kink{font-weight:600}section.introduction div.eyebrows span.domain span.extends{display:inline-block;padding:0 .35em;font-family:"Literata",serif}section.introduction div.eyebrows span.domain span.jump:before,section.introduction div.eyebrows span.domain span.jump:after{margin:0 .2rem}section.introduction div.eyebrows span.domain span.jump:before{content:"("}section.introduction div.eyebrows span.domain span.jump:after{content:")"}section.introduction nav.breadcrumbs{margin-top:2em;color:var(--fg-swift);font-family:"IBM Plex Mono",monospace;font-weight:500;font-size:90.625%}section.introduction nav.breadcrumbs a:not(:hover){color:var(--fg-semi);text-decoration:underline dashed}section.introduction nav.breadcrumbs>*:first-child{margin-left:0}section.introduction nav.breadcrumbs>*{margin:0 .2rem}section.introduction nav.breadcrumbs>*:last-child{margin-right:0}section.introduction nav.breadcrumbs+h1{margin-top:.5em}section.introduction p.chyron{margin:0;max-width:inherit}section.introduction a.source+a.source{margin-top:.5rem}section.literature aside{position:relative;margin:2rem 0;border-bottom:2px solid var(--fg-semi)}section.literature aside h3{margin:0;padding:.5rem 0;font-size:inherit;color:var(--fg-semi);border-bottom:2px solid var(--fg-semi)}section.literature aside.important,section.literature aside.warning{border-color:var(--color-hot)}section.literature aside.important h3,section.literature aside.warning h3{color:var(--color-hot);border-color:var(--color-hot)}section.literature aside:last-child{border-bottom:none}section.literature dl{padding-left:0;margin-bottom:2rem;display:block}section.literature dl>dt{margin-top:2rem;font-weight:700}section.literature dl>dt>a::before{font-family:"IBM Plex Mono",monospace}section.literature dl>dt>a{color:inherit}section.literature dl>dt:not([id])::before,section.literature dl>dt>a::before{content:"$";display:inline-block;min-width:3rem;color:var(--fg-snippet-line-number);font-weight:normal}section.literature dl>dd{padding-left:3rem;margin-left:0}section.metadata details[open] summary::before{content:"▼"}section.metadata details summary::marker{content:""}section.metadata details summary::-webkit-details-marker{display:none}section.metadata details summary::before{content:"▶";color:var(--fg-light);display:inline-block;width:1.25rem}section.metadata details summary{margin:.5rem 0;position:relative;left:-1.25rem;font-style:italic;color:var(--fg-semi)}section.metadata details p.symbol:first-letter{font-weight:700}section.metadata details code span.fnv24{font-weight:700}section.metadata details:last-child{margin-bottom:2rem}span.placeholder,span.parenthetical{color:var(--fg-semi);font-style:italic}span.placeholder em,span.parenthetical em{font-style:normal}span.parenthetical::before{content:"(";font-style:normal;margin-left:.4em}span.parenthetical::after{content:")";font-style:normal}span.warn{color:var(--fg-warn)}table th{text-align:left;font-family:"DM Sans",sans-serif;padding-bottom:.5rem;color:var(--fg-semi);border-bottom:2px solid var(--fg-swift)}table tbody tr:first-child td{padding-top:.5rem}table th,table td{padding-left:.5rem}table th:first-child,table td:first-child{padding-left:0}table td.placeholder{color:var(--fg-semi);font-style:italic}table td.placeholder em{font-style:normal}table[data-type]{width:100%}table[data-type=constituents] td{font-style:italic}table[data-type=dependencies] td:first-child{font-style:italic}table[data-type=dependencies] td span.upto{padding:0 .5rem}table[data-type=consumers]{display:grid;grid-template-columns:2fr 1fr 1fr;border-collapse:collapse}table[data-type=consumers]>thead,table[data-type=consumers]>thead>tr,table[data-type=consumers]>tbody,table[data-type=consumers]>tbody>tr{display:contents}table[data-type=consumers] td:first-child{font-style:italic}table[data-type=consumers] td.ref,table[data-type=refs] td.ref{overflow:hidden;white-space:nowrap;text-overflow:"..."}table[data-type=refs]{display:grid;grid-template-columns:2fr 1fr 1fr 3fr;border-collapse:collapse}table[data-type=refs]>thead,table[data-type=refs]>thead>tr,table[data-type=refs]>tbody,table[data-type=refs]>tbody>tr{display:contents}table[data-type=refs]>tbody>tr>td.graph{display:flex;justify-content:space-between;font-weight:normal}table[data-type=refs]>tbody>tr>td.graph>div:first-child{color:var(--fg-semi)}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span.graph:first-child::before{content:"✓";color:var(--fg-accent);margin-right:.5rem}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span.graph.uplinking:first-child::before{content:"⟳"}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span.graph.unlinking:first-child::before{content:"▼"}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span.graph.deleting:first-child::before{content:"✗"}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span:first-child::before{content:"✗";color:var(--fg-semi);margin-right:.5rem}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span:first-child{font-weight:700}table[data-type=refs]>tbody>tr>td.graph>div:first-child>span.kb{font-weight:normal}table[data-type=refs]>tbody>tr>td.graph>div.menu>ul{right:0;min-width:15rem}table[data-type=refs]>tbody>tr.modern{font-weight:700}ul.cards{padding:0;margin-bottom:3rem;list-style-type:none}ul.cards>li:first-child>h3{margin-top:0}ul.cards>li{margin:.5rem 0 .5rem 0;padding:0}ul.cards>li>h3.module,ul.cards>li>h3.product{margin:.75rem 0 0 0;font-family:"Literata",serif;font-style:italic;font-size:100%;font-weight:normal}ul.cards>li>h3.article{margin:2rem 0 .5rem 0;font-family:"Literata",serif;font-weight:normal;font-size:125%}ul.cards>li>h3.article a{color:inherit;text-decoration:underline;text-decoration-style:dashed}ul.cards>li>h3.article a:hover{text-decoration-style:solid}ul.cards>li>a.read-more{font-style:italic}ul.cards>li>code.decl a{color:var(--fg-swift-comment)}ul.cards>li>code.decl a span.xv{color:var(--color-hot)}ul.cards>li>code.decl a.discouraged{text-decoration:line-through}ul.cards>li>code.decl a.discouraged span.xv{color:inherit}ul.cards>li>code.decl{display:block;padding:.8em 0 0 0;text-indent:-2.5rem;margin-left:2.5rem;white-space:pre-wrap;font-size:100%;line-height:1.5rem}ul.cards>li>code.decl.multiline{text-indent:0;margin-left:0}ul.cards>li>code.decl.multiline a{display:inline-block}ul.cards>li>code.decl+p{padding:0 0 0 2.5rem;line-height:1.3rem;font-size:90.625%}ul.cards>li[id]:target>code a{background-color:var(--bg-highlight)}ul.cards>li[id]>a:first-child{position:absolute;margin-left:-1.5em;margin-top:.8em}ul.cards>li[id]>a:first-child::before{content:"$";line-height:1.5rem;font-family:"IBM Plex Mono",monospace;font-size:100%;opacity:.25;color:var(--fg-semi)}ul.cards>li[id]>a:first-child:hover::before{opacity:1;color:var(--color-hot)}ul.cards.dense>li>code.decl a span.xv{color:var(--fg-accent)}ul.users{padding:0;margin-bottom:2rem;list-style-type:none}ul.users>li:first-child{border-top:none}ul.users>li{border-top:1px dashed var(--fg-light);padding:1rem 0}ul.users>li>header{display:flex;align-items:left}ul.users>li>header div.icon{border:2px dashed var(--fg-light);box-sizing:border-box}ul.users>li>header div.icon,ul.users>li>header img.icon{width:2rem;height:2rem;margin-right:.5rem;border-radius:50%}ul.users>li>header div.tools{margin-left:auto;display:flex;align-items:right;color:var(--fg-heavy)}ul.users>li>header div.tools>form{margin-left:.5em}ul.users>li>header div.tools>form button[type=submit]{margin:0 .25em;font-style:italic}ul.users>li>header div.tools>form::before,ul.users>li>header div.tools>form::after{color:var(--fg-semi)}ul.users>li>header div.tools>form::before{content:"("}ul.users>li>header div.tools>form::after{content:")"}html{scroll-behavior:smooth;scroll-padding-top:6rem}body{min-height:100vh;margin:0}body>header.app{position:fixed;z-index:1;width:100%;pointer-events:none}body>header.app nav{width:100%;height:1.5rem;position:relative;padding:.25rem 0;display:flex;flex-direction:row;justify-content:space-between;align-content:center;overflow:hidden;overflow-wrap:break-word}body>header.app>div.content::before{position:fixed;left:0;display:block;height:2rem;width:100%;content:"";background-color:var(--bg)}@supports(backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)){body>header.app>div.content::before{background-color:var(--bg-translucent);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%)}}body>header.app>div.content{pointer-events:auto}body>*{display:flex;flex-direction:row-reverse;justify-content:center}body>*.app>*.content{flex:0 1 48rem;overflow:hidden;overflow-wrap:break-word;padding-left:1rem;padding-right:1rem}body>*.app>*.sidebar{display:none}body main{margin-top:8rem}@media only screen and (min-width: 56rem){body main{margin-top:6rem}body>header.app>*.sidebar{pointer-events:none}body>*.app>*.sidebar{display:block;flex:0 1 16rem;min-width:12rem;max-width:16rem}body>*.app>*.content{min-width:24rem;padding-left:4rem;overflow:visible}}body main section.introduction>p,body main section.introduction>dl,body main section.parameters>p,body main section.parameters>dl,body main section.returns>p,body main section.returns>dl,body main section.throws>p,body main section.throws>dl{max-width:40rem}body main section.details p,body main section.details figure{max-width:40rem}body main section.details aside{max-width:41rem}body main section.extension ul>li>p{max-width:40rem}body main>*:last-child{padding-bottom:5rem}html{font-size:1rem;background-color:var(--bg)}body{color:var(--fg);font-family:"Literata",serif;font-variant-numeric:oldstyle-nums;text-size-adjust:none;-webkit-text-size-adjust:none}label.checkbox{color:var(--fg-heavy);line-height:1em;display:grid;grid-template-columns:1em auto;gap:.3em}input[type=checkbox],input[type=radio]{background-color:var(--bg)}input[type=checkbox],input[type=radio]{appearance:none;margin:0;font-size:inherit;height:1em;width:1em;display:grid;place-content:center;transform:translateY(2px);border:2px solid var(--fg-semi);border-radius:.1em}input[type=checkbox]::before,input[type=radio]::before{content:"";width:.65em;height:.65em;background-color:var(--bg)}input[type=checkbox]:checked,input[type=radio]:checked{border-color:var(--fg-accent);background-color:var(--fg-accent)}input[type=checkbox]:checked::before,input[type=radio]:checked::before{transform-origin:bottom left;clip-path:polygon(100% 13%, 39% 100%, 0 70%, 11% 53%, 37% 71%, 83% 0)}/*# sourceMappingURL=Main.css.map */ +@charset "UTF-8"; +:root { + --color-accent-light: rgb(255, 172, 200); + --color-accent-semi: rgb(255, 136, 176); + --color-accent: rgb(255, 86, 158); + --color-accent-dark: rgb(209, 23, 104); + --color-hot: rgb(253, 39, 110); + --color-secondary: rgb(189, 108, 255); + --color-secondary-dark: rgb(150, 80, 255); + --color-tertiary: rgb(0, 193, 207); +} + +:root { + --blur-shadow: rgba(0, 0, 0, 0.15); + --bg: rgb(250, 250, 250); + --bg-translucent: rgba(250, 250, 250, 0.5); + --bg-codewell: rgb(240, 240, 240); + --bg-codewell-gloss: rgb(236, 236, 236); + --bg-elevated: rgb(239, 239, 239); + --bg-elevated-shadow: rgb(182, 182, 182); + --bg-alert: rgb(255, 103, 103); + --bg-alert-shadow: rgb(235, 75, 75); + --bg-warn: rgb(255, 163, 58); + --bg-warn-shadow: rgb(248, 123, 21); + --bg-rose: rgb(255, 86, 158); + --bg-rose-shadow: rgb(209, 23, 104); + --bg-aqua: rgb(0, 193, 207); + --bg-aqua-shadow: rgb(0, 162, 173); + --bg-accent: rgb(255, 224, 239); + --bg-accent-translucent: rgba(255, 224, 239, 0.5); + --bg-accent-shadow: var(--color-accent); + --bg-highlight: rgba(255, 238, 0, 0.25); + --bg-diff-insert: rgba(0, 255, 255, 0.3); + --bg-diff-delete: rgba(255, 72, 0, 0.3); + --bg-diff-update: rgba(255, 0, 212, 0.3); + --fg: rgb(22, 22, 22); + --fg-heavy: rgb(50, 50, 50); + --fg-semi: rgb(109, 109, 109); + --fg-light: rgb(172, 172, 172); + --fg-snippet-line-number: rgb(192, 192, 192); + --fg-accent: var(--color-accent); + --fg-accent-semi: var(--color-accent-semi); + --fg-accent-light: var(--color-accent-light); + --fg-warn: rgb(255, 115, 0); + --fg-swift: rgb(134, 134, 134); + --fg-swift-comment: rgb(109, 109, 109); + --fg-swift-keyword: rgb(253, 39, 110); + --fg-swift-literal: rgb(141, 61, 231); + --fg-swift-type: rgb(255, 123, 0); +} + +@media (prefers-color-scheme: dark) { + :root { + --color-accent-light: rgb(153, 0, 71); + --color-accent-semi: rgb(218, 0, 102); + --color-accent: rgb(255, 40, 133); + --color-accent-dark: rgb(255, 86, 158); + --color-hot: rgb(253, 39, 110); + --color-secondary: rgb(189, 108, 255); + --color-secondary-dark: rgb(150, 80, 255); + --color-tertiary: rgb(0, 193, 207); + } + :root { + --blur-shadow: rgba(0, 0, 0, 0.75); + --bg: rgb(20, 20, 20); + --bg-translucent: rgba(20, 20, 20, 0.3); + --bg-codewell: rgb(30, 30, 30); + --bg-codewell-gloss: rgb(36, 36, 36); + --bg-elevated: rgb(32, 32, 32); + --bg-elevated-shadow: rgb(17, 17, 17); + --bg-accent: rgb(66, 0, 31); + --bg-accent-translucent: rgba(66, 0, 31, 0.5); + --bg-accent-shadow: rgb(190, 0, 95); + --bg-alert: rgb(211, 29, 29); + --bg-alert-shadow: rgb(155, 0, 0); + --bg-warn: rgb(233, 129, 10); + --bg-warn-shadow: rgb(189, 71, 2); + --bg-aqua: rgb(0, 133, 167); + --bg-aqua-shadow: rgb(0, 96, 121); + --bg-highlight: rgba(255, 184, 31, 0.15); + --bg-diff-insert: rgba(0, 183, 255, 0.2); + --bg-diff-delete: rgba(255, 72, 0, 0.2); + --bg-diff-update: rgba(255, 0, 212, 0.2); + --fg: rgb(211, 211, 211); + --fg-heavy: rgb(192, 192, 192); + --fg-semi: rgb(129, 129, 129); + --fg-light: rgb(59, 59, 59); + --fg-snippet-line-number: rgb(109, 109, 109); + --fg-swift: rgb(117, 117, 117); + --fg-swift-comment: rgb(139, 139, 139); + --fg-swift-keyword: rgb(255, 53, 97); + --fg-swift-literal: rgb(195, 143, 255); + --fg-swift-type: rgb(255, 161, 37); + } +} +:root { + --github-icon: url('data:image/svg+xml;utf8, '); + --leaving-icon: url('data:image/svg+xml;utf8, '); + --broken-link-icon: url('data:image/svg+xml;utf8, '); +} + +a { + color: var(--fg-accent); + text-decoration: none; + text-underline-offset: 0.35em; +} + +a:hover, a:focus { + text-decoration: underline; +} + +a:not([href]) { + color: var(--fg-semi); + text-decoration: none; +} + +a:not([href])::after { + display: inline-block; + content: ""; + height: 12px; + width: 12px; + margin-left: 0.5em; + margin-right: 0.25em; + background: var(--fg-accent); + mask-image: var(--broken-link-icon); + mask-size: 100% 100%; + -webkit-mask-image: var(--broken-link-icon); + -webkit-mask-size: 100% 100%; + background-color: var(--fg-semi); +} + +a.area { + margin: 1.5rem 0; + border: 2px dashed var(--fg-accent-semi); + text-decoration: none; + font-family: "DM Sans", sans-serif; + font-weight: 700; + font-size: 120%; + display: block; + padding: 0.5rem 0; + text-align: center; +} + +a.area:hover, +a.area:focus { + border-color: var(--fg-accent); +} + +a[rel~=external]::after { + display: inline-block; + content: ""; + height: 12px; + width: 12px; + margin-left: 0.5em; + margin-right: 0.25em; + background: var(--fg-accent); + mask-image: var(--leaving-icon); + mask-size: 100% 100%; + -webkit-mask-image: var(--leaving-icon); + -webkit-mask-size: 100% 100%; +} + +blockquote { + margin: 2rem 0; + font-style: italic; + color: var(--fg-heavy); +} +blockquote em { + font-style: normal; +} +blockquote img { + max-width: 100%; + max-height: 40rem; +} + +button.area { + margin: 1.5rem 0; + border: 2px dashed var(--fg-accent-semi); + text-decoration: none; + font-family: "DM Sans", sans-serif; + font-weight: 700; + font-size: 120%; + display: block; + padding: 0.5rem 0; + text-align: center; + width: 100%; + background: none; + box-shadow: none; + border-radius: 0; + color: var(--fg-accent); +} + +button.area:hover, +button.area:focus { + border-color: var(--fg-accent); + box-shadow: none; +} + +button.area:active { + box-shadow: none; + transform: none; +} + +button.area:disabled { + color: var(--fg-semi); + border-color: var(--fg-light); + cursor: not-allowed; +} + +button.text, +button.text:hover, +button.text:focus, +button.text:active { + display: inline-block; + background: none; + box-shadow: none; + transform: none; + padding: 0; + border-radius: 0; + font-family: "Literata", serif; + font-size: inherit; + color: var(--fg-accent); + text-underline-offset: 0.35em; +} + +button[disabled].text { + color: var(--fg-semi); + cursor: not-allowed; +} + +button:not([disabled]).text:hover { + text-decoration: underline; +} + +section.introduction > nav.calendar { + margin: 2rem 0 1rem 0; + display: flex; + justify-content: center; + align-items: center; + height: 3rem; +} +section.introduction > nav.calendar > h1 { + margin: 0 0.5em; +} +section.introduction > nav.calendar > h1, section.introduction > nav.calendar a { + font-size: 1.8rem; + font-family: "Literata", serif; + font-weight: normal; + line-height: 100%; +} +section.introduction > nav.calendar > a { + color: var(--fg-semi); +} +section.introduction > nav.calendar > a:hover, +section.introduction > nav.calendar > a:focus { + color: var(--fg-accent); + text-decoration: none; +} + +section.details > ol.calendar { + list-style: none; + padding: 0; + display: grid; + grid-gap: 0.25rem; + grid-template-columns: repeat(7, 1fr); +} +@media only screen and (min-width: 56rem) { + section.details > ol.calendar { + grid-gap: 0.5rem; + } +} +section.details > ol.calendar li > * { + min-height: 3rem; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: var(--bg-codewell); + border-radius: 0.25rem; +} +@media only screen and (min-width: 56rem) { + section.details > ol.calendar li > * { + padding: 0.35rem 0.5rem 0.5rem 0.5rem; + } +} +section.details > ol.calendar li > * p:first-child { + text-align: right; +} +section.details > ol.calendar li > * p { + padding: 0; + margin: 0; + line-height: 1em; +} +section.details > ol.calendar li > div p { + color: var(--fg-semi); +} +section.details > ol.calendar li > a p.crawled { + font-weight: bold; +} +section.details > ol.calendar li > * span.m { + color: var(--fg-light); +} +section.details > ol.calendar li > * span.d { + color: var(--fg); +} +section.details > ol.calendar li > a:hover, section.details > ol.calendar li a:focus { + background-color: var(--bg-accent); + text-decoration: none; +} +section.details > ol.calendar li > a:hover span.m, section.details > ol.calendar li a:focus span.m { + color: var(--fg-accent-semi); +} +section.details > ol.calendar li > a:hover span.d, section.details > ol.calendar li a:focus span.d { + color: var(--fg-accent); +} +section.details > ol.calendar li.first > * span.m { + color: var(--fg); +} +section.details > ol.calendar li.first > a:hover span.m, section.details > ol.calendar li.first a:focus span.m { + color: var(--fg-accent); +} + +p.chyron { + display: flex; + flex-direction: row; + justify-content: space-between; + color: var(--fg-semi); +} +p.chyron > span.release { + font-weight: bold; +} +p.chyron > span:last-child { + display: flex; + gap: 0.5em; +} +p.chyron > span:last-child span.pushed::before { + content: "▲ "; +} +p.chyron > span:last-child span.stars::before { + content: "★ "; + color: var(--fg-swift-type); +} + +ol.packages { + padding-left: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 1rem; +} +ol.packages > li:first-child > p:first-child { + border-top: none; +} +ol.packages > li > p:first-child { + display: flex; + flex-direction: row; + justify-content: space-between; +} +ol.packages > li > p { + margin: 0.25rem 0; +} +ol.packages > li > p:first-child { + margin-top: 0.5rem; + border-top: 2px solid var(--bg-elevated); +} +ol.packages > li > p:first-child a { + font-style: italic; +} +ol.packages > li > p:first-child a.dead { + color: var(--fg-semi); +} +ol.packages > li > p:first-child span.owner { + color: var(--fg-semi); +} +ol.packages > li > p:first-child span.owner::before { + content: " by "; +} +ol.packages > li > p.chyron > span.release { + font-weight: bold; +} + +code { + font-family: "IBM Plex Mono", monospace; + white-space: pre-wrap; + hyphens: none; +} + +code.multiline span.xi::before { + content: "\a"; +} +code.multiline span.xi::after { + content: " "; + display: inline-block; +} +code.multiline wbr { + display: block; +} + +@media only screen and (min-width: 56rem) { + div.columns { + display: grid; + } + div.columns > div { + overflow: scroll; + } +} +div.constraints code, +div.constraints code { + line-height: 1.6rem; + color: var(--fg-swift); + font-size: inherit; + display: block; + text-indent: -3.6rem; + margin-left: 3.6rem; +} +div.constraints code span.xk, +div.constraints code span.xk { + color: var(--fg-swift-comment); +} +div.constraints code span.xv, +div.constraints code span.xt, +div.constraints code span.xu, +div.constraints code span.xv, +div.constraints code span.xt, +div.constraints code span.xu { + color: var(--fg-swift-type); + font-weight: 500; +} +div.constraints code a, +div.constraints code a { + text-decoration: underline; + text-decoration-style: dashed; + text-decoration-color: var(--fg-swift-type); +} +div.constraints code a:hover, +div.constraints code a:hover { + text-decoration-style: solid; +} + +@media only screen and (min-width: 56rem) { + div.hstackable { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + justify-content: space-between; + align-items: center; + } + div.hstackable > * { + flex: 1 0; + } +} +div.menu { + display: inline-block; + position: relative; +} +div.menu > button { + background: none; + box-shadow: none; + border: none; + color: var(--fg-semi); + cursor: pointer; +} +div.menu > button:hover { + color: var(--fg-dark); +} +div.menu > ul { + position: absolute; + display: none; + background-color: var(--bg); + list-style-type: none; + margin: 0; + padding: 0.5em 1em 0.75em 1em; + box-shadow: 0 1rem 3rem var(--blur-shadow); + border-radius: 0.5rem; +} +@supports (backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)) { + div.menu > ul { + background-color: var(--bg-translucent); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} +div.menu > ul > li > a { + display: block; +} +div.menu > ul > li > * { + width: 100%; +} +div.menu > ul > li > form > button { + width: 100%; + text-align: left; +} + +div.menu.open > button { + color: var(--fg); +} +div.menu.open > ul { + display: flex; + flex-direction: column; + z-index: 1; +} + +div.more { + margin: 1.5rem 0; + text-align: center; +} +div.more > div.charts { + display: flex; + flex-flow: row wrap; + justify-content: center; +} +div.more > div.charts h2, div.more > div.charts h3 { + font-size: 100%; + margin: 0; +} +div.more > div.charts h3 { + font-weight: 500; + color: var(--fg-semi); +} +div.more > div.charts figure { + margin: 1rem; +} +div.more > div.charts figure div.pie { + font-size: 10px; +} + +div.sidebar ol.table-of-contents { + margin-top: 10rem; + position: sticky; + top: 3rem; + padding: 0 1rem 1rem 1rem; + list-style: none; + font-size: 0.875rem; + max-height: calc(100vh - 3rem - 1rem); + overflow-y: auto; + overflow-x: hidden; +} +div.sidebar ol.table-of-contents li { + position: relative; + color: var(--fg-semi); +} +div.sidebar ol.table-of-contents li a { + color: inherit; +} +div.sidebar ol.table-of-contents li::before { + position: absolute; + content: "•"; + left: -0.8em; + color: var(--fg-light); +} +div.sidebar ol.table-of-contents li.active::before { + content: "•"; + color: var(--fg-heavy); +} +div.sidebar ol.table-of-contents li.active + li.active::before { + color: var(--fg-light); +} +div.sidebar ol.table-of-contents li.title { + font-style: italic; + color: var(--fg); + padding-bottom: 0.4em; + margin-bottom: 0.6em; + border-bottom: 1px solid var(--fg-light); +} +div.sidebar ol.table-of-contents li.title::before { + content: ""; + display: none; +} +div.sidebar ol.table-of-contents li.active { + color: var(--fg); +} +div.sidebar ol.table-of-contents li.h2 { + margin-left: 0; +} +div.sidebar ol.table-of-contents li.h3 { + margin-left: 1rem; +} +div.sidebar ol.table-of-contents li.h4 { + margin-left: 2rem; +} +div.sidebar ol.table-of-contents li.group { + margin-top: 0.5rem; + display: flex; + align-content: center; +} +div.sidebar ol.table-of-contents li.group > a > span { + font-weight: 700; + font-style: italic; +} +div.sidebar ol.table-of-contents li.group > a > span, +div.sidebar ol.table-of-contents li.group > a > code { + display: block; +} +div.sidebar ol.table-of-contents li.group > a > code { + font-style: normal; + font-size: 0.75rem; + margin: 0.2em 0; + overflow-wrap: anywhere; +} +div.sidebar div.nountree { + margin-left: 1rem; + margin-top: 10rem; + font-family: "IBM Plex Mono", monospace; + font-size: 0.8125rem; +} +div.sidebar div.nountree > a::first-letter { + color: var(--fg-accent); +} +div.sidebar div.nountree > a.text { + font-family: "Literata", serif; + font-style: italic; + font-size: 0.875rem; +} +div.sidebar div.nountree > a.text + a:not(.text) { + margin-top: 0.5rem; +} +div.sidebar div.nountree > a.extension.local::first-letter { + color: var(--color-secondary); +} +div.sidebar div.nountree > a.extension.foreign::first-letter { + color: var(--color-tertiary); +} +div.sidebar div.nountree div.indent { + padding-left: 1rem; +} +div.sidebar div.nountree a, div.sidebar div.nountree span { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: "..."; + color: var(--fg-semi); +} +div.sidebar div.nountree a:hover { + text-decoration: none; + color: var(--fg-accent); +} +div.sidebar div.nountree a.extension.local:hover { + color: var(--color-secondary); +} +div.sidebar div.nountree a.extension.foreign:hover { + color: var(--color-tertiary); +} + +div.tooltips { + position: fixed; + z-index: 1; +} +div.tooltips > div { + position: fixed; + display: block; + pointer-events: none; + opacity: 0; + margin-top: 0; + margin-left: -1rem; + margin-right: -1rem; + transition: all 0.25s; + background-color: var(--bg); + padding: 0.5rem 1rem; + box-shadow: 0 1rem 3rem var(--blur-shadow); + border-radius: 0.5rem; + max-width: 30rem; +} +@supports (backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)) { + div.tooltips > div { + background-color: var(--bg-translucent); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} +div.tooltips > div pre { + margin: 0; + font-size: 90.625%; +} +div.tooltips > div p { + font-size: 90.625%; + line-height: 1.4em; + display: none; +} +div.tooltips > div.visible { + opacity: 1; + margin-top: 0.5rem; + transition: opacity 0.25s; + transition: margin-top 0.15s; +} +div.tooltips > div.overview p { + display: block; +} + +dl { + display: grid; + grid-template-columns: 33% 67%; +} +dl dt { + grid-column: 1; +} +dl dd { + grid-column: 2; +} +dl dd > p:first-child { + margin-top: 0; +} + +figure { + margin: 2rem 0; +} +figure img { + max-width: 100%; + max-height: 40rem; +} + +figure.chart { + display: flex; + align-items: center; + flex-direction: column; +} +@media only screen and (min-width: 56rem) { + figure.chart { + flex-direction: row; + } +} +figure.chart div.pie { + margin: 0; + font-size: 20px; +} +figure.chart div.pie > div.pie-color { + height: 0; + width: 0; + margin: 0; +} +figure.chart div.pie > div.pie-color > svg, +figure.chart div.pie > div.pie-geometry { + width: 12em; + height: 12em; + margin: 0; + position: relative; +} +figure.chart div.pie > div.pie-geometry { + transition: box-shadow 0.1s ease-out; + pointer-events: none; + border-radius: 6em; + box-shadow: inset 0 -0.5em 0.75em -0.75em rgba(255, 255, 255, 0.3), inset 0 -0.4em 0.5px 0.1em rgba(0, 0, 0, 0.24), inset 0 -1.3em 5em 0.1em rgba(0, 0, 0, 0.14); +} +figure.chart div.pie > div.pie-color:hover + div.pie-geometry { + box-shadow: inset 0 -0.5em 1.5em -0.75em rgba(255, 255, 255, 0.5), inset 0 -0.4em 0.5px 0.1em rgba(0, 0, 0, 0.18), inset 0 -1.3em 5em 0.1em rgba(0, 0, 0, 0.11); +} +@media (prefers-color-scheme: dark) { + figure.chart div.pie > div.pie-geometry { + box-shadow: inset 0 -0.5em 0.75em -0.75em rgba(255, 255, 255, 0.2), inset 0 -0.4em 0.5px 0.1em rgba(0, 0, 0, 0.34), inset 0 -1.3em 5em 0.1em rgba(0, 0, 0, 0.3); + } + figure.chart div.pie > div.pie-color:hover + div.pie-geometry { + box-shadow: inset 0 -0.5em 1.5em -0.75em rgba(255, 255, 255, 0.4), inset 0 -0.4em 0.5px 0.1em rgba(0, 0, 0, 0.18), inset 0 -1.3em 5em 0.1em rgba(0, 0, 0, 0.16); + } +} +figure.chart div.pie + figcaption { + align-self: flex-start; +} +figure.chart div.pie + figcaption dl { + margin: 0 0 0 1rem; + display: grid; + grid-template-columns: auto 4rem; + direction: rtl; +} +figure.chart div.pie + figcaption dl dd { + grid-column: 2; + margin: 0; +} +figure.chart div.pie + figcaption dl dt { + direction: ltr; + margin-left: 1rem; +} + +figure.chart.decl svg > g > * { + fill: rgb(255, 255, 255); +} +figure.chart.decl svg > g > *.function { + fill: rgb(255, 103, 103); +} +figure.chart.decl svg > g > *.operator { + fill: rgb(255, 50, 111); +} +figure.chart.decl svg > g > *.constructor { + fill: rgb(255, 103, 141); +} +figure.chart.decl svg > g > *.method { + fill: rgb(255, 170, 196); +} +figure.chart.decl svg > g > *.subscript { + fill: rgb(250, 104, 255); +} +figure.chart.decl svg > g > *.functor { + fill: rgb(198, 112, 255); +} +figure.chart.decl svg > g > *.protocol { + fill: rgb(82, 206, 255); +} +figure.chart.decl svg > g > *.requirement { + fill: rgb(130, 255, 249); +} +figure.chart.decl svg > g > *.witness { + fill: rgb(171, 255, 244); +} +figure.chart.decl svg > g > *.macro.attached { + fill: rgb(61, 255, 103); +} +figure.chart.decl svg > g > *.macro.freestanding { + fill: rgb(132, 255, 159); +} +figure.chart.decl svg > g > *.structure { + fill: rgb(255, 244, 94); +} +figure.chart.decl svg > g > *.class { + fill: rgb(255, 212, 72); +} +figure.chart.decl svg > g > *.actor { + fill: rgb(255, 197, 39); +} +figure.chart.decl svg > g > *.typealias { + fill: rgb(255, 154, 38); +} + +figure.chart.spis svg > g > *.none { + fill: rgb(238, 238, 238); +} +figure.chart.spis svg > g > *.underscored { + fill: rgb(177, 177, 177); +} +figure.chart.spis svg > g > *.unknown { + fill: rgb(255, 210, 85); +} +figure.chart.spis svg > g > *.nominal { + fill: rgb(103, 131, 255); +} + +figure.chart.coverage svg > g > *.undocumented { + fill: rgb(238, 238, 238); +} +figure.chart.coverage svg > g > *.indirect { + fill: rgb(255, 170, 196); +} +figure.chart.coverage svg > g > *.direct { + fill: rgb(255, 103, 141); +} + +form button { + display: inline-block; + background: var(--fg-accent); + box-shadow: 0 0.2rem 0 0 var(--fg-accent-semi); + border: none; + border-radius: 0.25rem; + color: white; + font-family: "DM Sans", sans-serif; + font-size: 87.5%; +} +form button:hover { + cursor: pointer; +} +form button:hover, +form button:focus { + box-shadow: 0 0.2rem 0 0 var(--fg-accent-light); +} +form button:active { + box-shadow: none; + transform: translateY(0.2rem); +} +form fieldset { + border: 2px var(--fg-light) solid; +} + +form.config input[type=url] { + width: 100%; + box-sizing: border-box; +} +form.config select, +form.config input[type=text], +form.config input[type=url] { + outline: none; + background: var(--bg-codewell); + margin-bottom: 0.5em; + padding: 0.1rem 0.5rem; + border-radius: 0.25rem; + border: none; + color: var(--fg); + font-family: "Literata", serif; + font-style: italic; + font-size: inherit; +} +form.config select { + padding-bottom: 0.2em; +} +form.config select[disabled], +form.config select[readonly], +form.config input[type=text][disabled], +form.config input[type=text][readonly], +form.config input[type=url][disabled], +form.config input[type=url][readonly] { + background: none; + color: var(--fg-semi); +} +form.config select:not([disabled]):not([readonly]):hover, +form.config select:not([disabled]):not([readonly]):focus, +form.config input[type=text]:not([disabled]):not([readonly]):hover, +form.config input[type=text]:not([disabled]):not([readonly]):focus, +form.config input[type=url]:not([disabled]):not([readonly]):hover, +form.config input[type=url]:not([disabled]):not([readonly]):focus { + background: var(--bg-codewell-gloss); +} +form.config input[type=text]:invalid, +form.config input[type=url]:invalid { + color: var(--fg-accent); +} +form.config input[type=text]::placeholder, +form.config input[type=url]::placeholder { + color: var(--fg-heavy); +} + +form.sort-controls fieldset { + display: flex; + gap: 1rem; +} +form.sort-controls fieldset > label { + display: flex; + align-items: center; +} +form.sort-controls fieldset > label > input { + margin-right: 0.5em; +} + +header.visual { + display: flex; + justify-content: space-between; + align-content: center; +} +header.visual > h2, header.visual h3 { + margin-top: auto; + margin-bottom: auto; +} +header.visual > div.visibility { + margin: 1rem 0; + filter: grayscale(100%); +} +header.visual > img.icon { + height: 5rem; + border-radius: 50%; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "DM Sans", sans-serif; +} + +dt[id] > a, dt[name] > a { + color: inherit; +} + +h1[id] > a, h1[name] > a, +h2[id] > a, h2[name] > a, +h3[id] > a, h3[name] > a, +h4[id] > a, h4[name] > a, +h5[id] > a, h5[name] > a, +h6[id] > a, h6[name] > a { + position: relative; + color: inherit; +} +@media only screen and (min-width: 56rem) { + h1[id] > a::before, h1[name] > a::before, + h2[id] > a::before, h2[name] > a::before, + h3[id] > a::before, h3[name] > a::before, + h4[id] > a::before, h4[name] > a::before, + h5[id] > a::before, h5[name] > a::before, + h6[id] > a::before, h6[name] > a::before { + content: "$"; + position: absolute; + margin-left: -1.6rem; + font-weight: 400; + font-family: "IBM Plex Mono", monospace; + font-size: 92.5%; + color: var(--fg-semi); + opacity: 0.5; + } + h1[id] > a:hover::before, h1[name] > a:hover::before, + h2[id] > a:hover::before, h2[name] > a:hover::before, + h3[id] > a:hover::before, h3[name] > a:hover::before, + h4[id] > a:hover::before, h4[name] > a:hover::before, + h5[id] > a:hover::before, h5[name] > a:hover::before, + h6[id] > a:hover::before, h6[name] > a:hover::before { + opacity: 1; + } +} + +h1 { + font-size: 200%; +} + +h2 { + font-size: 150%; +} + +h3 { + font-size: 130%; +} + +h4, h5, h6 { + font-size: 110%; +} + +kbd { + background-color: var(--bg-codewell); + padding: 0.2rem 0.5rem 0.25rem; + border-radius: 0.3em; + box-shadow: 0 0.25rem 0 var(--bg-elevated-shadow); +} + +main.home > div.search-tool { + height: 4rem; + position: relative; + z-index: 1; +} +main.home > div.search-tool div.searchbar { + max-width: 100%; +} +main.home > nav { + margin-bottom: 1rem; +} +main.home > nav ul { + list-style-type: none; + padding: 0; + margin: 0; + font-family: "DM Sans", sans-serif; + display: flex; + flex-direction: column; +} +main.home > nav ul li { + padding: 0.25rem 0; +} +@media only screen and (min-width: 56rem) { + main.home > nav ul { + display: grid; + grid-template-columns: 1fr 1fr; + } + main.home > nav ul > li { + display: flex; + } + main.home > nav ul > li:nth-child(even) { + justify-content: flex-end; + } +} +main.home > div.feeds section { + margin-bottom: 1rem; +} +main.home > div.feeds section:last-child { + text-align: right; +} +@media only screen and (min-width: 56rem) { + main.home > div.feeds { + display: flex; + flex-direction: row; + justify-content: space-between; + min-width: 30rem; + } +} +main.home > div.feeds h2 { + margin: 0; + padding: 0; + font-size: 112.5%; + color: var(--fg); +} +main.home > div.feeds ol { + list-style-type: none; + padding: 0; + margin: 0; +} +main.home > div.feeds ol li { + margin: 0.5rem 0; +} +main.home > div.feeds ol li > p.edition { + margin: 0; +} +main.home > div.feeds ol li > p.edition > span:first-child { + color: var(--fg-heavy); +} +main.home > div.feeds ol li > p.edition > *:first-child { + font-style: italic; +} +main.home > div.feeds ol li > p.edition > *:last-child { + margin-left: 0.5em; +} +main.home > div.feeds ol li > p.age { + margin: 0; + font-size: 87.5%; + color: var(--fg-semi); +} + +nav.cornice { + font-family: "DM Sans", sans-serif; + font-size: 12pt; +} +nav.cornice > div:first-child { + font-weight: 700; +} + +nav.paginator { + display: flex; + justify-content: space-between; + margin: 1rem 0; +} +nav.paginator > *:first-child::before { + content: "◀"; + padding-right: 0.5em; +} +nav.paginator > *:last-child::after { + content: "▶"; + padding-left: 0.5em; +} + +section.notice, +section.signage { + margin: 0.75rem 0; +} +section.notice p, +section.signage p { + margin: 0; +} + +section.notice { + font-style: italic; + color: var(--fg-accent); +} +section.notice code { + font-style: normal; + background-color: transparent; +} +section.notice a { + text-decoration: underline; + text-decoration-style: dashed; +} +section.notice a:hover { + text-decoration-style: solid; +} +section.notice em { + font-weight: 700; +} + +section.signage { + display: flex; + align-items: stretch; +} + +section.signage::before { + padding: 0.25rem 1rem 0.5rem 1rem; + font-family: "Literata", serif; + font-weight: 400; + border-radius: 0.5rem 0 0 0.5rem; + color: white; +} + +section.signage.spi::before { + font-size: 125%; + background-color: var(--bg-warn); + box-shadow: 0 0.25rem 0 0 var(--bg-warn-shadow); + content: "@"; +} + +section.signage.deprecation::before { + font-size: 150%; + font-family: "IBM Plex Mono", monospace; + background-color: var(--bg-alert); + box-shadow: 0 0.25rem 0 0 var(--bg-alert-shadow); + content: "!"; +} + +section.signage.deprecation.renamed::before { + background-color: var(--bg-aqua); + box-shadow: 0 0.25rem 0 0 var(--bg-aqua-shadow); + content: "→"; +} + +section.signage > p { + flex: 1 1 auto; + padding: 0.5rem 0.5rem 0.5rem 1rem; + font-family: "IBM Plex Mono", monospace; + background-color: var(--bg-elevated); + border-radius: 0 0.5rem 0.5rem 0; + box-shadow: 0 0.25rem 0 0 var(--bg-elevated-shadow); +} + +ol.steps { + padding-left: 0; +} + +ol.build-logs { + padding-left: 1em; +} +ol.build-logs > li::marker { + font-variant-numeric: oldstyle-nums; +} + +ol.builds-pending { + list-style-type: none; + padding: 0; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 1rem; +} +ol.builds-pending > li { + display: contents; +} +ol.builds-pending > li > div:first-child { + font-style: italic; + font-weight: bold; +} +ol.builds-pending > li form { + text-align: right; +} +ol.builds-pending > li form > button { + margin: 0; +} + +p { + line-height: 1.8rem; + hyphens: auto; +} +p code { + background-color: var(--bg-codewell); + font-size: 90.625%; + padding: 0.2rem 0.5rem 0.25rem; + line-height: 0; + border-radius: 0.4rem; +} +p img { + max-width: 100%; + max-height: 40rem; +} + +p.note { + color: var(--fg-semi); + font-style: italic; +} +p.note em { + font-style: normal; +} + +pre { + margin: 2rem 0 2rem; + line-height: 1.5rem; +} +pre code *.xv, +pre code *.xo, +pre code *.extendee { + color: var(--fg); +} +pre code *.xb { + color: var(--fg); +} +pre code *.xa, +pre code *.xr, +pre code *.xk, +pre code *.xm { + color: var(--fg-swift-keyword); +} +pre code *.xj, +pre code *.xp { + color: var(--fg-accent); +} +pre code *.xn, +pre code *.xs { + color: var(--fg-swift-literal); +} +pre code *.xy, +pre code *.xz, +pre code *.xt { + color: var(--fg-swift-type); +} +pre code *.xu { + color: var(--fg-accent); + font-style: italic; +} +pre code *.xc, +pre code *.xd { + color: var(--fg-swift-comment); + font-style: italic; +} +pre code a { + color: inherit; +} +pre code a { + text-decoration: underline dashed; +} +pre code a:hover { + text-decoration: underline; +} + +pre.snippet { + overflow: scroll; + padding: 0.5rem 0 0.5rem 3rem; + background-color: var(--bg-codewell); + scrollbar-color: var(--fg-accent) transparent; + scrollbar-gutter: stable; +} +pre.snippet code { + counter-set: line 0; + white-space: pre; +} +pre.snippet code span.newline { + counter-increment: line; +} +pre.snippet code span.newline:after { + color: var(--fg-snippet-line-number); + position: relative; + display: inline-block; + content: counter(line); + width: 0; + right: 2rem; + direction: rtl; + word-wrap: normal; +} +pre.snippet code ins { + background-color: var(--bg-diff-insert); + text-decoration: inherit; +} +pre.snippet code del { + background-color: var(--bg-diff-delete); + text-decoration: inherit; +} +pre.snippet code mark { + background-color: var(--bg-diff-update); + text-decoration: inherit; +} +pre.snippet code.language-swift { + color: var(--fg-swift); +} + +div.searchbar-container { + padding: 0.5rem 0; +} +div.searchbar-container label.checkbox { + background-color: var(--bg); + max-width: 20rem; + margin-top: 0.5rem; + margin-left: 0.5rem; +} +@supports (backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)) { + div.searchbar-container label.checkbox { + background-color: var(--bg-translucent); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} + +@media only screen and (min-width: 56rem) { + div.searchbar-container label.checkbox { + margin-top: 0; + } + div.searchbar-container { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + } +} +div.search-results-container { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; +} + +div.searchbar { + flex: 1 1 20rem; + max-width: 20rem; + padding: 0.25rem 0.5rem 0.25rem 1rem; + background-color: var(--bg); + box-shadow: 0 1rem 1.5rem var(--blur-shadow), 0 0.2rem 0 var(--blur-shadow); + border-radius: 0.5rem; +} +@supports (backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)) { + div.searchbar { + background-color: var(--bg-translucent); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} + +#search-results li:last-child { + box-shadow: 0 0.25rem 0 var(--bg-accent-shadow); +} + +#search-input::placeholder { + color: var(--fg-semi); + opacity: 1; +} + +#search-input { + width: 100%; + border-radius: 0; + border: none; + outline: none; + background: none; + color: var(--color-hot); + font-family: "IBM Plex Mono", monospace; + font-size: 100%; +} + +#search-results { + width: 100%; + padding: 0; + list-style-type: none; + display: flex; + flex-flow: column nowrap; + border-radius: 0.5rem; +} +#search-results li a { + display: flex; + justify-content: space-between; + color: var(--fg-accent); + -webkit-tap-highlight-color: transparent; +} +#search-results li a > span:first-child { + font-family: "IBM Plex Mono", monospace; + font-weight: 700; +} +#search-results li a > span.module { + font-style: italic; +} +#search-results li a > span:last-child { + flex-shrink: 0; + margin-left: 1rem; +} +#search-results li > * { + margin: 0; + word-wrap: anywhere; + padding: 0.5rem 1rem; +} +#search-results li.package a > span:first-child { + font-family: "Literata", serif; + font-style: italic; + font-weight: 400; +} +#search-results li:first-child { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} +#search-results li:last-child { + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} +#search-results li:hover { + background-color: var(--fg-accent); +} +#search-results li:focus, +#search-results li.selected { + background-color: var(--color-hot); +} +#search-results li:hover a, +#search-results li:focus a, +#search-results li.selected a { + text-decoration: none; + color: white; +} + +@media (hover: hover) and (pointer: fine) { + #search-results { + display: none; + } + div.search-tool:focus-within #search-results, + header:focus-within #search-results { + display: flex; + } +} +#search-results { + background-color: var(--bg-accent); +} +@supports (backdrop-filter: blur(10px) saturate(180%)) { + #search-results { + background-color: var(--bg-accent-translucent); + backdrop-filter: blur(10px) saturate(180%); + } +} + +section a.source.github::before { + display: inline-block; + content: ""; + height: 1.125rem; + width: 1.125rem; + margin-right: 0.5em; + background: var(--fg-accent); + mask-image: var(--github-icon); + mask-size: 100% 100%; + -webkit-mask-image: var(--github-icon); + -webkit-mask-size: 100% 100%; +} +section a.source { + display: flex; + align-items: center; + font-family: "IBM Plex Mono", monospace; + font-size: 87.5%; + color: var(--fg-semi); + text-decoration-color: var(--fg-accent); +} +section a.source span.file { + font-weight: 500; +} +section a.source span.file, +section a.source span.line { + color: var(--fg-accent); +} +section > h2, section > h3 { + margin-top: 1.5em; +} + +section.availability { + margin: 0.75rem 0; +} +section.availability dl { + display: block; + margin: 0; + font-size: 87.5%; + font-family: "DM Sans", sans-serif; +} +section.availability dl dt, section.availability dl dd { + display: inline-block; + background: var(--fg-accent); + color: white; +} +section.availability dl dt:first-child { + margin: 0; +} +section.availability dl dt { + margin: 0 0 0 0.5em; + padding: 0.1em 0.2em 0.1em 0.4em; + border-radius: 0.25rem 0 0 0.25rem; +} +section.availability dl dd { + margin: 0; + padding: 0.1em 0.4em 0.1em 0.2em; + border-radius: 0 0.25rem 0.25rem 0; +} + +section.declaration pre { + margin: 2.5rem 0 2.5rem; +} +section.declaration pre code { + display: block; + text-indent: -2.5rem; + margin-left: 2.5rem; + font-size: inherit; + color: var(--fg-swift); +} +section.declaration pre code a { + text-decoration: underline dashed; +} +section.declaration pre code a:hover { + text-decoration: underline; +} +section.declaration pre code a.xv { + color: var(--fg-accent); +} +section.declaration pre code.multiline { + text-indent: 0; + margin-left: 0; +} + +section.details aside code, +section.details table code { + background-color: var(--bg-codewell); + font-size: 90.625%; + padding: 0.2rem 0.5rem 0.25rem; + line-height: 0; + border-radius: 0.4rem; +} +section.details section.parameters > h2 { + padding-left: 0; +} +section.details section.parameters > dl { + counter-set: parameter-index -1; +} +section.details section.parameters > dl > dt { + counter-increment: parameter-index; + font-family: "IBM Plex Mono", monospace; +} +section.details section.parameters > dl > dt:not([id])::before, +section.details section.parameters > dl > dt > a::before { + content: "$" counter(parameter-index); +} +section.details section.returns > h2 { + padding-left: 0; +} +section.details section.returns > * { + padding-left: 3rem; + margin-left: 0; +} +section.details pre.title { + margin-top: 1rem; + margin-bottom: 0; + font-family: "IBM Plex Mono", monospace; + font-weight: 700; + color: var(--fg-heavy); +} +section.details pre.title + pre { + margin-top: 0.5rem; +} +section.details pre { + font-size: 90.625%; +} +section.details pre + a.source { + margin-top: 0rem; + position: relative; + top: -1rem; +} + +section.events ol { + list-style: none; + padding: 0; + border-top: 1px solid var(--fg-semi); +} +section.events ol > li time { + font-weight: 700; +} + +section.group details summary { + cursor: pointer; + display: flex; + flex-flow: column nowrap; + align-items: center; + text-align: center; +} +section.group details summary p { + margin: 0; +} +section.group details summary p.view, +section.group details summary p.hide { + font-family: "DM Sans", sans-serif; + font-weight: 700; + font-size: 125%; +} +section.group details summary p.hide { + display: none; +} +section.group details summary p.reason { + margin-top: 0.5rem; + font-style: italic; + color: var(--fg-semi); +} +section.group details summary p.reason span.count { + font-style: normal; +} +section.group details summary::marker { + content: ""; +} +section.group details summary::-webkit-details-marker { + display: none; +} +section.group details.impl summary p.view, section.group details.impl summary p.hide { + color: var(--fg-semi); + font-size: 1rem; +} +section.group details.impl + details.impl { + margin-top: 1em; + border-top: dashed 1px var(--fg-light); + padding-top: 1em; +} +section.group details[open] summary p.view, +section.group details[open] summary p.reason { + display: none; +} +section.group details[open] summary p.hide { + display: block; +} + +section.group > header > h2 > a[href^="#"] { + color: inherit; +} +section.group > header, +section.group > h2 { + margin-top: 2rem; + margin-bottom: 1rem; +} +section.group > div.constraints + h3 { + margin-top: 1rem; + border-top: solid 1px var(--fg-swift-type); +} +section.group > header + h3, +section.group > h2 + h3 { + margin-top: 1rem; + border-top: solid 1px var(--fg-light); +} +section.group > h3, +section.group > details > h3 { + margin-top: 1rem; + border-top: dashed 1px var(--fg-semi); + padding-top: 0.75rem; + font-weight: 600; + color: var(--fg-semi); +} + +section.introduction { + margin-top: 0; + padding: 0 0 0.75rem; + border-bottom: 2px solid var(--fg-accent); +} +section.introduction div.eyebrows { + color: var(--fg-semi); + font-style: italic; + display: flex; + flex-flow: row wrap; +} +section.introduction div.eyebrows .phylum { + flex: 1; +} +section.introduction div.eyebrows .phylum .kink { + font-weight: 600; +} +section.introduction div.eyebrows span.domain span.extends { + display: inline-block; + padding: 0 0.35em; + font-family: "Literata", serif; +} +section.introduction div.eyebrows span.domain span.jump:before, +section.introduction div.eyebrows span.domain span.jump:after { + margin: 0 0.2rem; +} +section.introduction div.eyebrows span.domain span.jump:before { + content: "("; +} +section.introduction div.eyebrows span.domain span.jump:after { + content: ")"; +} +section.introduction nav.breadcrumbs { + margin-top: 2em; + color: var(--fg-swift); + font-family: "IBM Plex Mono", monospace; + font-weight: 500; + font-size: 90.625%; +} +section.introduction nav.breadcrumbs a:not(:hover) { + color: var(--fg-semi); + text-decoration: underline dashed; +} +section.introduction nav.breadcrumbs > *:first-child { + margin-left: 0; +} +section.introduction nav.breadcrumbs > * { + margin: 0 0.2rem; +} +section.introduction nav.breadcrumbs > *:last-child { + margin-right: 0; +} +section.introduction nav.breadcrumbs + h1 { + margin-top: 0.5em; +} +section.introduction p.chyron { + margin: 0; + max-width: inherit; +} +section.introduction a.source + a.source { + margin-top: 0.5rem; +} + +section.literature aside { + position: relative; + margin: 2rem 0; + border-bottom: 2px solid var(--fg-semi); +} +section.literature aside h3 { + margin: 0; + padding: 0.5rem 0; + font-size: inherit; + color: var(--fg-semi); + border-bottom: 2px solid var(--fg-semi); +} +section.literature aside.important, +section.literature aside.warning { + border-color: var(--color-hot); +} +section.literature aside.important h3, +section.literature aside.warning h3 { + color: var(--color-hot); + border-color: var(--color-hot); +} +section.literature aside:last-child { + border-bottom: none; +} +section.literature dl { + padding-left: 0; + margin-bottom: 2rem; + display: block; +} +section.literature dl > dt { + margin-top: 2rem; + font-weight: 700; +} +section.literature dl > dt > a::before { + font-family: "IBM Plex Mono", monospace; +} +section.literature dl > dt > a { + color: inherit; +} +section.literature dl > dt:not([id])::before, +section.literature dl > dt > a::before { + content: "$"; + display: inline-block; + min-width: 3rem; + color: var(--fg-snippet-line-number); + font-weight: normal; +} +section.literature dl > dd { + padding-left: 3rem; + margin-left: 0; +} + +section.metadata details[open] summary::before { + content: "▼"; +} +section.metadata details summary::marker { + content: ""; +} +section.metadata details summary::-webkit-details-marker { + display: none; +} +section.metadata details summary::before { + content: "▶"; + color: var(--fg-light); + display: inline-block; + width: 1.25rem; +} +section.metadata details summary { + margin: 0.5rem 0; + position: relative; + left: -1.25rem; + font-style: italic; + color: var(--fg-semi); +} +section.metadata details p.symbol:first-letter { + font-weight: 700; +} +section.metadata details code span.fnv24 { + font-weight: 700; +} +section.metadata details:last-child { + margin-bottom: 2rem; +} + +span.placeholder, +span.parenthetical { + color: var(--fg-semi); + font-style: italic; +} +span.placeholder em, +span.parenthetical em { + font-style: normal; +} + +span.parenthetical::before { + content: "("; + font-style: normal; + margin-left: 0.4em; +} + +span.parenthetical::after { + content: ")"; + font-style: normal; +} + +span.warn { + color: var(--fg-warn); +} + +table th { + text-align: left; + font-family: "DM Sans", sans-serif; + padding-bottom: 0.5rem; + color: var(--fg-semi); + border-bottom: 2px solid var(--fg-swift); +} +table tbody tr:first-child td { + padding-top: 0.5rem; +} +table th, +table td { + padding-left: 0.5rem; +} +table th:first-child, +table td:first-child { + padding-left: 0; +} +table td.placeholder { + color: var(--fg-semi); + font-style: italic; +} +table td.placeholder em { + font-style: normal; +} + +table[data-type] { + width: 100%; +} + +table[data-type=constituents] td { + font-style: italic; +} + +table[data-type=dependencies] td:first-child { + font-style: italic; +} +table[data-type=dependencies] td span.upto { + padding: 0 0.5rem; +} + +table[data-type=consumers] { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + border-collapse: collapse; +} +table[data-type=consumers] > thead, +table[data-type=consumers] > thead > tr, +table[data-type=consumers] > tbody, +table[data-type=consumers] > tbody > tr { + display: contents; +} +table[data-type=consumers] td:first-child { + font-style: italic; +} + +table[data-type=consumers] td.ref, +table[data-type=refs] td.ref { + overflow: hidden; + white-space: nowrap; + text-overflow: "..."; +} + +table[data-type=refs] { + display: grid; + grid-template-columns: 2fr 1fr 1fr 3fr; + border-collapse: collapse; +} +table[data-type=refs] > thead, +table[data-type=refs] > thead > tr, +table[data-type=refs] > tbody, +table[data-type=refs] > tbody > tr { + display: contents; +} +table[data-type=refs] > tbody > tr > td.graph { + display: flex; + justify-content: space-between; + font-weight: normal; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child { + color: var(--fg-semi); +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.graph:first-child::before { + content: "✓"; + color: var(--fg-accent); + margin-right: 0.5rem; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.graph.uplinking:first-child::before { + content: "⟳"; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.graph.unlinking:first-child::before { + content: "▼"; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.graph.deleting:first-child::before { + content: "✗"; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span:first-child::before { + content: "✗"; + color: var(--fg-semi); + margin-right: 0.5rem; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span:first-child { + font-weight: 700; +} +table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.kb { + font-weight: normal; +} +table[data-type=refs] > tbody > tr > td.graph > div.menu > ul { + right: 0; + min-width: 15rem; +} +table[data-type=refs] > tbody > tr.modern { + font-weight: 700; +} + +ul.cards { + padding: 0; + margin-bottom: 3rem; + list-style-type: none; +} +ul.cards > li:first-child > h3 { + margin-top: 0; +} +ul.cards > li { + margin: 0.5rem 0 0.5rem 0; + padding: 0; +} +ul.cards > li > h3.module, +ul.cards > li > h3.product { + margin: 0.75rem 0 0 0; + font-family: "Literata", serif; + font-style: italic; + font-size: 100%; + font-weight: normal; +} +ul.cards > li > h3.article { + margin: 2rem 0 0.5rem 0; + font-family: "Literata", serif; + font-weight: normal; + font-size: 125%; +} +ul.cards > li > h3.article a { + color: inherit; + text-decoration: underline; + text-decoration-style: dashed; +} +ul.cards > li > h3.article a:hover { + text-decoration-style: solid; +} +ul.cards > li > a.read-more { + font-style: italic; +} +ul.cards > li > code.decl a { + color: var(--fg-swift-comment); +} +ul.cards > li > code.decl a span.xv { + color: var(--color-hot); +} +ul.cards > li > code.decl a.discouraged { + text-decoration: line-through; +} +ul.cards > li > code.decl a.discouraged span.xv { + color: inherit; +} +ul.cards > li > code.decl { + display: block; + padding: 0.8em 0 0 0; + text-indent: -2.5rem; + margin-left: 2.5rem; + white-space: pre-wrap; + font-size: 100%; + line-height: 1.5rem; +} +ul.cards > li > code.decl.multiline { + text-indent: 0; + margin-left: 0; +} +ul.cards > li > code.decl.multiline a { + display: inline-block; +} +ul.cards > li > code.decl + p { + padding: 0 0 0 2.5rem; + line-height: 1.3rem; + font-size: 90.625%; +} +ul.cards > li[id]:target > code a { + background-color: var(--bg-highlight); +} +ul.cards > li[id] > a:first-child { + position: absolute; + margin-left: -1.5em; + margin-top: 0.8em; +} +ul.cards > li[id] > a:first-child::before { + content: "$"; + line-height: 1.5rem; + font-family: "IBM Plex Mono", monospace; + font-size: 100%; + opacity: 0.25; + color: var(--fg-semi); +} +ul.cards > li[id] > a:first-child:hover::before { + opacity: 1; + color: var(--color-hot); +} + +ul.cards.dense > li > code.decl a span.xv { + color: var(--fg-accent); +} + +ul.users { + padding: 0; + margin-bottom: 2rem; + list-style-type: none; +} + +ul.users > li:first-child { + border-top: none; +} + +ul.users > li { + border-top: 1px dashed var(--fg-light); + padding: 1rem 0; +} +ul.users > li > header { + display: flex; + align-items: left; +} +ul.users > li > header div.icon { + border: 2px dashed var(--fg-light); + box-sizing: border-box; +} +ul.users > li > header div.icon, +ul.users > li > header img.icon { + width: 2rem; + height: 2rem; + margin-right: 0.5rem; + border-radius: 50%; +} +ul.users > li > header div.tools { + margin-left: auto; + display: flex; + align-items: right; + color: var(--fg-heavy); +} +ul.users > li > header div.tools > form { + margin-left: 0.5em; +} +ul.users > li > header div.tools > form button[type=submit] { + margin: 0 0.25em; + font-style: italic; +} +ul.users > li > header div.tools > form::before, +ul.users > li > header div.tools > form::after { + color: var(--fg-semi); +} +ul.users > li > header div.tools > form::before { + content: "("; +} +ul.users > li > header div.tools > form::after { + content: ")"; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 6rem; +} + +body { + min-height: 100vh; + margin: 0; +} +body > header.app { + position: fixed; + z-index: 1; + width: 100%; + pointer-events: none; +} +body > header.app nav { + width: 100%; + height: 1.5rem; + position: relative; + padding: 0.25rem 0; + display: flex; + flex-direction: row; + justify-content: space-between; + align-content: center; + overflow: hidden; + overflow-wrap: break-word; +} +body > header.app > div.content::before { + position: fixed; + left: 0; + display: block; + height: 2rem; + width: 100%; + content: ""; + background-color: var(--bg); +} +@supports (backdrop-filter: blur(10px) saturate(180%)) or (-webkit-backdrop-filter: blur(10px) saturate(180%)) { + body > header.app > div.content::before { + background-color: var(--bg-translucent); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} +body > header.app > div.content { + pointer-events: auto; +} +body > * { + display: flex; + flex-direction: row-reverse; + justify-content: center; +} +body > *.app > *.content { + flex: 0 1 48rem; + overflow: hidden; + overflow-wrap: break-word; + padding-left: 1rem; + padding-right: 1rem; +} +body > *.app > *.sidebar { + display: none; +} +body main { + margin-top: 8rem; +} +@media only screen and (min-width: 56rem) { + body main { + margin-top: 6rem; + } + body > header.app > *.sidebar { + pointer-events: none; + } + body > *.app > *.sidebar { + display: block; + flex: 0 1 16rem; + min-width: 12rem; + max-width: 16rem; + } + body > *.app > *.content { + min-width: 24rem; + padding-left: 4rem; + overflow: visible; + } +} + +body main section.introduction > p, +body main section.introduction > dl, +body main section.parameters > p, +body main section.parameters > dl, +body main section.returns > p, +body main section.returns > dl, +body main section.throws > p, +body main section.throws > dl { + max-width: 40rem; +} +body main section.details p, body main section.details figure { + max-width: 40rem; +} +body main section.details aside { + max-width: 41rem; +} +body main section.extension ul > li > p { + max-width: 40rem; +} +body main > *:last-child { + padding-bottom: 5rem; +} + +html { + font-size: 1rem; + background-color: var(--bg); +} + +body { + color: var(--fg); + font-family: "Literata", serif; + font-variant-numeric: oldstyle-nums; + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +label.checkbox { + color: var(--fg-heavy); + line-height: 1em; + display: grid; + grid-template-columns: 1em auto; + gap: 0.3em; +} + +input[type=checkbox], +input[type=radio] { + background-color: var(--bg); +} + +input[type=checkbox], input[type=radio] { + appearance: none; + margin: 0; + font-size: inherit; + height: 1em; + width: 1em; + display: grid; + place-content: center; + transform: translateY(2px); + border: 2px solid var(--fg-semi); + border-radius: 0.1em; +} + +input[type=checkbox]::before, input[type=radio]::before { + content: ""; + width: 0.65em; + height: 0.65em; + background-color: var(--bg); +} + +input[type=checkbox]:checked, input[type=radio]:checked { + border-color: var(--fg-accent); + background-color: var(--fg-accent); +} + +input[type=checkbox]:checked::before, input[type=radio]:checked::before { + transform-origin: bottom left; + clip-path: polygon(100% 13%, 39% 100%, 0 70%, 11% 53%, 37% 71%, 83% 0); +} + +/*# sourceMappingURL=Main.css.map */ diff --git a/Assets/css/Main.css.map b/Assets/css/Main.css.map index 2e315ac39..59a1651dd 100644 --- a/Assets/css/Main.css.map +++ b/Assets/css/Main.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../Stylesheets/Variables/_root.scss","../../Stylesheets/_a.scss","../../Stylesheets/Mixins/_ControlArea.scss","../../Stylesheets/_Typefaces.scss","../../Stylesheets/_blockquote.scss","../../Stylesheets/Mixins/_InlineImage.scss","../../Stylesheets/_button.scss","../../Stylesheets/_Calendar.scss","../../Stylesheets/_Cards.scss","../../Stylesheets/_code.scss","../../Stylesheets/_div.build-pipeline.scss","../../Stylesheets/_div.columns.scss","../../Stylesheets/_div.constraints.scss","../../Stylesheets/_div.menu.scss","../../Stylesheets/Mixins/_BackdropBlur.scss","../../Stylesheets/_div.more.scss","../../Stylesheets/_div.sidebar.scss","../../Stylesheets/_div.tooltips.scss","../../Stylesheets/_dl.scss","../../Stylesheets/_figure.scss","../../Stylesheets/_form.scss","../../Stylesheets/_header.visual.scss","../../Stylesheets/_Headings.scss","../../Stylesheets/_kbd.scss","../../Stylesheets/_main.home.scss","../../Stylesheets/_nav.cornice.scss","../../Stylesheets/_nav.paginator.scss","../../Stylesheets/_Notices.scss","../../Stylesheets/_ol.scss","../../Stylesheets/_p.scss","../../Stylesheets/Mixins/_InlineCode.scss","../../Stylesheets/Mixins/_Note.scss","../../Stylesheets/_pre.scss","../../Stylesheets/_Search.scss","../../Stylesheets/_section.scss","../../Stylesheets/_section.availability.scss","../../Stylesheets/_section.declaration.scss","../../Stylesheets/_section.details.scss","../../Stylesheets/_section.events.scss","../../Stylesheets/_section.group.scss","../../Stylesheets/_section.introduction.scss","../../Stylesheets/_section.literature.scss","../../Stylesheets/_section.metadata.scss","../../Stylesheets/_span.scss","../../Stylesheets/_table.scss","../../Stylesheets/_ul.cards.scss","../../Stylesheets/_ul.users.scss","../../Stylesheets/Main.scss"],"names":[],"mappings":"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","file":"Main.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../Stylesheets/Variables/_root.scss","../../Stylesheets/_a.scss","../../Stylesheets/Mixins/_ControlArea.scss","../../Stylesheets/_Typefaces.scss","../../Stylesheets/_blockquote.scss","../../Stylesheets/Mixins/_InlineImage.scss","../../Stylesheets/_button.scss","../../Stylesheets/_Calendar.scss","../../Stylesheets/_Cards.scss","../../Stylesheets/_code.scss","../../Stylesheets/_div.columns.scss","../../Stylesheets/_div.constraints.scss","../../Stylesheets/_div.hstack.scss","../../Stylesheets/_div.menu.scss","../../Stylesheets/Mixins/_BackdropBlur.scss","../../Stylesheets/_div.more.scss","../../Stylesheets/_div.sidebar.scss","../../Stylesheets/_div.tooltips.scss","../../Stylesheets/_dl.scss","../../Stylesheets/_figure.scss","../../Stylesheets/_form.scss","../../Stylesheets/_header.visual.scss","../../Stylesheets/_Headings.scss","../../Stylesheets/_kbd.scss","../../Stylesheets/_main.home.scss","../../Stylesheets/_nav.cornice.scss","../../Stylesheets/_nav.paginator.scss","../../Stylesheets/_Notices.scss","../../Stylesheets/_ol.scss","../../Stylesheets/_ol.builds-pending.scss","../../Stylesheets/_p.scss","../../Stylesheets/Mixins/_InlineCode.scss","../../Stylesheets/Mixins/_Note.scss","../../Stylesheets/_pre.scss","../../Stylesheets/_Search.scss","../../Stylesheets/_section.scss","../../Stylesheets/_section.availability.scss","../../Stylesheets/_section.declaration.scss","../../Stylesheets/_section.details.scss","../../Stylesheets/_section.events.scss","../../Stylesheets/_section.group.scss","../../Stylesheets/_section.introduction.scss","../../Stylesheets/_section.literature.scss","../../Stylesheets/_section.metadata.scss","../../Stylesheets/_span.scss","../../Stylesheets/_table.scss","../../Stylesheets/_ul.cards.scss","../../Stylesheets/_ul.users.scss","../../Stylesheets/Main.scss"],"names":[],"mappings":"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","file":"Main.css"} \ No newline at end of file diff --git a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift index 43e48f591..281645546 100644 --- a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift +++ b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift @@ -1,5 +1,6 @@ import BSON import MongoQL +import Symbols import UnidocAPI import UnidocRecords import UnixTime @@ -18,7 +19,12 @@ extension Unidoc let finished:UnixMillisecond public - var failure:BuildFailure? + let failure:BuildFailure? + + /// Used for display purposes only. + public + let name:Symbol.PackageAtRef + public var logs:[BuildLogType] @@ -27,31 +33,18 @@ extension Unidoc launched:UnixMillisecond, finished:UnixMillisecond, failure:BuildFailure?, - logs:[BuildLogType]) + name:Symbol.PackageAtRef, + logs:[BuildLogType] = []) { self.edition = edition self.launched = launched self.finished = finished self.failure = failure self.logs = logs + self.name = name } } } -extension Unidoc.CompleteBuild -{ - @inlinable public - init(id:Unidoc.BuildIdentifier, - finished:UnixMillisecond, - failure:Unidoc.BuildFailure?, - logs:[Unidoc.BuildLogType]) - { - self.init(edition: id.edition, - launched: id.instant, - finished: finished, - failure: failure, - logs: logs) - } -} extension Unidoc.CompleteBuild:Identifiable { @inlinable public @@ -65,6 +58,7 @@ extension Unidoc.CompleteBuild:Mongo.MasterCodingModel case id = "_id" case finished = "F" case failure = "E" + case name = "N" case logs = "O" case package = "p" @@ -78,6 +72,7 @@ extension Unidoc.CompleteBuild:BSONDocumentEncodable bson[.id] = self.id bson[.finished] = self.finished bson[.failure] = self.failure + bson[.name] = self.name bson[.logs] = self.logs.isEmpty ? nil : self.logs bson[.package] = self.id.edition.package @@ -88,9 +83,12 @@ extension Unidoc.CompleteBuild:BSONDocumentDecodable @inlinable public init(bson:BSON.DocumentDecoder) throws { - self.init(id: try bson[.id].decode(), + let id:Unidoc.BuildIdentifier = try bson[.id].decode() + self.init(edition: id.edition, + launched: id.instant, finished: try bson[.finished].decode(), failure: try bson[.failure]?.decode(), + name: try bson[.name].decode(), logs: try bson[.logs]?.decode() ?? []) } } diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift index 82777d795..66f744c0c 100644 --- a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift +++ b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift @@ -206,6 +206,7 @@ extension Unidoc.DB.PendingBuilds } } + public func finishBuild(id:Unidoc.Edition) async throws -> Unidoc.PendingBuild? { try await self.remove @@ -235,30 +236,3 @@ extension Unidoc.DB.PendingBuilds } } } -extension Unidoc.DB.PendingBuilds -{ - public - func completeBuild(id:Unidoc.Edition, - duration:Seconds) async throws -> (launched:UnixMillisecond, finished:UnixMillisecond) - { - let launched:UnixMillisecond - let finished:UnixMillisecond - - if let finishedBuild:Unidoc.PendingBuild = try await self.finishBuild(id: id), - let instant:UnixMillisecond = finishedBuild.launched - { - launched = instant - finished = launched.advanced(by: .init(duration)) - } - else - { - // Build was timed out on the server side, but still delivered to the server. - // In this situation, we use the current time as the finish time, and work out - // launch time by subtracting the duration from the finish time. - finished = .now() - launched = finished.regressed(by: .init(duration)) - } - - return (launched, finished) - } -} diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift index bbdc101c3..43e26ef2b 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift @@ -39,16 +39,23 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation case .labeled: var build:Unidoc.BuildArtifact = try .init(bson: bson) - let launched:UnixMillisecond - let finished:UnixMillisecond + guard + let pending:Unidoc.PendingBuild = try await db.pendingBuilds.finishBuild( + id: build.edition), + let launched:UnixMillisecond = pending.launched + else + { + return .notFound("Build not found or timed-out\n") + } - (launched, finished) = try await db.pendingBuilds.completeBuild(id: build.edition, - duration: .seconds(build.seconds)) + let duration:Seconds = .seconds(build.seconds) + let finished:UnixMillisecond = launched.advanced(by: .init(duration)) var complete:Unidoc.CompleteBuild = .init(edition: build.edition, launched: launched, finished: finished, failure: build.failure, + name: pending.name, logs: []) complete.logs = try await build.export(as: complete.id, from: server) diff --git a/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift b/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift index d7e6c93f5..ee09fec6b 100644 --- a/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift +++ b/Sources/UnidocServer/Requests/Unidoc.BuildRequestPage.swift @@ -46,6 +46,11 @@ extension Unidoc.BuildRequestPage:Unidoc.ConfirmationPage } } + if case .cancel = self.form.action + { + return + } + form[.p] { $0[.label] diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift new file mode 100644 index 000000000..ba3ceda36 --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButton.swift @@ -0,0 +1,14 @@ +extension Unidoc +{ + struct BuildButton + { + let text:String? + let type:BuildButtonType + + init(text:String?, type:BuildButtonType) + { + self.text = text + self.type = type + } + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButtonType.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButtonType.swift new file mode 100644 index 000000000..ea2ae1263 --- /dev/null +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildButtonType.swift @@ -0,0 +1,8 @@ +extension Unidoc +{ + enum BuildButtonType + { + case zone + case inline + } +} diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift index c17363a80..9a1d578ea 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildFormTool.swift @@ -7,10 +7,10 @@ extension Unidoc struct BuildFormTool { let form:BuildForm - let area:Bool + let area:BuildButton let disabled:Inhibitor? - init(form:BuildForm, area:Bool, disabled:Inhibitor? = nil) + init(form:BuildForm, area:BuildButton, disabled:Inhibitor? = nil) { self.form = form self.area = area @@ -24,8 +24,11 @@ extension Unidoc.BuildFormTool func shortcut(buildable:String?, submitted:Bool, package:Symbol.Package, + label:String, view:Unidoc.Permissions) -> Self { + let area:Unidoc.BuildButton = .init(text: label, type: .zone) + guard let buildable:String else @@ -34,7 +37,7 @@ extension Unidoc.BuildFormTool return .init(form: .init( symbol: .init(package: package, ref: ""), action: .submit), - area: true, + area: area, disabled: .unavailable) } @@ -44,48 +47,49 @@ extension Unidoc.BuildFormTool if submitted { - return .init(form: form, area: true, disabled: .alreadySubmitted) + return .init(form: form, area: area, disabled: .alreadySubmitted) } guard case _? = view.global else { - return .init(form: form, area: true, disabled: .unauthenticated) + return .init(form: form, area: area, disabled: .unauthenticated) } guard view.editor else { - return .init(form: form, area: true, disabled: .unauthorized) + return .init(form: form, area: area, disabled: .unauthorized) } - return .init(form: form, area: true) + return .init(form: form, area: area) } static func control(pending build:Unidoc.PendingBuild, view:Unidoc.Permissions) -> Self { + let area:Unidoc.BuildButton = .init(text: nil, type: .inline) let form:Unidoc.BuildForm = .init(symbol: build.name, action: .cancel) guard case nil = build.launched else { - return .init(form: form, area: true, disabled: .alreadyStarted) + return .init(form: form, area: area, disabled: .alreadyStarted) } guard case _? = view.global else { - return .init(form: form, area: true, disabled: .unauthenticated) + return .init(form: form, area: area, disabled: .unauthenticated) } guard view.editor else { - return .init(form: form, area: true, disabled: .unauthorized) + return .init(form: form, area: area, disabled: .unauthorized) } - return .init(form: form, area: true) + return .init(form: form, area: area) } } extension Unidoc.BuildFormTool:HTML.OutputStreamable @@ -111,13 +115,18 @@ extension Unidoc.BuildFormTool:HTML.OutputStreamable switch self.form.action { - case .submit: label = "Request build" - case .cancel: label = "Cancel build" + case .submit: label = self.area.text ?? "Request build" + case .cancel: label = self.area.text ?? "Cancel build" } form[.button] { - $0.class = self.area ? "area" : "text" + switch self.area.type + { + case .inline: $0.class = "text" + case .zone: $0.class = "area" + } + $0.type = "submit" guard diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift index 6cfdd388b..68dd9af43 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.BuildTools.swift @@ -19,9 +19,9 @@ extension Unidoc.BuildTools:HTML.OutputStreamable static func += (section:inout HTML.ContentEncoder, self:Self) { - section[.div] + section[.div, { $0.class = "hstackable" }] { - for shortcut:Unidoc.BuildFormTool in [self.prerelease, self.release] + for shortcut:Unidoc.BuildFormTool in [self.release, self.prerelease] { $0[.form] { @@ -32,55 +32,64 @@ extension Unidoc.BuildTools:HTML.OutputStreamable } } - section[.ol] + section[.ol, { $0.class = "builds-pending" }] { for build:Unidoc.PendingBuild in self.running { - let tooltip:String - let label:String - - switch build.stage + $0[.li] { - case nil: - tooltip = "The build has not yet started." - label = "Queued" + if let assignee:Unidoc.Account = build.assignee, + let stage:Unidoc.BuildStage = build.stage + { + $0[.div] + { + $0.title = "This build has been assigned to a builder \(assignee)." + } = "Started" - case .initializing?: - tooltip = "The builder is initializing." - label = "Git" + $0[.div] { $0.class = "ref" } = build.name.ref - case .cloningRepository?: - tooltip = "The builder is cloning the package’s repository." - label = "Git" + let tooltip:String + let label:String - case .resolvingDependencies?: - tooltip = "The builder is resolving the package’s dependencies." - label = "SwiftPM" + switch stage + { + case .initializing: + tooltip = "The builder is initializing." + label = "Git" - case .compilingCode?: - tooltip = "The builder is compiling the package’s source code." - label = "Swift" - } + case .cloningRepository: + tooltip = "The builder is cloning the package’s repository." + label = "Git" + + case .resolvingDependencies: + tooltip = "The builder is resolving the package’s dependencies." + label = "SwiftPM" + + case .compilingCode: + tooltip = "The builder is compiling the package’s source code." + label = "Swift" + } + + $0[.div] { $0.title = tooltip } = label + } + else + { + $0[.div] + { + $0.title = "This build has not yet started." + } = "Queued" + + $0[.div] { $0.class = "ref" } = build.name.ref + + $0[.div] + } - $0[.li] - { $0[.form] { $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" $0.action = "\(Unidoc.Post[.build, confirm: true])" $0.method = "post" } = Unidoc.BuildFormTool.control(pending: build, view: self.view) - - $0[.div] - { - $0.class = build.stage == nil ? "phase queued" : "phase started" - $0.title = tooltip - } = label - - $0[.div] - { - $0.class = "ref" - } = build.name.ref } } } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift index aa59c387d..ef2297815 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift @@ -43,6 +43,7 @@ extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable $0[.tr] { $0[.th] = "Run time" + $0[.th] = "Ref" $0[.th] = "Status" $0[.th] = "Logs" } @@ -64,10 +65,12 @@ extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable } = duration.short } + $0[.td] = row.name.ref + switch row.failure { case nil: - $0[.td] + $0[.td] = "OK" case .killed?: $0[.td] = "Killed" diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift index d6ff6f22a..db64fd7bf 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsEndpoint.swift @@ -81,10 +81,12 @@ extension Unidoc.RefsEndpoint:HTTP.ServerEndpoint let prereleaseTool:Unidoc.BuildFormTool = .shortcut(buildable: prerelease?.edition.name, submitted: submitted.prerelease, package: output.package.symbol, + label: "Build latest prerelease", view: view) let releaseTool:Unidoc.BuildFormTool = .shortcut(buildable: release?.edition.name, submitted: submitted.release, package: output.package.symbol, + label: "Build latest release", view: view) let buildTools:Unidoc.BuildTools = .init( diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift index a4b506430..5ec04347f 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.Heading.swift @@ -10,8 +10,9 @@ extension Unidoc.RefsPage case consumers case settings case settingsAdmin - case buildTools case builds + case builtRecently + case buildConfiguration case importRefs } } @@ -21,15 +22,16 @@ extension Unidoc.RefsPage.Heading:Identifiable { switch self { - case .repo: "ss:repo" - case .tags: "ss:tags" - case .branches: "ss:branches" - case .consumers: "ss:consumers" - case .settings: "ss:settings" - case .settingsAdmin: "ss:settings-admin" - case .buildTools: "ss:build-tools" - case .builds: "ss:builds" - case .importRefs: "ss:import-refs" + case .repo: "ss:repo" + case .tags: "ss:tags" + case .branches: "ss:branches" + case .consumers: "ss:consumers" + case .settings: "ss:settings" + case .settingsAdmin: "ss:settings-admin" + case .builds: "ss:builds" + case .builtRecently: "ss:built-recently" + case .buildConfiguration: "ss:build-configuration" + case .importRefs: "ss:import-refs" } } } @@ -39,15 +41,16 @@ extension Unidoc.RefsPage.Heading:HTML.OutputStreamableHeading { switch self { - case .repo: "Package repository" - case .tags: "Package tags" - case .branches: "Package branches" - case .consumers: "Consumers" - case .settings: "Settings" - case .settingsAdmin: "Admin actions" - case .buildTools: "Request builds" - case .builds: "Recent builds" - case .importRefs: "Add branches" + case .repo: "Package repository" + case .tags: "Package tags" + case .branches: "Package branches" + case .consumers: "Consumers" + case .settings: "Settings" + case .settingsAdmin: "Admin actions" + case .builds: "Builds" + case .builtRecently: "Built recently" + case .buildConfiguration: "Build configuration" + case .importRefs: "Add branches" } } } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift index e8962ce00..c327a449c 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsPage.swift @@ -161,6 +161,60 @@ extension Unidoc.RefsPage type: .branches) } + section[.h3] = Heading.importRefs + section[.form] + { + $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" + $0.action = "\(Unidoc.Post[.packageIndex])" + $0.method = "post" + + $0.class = "config" + } + content: + { + $0[.dl] + { + $0[.dt] = "Branch name" + $0[.dd] + { + $0[.input] + { + $0.type = "hidden" + $0.name = "package" + $0.value = "\(self.package.id)" + } + + $0[.input] + { + $0.type = "text" + $0.name = "ref" + $0.required = true + $0.readonly = !self.view.editor + + $0.placeholder = "master" + $0.pattern = #"^[a-zA-Z0-9_\-\.\/]+$"# + } + } + } + + $0[.button] + { + $0.class = "area" + $0.type = "submit" + + if case nil = self.view.global + { + $0.disabled = true + $0.title = "You are not logged in!" + } + else if !self.view.editor + { + $0.disabled = true + $0.title = "You are not an editor for this package!" + } + } = "Import ref" + } + section[.h2] = Heading.consumers if self.consumers.table.rows.isEmpty @@ -358,7 +412,7 @@ extension Unidoc.RefsPage section[.form] = Unidoc.DisabledButton.init(label: "Refresh tags", view: self.view) } - section[.h2] = Heading.buildTools + section[.h2] = Heading.builds section += self.buildTools // All logged-in users can see the build logs. The only reason they are not totally @@ -366,65 +420,15 @@ extension Unidoc.RefsPage // CDN firewall is less effective than our apex firewall. if !self.builds.table.rows.isEmpty { - section[.h3] = Heading.builds + section[.h3] = Heading.builtRecently section[.table] = self.builds.table } - - section[.h3] = Heading.importRefs - section[.form] - { - $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" - $0.action = "\(Unidoc.Post[.packageIndex])" - $0.method = "post" - - $0.class = "config" - } - content: + if let more:URI = self.builds.next { - $0[.dl] - { - $0[.dt] = "Branch name" - $0[.dd] - { - $0[.input] - { - $0.type = "hidden" - $0.name = "package" - $0.value = "\(self.package.id)" - } - - $0[.input] - { - $0.type = "text" - $0.name = "ref" - $0.required = true - $0.readonly = !self.view.editor - - $0.placeholder = "master" - $0.pattern = #"^[a-zA-Z0-9_\-\.\/]+$"# - } - } - } - - $0[.button] - { - $0.class = "area" - $0.type = "submit" - - if case nil = self.view.global - { - $0.disabled = true - $0.title = "You are not logged in!" - } - else if !self.view.editor - { - $0.disabled = true - $0.title = "You are not an editor for this package!" - } - } = "Import ref" + section[.a] { $0.class = "area" ; $0.href = "\(more)" } = "View all builds" } - section[.h3] = "Build configuration" + section[.h3] = Heading.buildConfiguration section[.form] { $0.enctype = "\(MediaType.application(.x_www_form_urlencoded))" diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift index 2c30b9416..5517c3df7 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.RefsTable.Row.Graph.swift @@ -109,7 +109,7 @@ extension Unidoc.RefsTable.Row.Graph:HTML.OutputStreamable $0.method = "post" } = Unidoc.BuildFormTool.init( form: .init(symbol: self.symbol, action: .submit), - area: false) + area: .init(text: nil, type: .inline)) } guard diff --git a/Stylesheets/Main.scss b/Stylesheets/Main.scss index 2be7a3c4f..cf0f019c6 100644 --- a/Stylesheets/Main.scss +++ b/Stylesheets/Main.scss @@ -6,9 +6,9 @@ @import 'Calendar'; @import 'Cards'; @import 'code'; -@import 'div.build-pipeline'; @import 'div.columns'; @import 'div.constraints'; +@import 'div.hstack'; @import 'div.menu'; @import 'div.more'; @import 'div.sidebar'; @@ -24,6 +24,7 @@ @import 'nav.paginator'; @import 'Notices'; @import 'ol'; +@import 'ol.builds-pending'; @import 'p'; @import 'pre'; @import 'Search'; diff --git a/Stylesheets/_button.scss b/Stylesheets/_button.scss index ddbd2f1ea..69e4d81a1 100644 --- a/Stylesheets/_button.scss +++ b/Stylesheets/_button.scss @@ -49,7 +49,12 @@ button.text:active color: var(--fg-accent); text-underline-offset: 0.35em; } -button.text:hover +button[disabled].text +{ + color: var(--fg-semi); + cursor: not-allowed; +} +button:not([disabled]).text:hover { text-decoration: underline; } diff --git a/Stylesheets/_div.build-pipeline.scss b/Stylesheets/_div.build-pipeline.scss deleted file mode 100644 index d06955429..000000000 --- a/Stylesheets/_div.build-pipeline.scss +++ /dev/null @@ -1,39 +0,0 @@ -div.build-pipeline -{ - display: flex; - gap: 1rem; - - button.area - { - margin: 0; - height: 100%; - } - - > * - { - flex: 1; - height: 5rem; - } - - > div - { - @include dashed-area(var(--fg-light)); - - box-sizing: border-box; - display: flex; - - // Horizontal alignment, when not wrapped - justify-content: center; - // Horizontal alignment, when wrapped - text-align: center; - // Vertical alignment - align-items: center; - - color: var(--fg-semi); - } - > div.phase - { - border-color: var(--fg-semi); - color: var(--fg); - } -} diff --git a/Stylesheets/_div.hstack.scss b/Stylesheets/_div.hstack.scss new file mode 100644 index 000000000..e9ab5480f --- /dev/null +++ b/Stylesheets/_div.hstack.scss @@ -0,0 +1,17 @@ +@media only screen and (min-width: 56rem) +{ + div.hstackable + { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + justify-content: space-between; + align-items: center; + + > * + { + flex: 1 0; + } + } +} diff --git a/Stylesheets/_ol.builds-pending.scss b/Stylesheets/_ol.builds-pending.scss new file mode 100644 index 000000000..6252be54e --- /dev/null +++ b/Stylesheets/_ol.builds-pending.scss @@ -0,0 +1,29 @@ +ol.builds-pending +{ + list-style-type: none; + padding: 0; + + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 1rem; + + > li + { + display: contents; + + > div:first-child + { + font-style: italic; + font-weight: bold; + } + + form + { + text-align: right; + } + form > button + { + margin: 0; + } + } +} From 39d698cf6a6998e0c7d8e7d39c9891b90c51d621 Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 22:12:29 +0000 Subject: [PATCH 04/13] expose the /runs endpoint --- .../UnidocServer/Requests/Unidoc.Router.swift | 27 ++++++++++++++++--- .../Tags/Unidoc.CompleteBuildsEndpoint.swift | 3 ++- .../Tags/Unidoc.ConsumersEndpoint.swift | 3 ++- .../Tags/Unidoc.PackageCursorPage.swift | 7 +++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Sources/UnidocServer/Requests/Unidoc.Router.swift b/Sources/UnidocServer/Requests/Unidoc.Router.swift index ad90b4dce..2d45d510a 100644 --- a/Sources/UnidocServer/Requests/Unidoc.Router.swift +++ b/Sources/UnidocServer/Requests/Unidoc.Router.swift @@ -236,7 +236,7 @@ extension Unidoc.Router case .render: return self.render() case .robots_txt: return self.robots() case .rules: return self.rules() - case .runs: return nil // Unimplemented. + case .runs: return self.runs() case .sitemap_xml: return self.sitemap() case .sitemaps: return self.sitemaps() case .stats: return self.stats() @@ -1073,7 +1073,7 @@ extension Unidoc.Router private mutating func consumers() -> Unidoc.AnyOperation? { - guard let symbol:Symbol.Package = self.descend() + guard let package:Symbol.Package = self.descend() else { return nil @@ -1081,7 +1081,28 @@ extension Unidoc.Router let parameters:Unidoc.PipelineParameters = .init(self.query) return .explainable(Unidoc.ConsumersEndpoint.init(query: .init( - symbol: symbol, + symbol: package, + limit: 20, + page: parameters.page ?? 0, + as: self.authorization.account)), + parameters: parameters, + etag: self.etag) + } + + private mutating + func runs() -> Unidoc.AnyOperation? + { + guard + let package:Symbol.Package = self.descend() + else + { + return nil + } + + let parameters:Unidoc.PipelineParameters = .init(self.query) + + return .explainable(Unidoc.CompleteBuildsEndpoint.init(query: .init( + symbol: package, limit: 20, page: parameters.page ?? 0, as: self.authorization.account)), diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift index 1da06815e..c9e0b3ddb 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsEndpoint.swift @@ -65,7 +65,8 @@ extension Unidoc.CompleteBuildsEndpoint:HTTP.ServerEndpoint let completeBuildsPage:Unidoc.PackageCursorPage = .init( location: Self[content.table.package, page: content.index], package: output.package, - content: content) + content: content, + name: "Runs") return .ok(completeBuildsPage.resource(format: format)) } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift index e9403096d..b979fbe1f 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.ConsumersEndpoint.swift @@ -59,7 +59,8 @@ extension Unidoc.ConsumersEndpoint:HTTP.ServerEndpoint let consumersPage:Unidoc.PackageCursorPage = .init( location: Self[content.table.package, page: content.index], package: output.package, - content: content) + content: content, + name: "Consumers") return .ok(consumersPage.resource(format: format)) } diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift index 9a4017abd..5a274acd9 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.PackageCursorPage.swift @@ -13,18 +13,21 @@ extension Unidoc let package:PackageMetadata private let content:Paginated
+ private + let name:String - init(location:URI, package:PackageMetadata, content:Paginated
) + init(location:URI, package:PackageMetadata, content:Paginated
, name:String) { self.location = location self.package = package self.content = content + self.name = name } } } extension Unidoc.PackageCursorPage:Unidoc.RenderablePage { - var title:String { "Consumers · \(self.package.symbol)" } + var title:String { "\(self.name) · \(self.package.symbol)" } } extension Unidoc.PackageCursorPage:Unidoc.StaticPage { From 3e8ada5bd5c7321d10f131c575f7aef50eb1ca9f Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 22:45:10 +0000 Subject: [PATCH 05/13] hide sensitive build logs --- Assets/css/Main.css | 14 +++- Assets/css/Main.css.map | 2 +- Sources/UnidocClient/Unidoc.Client.swift | 5 +- .../Building/Unidoc.CompleteBuild.swift | 13 +++- .../Building/Unidoc.BuildArtifact.swift | 11 ++- .../Unidoc.BuilderUploadOperation.swift | 10 ++- .../Tags/Unidoc.CompleteBuildsTable.swift | 73 +++++++++++-------- Stylesheets/_table.scss | 28 ++++--- 8 files changed, 104 insertions(+), 52 deletions(-) diff --git a/Assets/css/Main.css b/Assets/css/Main.css index 0537bfa38..d8ee2208b 100644 --- a/Assets/css/Main.css +++ b/Assets/css/Main.css @@ -1892,6 +1892,10 @@ table td.placeholder em { table[data-type] { width: 100%; } +table[data-type] > tbody > tr > td > div.menu > ul { + right: 0; + min-width: 15rem; +} table[data-type=constituents] td { font-style: italic; @@ -1919,6 +1923,7 @@ table[data-type=consumers] td:first-child { font-style: italic; } +table[data-type=complete-builds] td.ref, table[data-type=consumers] td.ref, table[data-type=refs] td.ref { overflow: hidden; @@ -1970,14 +1975,15 @@ table[data-type=refs] > tbody > tr > td.graph > div:first-child > span:first-chi table[data-type=refs] > tbody > tr > td.graph > div:first-child > span.kb { font-weight: normal; } -table[data-type=refs] > tbody > tr > td.graph > div.menu > ul { - right: 0; - min-width: 15rem; -} table[data-type=refs] > tbody > tr.modern { font-weight: 700; } +table[data-type=complete-builds] > tbody > tr > td.status { + display: flex; + justify-content: space-between; +} + ul.cards { padding: 0; margin-bottom: 3rem; diff --git a/Assets/css/Main.css.map b/Assets/css/Main.css.map index 59a1651dd..dd6656c4a 100644 --- a/Assets/css/Main.css.map +++ b/Assets/css/Main.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../Stylesheets/Variables/_root.scss","../../Stylesheets/_a.scss","../../Stylesheets/Mixins/_ControlArea.scss","../../Stylesheets/_Typefaces.scss","../../Stylesheets/_blockquote.scss","../../Stylesheets/Mixins/_InlineImage.scss","../../Stylesheets/_button.scss","../../Stylesheets/_Calendar.scss","../../Stylesheets/_Cards.scss","../../Stylesheets/_code.scss","../../Stylesheets/_div.columns.scss","../../Stylesheets/_div.constraints.scss","../../Stylesheets/_div.hstack.scss","../../Stylesheets/_div.menu.scss","../../Stylesheets/Mixins/_BackdropBlur.scss","../../Stylesheets/_div.more.scss","../../Stylesheets/_div.sidebar.scss","../../Stylesheets/_div.tooltips.scss","../../Stylesheets/_dl.scss","../../Stylesheets/_figure.scss","../../Stylesheets/_form.scss","../../Stylesheets/_header.visual.scss","../../Stylesheets/_Headings.scss","../../Stylesheets/_kbd.scss","../../Stylesheets/_main.home.scss","../../Stylesheets/_nav.cornice.scss","../../Stylesheets/_nav.paginator.scss","../../Stylesheets/_Notices.scss","../../Stylesheets/_ol.scss","../../Stylesheets/_ol.builds-pending.scss","../../Stylesheets/_p.scss","../../Stylesheets/Mixins/_InlineCode.scss","../../Stylesheets/Mixins/_Note.scss","../../Stylesheets/_pre.scss","../../Stylesheets/_Search.scss","../../Stylesheets/_section.scss","../../Stylesheets/_section.availability.scss","../../Stylesheets/_section.declaration.scss","../../Stylesheets/_section.details.scss","../../Stylesheets/_section.events.scss","../../Stylesheets/_section.group.scss","../../Stylesheets/_section.introduction.scss","../../Stylesheets/_section.literature.scss","../../Stylesheets/_section.metadata.scss","../../Stylesheets/_span.scss","../../Stylesheets/_table.scss","../../Stylesheets/_ul.cards.scss","../../Stylesheets/_ul.users.scss","../../Stylesheets/Main.scss"],"names":[],"mappings":"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","file":"Main.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../Stylesheets/Variables/_root.scss","../../Stylesheets/_a.scss","../../Stylesheets/Mixins/_ControlArea.scss","../../Stylesheets/_Typefaces.scss","../../Stylesheets/_blockquote.scss","../../Stylesheets/Mixins/_InlineImage.scss","../../Stylesheets/_button.scss","../../Stylesheets/_Calendar.scss","../../Stylesheets/_Cards.scss","../../Stylesheets/_code.scss","../../Stylesheets/_div.columns.scss","../../Stylesheets/_div.constraints.scss","../../Stylesheets/_div.hstack.scss","../../Stylesheets/_div.menu.scss","../../Stylesheets/Mixins/_BackdropBlur.scss","../../Stylesheets/_div.more.scss","../../Stylesheets/_div.sidebar.scss","../../Stylesheets/_div.tooltips.scss","../../Stylesheets/_dl.scss","../../Stylesheets/_figure.scss","../../Stylesheets/_form.scss","../../Stylesheets/_header.visual.scss","../../Stylesheets/_Headings.scss","../../Stylesheets/_kbd.scss","../../Stylesheets/_main.home.scss","../../Stylesheets/_nav.cornice.scss","../../Stylesheets/_nav.paginator.scss","../../Stylesheets/_Notices.scss","../../Stylesheets/_ol.scss","../../Stylesheets/_ol.builds-pending.scss","../../Stylesheets/_p.scss","../../Stylesheets/Mixins/_InlineCode.scss","../../Stylesheets/Mixins/_Note.scss","../../Stylesheets/_pre.scss","../../Stylesheets/_Search.scss","../../Stylesheets/_section.scss","../../Stylesheets/_section.availability.scss","../../Stylesheets/_section.declaration.scss","../../Stylesheets/_section.details.scss","../../Stylesheets/_section.events.scss","../../Stylesheets/_section.group.scss","../../Stylesheets/_section.introduction.scss","../../Stylesheets/_section.literature.scss","../../Stylesheets/_section.metadata.scss","../../Stylesheets/_span.scss","../../Stylesheets/_table.scss","../../Stylesheets/_ul.cards.scss","../../Stylesheets/_ul.users.scss","../../Stylesheets/Main.scss"],"names":[],"mappings":"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","file":"Main.css"} \ No newline at end of file diff --git a/Sources/UnidocClient/Unidoc.Client.swift b/Sources/UnidocClient/Unidoc.Client.swift index 69d7c4551..fd45f2531 100644 --- a/Sources/UnidocClient/Unidoc.Client.swift +++ b/Sources/UnidocClient/Unidoc.Client.swift @@ -254,10 +254,13 @@ extension Unidoc.Client action: action)) } - /// Attach build logs. + // Attach build logs. try artifact.attach(log: output, as: .ssgc) try artifact.attach(log: diagnostics, as: .ssgcDiagnostics) + // If the repo URL contains a token, we should keep the logs secret. + artifact.logsAreSecret = labels.repo.contains("@") + artifact.seconds = (.now - started).components.seconds try await self.connect diff --git a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift index 281645546..90837e422 100644 --- a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift +++ b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift @@ -27,6 +27,8 @@ extension Unidoc public var logs:[BuildLogType] + public + var logsAreSecret:Bool @inlinable public init(edition:Edition, @@ -34,14 +36,16 @@ extension Unidoc finished:UnixMillisecond, failure:BuildFailure?, name:Symbol.PackageAtRef, - logs:[BuildLogType] = []) + logs:[BuildLogType] = [], + logsAreSecret:Bool = false) { self.edition = edition self.launched = launched self.finished = finished self.failure = failure - self.logs = logs self.name = name + self.logs = logs + self.logsAreSecret = logsAreSecret } } } @@ -60,6 +64,7 @@ extension Unidoc.CompleteBuild:Mongo.MasterCodingModel case failure = "E" case name = "N" case logs = "O" + case logsAreSecret = "A" case package = "p" } @@ -74,6 +79,7 @@ extension Unidoc.CompleteBuild:BSONDocumentEncodable bson[.failure] = self.failure bson[.name] = self.name bson[.logs] = self.logs.isEmpty ? nil : self.logs + bson[.logsAreSecret] = self.logsAreSecret ? true : nil bson[.package] = self.id.edition.package } @@ -89,6 +95,7 @@ extension Unidoc.CompleteBuild:BSONDocumentDecodable finished: try bson[.finished].decode(), failure: try bson[.failure]?.decode(), name: try bson[.name].decode(), - logs: try bson[.logs]?.decode() ?? []) + logs: try bson[.logs]?.decode() ?? [], + logsAreSecret: try bson[.logsAreSecret]?.decode() ?? false) } } diff --git a/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift b/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift index 292899191..fe6370a77 100644 --- a/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift +++ b/Sources/UnidocRecords/Building/Unidoc.BuildArtifact.swift @@ -14,17 +14,21 @@ extension Unidoc var seconds:Int64 public var logs:[BuildLog] + public + var logsAreSecret:Bool @inlinable public init(edition:Edition, outcome:Result, seconds:Int64 = 0, - logs:[BuildLog] = []) + logs:[BuildLog] = [], + logsAreSecret:Bool = false) { self.edition = edition self.seconds = seconds self.outcome = outcome self.logs = logs + self.logsAreSecret = logsAreSecret } } } @@ -50,6 +54,7 @@ extension Unidoc.BuildArtifact case failure = "F" case seconds = "D" case logs = "L" + case logsAreSecret = "A" } } extension Unidoc.BuildArtifact:BSONDocumentEncodable @@ -67,6 +72,7 @@ extension Unidoc.BuildArtifact:BSONDocumentEncodable bson[.seconds] = self.seconds bson[.logs] = self.logs.isEmpty ? nil : self.logs + bson[.logsAreSecret] = self.logsAreSecret } } extension Unidoc.BuildArtifact:BSONDocumentDecodable @@ -89,6 +95,7 @@ extension Unidoc.BuildArtifact:BSONDocumentDecodable edition: try bson[.edition].decode(), outcome: outcome, seconds: try bson[.seconds].decode(), - logs: try bson[.logs]?.decode() ?? []) + logs: try bson[.logs]?.decode() ?? [], + logsAreSecret: try bson[.logsAreSecret].decode()) } } diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift index 43e26ef2b..53c23ffac 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift @@ -51,12 +51,20 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation let duration:Seconds = .seconds(build.seconds) let finished:UnixMillisecond = launched.advanced(by: .init(duration)) + /// The logs are secret if the builder indicated that they are. + /// + /// Log secrecy is on a per-build basis, so that repositories can be made public + /// without accidentally exposing sensitive logs from the past. + /// + /// Log secrecy is determined when the build starts, not when it finishes, to + /// avoid leaking secrets if a repository is made public while a build is running. var complete:Unidoc.CompleteBuild = .init(edition: build.edition, launched: launched, finished: finished, failure: build.failure, name: pending.name, - logs: []) + logs: [], + logsAreSecret: build.logsAreSecret) complete.logs = try await build.export(as: complete.id, from: server) diff --git a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift index ef2297815..4290bc291 100644 --- a/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift +++ b/Sources/UnidocUI/Endpoints/Tags/Unidoc.CompleteBuildsTable.swift @@ -45,7 +45,6 @@ extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable $0[.th] = "Run time" $0[.th] = "Ref" $0[.th] = "Status" - $0[.th] = "Logs" } } @@ -66,49 +65,50 @@ extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable } $0[.td] = row.name.ref - - switch row.failure + $0[.td, { $0.class = "status"}] { - case nil: - $0[.td] = "OK" + switch row.failure + { + case nil: + $0[.div] = "OK" - case .killed?: - $0[.td] = "Killed" + case .killed?: + $0[.div] = "Killed" - case .noValidVersion?: - $0[.td] = "No Valid Version" + case .noValidVersion?: + $0[.div] = "No Valid Version" - case .failedToCloneRepository?: - $0[.td] = "Failed to Clone Repo" + case .failedToCloneRepository?: + $0[.div] = "Failed to Clone Repo" - case .failedToReadManifest?: - $0[.td] = "Failed to Read Root Manifest" + case .failedToReadManifest?: + $0[.div] = "Failed to Read Root Manifest" - case .failedToReadManifestForDependency?: - $0[.td] = "Failed to Read Dependency Manifest" + case .failedToReadManifestForDependency?: + $0[.div] = "Failed to Read Dependency Manifest" - case .failedToResolveDependencies?: - $0[.td] = "Failed to Resolve Dependencies" + case .failedToResolveDependencies?: + $0[.div] = "Failed to Resolve Dependencies" - case .failedToBuild?: - $0[.td] = "Failed to Build Package" + case .failedToBuild?: + $0[.div] = "Failed to Build Package" - case .failedToExtractSymbolGraph?: - $0[.td] = "Failed to Extract Symbol Graph" + case .failedToExtractSymbolGraph?: + $0[.div] = "Failed to Extract Symbol Graph" - case .failedToLoadSymbolGraph?: - $0[.td] = "Failed to Load Symbol Graph" + case .failedToLoadSymbolGraph?: + $0[.div] = "Failed to Load Symbol Graph" - case .failedToLinkSymbolGraph?: - $0[.td] = "Failed to Link Symbol Graph" + case .failedToLinkSymbolGraph?: + $0[.div] = "Failed to Link Symbol Graph" - case .failedForUnknownReason?: - $0[.td] = "Failed for Unknown Reason" - } + case .failedForUnknownReason?: + $0[.div] = "Failed for Unknown Reason" + } - $0[.td] - { - if row.logs.isEmpty + // You need to be logged in to view build logs. + guard case _? = self.view.global + else { return } @@ -118,6 +118,17 @@ extension Unidoc.CompleteBuildsTable:HTML.OutputStreamable $0[.button] = "•••" $0[.ul] { + if row.logs.isEmpty + { + $0[.li] = "No logs available" + } + if row.logsAreSecret, !self.view.editor + { + $0[.li] = """ + You are not authorized to view logs from this run. + """ + } + for log:Unidoc.BuildLogType in row.logs { $0[.li] diff --git a/Stylesheets/_table.scss b/Stylesheets/_table.scss index a204f277f..b66eb594e 100644 --- a/Stylesheets/_table.scss +++ b/Stylesheets/_table.scss @@ -33,6 +33,12 @@ table table[data-type] { width: 100%; + + > tbody > tr > td > div.menu > ul + { + right: 0; + min-width: 15rem; + } } table[data-type='constituents'] @@ -74,6 +80,7 @@ table[data-type='consumers'] } } +table[data-type='complete-builds'], table[data-type='consumers'], table[data-type='refs'] { @@ -146,15 +153,6 @@ table[data-type='refs'] font-weight: normal; } } - - > div.menu - { - > ul - { - right: 0; - min-width: 15rem; - } - } } } @@ -163,3 +161,15 @@ table[data-type='refs'] font-weight: 700; } } + +table[data-type='complete-builds'] +{ + > tbody > tr + { + > td.status + { + display: flex; + justify-content: space-between; + } + } +} From 2bbeb5a8639e7b0fc4acefca49ea1fd4bd8bdf5e Mon Sep 17 00:00:00 2001 From: Dianna Date: Fri, 6 Sep 2024 23:10:05 +0000 Subject: [PATCH 06/13] determing completed build identity at submission time, to facilitate an improved life cycle API --- .../Building/Unidoc.BuildIdentifier.swift | 12 +++++------ .../Building/Unidoc.BuildLogPath.swift | 2 +- .../Building/Unidoc.CompleteBuild.swift | 20 ++++++++----------- .../Building/Unidoc.DB.PendingBuilds.swift | 5 ++++- .../Building/Unidoc.PendingBuild.swift | 8 ++++++++ .../Unidoc.BuilderUploadOperation.swift | 4 +++- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift index 41fa67012..856535b77 100644 --- a/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift +++ b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift @@ -11,13 +11,13 @@ extension Unidoc public let edition:Unidoc.Edition public - let instant:UnixMillisecond + let date:UnixMillisecond @inlinable public - init(edition:Unidoc.Edition, instant:UnixMillisecond) + init(edition:Unidoc.Edition, date:UnixMillisecond) { self.edition = edition - self.instant = instant + self.date = date } } } @@ -27,7 +27,7 @@ extension Unidoc.BuildIdentifier:Mongo.MasterCodingModel enum CodingKey:String, Sendable, BSONDecodable { case edition = "e" - case instant = "T" + case date = "T" } } extension Unidoc.BuildIdentifier:BSONDocumentEncodable @@ -36,7 +36,7 @@ extension Unidoc.BuildIdentifier:BSONDocumentEncodable func encode(to bson:inout BSON.DocumentEncoder) { bson[.edition] = self.edition - bson[.instant] = self.instant + bson[.date] = self.date } } extension Unidoc.BuildIdentifier:BSONDocumentDecodable @@ -44,6 +44,6 @@ extension Unidoc.BuildIdentifier:BSONDocumentDecodable @inlinable public init(bson:BSON.DocumentDecoder) throws { - self.init(edition: try bson[.edition].decode(), instant: try bson[.instant].decode()) + self.init(edition: try bson[.edition].decode(), date: try bson[.date].decode()) } } diff --git a/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift index 792afdd50..0e2eb7730 100644 --- a/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift +++ b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift @@ -29,7 +29,7 @@ extension Unidoc.BuildLogPath // As this is public-facing, we want it to be at least somewhat human-readable. """ logs/\ - \(self.id.instant.timestamp?.date.description ?? "0000-00-00")/\ + \(self.id.date.timestamp?.date.description ?? "0000-00-00")/\ \(self.id.edition.package)/\ \(self.id.edition.version).\(self.type.name).log """ diff --git a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift index 90837e422..b8e1721dd 100644 --- a/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift +++ b/Sources/UnidocDB/Building/Unidoc.CompleteBuild.swift @@ -8,10 +8,10 @@ import UnixTime extension Unidoc { @frozen public - struct CompleteBuild:Sendable + struct CompleteBuild:Identifiable, Sendable { public - let edition:Edition + let id:BuildIdentifier public let launched:UnixMillisecond @@ -31,7 +31,7 @@ extension Unidoc var logsAreSecret:Bool @inlinable public - init(edition:Edition, + init(id:BuildIdentifier, launched:UnixMillisecond, finished:UnixMillisecond, failure:BuildFailure?, @@ -39,7 +39,7 @@ extension Unidoc logs:[BuildLogType] = [], logsAreSecret:Bool = false) { - self.edition = edition + self.id = id self.launched = launched self.finished = finished self.failure = failure @@ -49,17 +49,13 @@ extension Unidoc } } } -extension Unidoc.CompleteBuild:Identifiable -{ - @inlinable public - var id:Unidoc.BuildIdentifier { .init(edition: self.edition, instant: self.launched) } -} extension Unidoc.CompleteBuild:Mongo.MasterCodingModel { @frozen public enum CodingKey:String, Sendable, BSONDecodable { case id = "_id" + case launched = "L" case finished = "F" case failure = "E" case name = "N" @@ -75,6 +71,7 @@ extension Unidoc.CompleteBuild:BSONDocumentEncodable func encode(to bson:inout BSON.DocumentEncoder) { bson[.id] = self.id + bson[.launched] = self.launched bson[.finished] = self.finished bson[.failure] = self.failure bson[.name] = self.name @@ -89,9 +86,8 @@ extension Unidoc.CompleteBuild:BSONDocumentDecodable @inlinable public init(bson:BSON.DocumentDecoder) throws { - let id:Unidoc.BuildIdentifier = try bson[.id].decode() - self.init(edition: id.edition, - launched: id.instant, + self.init(id: try bson[.id].decode(), + launched: try bson[.launched].decode(), finished: try bson[.finished].decode(), failure: try bson[.failure]?.decode(), name: try bson[.name].decode(), diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift index 66f744c0c..ffdbea9b0 100644 --- a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift +++ b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift @@ -143,11 +143,14 @@ extension Unidoc.DB.PendingBuilds upserting: id, returning: .new) { + let now:UnixMillisecond = .now() + $0[.setOnInsert] = Unidoc.PendingBuild.init(id: id, - enqueued: UnixMillisecond.now(), + enqueued: now, launched: nil, assignee: nil, stage: nil, + date: now, name: name) } return pendingBuild diff --git a/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift index c8e7c5acb..1d6649a13 100644 --- a/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift +++ b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift @@ -23,6 +23,9 @@ extension Unidoc public var stage:BuildStage? + /// This is used to identify the build when it completes. + public + let date:UnixMillisecond /// Used for display purposes only. public let name:Symbol.PackageAtRef @@ -33,6 +36,7 @@ extension Unidoc launched:UnixMillisecond?, assignee:Account?, stage:BuildStage?, + date:UnixMillisecond, name:Symbol.PackageAtRef) { self.id = id @@ -40,6 +44,7 @@ extension Unidoc self.launched = launched self.assignee = assignee self.stage = stage + self.date = date self.name = name } } @@ -54,6 +59,7 @@ extension Unidoc.PendingBuild:Mongo.MasterCodingModel case launched = "L" case assignee = "A" case stage = "S" + case date = "T" case name = "N" case package = "p" @@ -69,6 +75,7 @@ extension Unidoc.PendingBuild:BSONDocumentEncodable bson[.launched] = self.launched bson[.assignee] = self.assignee bson[.stage] = self.stage + bson[.date] = self.date bson[.name] = self.name bson[.package] = self.id.package @@ -84,6 +91,7 @@ extension Unidoc.PendingBuild:BSONDocumentDecodable launched: try bson[.launched]?.decode(), assignee: try bson[.assignee]?.decode(), stage: try bson[.stage]?.decode(), + date: try bson[.date].decode(), name: try bson[.name].decode()) } } diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift index 53c23ffac..41893e0ee 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift @@ -58,7 +58,9 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation /// /// Log secrecy is determined when the build starts, not when it finishes, to /// avoid leaking secrets if a repository is made public while a build is running. - var complete:Unidoc.CompleteBuild = .init(edition: build.edition, + var complete:Unidoc.CompleteBuild = .init(id: .init( + edition: pending.id, + date: pending.date), launched: launched, finished: finished, failure: build.failure, From 74951e8af668cb92e8ad2f49c1c933ef62862a73 Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 02:57:14 +0000 Subject: [PATCH 07/13] internalize Unidoc.VersionPredicate --- Sources/UnidocQueries/Unidoc.DB (ext).swift | 16 +-- .../Versions/Unidoc.VersionPredicate.swift | 1 - .../Unidoc.LoadEditionStateOperation.swift | 13 +-- .../Unidoc.PackageBuildOperation.swift | 4 +- .../UnidocServer/Requests/Unidoc.Router.swift | 105 +++++++++++------- 5 files changed, 75 insertions(+), 64 deletions(-) diff --git a/Sources/UnidocQueries/Unidoc.DB (ext).swift b/Sources/UnidocQueries/Unidoc.DB (ext).swift index 468e13f81..fca68b286 100644 --- a/Sources/UnidocQueries/Unidoc.DB (ext).swift +++ b/Sources/UnidocQueries/Unidoc.DB (ext).swift @@ -25,19 +25,19 @@ extension Unidoc.DB } public - func edition(package:Symbol.Package, - version:Unidoc.VersionPredicate) async throws -> Unidoc.EditionOutput? + func edition(named symbol:Symbol.PackageAtRef) async throws -> Unidoc.EditionOutput? { - try await self.query( - with: Unidoc.EditionMetadataSymbolicQuery.init(package: package, version: version)) + try await self.query(with: Unidoc.EditionMetadataSymbolicQuery.init( + package: symbol.package, + version: .name(symbol.ref))) } public - func editionState(package:Symbol.Package, - version:Unidoc.VersionPredicate) async throws -> Unidoc.EditionState? + func editionState(named symbol:Symbol.PackageAtRef) async throws -> Unidoc.EditionState? { - try await self.query( - with: Unidoc.EditionStateSymbolicQuery.init(package: package, version: version)) + try await self.query(with: Unidoc.EditionStateSymbolicQuery.init( + package: symbol.package, + version: .name(symbol.ref))) } public diff --git a/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift b/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift index f8d4008c0..d8ca3cb56 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift @@ -3,7 +3,6 @@ import UnidocAPI extension Unidoc { - @frozen public enum VersionPredicate:Sendable { case latest(VersionSeries) diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift index 69950aabb..1c3b521b4 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift @@ -11,15 +11,12 @@ extension Unidoc private let authorization:Authorization private - let package:Symbol.Package - private - let version:VersionPredicate + let symbol:Symbol.PackageAtRef - init(authorization:Authorization, package:Symbol.Package, version:VersionPredicate) + init(authorization:Authorization, symbol:Symbol.PackageAtRef) { self.authorization = authorization - self.package = package - self.version = version + self.symbol = symbol } } } @@ -55,9 +52,7 @@ extension Unidoc.LoadEditionStateOperation:Unidoc.PublicOperation } guard - let edition:Unidoc.EditionState = try await db.editionState( - package: self.package, - version: self.version) + let edition:Unidoc.EditionState = try await db.editionState(named: self.symbol) else { return .resource("No such edition\n", status: 404) diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift index 9a796af99..b75f0d785 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift @@ -38,9 +38,7 @@ extension Unidoc.PackageBuildOperation:Unidoc.RestrictedOperation as _:Unidoc.RenderFormat) async throws -> HTTP.ServerResponse? { guard - let outputs:Unidoc.EditionOutput = try await db.edition( - package: self.symbol.package, - version: .name(self.symbol.ref)), + let outputs:Unidoc.EditionOutput = try await db.edition(named: self.symbol), let edition:Unidoc.EditionMetadata = outputs.edition else { diff --git a/Sources/UnidocServer/Requests/Unidoc.Router.swift b/Sources/UnidocServer/Requests/Unidoc.Router.swift index 2d45d510a..48062216c 100644 --- a/Sources/UnidocServer/Requests/Unidoc.Router.swift +++ b/Sources/UnidocServer/Requests/Unidoc.Router.swift @@ -137,6 +137,20 @@ extension Unidoc.Router return .init(next) } + + mutating + func descendIntoRef() -> Symbol.PackageAtRef? + { + guard + let package:Symbol.Package = self.descend(), + let name:String = self.descend() + else + { + return nil + } + + return .init(package: package, ref: name) + } } extension Unidoc.Router { @@ -232,7 +246,7 @@ extension Unidoc.Router case .really: return nil // POST only case .realm: return self.realm() case .reference: return self.docsLegacy() - case .ref: return self.ref(form: nil) + case .ref: return self.ref() case .render: return self.render() case .robots_txt: return self.robots() case .rules: return self.rules() @@ -406,6 +420,53 @@ extension Unidoc.Router } } extension Unidoc.Router +{ + private mutating + func ref() -> Unidoc.AnyOperation? + { + guard + let symbol:Symbol.PackageAtRef = self.descendIntoRef() + else + { + return nil + } + + guard + case "state" = self.descend() + else + { + return nil + } + + return .unordered(Unidoc.LoadEditionStateOperation.init( + authorization: self.authorization, + symbol: symbol)) + } + + private mutating + func ref(form _:URI.Query) -> Unidoc.AnyOperation? + { + guard + let account:Unidoc.Account = self.authorization.account, + let symbol:Symbol.PackageAtRef = self.descendIntoRef() + else + { + return nil + } + + guard + case "build" = self.descend() + else + { + return nil + } + + return .unordered(Unidoc.PackageBuildOperation.init(account: account, + symbol: symbol, + action: .submit)) + } +} +extension Unidoc.Router { private mutating func builder() -> Unidoc.AnyOperation? @@ -837,48 +898,6 @@ extension Unidoc.Router etag: self.etag) } - private mutating - func ref(form:URI.Query?) -> Unidoc.AnyOperation? - { - guard - let symbol:Symbol.Package = self.descend(), - let name:String = self.descend() - else - { - return nil - } - - guard let next:String = self.descend() - else - { - return nil - } - - switch next - { - case "build": - guard - let account:Unidoc.Account = self.authorization.account - else - { - return nil - } - - return .unordered(Unidoc.PackageBuildOperation.init(account: account, - symbol: .init(package: symbol, ref: name), - action: .submit)) - - case "state": - return .unordered(Unidoc.LoadEditionStateOperation.init( - authorization: self.authorization, - package: symbol, - version: .name(name))) - - default: - return nil - } - } - private mutating func render() -> Unidoc.AnyOperation? { From 014ff2efd5a495e5b51a76bad5a1dc2f0a4a0cd3 Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 03:10:26 +0000 Subject: [PATCH 08/13] rename some types for clarity --- Sources/UnidocQueries/Unidoc.DB (ext).swift | 11 ++++---- ...itionState.swift => Unidoc.RefState.swift} | 6 ++-- ...swift => Unidoc.RefStateDirectQuery.swift} | 20 ++++++------- ...ift => Unidoc.RefStateSymbolicQuery.swift} | 28 +++++++++---------- ...Unidoc.BuildCoordinator.Notification.swift | 4 +-- .../Unidoc.BuilderLabelOperation.swift | 4 +-- ...n.swift => Unidoc.RefBuildOperation.swift} | 12 ++++---- ...n.swift => Unidoc.RefStateOperation.swift} | 14 +++++----- .../UnidocServer/Requests/Unidoc.Router.swift | 6 ++-- Sources/UnidocServer/Unidoc.Registrar.swift | 2 +- 10 files changed, 53 insertions(+), 54 deletions(-) rename Sources/UnidocQueries/Versions/{Unidoc.EditionState.swift => Unidoc.RefState.swift} (87%) rename Sources/UnidocQueries/Versions/{Unidoc.EditionStateDirectQuery.swift => Unidoc.RefStateDirectQuery.swift} (75%) rename Sources/UnidocQueries/Versions/{Unidoc.EditionStateSymbolicQuery.swift => Unidoc.RefStateSymbolicQuery.swift} (51%) rename Sources/UnidocServer/Operations/Interactions/{Unidoc.PackageBuildOperation.swift => Unidoc.RefBuildOperation.swift} (87%) rename Sources/UnidocServer/Operations/Interactions/{Unidoc.LoadEditionStateOperation.swift => Unidoc.RefStateOperation.swift} (80%) diff --git a/Sources/UnidocQueries/Unidoc.DB (ext).swift b/Sources/UnidocQueries/Unidoc.DB (ext).swift index fca68b286..a9cc91623 100644 --- a/Sources/UnidocQueries/Unidoc.DB (ext).swift +++ b/Sources/UnidocQueries/Unidoc.DB (ext).swift @@ -33,21 +33,20 @@ extension Unidoc.DB } public - func editionState(named symbol:Symbol.PackageAtRef) async throws -> Unidoc.EditionState? + func ref(by symbol:Symbol.PackageAtRef) async throws -> Unidoc.RefState? { - try await self.query(with: Unidoc.EditionStateSymbolicQuery.init( + try await self.query(with: Unidoc.RefStateSymbolicQuery.init( package: symbol.package, version: .name(symbol.ref))) } public - func editionState( - of selector:Unidoc.BuildSelector) async throws -> Unidoc.EditionState? + func ref(of selector:Unidoc.BuildSelector) async throws -> Unidoc.RefState? { switch selector { case .id(let id): - var pipeline:Mongo.SingleOutputFromPrimary = .init( + var pipeline:Mongo.SingleOutputFromPrimary = .init( query: .init(package: id.package, version: .exact(id.version))) try await pipeline.pull(from: self) @@ -55,7 +54,7 @@ extension Unidoc.DB return pipeline.value case .latest(let series, of: let package): - var pipeline:Mongo.SingleOutputFromPrimary = .init( + var pipeline:Mongo.SingleOutputFromPrimary = .init( query: .init(package: package, version: .match(.latest(series)))) try await pipeline.pull(from: self) diff --git a/Sources/UnidocQueries/Versions/Unidoc.EditionState.swift b/Sources/UnidocQueries/Versions/Unidoc.RefState.swift similarity index 87% rename from Sources/UnidocQueries/Versions/Unidoc.EditionState.swift rename to Sources/UnidocQueries/Versions/Unidoc.RefState.swift index 2543f7795..9a080bd22 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.EditionState.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefState.swift @@ -6,7 +6,7 @@ import UnidocRecords extension Unidoc { @frozen public - struct EditionState:Sendable + struct RefState:Sendable { public let package:PackageMetadata @@ -26,7 +26,7 @@ extension Unidoc } } } -extension Unidoc.EditionState:Mongo.MasterCodingModel +extension Unidoc.RefState:Mongo.MasterCodingModel { @frozen public enum CodingKey:String, Sendable @@ -37,7 +37,7 @@ extension Unidoc.EditionState:Mongo.MasterCodingModel case owner } } -extension Unidoc.EditionState:BSONDocumentDecodable +extension Unidoc.RefState:BSONDocumentDecodable { public init(bson:BSON.DocumentDecoder) throws diff --git a/Sources/UnidocQueries/Versions/Unidoc.EditionStateDirectQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift similarity index 75% rename from Sources/UnidocQueries/Versions/Unidoc.EditionStateDirectQuery.swift rename to Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift index 2212913eb..62bd4d896 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.EditionStateDirectQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift @@ -4,7 +4,7 @@ import UnidocDB extension Unidoc { - struct EditionStateDirectQuery + struct RefStateDirectQuery { let package:Package let version:VersionSelector @@ -16,11 +16,11 @@ extension Unidoc } } } -extension Unidoc.EditionStateDirectQuery:Mongo.PipelineQuery +extension Unidoc.RefStateDirectQuery:Mongo.PipelineQuery { typealias CollectionOrigin = Unidoc.DB.Packages typealias Collation = SimpleCollation - typealias Iteration = Mongo.Single + typealias Iteration = Mongo.Single var hint:Mongo.CollectionIndex? { nil } @@ -30,7 +30,7 @@ extension Unidoc.EditionStateDirectQuery:Mongo.PipelineQuery { $0[Unidoc.PackageMetadata[.id]] = self.package } - pipeline[stage: .replaceWith] = .init(Unidoc.EditionState.CodingKey.self) + pipeline[stage: .replaceWith] = .init(Unidoc.RefState.CodingKey.self) { $0[.package] = Mongo.Pipeline.ROOT } @@ -39,8 +39,8 @@ extension Unidoc.EditionStateDirectQuery:Mongo.PipelineQuery { case .match(let predicate): pipeline.loadTags(matching: predicate, - from: Unidoc.EditionState[.package], - into: Unidoc.EditionState[.version]) + from: Unidoc.RefState[.package], + into: Unidoc.RefState[.version]) case .exact(let id): let id:Unidoc.Edition = .init(package: self.package, version: id) @@ -66,15 +66,15 @@ extension Unidoc.EditionStateDirectQuery:Mongo.PipelineQuery graph: Unidoc.VersionState[.graph]) } - $0[.as] = Unidoc.EditionState[.version] + $0[.as] = Unidoc.RefState[.version] } } // Unbox single-element array. - pipeline[stage: .unwind] = Unidoc.EditionState[.version] + pipeline[stage: .unwind] = Unidoc.RefState[.version] pipeline.loadUser( - owning: Unidoc.EditionState[.package], - as: Unidoc.EditionState[.owner]) + owning: Unidoc.RefState[.package], + as: Unidoc.RefState[.owner]) } } diff --git a/Sources/UnidocQueries/Versions/Unidoc.EditionStateSymbolicQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift similarity index 51% rename from Sources/UnidocQueries/Versions/Unidoc.EditionStateSymbolicQuery.swift rename to Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift index d8fb2b7ab..e185fd2ed 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.EditionStateSymbolicQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift @@ -5,7 +5,7 @@ import UnidocDB extension Unidoc { - struct EditionStateSymbolicQuery + struct RefStateSymbolicQuery { let package:Symbol.Package let version:VersionPredicate @@ -17,11 +17,11 @@ extension Unidoc } } } -extension Unidoc.EditionStateSymbolicQuery:Mongo.PipelineQuery +extension Unidoc.RefStateSymbolicQuery:Mongo.PipelineQuery { - typealias Iteration = Mongo.Single + typealias Iteration = Mongo.Single } -extension Unidoc.EditionStateSymbolicQuery:Unidoc.AliasingQuery +extension Unidoc.RefStateSymbolicQuery:Unidoc.AliasingQuery { typealias CollectionOrigin = Unidoc.DB.PackageAliases typealias CollectionTarget = Unidoc.DB.Packages @@ -29,32 +29,32 @@ extension Unidoc.EditionStateSymbolicQuery:Unidoc.AliasingQuery var symbol:Symbol.Package { self.package } static - var target:Mongo.AnyKeyPath { Unidoc.EditionState[.package] } + var target:Mongo.AnyKeyPath { Unidoc.RefState[.package] } func extend(pipeline:inout Mongo.PipelineEncoder) { pipeline.loadTags(matching: self.version, - from: Unidoc.EditionState[.package], - into: Unidoc.EditionState[.version]) + from: Unidoc.RefState[.package], + into: Unidoc.RefState[.version]) // Unbox single-element array. - pipeline[stage: .unwind] = Unidoc.EditionState[.version] + pipeline[stage: .unwind] = Unidoc.RefState[.version] pipeline.loadUser( - owning: Unidoc.EditionState[.package], - as: Unidoc.EditionState[.owner]) + owning: Unidoc.RefState[.package], + as: Unidoc.RefState[.owner]) pipeline[stage: .lookup] { $0[.from] = Unidoc.DB.PackageBuilds.name - $0[.localField] = Unidoc.EditionState[.package] / Unidoc.PackageMetadata[.id] + $0[.localField] = Unidoc.RefState[.package] / Unidoc.PackageMetadata[.id] $0[.foreignField] = Unidoc.BuildMetadata[.id] - $0[.as] = Unidoc.EditionState[.build] + $0[.as] = Unidoc.RefState[.build] } - pipeline[stage: .set, using: Unidoc.EditionState.CodingKey.self] + pipeline[stage: .set, using: Unidoc.RefState.CodingKey.self] { - $0[.build] { $0[.first] = Unidoc.EditionState[.build] } + $0[.build] { $0[.first] = Unidoc.RefState[.build] } } } } diff --git a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift index 3176387f7..a79b29c52 100644 --- a/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift +++ b/Sources/UnidocServer/Building/Unidoc.BuildCoordinator.Notification.swift @@ -114,8 +114,8 @@ extension Unidoc.BuildCoordinator.Notification return nil } - if let edition:Unidoc.EditionState = try await db.editionState(of: .id(self.request)), - let labels:Unidoc.BuildLabels = try await registrar.resolve(edition, rebuild: true) + if let ref:Unidoc.RefState = try await db.ref(of: .id(self.request)), + let labels:Unidoc.BuildLabels = try await registrar.resolve(ref, rebuild: true) { return labels } diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderLabelOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderLabelOperation.swift index 7dc1c0b94..1ddcb8f15 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderLabelOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.BuilderLabelOperation.swift @@ -22,8 +22,8 @@ extension Unidoc.BuilderLabelOperation:Unidoc.MachineOperation as _:Unidoc.RenderFormat) async throws -> HTTP.ServerResponse? { guard - let edition:Unidoc.EditionState = try await db.editionState(of: self.request.version), - let labels:Unidoc.BuildLabels = try await server.github?.resolve(edition, + let ref:Unidoc.RefState = try await db.ref(of: self.request.version), + let labels:Unidoc.BuildLabels = try await server.github?.resolve(ref, rebuild: self.request.rebuild) else { diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefBuildOperation.swift similarity index 87% rename from Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift rename to Sources/UnidocServer/Operations/Interactions/Unidoc.RefBuildOperation.swift index b75f0d785..3b9e54ec2 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.PackageBuildOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefBuildOperation.swift @@ -6,15 +6,15 @@ import UnidocUI extension Unidoc { - struct PackageBuildOperation:MeteredOperation + struct RefBuildOperation:MeteredOperation { let account:Account let symbol:Symbol.PackageAtRef - let action:Unidoc.BuildForm.Action + let action:BuildForm.Action - var rights:Unidoc.UserRights + var rights:UserRights - init(account:Account, symbol:Symbol.PackageAtRef, action:Unidoc.BuildForm.Action) + init(account:Account, symbol:Symbol.PackageAtRef, action:BuildForm.Action) { self.account = account self.symbol = symbol @@ -24,14 +24,14 @@ extension Unidoc } } } -extension Unidoc.PackageBuildOperation +extension Unidoc.RefBuildOperation { init(account:Unidoc.Account, form:Unidoc.BuildForm) { self.init(account: account, symbol: form.symbol, action: form.action) } } -extension Unidoc.PackageBuildOperation:Unidoc.RestrictedOperation +extension Unidoc.RefBuildOperation:Unidoc.RestrictedOperation { func load(from server:Unidoc.Server, db:Unidoc.DB, diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift similarity index 80% rename from Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift rename to Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift index 1c3b521b4..ff56790b7 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.LoadEditionStateOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift @@ -6,7 +6,7 @@ import URI extension Unidoc { - struct LoadEditionStateOperation:Sendable + struct RefStateOperation:Sendable { private let authorization:Authorization @@ -20,7 +20,7 @@ extension Unidoc } } } -extension Unidoc.LoadEditionStateOperation:Unidoc.PublicOperation +extension Unidoc.RefStateOperation:Unidoc.PublicOperation { func load(from server:Unidoc.Server, as _:Unidoc.RenderFormat) async throws -> HTTP.ServerResponse? @@ -52,19 +52,19 @@ extension Unidoc.LoadEditionStateOperation:Unidoc.PublicOperation } guard - let edition:Unidoc.EditionState = try await db.editionState(named: self.symbol) + let ref:Unidoc.RefState = try await db.ref(by: self.symbol) else { return .resource("No such edition\n", status: 404) } - let report:Unidoc.EditionStateReport = .init(id: edition.version.edition.id, - volume: edition.version.volume?.symbol, - build: edition.build.map + let report:Unidoc.EditionStateReport = .init(id: ref.version.edition.id, + volume: ref.version.volume?.symbol, + build: ref.build.map { .init(request: $0.request, stage: $0.progress?.stage, failure: $0.failure) }, - graph: edition.version.graph.map + graph: ref.version.graph.map { .init(action: $0.action, commit: $0.commit) }) diff --git a/Sources/UnidocServer/Requests/Unidoc.Router.swift b/Sources/UnidocServer/Requests/Unidoc.Router.swift index 48062216c..c9719841d 100644 --- a/Sources/UnidocServer/Requests/Unidoc.Router.swift +++ b/Sources/UnidocServer/Requests/Unidoc.Router.swift @@ -438,7 +438,7 @@ extension Unidoc.Router return nil } - return .unordered(Unidoc.LoadEditionStateOperation.init( + return .unordered(Unidoc.RefStateOperation.init( authorization: self.authorization, symbol: symbol)) } @@ -461,7 +461,7 @@ extension Unidoc.Router return nil } - return .unordered(Unidoc.PackageBuildOperation.init(account: account, + return .unordered(Unidoc.RefBuildOperation.init(account: account, symbol: symbol, action: .submit)) } @@ -517,7 +517,7 @@ extension Unidoc.Router if let account:Unidoc.Account = self.authorization.account, let build:Unidoc.BuildForm = .init(from: form) { - return .unordered(Unidoc.PackageBuildOperation.init( + return .unordered(Unidoc.RefBuildOperation.init( account: account, form: build)) } diff --git a/Sources/UnidocServer/Unidoc.Registrar.swift b/Sources/UnidocServer/Unidoc.Registrar.swift index fd05a79fb..e40bf7d99 100644 --- a/Sources/UnidocServer/Unidoc.Registrar.swift +++ b/Sources/UnidocServer/Unidoc.Registrar.swift @@ -11,6 +11,6 @@ extension Unidoc func connect(with access:Unidoc.RegistrarAccessMechanisms, _ body:(Session) async throws -> T) async throws -> T - func resolve(_ edition:EditionState, rebuild:Bool) async throws -> BuildLabels? + func resolve(_ edition:RefState, rebuild:Bool) async throws -> BuildLabels? } } From c99bdb3825a5df9884e929e64df22ef3d5430ddf Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 03:49:31 +0000 Subject: [PATCH 09/13] update the external API --- .../Building/Unidoc.BuildStatus.swift | 52 ++++--------------- .../Building/Unidoc.EditionStateReport.swift | 17 +----- .../Building/Unidoc.BuildIdentifier.swift | 12 ++--- .../Building/Unidoc.BuildLogPath.swift | 2 +- .../Building/Unidoc.DB.PendingBuilds.swift | 2 +- .../Building/Unidoc.PendingBuild.swift | 17 +++--- .../Versions/Unidoc.RefState.swift | 13 ++++- .../Versions/Unidoc.RefStateDirectQuery.swift | 1 + .../Unidoc.RefStateSymbolicQuery.swift | 15 ++++-- .../Versions/Unidoc.VersionPredicate.swift | 1 - .../Unidoc.RefStateOperation.swift | 24 +++++++-- .../Unidoc.BuilderUploadOperation.swift | 2 +- 12 files changed, 75 insertions(+), 83 deletions(-) diff --git a/Sources/UnidocAPI/Building/Unidoc.BuildStatus.swift b/Sources/UnidocAPI/Building/Unidoc.BuildStatus.swift index 14a9e2fcd..52ec10b27 100644 --- a/Sources/UnidocAPI/Building/Unidoc.BuildStatus.swift +++ b/Sources/UnidocAPI/Building/Unidoc.BuildStatus.swift @@ -6,19 +6,19 @@ extension Unidoc struct BuildStatus { public - let request:Unidoc.BuildRequest? + let request:Edition public - let stage:Unidoc.BuildStage? + let pending:BuildStage? public - let failure:Unidoc.BuildFailure? + let failure:BuildFailure? @inlinable public - init(request:Unidoc.BuildRequest?, - stage:Unidoc.BuildStage?, - failure:Unidoc.BuildFailure?) + init(request:Edition, + pending:Unidoc.BuildStage?, + failure:BuildFailure?) { self.request = request - self.stage = stage + self.pending = pending self.failure = failure } } @@ -29,8 +29,6 @@ extension Unidoc.BuildStatus enum CodingKey:String, Sendable { case version - case series - case force case stage case failure } @@ -40,21 +38,8 @@ extension Unidoc.BuildStatus:JSONObjectEncodable public func encode(to json:inout JSON.ObjectEncoder) { - if let request:Unidoc.BuildRequest = self.request - { - switch request.version - { - case .latest(let series, of: ()): - json[.series] = series - - case .id(let id): - json[.version] = id - } - - json[.force] = request.rebuild - } - - json[.stage] = self.stage + json[.version] = request + json[.stage] = self.pending json[.failure] = self.failure } } @@ -63,23 +48,8 @@ extension Unidoc.BuildStatus:JSONObjectDecodable public init(json:JSON.ObjectDecoder) throws { - let request:Unidoc.BuildRequest? - if let series:Unidoc.VersionSeries = try json[.series]?.decode() - { - request = .init(version: .latest(series), rebuild: try json[.force].decode()) - } - else if - let id:Unidoc.Edition = try json[.version]?.decode() - { - request = .init(version: .id(id), rebuild: try json[.force].decode()) - } - else - { - request = nil - } - - self.init(request: request, - stage: try json[.stage]?.decode(), + self.init(request: try json[.version].decode(), + pending: try json[.stage]?.decode(), failure: try json[.failure]?.decode()) } } diff --git a/Sources/UnidocAPI/Building/Unidoc.EditionStateReport.swift b/Sources/UnidocAPI/Building/Unidoc.EditionStateReport.swift index 921b656e8..9ce7389ce 100644 --- a/Sources/UnidocAPI/Building/Unidoc.EditionStateReport.swift +++ b/Sources/UnidocAPI/Building/Unidoc.EditionStateReport.swift @@ -63,7 +63,7 @@ extension Unidoc.EditionStateReport } } else if - let stage:Unidoc.BuildStage = build.stage + let stage:Unidoc.BuildStage = build.pending { switch stage { @@ -75,20 +75,7 @@ extension Unidoc.EditionStateReport } else { - switch build.request?.version - { - case .latest?: - return .QUEUED_FLOATING_VERSION - - case .id(self.id)?: - return .QUEUED - - case .id?: - return .QUEUED_DIFFERENT_VERSION - - case .none: - return .DEFAULT - } + return self.id == build.request ? .QUEUED : .QUEUED_DIFFERENT_VERSION } } } diff --git a/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift index 856535b77..372302e9a 100644 --- a/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift +++ b/Sources/UnidocDB/Building/Unidoc.BuildIdentifier.swift @@ -11,13 +11,13 @@ extension Unidoc public let edition:Unidoc.Edition public - let date:UnixMillisecond + let run:UnixMillisecond @inlinable public - init(edition:Unidoc.Edition, date:UnixMillisecond) + init(edition:Unidoc.Edition, run:UnixMillisecond) { self.edition = edition - self.date = date + self.run = run } } } @@ -27,7 +27,7 @@ extension Unidoc.BuildIdentifier:Mongo.MasterCodingModel enum CodingKey:String, Sendable, BSONDecodable { case edition = "e" - case date = "T" + case run = "T" } } extension Unidoc.BuildIdentifier:BSONDocumentEncodable @@ -36,7 +36,7 @@ extension Unidoc.BuildIdentifier:BSONDocumentEncodable func encode(to bson:inout BSON.DocumentEncoder) { bson[.edition] = self.edition - bson[.date] = self.date + bson[.run] = self.run } } extension Unidoc.BuildIdentifier:BSONDocumentDecodable @@ -44,6 +44,6 @@ extension Unidoc.BuildIdentifier:BSONDocumentDecodable @inlinable public init(bson:BSON.DocumentDecoder) throws { - self.init(edition: try bson[.edition].decode(), date: try bson[.date].decode()) + self.init(edition: try bson[.edition].decode(), run: try bson[.run].decode()) } } diff --git a/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift index 0e2eb7730..0452116eb 100644 --- a/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift +++ b/Sources/UnidocDB/Building/Unidoc.BuildLogPath.swift @@ -29,7 +29,7 @@ extension Unidoc.BuildLogPath // As this is public-facing, we want it to be at least somewhat human-readable. """ logs/\ - \(self.id.date.timestamp?.date.description ?? "0000-00-00")/\ + \(self.id.run.timestamp?.date.description ?? "0000-00-00")/\ \(self.id.edition.package)/\ \(self.id.edition.version).\(self.type.name).log """ diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift index ffdbea9b0..81143b2e8 100644 --- a/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift +++ b/Sources/UnidocDB/Building/Unidoc.DB.PendingBuilds.swift @@ -146,11 +146,11 @@ extension Unidoc.DB.PendingBuilds let now:UnixMillisecond = .now() $0[.setOnInsert] = Unidoc.PendingBuild.init(id: id, + run: now, enqueued: now, launched: nil, assignee: nil, stage: nil, - date: now, name: name) } return pendingBuild diff --git a/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift index 1d6649a13..71e890d65 100644 --- a/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift +++ b/Sources/UnidocDB/Building/Unidoc.PendingBuild.swift @@ -12,6 +12,9 @@ extension Unidoc { public let id:Edition + /// This is used to identify the build when it completes. + public + let run:UnixMillisecond public let enqueued:UnixMillisecond? @@ -23,28 +26,26 @@ extension Unidoc public var stage:BuildStage? - /// This is used to identify the build when it completes. - public - let date:UnixMillisecond /// Used for display purposes only. public let name:Symbol.PackageAtRef + @inlinable public init(id:Edition, + run:UnixMillisecond, enqueued:UnixMillisecond?, launched:UnixMillisecond?, assignee:Account?, stage:BuildStage?, - date:UnixMillisecond, name:Symbol.PackageAtRef) { self.id = id + self.run = run self.enqueued = enqueued self.launched = launched self.assignee = assignee self.stage = stage - self.date = date self.name = name } } @@ -55,11 +56,11 @@ extension Unidoc.PendingBuild:Mongo.MasterCodingModel enum CodingKey:String, Sendable, BSONDecodable { case id = "_id" + case run = "T" case enqueued = "Q" case launched = "L" case assignee = "A" case stage = "S" - case date = "T" case name = "N" case package = "p" @@ -71,11 +72,11 @@ extension Unidoc.PendingBuild:BSONDocumentEncodable func encode(to bson:inout BSON.DocumentEncoder) { bson[.id] = self.id + bson[.run] = self.run bson[.enqueued] = self.enqueued bson[.launched] = self.launched bson[.assignee] = self.assignee bson[.stage] = self.stage - bson[.date] = self.date bson[.name] = self.name bson[.package] = self.id.package @@ -87,11 +88,11 @@ extension Unidoc.PendingBuild:BSONDocumentDecodable init(bson:BSON.DocumentDecoder) throws { self.init(id: try bson[.id].decode(), + run: try bson[.run].decode(), enqueued: try bson[.enqueued]?.decode(), launched: try bson[.launched]?.decode(), assignee: try bson[.assignee]?.decode(), stage: try bson[.stage]?.decode(), - date: try bson[.date].decode(), name: try bson[.name].decode()) } } diff --git a/Sources/UnidocQueries/Versions/Unidoc.RefState.swift b/Sources/UnidocQueries/Versions/Unidoc.RefState.swift index 9a080bd22..beed396b4 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.RefState.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefState.swift @@ -13,15 +13,22 @@ extension Unidoc public let version:VersionState public - let build:BuildMetadata? + let build:PendingBuild? + public + let built:CompleteBuild? public let owner:User? - init(package:PackageMetadata, version:VersionState, build:BuildMetadata?, owner:User?) + init(package:PackageMetadata, + version:VersionState, + build:PendingBuild?, + built:CompleteBuild?, + owner:User?) { self.package = package self.version = version self.build = build + self.built = built self.owner = owner } } @@ -34,6 +41,7 @@ extension Unidoc.RefState:Mongo.MasterCodingModel case package case version case build + case built case owner } } @@ -46,6 +54,7 @@ extension Unidoc.RefState:BSONDocumentDecodable package: try bson[.package].decode(), version: try bson[.version].decode(), build: try bson[.build]?.decode(), + built: try bson[.built]?.decode(), owner: try bson[.owner]?.decode()) } } diff --git a/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift index 62bd4d896..b9828c6d8 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefStateDirectQuery.swift @@ -4,6 +4,7 @@ import UnidocDB extension Unidoc { + /// Note that this query does not return information about builds. struct RefStateDirectQuery { let package:Package diff --git a/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift b/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift index e185fd2ed..680b3a386 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.RefStateSymbolicQuery.swift @@ -44,17 +44,26 @@ extension Unidoc.RefStateSymbolicQuery:Unidoc.AliasingQuery owning: Unidoc.RefState[.package], as: Unidoc.RefState[.owner]) + Unidoc.CompleteBuildsPageSegment.bridge(pipeline: &pipeline, + limit: 1, + from: Self.target, + into: Unidoc.RefState[.built]) + pipeline[stage: .lookup] { - $0[.from] = Unidoc.DB.PackageBuilds.name - $0[.localField] = Unidoc.RefState[.package] / Unidoc.PackageMetadata[.id] - $0[.foreignField] = Unidoc.BuildMetadata[.id] + $0[.from] = Unidoc.DB.PendingBuilds.name + $0[.localField] = Unidoc.RefState[.version] + / Unidoc.VersionState[.edition] + / Unidoc.EditionMetadata[.id] + + $0[.foreignField] = Unidoc.PendingBuild[.id] $0[.as] = Unidoc.RefState[.build] } pipeline[stage: .set, using: Unidoc.RefState.CodingKey.self] { $0[.build] { $0[.first] = Unidoc.RefState[.build] } + $0[.built] { $0[.first] = Unidoc.RefState[.built] } } } } diff --git a/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift b/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift index d8ca3cb56..218425546 100644 --- a/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift +++ b/Sources/UnidocQueries/Versions/Unidoc.VersionPredicate.swift @@ -1,5 +1,4 @@ import MongoQL -import UnidocAPI extension Unidoc { diff --git a/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift index ff56790b7..3b5534868 100644 --- a/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift +++ b/Sources/UnidocServer/Operations/Interactions/Unidoc.RefStateOperation.swift @@ -58,12 +58,28 @@ extension Unidoc.RefStateOperation:Unidoc.PublicOperation return .resource("No such edition\n", status: 404) } + let status:Unidoc.BuildStatus? + + if let pending:Unidoc.PendingBuild = ref.build + { + status = .init(request: pending.id, pending: pending.stage, failure: nil) + } + else if + let complete:Unidoc.CompleteBuild = ref.built + { + status = .init( + request: complete.id.edition, + pending: nil, + failure: complete.failure) + } + else + { + status = nil + } + let report:Unidoc.EditionStateReport = .init(id: ref.version.edition.id, volume: ref.version.volume?.symbol, - build: ref.build.map - { - .init(request: $0.request, stage: $0.progress?.stage, failure: $0.failure) - }, + build: status, graph: ref.version.graph.map { .init(action: $0.action, commit: $0.commit) diff --git a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift index 41893e0ee..8ede7476d 100644 --- a/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift +++ b/Sources/UnidocServer/Operations/Procedures/Unidoc.BuilderUploadOperation.swift @@ -60,7 +60,7 @@ extension Unidoc.BuilderUploadOperation:Unidoc.BlockingOperation /// avoid leaking secrets if a repository is made public while a build is running. var complete:Unidoc.CompleteBuild = .init(id: .init( edition: pending.id, - date: pending.date), + run: pending.run), launched: launched, finished: finished, failure: build.failure, From c67c5421633fa73c4af43f26e7b4d65f45d8776f Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 03:54:27 +0000 Subject: [PATCH 10/13] detach dead types --- .../Building/Unidoc.BuildBehavior.swift | 46 --- .../Building/Unidoc.BuildMetadata.swift | 96 ----- .../Building/Unidoc.BuildMetadataDelta.swift | 39 -- .../Building/Unidoc.BuildProgress.swift | 64 ---- .../Building/Unidoc.BuildRequest (ext).swift | 14 - .../Building/Unidoc.DB.PackageBuilds.swift | 353 ------------------ 6 files changed, 612 deletions(-) delete mode 100644 Sources/UnidocDB/Building/Unidoc.BuildBehavior.swift delete mode 100644 Sources/UnidocDB/Building/Unidoc.BuildMetadata.swift delete mode 100644 Sources/UnidocDB/Building/Unidoc.BuildMetadataDelta.swift delete mode 100644 Sources/UnidocDB/Building/Unidoc.BuildProgress.swift delete mode 100644 Sources/UnidocDB/Building/Unidoc.BuildRequest (ext).swift delete mode 100644 Sources/UnidocDB/Building/Unidoc.DB.PackageBuilds.swift diff --git a/Sources/UnidocDB/Building/Unidoc.BuildBehavior.swift b/Sources/UnidocDB/Building/Unidoc.BuildBehavior.swift deleted file mode 100644 index 771d3a15f..000000000 --- a/Sources/UnidocDB/Building/Unidoc.BuildBehavior.swift +++ /dev/null @@ -1,46 +0,0 @@ -import BSON -import UnidocAPI - -extension Unidoc -{ - @frozen public - enum BuildBehavior:Equatable, Sendable - { - case latest(VersionSeries, force:Bool) - case id(force:Bool) - } -} -extension Unidoc.BuildBehavior:RawRepresentable -{ - @inlinable public - init?(rawValue:Int32) - { - switch rawValue - { - case 0: self = .latest(.release, force: false) - case 1: self = .latest(.release, force: true) - case 2: self = .latest(.prerelease, force: false) - case 3: self = .latest(.prerelease, force: true) - case 256: self = .id(force: false) - case 257: self = .id(force: true) - default: return nil - } - } - - @inlinable public - var rawValue:Int32 - { - switch self - { - case .latest(.release, false): 0 - case .latest(.release, true): 1 - case .latest(.prerelease, false): 2 - case .latest(.prerelease, true): 3 - case .id(false): 256 - case .id(true): 257 - } - } -} -extension Unidoc.BuildBehavior:BSONDecodable, BSONEncodable -{ -} diff --git a/Sources/UnidocDB/Building/Unidoc.BuildMetadata.swift b/Sources/UnidocDB/Building/Unidoc.BuildMetadata.swift deleted file mode 100644 index c680d5887..000000000 --- a/Sources/UnidocDB/Building/Unidoc.BuildMetadata.swift +++ /dev/null @@ -1,96 +0,0 @@ -import BSON -import MongoQL -import UnidocAPI -import UnidocRecords - -extension Unidoc -{ - @frozen public - struct BuildMetadata:Identifiable, Sendable - { - public - let id:Package - - public - var progress:BuildProgress? - public - var request:BuildRequest? - public - var failure:BuildFailure? - public - var logs:[BuildLogType] - - @inlinable public - init(id:Unidoc.Package, - progress:Unidoc.BuildProgress? = nil, - request:Unidoc.BuildRequest? = nil, - failure:Unidoc.BuildFailure? = nil, - logs:[BuildLogType] = []) - { - self.id = id - self.progress = progress - self.request = request - self.failure = failure - self.logs = logs - } - } -} -extension Unidoc.BuildMetadata:Mongo.MasterCodingModel -{ - @frozen public - enum CodingKey:String, Sendable, BSONDecodable - { - case id = "_id" - case progress = "P" - case behavior = "Q" - case edition = "e" - case failure = "F" - case logs = "L" - - @available(*, deprecated, renamed: "behavior") - @inlinable public static - var request:Self { .behavior } - } -} -extension Unidoc.BuildMetadata:BSONDocumentEncodable -{ - public - func encode(to bson:inout BSON.DocumentEncoder) - { - bson[.id] = self.id - bson[.progress] = self.progress - bson[.behavior] = self.request?.behavior - bson[.edition] = self.request?.version.exact - bson[.failure] = self.failure - bson[.logs] = self.logs.isEmpty ? nil : self.logs - } -} -extension Unidoc.BuildMetadata:BSONDocumentDecodable -{ - @inlinable public - init(bson:BSON.DocumentDecoder) throws - { - let request:Unidoc.BuildRequest? - if let behavior:Unidoc.BuildBehavior = try bson[.behavior]?.decode() - { - switch behavior - { - case .latest(let series, force: let force): - request = .init(version: .latest(series), rebuild: force) - - case .id(force: let force): - request = .init(version: .id(try bson[.edition].decode()), rebuild: force) - } - } - else - { - request = nil - } - - self.init(id: try bson[.id].decode(), - progress: try bson[.progress]?.decode(), - request: request, - failure: try bson[.failure]?.decode(), - logs: try bson[.logs]?.decode() ?? []) - } -} diff --git a/Sources/UnidocDB/Building/Unidoc.BuildMetadataDelta.swift b/Sources/UnidocDB/Building/Unidoc.BuildMetadataDelta.swift deleted file mode 100644 index 279c610f1..000000000 --- a/Sources/UnidocDB/Building/Unidoc.BuildMetadataDelta.swift +++ /dev/null @@ -1,39 +0,0 @@ -import BSON -import MongoQL - -extension Unidoc -{ - struct BuildMetadataDelta:Sendable - { - var progress:BuildProgress? - var behavior:BuildBehavior? - var edition:Edition? - var failure:BuildFailure? - - init(progress:BuildProgress? = nil, - behavior:BuildBehavior? = nil, - edition:Edition? = nil, - failure:BuildFailure? = nil) - { - self.progress = progress - self.behavior = behavior - self.edition = edition - self.failure = failure - } - } -} -extension Unidoc.BuildMetadataDelta:Mongo.MasterCodingDelta -{ - typealias Model = Unidoc.BuildMetadata -} -extension Unidoc.BuildMetadataDelta:BSONDocumentDecodable -{ - init(bson:BSON.DocumentDecoder) throws - { - self.init( - progress: try bson[.progress]?.decode(), - behavior: try bson[.behavior]?.decode(), - edition: try bson[.edition]?.decode(), - failure: try bson[.failure]?.decode()) - } -} diff --git a/Sources/UnidocDB/Building/Unidoc.BuildProgress.swift b/Sources/UnidocDB/Building/Unidoc.BuildProgress.swift deleted file mode 100644 index cf8637260..000000000 --- a/Sources/UnidocDB/Building/Unidoc.BuildProgress.swift +++ /dev/null @@ -1,64 +0,0 @@ -import BSON -import MongoQL -import UnidocAPI -import UnidocRecords -import UnixTime - -extension Unidoc -{ - @frozen public - struct BuildProgress:Equatable, Sendable - { - public - var started:UnixMillisecond - public - var builder:Account - public - var request:BuildBehavior - public - var stage:BuildStage - - @inlinable public - init(started:UnixMillisecond, builder:Account, request:BuildBehavior, stage:BuildStage) - { - self.started = started - self.builder = builder - self.request = request - self.stage = stage - } - } -} -extension Unidoc.BuildProgress:Mongo.MasterCodingModel -{ - @frozen public - enum CodingKey:String, Sendable - { - case started = "S" - case builder = "b" - case request = "R" - case stage = "P" - } -} -extension Unidoc.BuildProgress:BSONDocumentEncodable -{ - public - func encode(to bson:inout BSON.DocumentEncoder) - { - bson[.started] = self.started - bson[.builder] = self.builder - bson[.request] = self.request - bson[.stage] = self.stage - } -} -extension Unidoc.BuildProgress:BSONDocumentDecodable -{ - public - init(bson:BSON.DocumentDecoder) throws - { - self.init( - started: try bson[.started].decode(), - builder: try bson[.builder].decode(), - request: try bson[.request].decode(), - stage: try bson[.stage].decode()) - } -} diff --git a/Sources/UnidocDB/Building/Unidoc.BuildRequest (ext).swift b/Sources/UnidocDB/Building/Unidoc.BuildRequest (ext).swift deleted file mode 100644 index 5fd818188..000000000 --- a/Sources/UnidocDB/Building/Unidoc.BuildRequest (ext).swift +++ /dev/null @@ -1,14 +0,0 @@ -import UnidocAPI - -extension Unidoc.BuildRequest -{ - @inlinable public - var behavior:Unidoc.BuildBehavior - { - switch self.version - { - case .latest(let series, of: _): .latest(series, force: self.rebuild) - case .id: .id(force: self.rebuild) - } - } -} diff --git a/Sources/UnidocDB/Building/Unidoc.DB.PackageBuilds.swift b/Sources/UnidocDB/Building/Unidoc.DB.PackageBuilds.swift deleted file mode 100644 index d10b84580..000000000 --- a/Sources/UnidocDB/Building/Unidoc.DB.PackageBuilds.swift +++ /dev/null @@ -1,353 +0,0 @@ -import BSON -import MongoDB -import UnidocAPI -import UnidocRecords -import UnixTime - -extension Unidoc.DB -{ - @frozen public - struct PackageBuilds - { - public - let database:Mongo.Database - public - let session:Mongo.Session - - @inlinable - init(database:Mongo.Database, session:Mongo.Session) - { - self.database = database - self.session = session - } - } -} -extension Unidoc.DB.PackageBuilds -{ - public static - let indexQueue:Mongo.CollectionIndex = .init("Queued", unique: false) - { - $0[Unidoc.BuildMetadata[.behavior]] = (+) - } - where: - { - $0[Unidoc.BuildMetadata[.behavior]] { $0[.exists] = true } - } - - public static - let indexStarted:Mongo.CollectionIndex = .init("Started", unique: false) - { - $0[Unidoc.BuildMetadata[.progress] / Unidoc.BuildProgress[.started]] = (+) - } - where: - { - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = true } - } -} -extension Unidoc.DB.PackageBuilds:Mongo.CollectionModel -{ - public - typealias Element = Unidoc.BuildMetadata - - @inlinable public static - var name:Mongo.Collection { "Builds" } - - @inlinable public static - var indexes:[Mongo.CollectionIndex] - { - [ - Self.indexQueue, - Self.indexStarted, - ] - } -} -extension Unidoc.DB.PackageBuilds -{ - public - func selectBuild(await awaits:Bool) async throws -> Unidoc.BuildMetadata? - { - // Find a build, any build... - if let build:Unidoc.BuildMetadata = try await session.run( - command: Mongo.Find>.init(Self.name, limit: 1) - { - $0[.hint] = Self.indexQueue.id - $0[.filter] - { - $0[Unidoc.BuildMetadata[.behavior]] { $0[.exists] = true } - } - }, - against: self.database) - { - return build - } - - guard awaits - else - { - return nil - } - - let startTime:BSON.Timestamp? = session.preconditionTime - - // Open a change stream and wait for a build to be enqueued... - return try await session.run( - command: Mongo.Aggregate>>.init(Self.name, - tailing: .init(timeout: .milliseconds(30_000), awaits: true)) - { - $0[stage: .changeStream] - { - // This prevents us from missing any builds enqueued between when we ran - // the find query and when we open the change stream. - $0[.startAtOperationTime] = startTime - } - // TODO: filter change events - }, - against: self.database) - { - for try await events:[Mongo.ChangeEvent] in $0 - { - for event:Mongo.ChangeEvent in events - { - switch event.change - { - case .replace(_, before: _, after: let build): return build - case .insert(let build): return build - default: continue - } - } - } - - return nil - } - } - - public - func submitBuild( - request:Unidoc.BuildRequest, - package:Unidoc.Package) async throws -> Bool - { - do - { - let (_, _):(Unidoc.BuildMetadata?, Unidoc.Package?) = try await session.run( - command: Mongo.FindAndModify>.init(Self.name, - returning: .new) - { - $0[.query] - { - $0[Unidoc.BuildMetadata[.id]] = package - $0[Unidoc.BuildMetadata[.behavior]] { $0[.exists] = false } - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = false } - } - $0[.update] = Unidoc.BuildMetadata.init(id: package, request: request) - }, - against: self.database) - - return true - } - catch let error as Mongo.ServerError - { - // Duplicate key error - guard case 11000 = error.code - else - { - throw error - } - - return false - } - } - - public - func cancelBuild( - package:Unidoc.Package) async throws -> Bool - { - let deleted:Mongo.DeleteResponse = try await session.run( - command: Mongo.Delete.init(Self.name) - { - $0 - { - $0[.q] - { - $0[Unidoc.BuildMetadata[.id]] = package - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = false } - } - $0[.limit] = .one - } - }, - against: self.database) - - let deletions:Mongo.Deletions = try deleted.deletions() - return deletions.deleted != 0 - } - - public - func assignBuild( - request:Unidoc.BuildBehavior, - package:Unidoc.Package, - builder:Unidoc.Account) async throws -> Bool - { - let (update, _):(Unidoc.BuildMetadata?, Never?) = try await session.run( - command: Mongo.FindAndModify>.init(Self.name, - returning: .new) - { - $0[.query] - { - $0[Unidoc.BuildMetadata[.id]] = package - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = false } - } - $0[.update] - { - $0[.unset] - { - $0[Unidoc.BuildMetadata[.behavior]] = () - } - $0[.set] - { - $0[Unidoc.BuildMetadata[.progress]] = Unidoc.BuildProgress.init( - started: .now(), - builder: builder, - request: request, - stage: .initializing) - } - } - }, - against: self.database) - - return update != nil - } - - @discardableResult - public - func updateBuild( - package:Unidoc.Package, - entered:Unidoc.BuildStage) async throws -> Unidoc.BuildMetadata? - { - let (status, _):(Unidoc.BuildMetadata?, Never?) = try await session.run( - command: Mongo.FindAndModify>.init(Self.name, - returning: .old) - { - $0[.query] - { - // We must only write to build metadata that already contain `progress`, - // otherwise we may generate undecodable structures! - $0[Unidoc.BuildMetadata[.id]] = package - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = true } - } - $0[.update] - { - $0[.set] - { - $0[ Unidoc.BuildMetadata[.progress] / - Unidoc.BuildProgress[.stage]] = entered - } - } - }, - against: self.database) - - return status - } - - @discardableResult - public - func finishBuild( - package:Unidoc.Package, - failure:Unidoc.BuildFailure? = nil, - logs:[Unidoc.BuildLogType] = []) async throws -> Unidoc.BuildMetadata? - { - let (status, _):(Unidoc.BuildMetadata?, Never?) = try await session.run( - command: Mongo.FindAndModify>.init(Self.name, - returning: .old) - { - $0[.query] - { - $0[Unidoc.BuildMetadata[.id]] = package - } - $0[.update] - { - if let failure:Unidoc.BuildFailure - { - $0[.set] - { - $0[Unidoc.BuildMetadata[.failure]] = failure - $0[Unidoc.BuildMetadata[.logs]] = logs - } - $0[.unset] - { - $0[Unidoc.BuildMetadata[.progress]] = () - } - } - else - { - $0[.set] - { - $0[Unidoc.BuildMetadata[.logs]] = logs - } - $0[.unset] - { - $0[Unidoc.BuildMetadata[.progress]] = () - $0[Unidoc.BuildMetadata[.failure]] = () - } - } - } - }, - against: self.database) - - return status - } - - public - func lintBuilds(startedBefore:UnixMillisecond) async throws -> Int - { - try await self.killBuilds - { - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = true } - $0[Unidoc.BuildMetadata[.progress] / Unidoc.BuildProgress[.started]] - { - $0[.lt] = startedBefore - } - } - } - - public - func killBuilds(builder:Unidoc.Account) async throws -> Int - { - try await self.killBuilds - { - $0[Unidoc.BuildMetadata[.progress]] { $0[.exists] = true } - $0[Unidoc.BuildMetadata[.progress] / Unidoc.BuildProgress[.builder]] = builder - } - } - - private - func killBuilds(where predicate:(inout Mongo.PredicateEncoder) -> ()) async throws -> Int - { - let failure:Unidoc.BuildFailure = .killed - let response:Mongo.UpdateResponse = try await session.run( - command: Mongo.Update.init(Self.name) - { - $0 - { - $0[.multi] = true - $0[.q, predicate] - $0[.u] - { - $0[.unset] - { - $0[Unidoc.BuildMetadata[.progress]] = () - } - $0[.set] - { - $0[Unidoc.BuildMetadata[.failure]] = failure - } - } - } - }, - against: self.database) - - let updates:Mongo.Updates = try response.updates() - return updates.selected - } -} From 9739251aa77b1024dbbbabfbf2347a43d71967f8 Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 03:58:43 +0000 Subject: [PATCH 11/13] visionOS --- Sources/Availability/Domains/Availability.PlatformDomain.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Availability/Domains/Availability.PlatformDomain.swift b/Sources/Availability/Domains/Availability.PlatformDomain.swift index 7b516feba..f50286b2a 100644 --- a/Sources/Availability/Domains/Availability.PlatformDomain.swift +++ b/Sources/Availability/Domains/Availability.PlatformDomain.swift @@ -10,6 +10,7 @@ extension Availability case macCatalyst case openBSD = "OpenBSD" case tvOS + case visionOS case watchOS case windows = "Windows" @@ -41,6 +42,7 @@ extension Availability.PlatformDomain:CustomStringConvertible case .macCatalyst: "Mac Catalyst" case .openBSD: "OpenBSD" case .tvOS: "tvOS" + case .visionOS: "visionOS" case .watchOS: "watchOS" case .windows: "Windows" case .iOSApplicationExtension: "iOS App Extension" From 6c714187daa6dce8b47d3ae5dd3e142903af867f Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 04:13:41 +0000 Subject: [PATCH 12/13] add missing CodingKey defs --- Sources/SymbolGraphs/Declarations/Availability.CodingKey.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SymbolGraphs/Declarations/Availability.CodingKey.swift b/Sources/SymbolGraphs/Declarations/Availability.CodingKey.swift index dbce956f7..fde128459 100644 --- a/Sources/SymbolGraphs/Declarations/Availability.CodingKey.swift +++ b/Sources/SymbolGraphs/Declarations/Availability.CodingKey.swift @@ -31,6 +31,7 @@ extension Availability.CodingKey:RawRepresentable case "m": self.init(.platform(.macOS)) case "c": self.init(.platform(.macCatalyst)) case "t": self.init(.platform(.tvOS)) + case "v": self.init(.platform(.visionOS)) case "w": self.init(.platform(.watchOS)) case "n": self.init(.platform(.windows)) case "o": self.init(.platform(.openBSD)) @@ -54,6 +55,7 @@ extension Availability.CodingKey:RawRepresentable case .platform(.macOS): "m" case .platform(.macCatalyst): "c" case .platform(.tvOS): "t" + case .platform(.visionOS): "v" case .platform(.watchOS): "w" case .platform(.windows): "n" case .platform(.openBSD): "o" From edfecafa7cf3ca76036ee517f2b47deea2e4985b Mon Sep 17 00:00:00 2001 From: Dianna Date: Sat, 7 Sep 2024 19:15:30 +0000 Subject: [PATCH 13/13] increment versions --- Sources/SymbolGraphs/SymbolGraphABI.swift | 2 +- Sources/UnidocRender/Formats/Unidoc.RenderFormat.Assets.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SymbolGraphs/SymbolGraphABI.swift b/Sources/SymbolGraphs/SymbolGraphABI.swift index 95652144c..2797a91dd 100644 --- a/Sources/SymbolGraphs/SymbolGraphABI.swift +++ b/Sources/SymbolGraphs/SymbolGraphABI.swift @@ -3,5 +3,5 @@ import SemanticVersions @frozen public enum SymbolGraphABI { - @inlinable public static var version:PatchVersion { .v(0, 11, 0) } + @inlinable public static var version:PatchVersion { .v(0, 11, 1) } } diff --git a/Sources/UnidocRender/Formats/Unidoc.RenderFormat.Assets.swift b/Sources/UnidocRender/Formats/Unidoc.RenderFormat.Assets.swift index 62aa7a378..601f97141 100644 --- a/Sources/UnidocRender/Formats/Unidoc.RenderFormat.Assets.swift +++ b/Sources/UnidocRender/Formats/Unidoc.RenderFormat.Assets.swift @@ -19,7 +19,7 @@ extension Unidoc.RenderFormat.Assets /// To reduce cache churn, not all assets are versioned. For example, the fonts and /// the favicon do not use the version numbers. @inlinable public static - var version:MajorVersion { .v(33) } + var version:MajorVersion { .v(34) } } extension Unidoc.RenderFormat.Assets {