diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift index 0b76352f9..0c33da016 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction+SynthesizedLandingPage.swift @@ -36,7 +36,10 @@ extension MergeAction { func readRootNodeRenderReferencesIn(dataDirectory: URL) throws -> RootRenderReferences { func inner(url: URL) throws -> [RootRenderReferences.Information] { - try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) + // Path might not exist (e.g. tutorials for a reference-only archive) + guard let contents = try? fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) + else { return [] } + return try contents .compactMap { guard $0.pathExtension == "json" else { return nil diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift index cc94bc1b8..d68fa32af 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift @@ -65,6 +65,8 @@ struct MergeAction: AsyncAction { let fromDirectory = archive.appendingPathComponent(directoryToCopy, isDirectory: true) let toDirectory = targetURL.appendingPathComponent(directoryToCopy, isDirectory: true) + guard fileManager.directoryExists(atPath: fromDirectory.path) else { continue } + // Ensure that the destination directory exist in case the first archive didn't have that kind of pages. // This is necessary when merging a reference-only archive with a tutorial-only archive. try? fileManager.createDirectory(at: toDirectory, withIntermediateDirectories: false, attributes: nil) diff --git a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift index e03f03330..95b907c6b 100644 --- a/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/MergeActionTests.swift @@ -659,7 +659,50 @@ class MergeActionTests: XCTestCase { "doc://org.swift.test/documentation/second.json", ]) } - + + func testSingleReferenceOnlyArchiveMerging() async throws { + let fileSystem = try TestFileSystem( + folders: [ + Folder(name: "Output.doccarchive", content: []), + Self.makeArchive( + name: "First", + documentationPages: [ + "First", + "First/SomeClass", + "First/SomeClass/someProperty", + "First/SomeClass/someFunction(:_)", + ], + tutorialPages: [] + ), + ] + ) + + let logStorage = LogHandle.LogStorage() + let action = MergeAction( + archives: [ + URL(fileURLWithPath: "/First.doccarchive"), + ], + landingPageInfo: testLandingPageInfo, + outputURL: URL(fileURLWithPath: "/Output.doccarchive"), + fileManager: fileSystem + ) + + _ = try await action.perform(logHandle: .memory(logStorage)) + XCTAssertEqual(logStorage.text, "", "The action didn't log anything") + + let synthesizedRootNode = try fileSystem.renderNode(atPath: "/Output.doccarchive/data/documentation.json") + XCTAssertEqual(synthesizedRootNode.metadata.title, "Test Landing Page Name") + XCTAssertEqual(synthesizedRootNode.metadata.roleHeading, "Test Landing Page Kind") + XCTAssertEqual(synthesizedRootNode.topicSectionsStyle, .detailedGrid) + XCTAssertEqual(synthesizedRootNode.topicSections.flatMap { [$0.title].compactMap({ $0 }) + $0.identifiers }, [ + // No title + "doc://org.swift.test/documentation/first.json", + ]) + XCTAssertEqual(synthesizedRootNode.references.keys.sorted(), [ + "doc://org.swift.test/documentation/first.json", + ]) + } + func testErrorWhenArchivesContainOverlappingData() async throws { let fileSystem = try TestFileSystem( folders: [ @@ -953,14 +996,13 @@ class MergeActionTests: XCTestCase { Output.doccarchive/ ├─ data/ │ ├─ documentation.json - │ ├─ documentation/ - │ │ ├─ first.json - │ │ ├─ first/ - │ │ │ ╰─ article.json - │ │ ├─ second.json - │ │ ╰─ second/ - │ │ ╰─ article.json - │ ╰─ tutorials/ + │ ╰─ documentation/ + │ ├─ first.json + │ ├─ first/ + │ │ ╰─ article.json + │ ├─ second.json + │ ╰─ second/ + │ ╰─ article.json ├─ downloads/ │ ├─ First/ │ ╰─ Second/