Oops! Something went wrong
12 |13 |
14 | We couldn't find what you were looking for. Try heading back to the home page. 15 |
16 |├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── deploy-api-docs.yml │ └── build-and-deploy-docs-workflow.yml ├── .gitignore ├── api-docs.png ├── README.md ├── error.html ├── images ├── collection.svg ├── curly-brackets.svg ├── article.svg └── technology.svg ├── index.html ├── LICENSE ├── theme-settings.json ├── styles.css ├── stack.yaml ├── generate-api-docs.swift └── generate-package-api-docs.swift /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @0xTim @gwynne 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | code 3 | /packages 4 | /swift-doc 5 | /public 6 | -------------------------------------------------------------------------------- /api-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vapor/api-docs/HEAD/api-docs.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | groups: 8 | dependencies: 9 | patterns: 10 | - "*" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | View the documentation at [api.vapor.codes](http://api.vapor.codes). 4 | 5 | This repo contains the script that generates all vapor api-docs when a new release is cut. 6 | For every repo, on release a github action calls into a script that pulls in the latest version of this repo to the server, then running the provided script. 7 | -------------------------------------------------------------------------------- /error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |14 | We couldn't find what you were looking for. Try heading back to the home page. 15 |
16 |Explore the complete API documentation for Vapor and its ecosystem
14 |\(description)
90 | 91 | """ 92 | }.joined(separator: "\n") 93 | 94 | do { 95 | let publicDirUrl = URL(fileURLWithPath: "./public", isDirectory: true) 96 | try FileManager.default.removeItemIfExists(at: publicDirUrl) 97 | try FileManager.default.createDirectory(at: publicDirUrl, withIntermediateDirectories: true) 98 | 99 | var htmlIndex = try String(contentsOf: URL(fileURLWithPath: "./index.html", isDirectory: false), encoding: .utf8) 100 | htmlIndex.replace("{{PackageCards}}", with: packageCards, maxReplacements: 1) 101 | 102 | try htmlIndex.write(to: publicDirUrl.appendingPathComponent("index.html", isDirectory: false), atomically: true, encoding: .utf8) 103 | try FileManager.default.copyItem(at: URL(fileURLWithPath: "./api-docs.png", isDirectory: false), into: publicDirUrl) 104 | try FileManager.default.copyItem(at: URL(fileURLWithPath: "./error.html", isDirectory: false), into: publicDirUrl) 105 | try FileManager.default.copyItem(at: URL(fileURLWithPath: "./styles.css", isDirectory: false), into: publicDirUrl) 106 | } catch let error as NSError { 107 | print("❌ ERROR: \(String(reflecting: error)): \(error.userInfo)") 108 | exit(1) 109 | } 110 | 111 | extension NSError { 112 | func isCocoaError(_ code: CocoaError.Code) -> Bool { 113 | self.domain == CocoaError.errorDomain && self.code == code.rawValue 114 | } 115 | } 116 | 117 | extension FileManager { 118 | func removeItemIfExists(at url: URL) throws { 119 | do { 120 | try self.removeItem(at: url) 121 | } catch let error as NSError where error.isCocoaError(.fileNoSuchFile) { 122 | // ignore 123 | } 124 | } 125 | 126 | func copyItem(at src: URL, into dst: URL) throws { 127 | assert(dst.hasDirectoryPath) 128 | 129 | let dstItem = dst.appendingPathComponent(src.lastPathComponent, isDirectory: dst.hasDirectoryPath) 130 | try self.copyItem(at: src, to: dstItem) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /generate-package-api-docs.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | guard CommandLine.argc == 3 else { 4 | print("❌ ERROR: You must provide the package name and modules as a comma separated string.") 5 | exit(1) 6 | } 7 | 8 | let packageName = CommandLine.arguments[1] 9 | let moduleList = CommandLine.arguments[2] 10 | 11 | let modules = moduleList.components(separatedBy: ",") 12 | 13 | let publicDirectoryUrl = URL.currentDirectory().appending(component: "public") 14 | 15 | do { 16 | try run() 17 | } catch { 18 | print("❌ ERROR: \(error).") 19 | exit(1) 20 | } 21 | 22 | // MARK: Functions 23 | 24 | func run() throws { 25 | // Set up 26 | print("⚙️ Ensuring public directory...") 27 | try FileManager.default.removeItemIfExists(at: publicDirectoryUrl) 28 | try FileManager.default.createDirectory(at: publicDirectoryUrl, withIntermediateDirectories: true) 29 | 30 | print("⚙️ Ensuring plugin...") 31 | try ensurePluginAvailable() 32 | 33 | // Run 34 | for module in modules { 35 | print("⚙️ Generating api-docs for package: \(packageName), module: \(module)") 36 | try generateDocs(module: module) 37 | } 38 | 39 | print("✅ Finished generating api-docs for package: \(packageName)") 40 | } 41 | 42 | func ensurePluginAvailable() throws { 43 | var foundAtLeastOne = false 44 | for manifestName in ["6.2", "6.1", "6.0", "5.10", "5.9", "5.8", "5.7"].map({ "Package@swift-\($0).swift" }) + ["Package.swift"] { 45 | print("⚙️ Checking for manifest \(manifestName)") 46 | let manifestUrl = URL.currentDirectory().appending(component: manifestName) 47 | var manifestContents: String 48 | do { manifestContents = try String(contentsOf: manifestUrl, encoding: .utf8) } 49 | catch let error as NSError where error.isCocoaError(.fileReadNoSuchFile) { continue } 50 | catch let error as NSError where error.isPOSIXError(.ENOENT) { continue } 51 | 52 | if !manifestContents.contains(".package(url: \"https://github.com/apple/swift-docc-plugin") { 53 | // This is freely admitted to be quick and dirty. Unfortunately, swift package add-dependency doesn't understand version-tagged manifests. 54 | print("🧬 Injecting DocC plugin dependency into \(manifestName)") 55 | guard let depsArrayRange = manifestContents.firstRange(of: "dependencies: [") else { 56 | print("❌ ERROR: Can't inject swift-docc-plugin dependency (can't find deps array).") 57 | exit(1) 58 | } 59 | manifestContents.insert( 60 | contentsOf: "\n.package(url: \"https://github.com/apple/swift-docc-plugin.git\", from: \"1.4.0\"),\n", 61 | at: depsArrayRange.upperBound 62 | ) 63 | try manifestContents.write(to: manifestUrl, atomically: true, encoding: .utf8) 64 | } 65 | foundAtLeastOne = true 66 | } 67 | guard foundAtLeastOne else { 68 | print("❌ ERROR: Can't inject swift-docc-plugin dependency (no usable manifest found).") 69 | exit(1) 70 | } 71 | } 72 | 73 | func generateDocs(module: String) throws { 74 | print("🔎 Finding DocC catalog") 75 | let doccCatalogs = try FileManager.default.contentsOfDirectory( 76 | at: URL.currentDirectory().appending(components: "Sources", "\(module)"), 77 | includingPropertiesForKeys: nil, 78 | options: [.skipsSubdirectoryDescendants] 79 | ).filter { $0.hasDirectoryPath && $0.pathExtension == "docc" } 80 | guard !doccCatalogs.isEmpty else { 81 | print("❌ ERROR: No DocC catalog found for \(module)") 82 | exit(1) 83 | } 84 | guard doccCatalogs.count == 1 else { 85 | print("❌ ERROR: More than one DocC catalog found for \(module):\n\(doccCatalogs.map(\.lastPathComponent))") 86 | exit(1) 87 | } 88 | let doccCatalogUrl = doccCatalogs[0] 89 | print("🗂️ Using DocC catalog \(doccCatalogUrl.lastPathComponent)") 90 | 91 | print("📐 Copying theme") 92 | try FileManager.default.copyItemIfExistsWithoutOverwrite( 93 | at: URL.currentDirectory().appending(component: "theme-settings.json"), 94 | to: doccCatalogUrl.appending(component: "theme-settings.json") 95 | ) 96 | 97 | print("📝 Generating docs") 98 | try shell([ 99 | "swift", "package", 100 | "--allow-writing-to-directory", publicDirectoryUrl.path, 101 | "generate-documentation", 102 | "--target", module, 103 | "--disable-indexing", 104 | "--experimental-skip-synthesized-symbols", 105 | "--enable-inherited-docs", 106 | "--enable-experimental-overloaded-symbol-presentation", 107 | "--enable-experimental-mentioned-in", 108 | "--hosting-base-path", "/\(module.lowercased())", 109 | "--output-path", publicDirectoryUrl.appending(component: "\(module.lowercased())").path, 110 | ]) 111 | } 112 | 113 | func shell(_ args: String...) throws { try shell(args) } 114 | func shell(_ args: [String]) throws { 115 | // For fun, echo the command: 116 | var sawXOpt = false, seenOpt = false, lastWasOpt = false 117 | print("+ /usr/bin/env \\\n ", terminator: "") 118 | for arg in (args.dropLast() + [args.last! + "\n"]) { 119 | if (seenOpt && !lastWasOpt) || ((!seenOpt || (lastWasOpt && !sawXOpt)) && arg.starts(with: "-")) { 120 | print(" \\\n ", terminator: "") 121 | } 122 | print(" \(arg)", terminator: "") 123 | lastWasOpt = arg.starts(with: "-") 124 | (seenOpt, sawXOpt) = (seenOpt || lastWasOpt, arg.starts(with: "-X")) 125 | } 126 | 127 | // Run the command: 128 | let task = try Process.run(URL(filePath: "/usr/bin/env"), arguments: args) 129 | task.waitUntilExit() 130 | guard task.terminationStatus == 0 else { 131 | throw ShellError(terminationStatus: task.terminationStatus) 132 | } 133 | } 134 | 135 | struct ShellError: Error { 136 | var terminationStatus: Int32 137 | } 138 | 139 | extension FileManager { 140 | func removeItemIfExists(at url: URL) throws { 141 | do { 142 | try self.removeItem(at: url) 143 | } catch let error as NSError where error.isCocoaError(.fileNoSuchFile) { 144 | // ignore 145 | } 146 | } 147 | 148 | func copyItemIfExistsWithoutOverwrite(at src: URL, to dst: URL) throws { 149 | do { 150 | // https://github.com/apple/swift-corelibs-foundation/pull/4808 151 | #if !canImport(Darwin) 152 | do { 153 | _ = try dst.checkResourceIsReachable() 154 | throw NSError(domain: CocoaError.errorDomain, code: CocoaError.fileWriteFileExists.rawValue) 155 | } catch let error as NSError where error.isCocoaError(.fileReadNoSuchFile) {} 156 | #endif 157 | try self.copyItem(at: src, to: dst) 158 | } catch let error as NSError where error.isCocoaError(.fileReadNoSuchFile) { 159 | // ignore 160 | } catch let error as NSError where error.isCocoaError(.fileWriteFileExists) { 161 | // ignore 162 | } 163 | } 164 | } 165 | 166 | extension NSError { 167 | func isCocoaError(_ code: CocoaError.Code) -> Bool { 168 | self.domain == CocoaError.errorDomain && self.code == code.rawValue 169 | } 170 | func isPOSIXError(_ code: POSIXError.Code) -> Bool { 171 | self.domain == POSIXError.errorDomain && self.code == code.rawValue 172 | } 173 | } 174 | --------------------------------------------------------------------------------