@nshipster/sosumi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +19 -0
- package/README.md +304 -0
- package/bin/sosumi.mjs +76 -0
- package/package.json +53 -0
- package/public/_headers +2 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +7 -0
- package/public/icons/square.and.pencil.svg +15 -0
- package/public/index.html +898 -0
- package/public/llms.txt +184 -0
- package/public/sosumi.m4a +0 -0
- package/src/cli.ts +214 -0
- package/src/index.ts +507 -0
- package/src/lib/cli-endpoints.ts +106 -0
- package/src/lib/external/fetch.ts +133 -0
- package/src/lib/external/index.ts +8 -0
- package/src/lib/external/policy.ts +308 -0
- package/src/lib/external/types.ts +10 -0
- package/src/lib/fetch.ts +43 -0
- package/src/lib/hig/fetch.ts +186 -0
- package/src/lib/hig/index.ts +9 -0
- package/src/lib/hig/render.ts +514 -0
- package/src/lib/hig/types.ts +206 -0
- package/src/lib/hig/util.ts +30 -0
- package/src/lib/mcp.ts +315 -0
- package/src/lib/reference/fetch.ts +53 -0
- package/src/lib/reference/index.ts +8 -0
- package/src/lib/reference/render.ts +739 -0
- package/src/lib/reference/types.ts +31 -0
- package/src/lib/search.ts +221 -0
- package/src/lib/types.ts +334 -0
- package/src/lib/url.ts +55 -0
- package/src/lib/video/index.ts +179 -0
- package/wrangler.jsonc +27 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple Developer Reference documentation specific types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Most types used by reference docs are already in the shared types.ts
|
|
6
|
+
// This file is for reference-specific types only, if any are needed in the future
|
|
7
|
+
|
|
8
|
+
// Re-export commonly used types from shared types for convenience
|
|
9
|
+
export type {
|
|
10
|
+
AppleDocJSON,
|
|
11
|
+
ContentItem,
|
|
12
|
+
Declaration,
|
|
13
|
+
DocumentationIdentifier,
|
|
14
|
+
DocumentationMetadata,
|
|
15
|
+
ImageVariant,
|
|
16
|
+
IndexContentItem,
|
|
17
|
+
isImageVariant,
|
|
18
|
+
isLanguageVariant,
|
|
19
|
+
isSymbolVariant,
|
|
20
|
+
LanguageVariant,
|
|
21
|
+
Parameter,
|
|
22
|
+
Platform,
|
|
23
|
+
PrimaryContentSection,
|
|
24
|
+
PropertyItem,
|
|
25
|
+
SeeAlsoSection,
|
|
26
|
+
SwiftInterfaceItem,
|
|
27
|
+
SymbolVariant,
|
|
28
|
+
TextFragment,
|
|
29
|
+
TopicSection,
|
|
30
|
+
Variant,
|
|
31
|
+
} from "../types"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { getRandomUserAgent } from "./fetch"
|
|
2
|
+
|
|
3
|
+
export interface SearchResult {
|
|
4
|
+
title: string
|
|
5
|
+
url: string
|
|
6
|
+
description: string
|
|
7
|
+
breadcrumbs: string[]
|
|
8
|
+
tags: string[]
|
|
9
|
+
type: string // 'documentation' | 'general' etc.
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SearchResponse {
|
|
13
|
+
query: string
|
|
14
|
+
results: SearchResult[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class SearchResultParser {
|
|
18
|
+
private results: SearchResult[] = []
|
|
19
|
+
private currentResult: Partial<SearchResult> = {}
|
|
20
|
+
private currentBreadcrumbs: string[] = []
|
|
21
|
+
private currentTags: string[] = []
|
|
22
|
+
private isInResultTitle = false
|
|
23
|
+
private isInResultDescription = false
|
|
24
|
+
private isInBreadcrumb = false
|
|
25
|
+
private isInTag = false
|
|
26
|
+
|
|
27
|
+
getResults(): SearchResult[] {
|
|
28
|
+
return this.results
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private resetCurrentResult() {
|
|
32
|
+
this.currentResult = {}
|
|
33
|
+
this.currentBreadcrumbs = []
|
|
34
|
+
this.currentTags = []
|
|
35
|
+
this.isInResultTitle = false
|
|
36
|
+
this.isInResultDescription = false
|
|
37
|
+
this.isInBreadcrumb = false
|
|
38
|
+
this.isInTag = false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private finalizeCurrentResult() {
|
|
42
|
+
if (this.currentResult.title && this.currentResult.url) {
|
|
43
|
+
this.results.push({
|
|
44
|
+
title: this.currentResult.title,
|
|
45
|
+
url: this.currentResult.url,
|
|
46
|
+
description: this.currentResult.description || "",
|
|
47
|
+
breadcrumbs: [...this.currentBreadcrumbs],
|
|
48
|
+
tags: [...this.currentTags],
|
|
49
|
+
type: this.currentResult.type || "unknown",
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
this.resetCurrentResult()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
element(element: Element) {
|
|
56
|
+
// Start of a search result
|
|
57
|
+
if (element.tagName === "li" && element.getAttribute("class")?.includes("search-result")) {
|
|
58
|
+
this.finalizeCurrentResult() // Finalize previous result if any
|
|
59
|
+
|
|
60
|
+
// Extract result type from class
|
|
61
|
+
const className = element.getAttribute("class") || ""
|
|
62
|
+
if (className.includes("documentation")) {
|
|
63
|
+
this.currentResult.type = "documentation"
|
|
64
|
+
} else if (className.includes("general")) {
|
|
65
|
+
this.currentResult.type = "general"
|
|
66
|
+
} else {
|
|
67
|
+
this.currentResult.type = "other"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Result title link
|
|
72
|
+
if (
|
|
73
|
+
element.tagName === "a" &&
|
|
74
|
+
element.getAttribute("class")?.includes("click-analytics-result")
|
|
75
|
+
) {
|
|
76
|
+
const href = element.getAttribute("href")
|
|
77
|
+
if (href) {
|
|
78
|
+
this.currentResult.url = href.startsWith("/") ? `https://developer.apple.com${href}` : href
|
|
79
|
+
}
|
|
80
|
+
this.isInResultTitle = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Result description
|
|
84
|
+
if (element.tagName === "p" && element.getAttribute("class")?.includes("result-description")) {
|
|
85
|
+
this.isInResultDescription = true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Breadcrumb items
|
|
89
|
+
if (
|
|
90
|
+
element.tagName === "li" &&
|
|
91
|
+
element.getAttribute("class")?.includes("breadcrumb-list-item")
|
|
92
|
+
) {
|
|
93
|
+
this.isInBreadcrumb = true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Tag spans
|
|
97
|
+
if (
|
|
98
|
+
element.tagName === "span" &&
|
|
99
|
+
element.parentElement?.getAttribute("class")?.includes("result-tag")
|
|
100
|
+
) {
|
|
101
|
+
this.isInTag = true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Tag list items (for languages like "Swift", "Objective-C")
|
|
105
|
+
if (
|
|
106
|
+
element.tagName === "li" &&
|
|
107
|
+
element.getAttribute("class")?.includes("result-tag language")
|
|
108
|
+
) {
|
|
109
|
+
this.isInTag = true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
text(text: Text) {
|
|
114
|
+
const content = text.text.trim()
|
|
115
|
+
if (!content) return
|
|
116
|
+
|
|
117
|
+
if (this.isInResultTitle && this.currentResult.url) {
|
|
118
|
+
this.currentResult.title = content
|
|
119
|
+
this.isInResultTitle = false
|
|
120
|
+
} else if (this.isInResultDescription) {
|
|
121
|
+
this.currentResult.description = content
|
|
122
|
+
this.isInResultDescription = false
|
|
123
|
+
} else if (this.isInBreadcrumb) {
|
|
124
|
+
this.currentBreadcrumbs.push(content)
|
|
125
|
+
this.isInBreadcrumb = false
|
|
126
|
+
} else if (this.isInTag) {
|
|
127
|
+
this.currentTags.push(content)
|
|
128
|
+
this.isInTag = false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
end() {
|
|
133
|
+
this.finalizeCurrentResult() // Finalize the last result
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function searchAppleDeveloperDocs(query: string): Promise<SearchResponse> {
|
|
138
|
+
const searchUrl = `https://developer.apple.com/search/?q=${encodeURIComponent(query)}`
|
|
139
|
+
const response = await fetch(searchUrl, {
|
|
140
|
+
headers: {
|
|
141
|
+
"User-Agent": getRandomUserAgent(),
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`Search request failed: ${response.status}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const html = await response.text()
|
|
150
|
+
let results: SearchResult[] = []
|
|
151
|
+
if (typeof HTMLRewriter !== "undefined") {
|
|
152
|
+
const parser = new SearchResultParser()
|
|
153
|
+
const rewriter = new HTMLRewriter()
|
|
154
|
+
.on("li.search-result", parser)
|
|
155
|
+
.on("li.search-result a.click-analytics-result", parser)
|
|
156
|
+
.on("li.search-result p.result-description", parser)
|
|
157
|
+
.on("li.search-result li.breadcrumb-list-item", parser)
|
|
158
|
+
.on("li.search-result li.result-tag", parser)
|
|
159
|
+
.on("li.search-result li.result-tag span", parser)
|
|
160
|
+
|
|
161
|
+
// We need to consume the transformed response to trigger parsing callbacks.
|
|
162
|
+
await rewriter.transform(new Response(html)).text()
|
|
163
|
+
parser.end()
|
|
164
|
+
results = parser.getResults()
|
|
165
|
+
} else {
|
|
166
|
+
results = await parseSearchResultsWithCheerio(html)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
query,
|
|
171
|
+
results,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function parseSearchResultsWithCheerio(html: string): Promise<SearchResult[]> {
|
|
176
|
+
const { load } = await import("cheerio")
|
|
177
|
+
const $ = load(html)
|
|
178
|
+
const results: SearchResult[] = []
|
|
179
|
+
|
|
180
|
+
$("li.search-result").each((_, element) => {
|
|
181
|
+
const item = $(element)
|
|
182
|
+
const link = item.find("a.click-analytics-result").first()
|
|
183
|
+
const rawHref = link.attr("href")
|
|
184
|
+
const title = link.text().trim()
|
|
185
|
+
|
|
186
|
+
if (!rawHref || !title) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const description = item.find("p.result-description").first().text().trim()
|
|
191
|
+
const breadcrumbs = item
|
|
192
|
+
.find("li.breadcrumb-list-item")
|
|
193
|
+
.toArray()
|
|
194
|
+
.map((breadcrumb) => $(breadcrumb).text().trim())
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
|
|
197
|
+
const tags = item
|
|
198
|
+
.find("li.result-tag span, li.result-tag.language")
|
|
199
|
+
.toArray()
|
|
200
|
+
.map((tag) => $(tag).text().trim())
|
|
201
|
+
.filter(Boolean)
|
|
202
|
+
|
|
203
|
+
const className = item.attr("class") ?? ""
|
|
204
|
+
const type = className.includes("documentation")
|
|
205
|
+
? "documentation"
|
|
206
|
+
: className.includes("general")
|
|
207
|
+
? "general"
|
|
208
|
+
: "other"
|
|
209
|
+
|
|
210
|
+
results.push({
|
|
211
|
+
title,
|
|
212
|
+
url: rawHref.startsWith("/") ? `https://developer.apple.com${rawHref}` : rawHref,
|
|
213
|
+
description,
|
|
214
|
+
breadcrumbs,
|
|
215
|
+
tags,
|
|
216
|
+
type,
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return results
|
|
221
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Apple Developer documentation JSON API
|
|
3
|
+
*
|
|
4
|
+
* This module consolidates all types used across the codebase for processing
|
|
5
|
+
* Apple documentation. Types are organized by category and complexity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// CORE CONTENT TYPES
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents a text fragment with optional styling information
|
|
14
|
+
*/
|
|
15
|
+
export interface TextFragment {
|
|
16
|
+
text: string
|
|
17
|
+
type?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Represents a code fragment with syntax highlighting
|
|
22
|
+
*/
|
|
23
|
+
export interface CodeFragment {
|
|
24
|
+
code: string | string[]
|
|
25
|
+
syntax?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents a tokenized piece of content
|
|
30
|
+
*/
|
|
31
|
+
export interface Token {
|
|
32
|
+
text?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The main content item type used throughout the documentation structure.
|
|
37
|
+
* Can represent text, code, lists, headings, and other content elements.
|
|
38
|
+
*/
|
|
39
|
+
export interface ContentItem {
|
|
40
|
+
// Basic content properties
|
|
41
|
+
text?: string
|
|
42
|
+
type?: string
|
|
43
|
+
title?: string
|
|
44
|
+
name?: string
|
|
45
|
+
|
|
46
|
+
// Tokenized content
|
|
47
|
+
tokens?: Token[]
|
|
48
|
+
|
|
49
|
+
// Nested content
|
|
50
|
+
content?: ContentItem[]
|
|
51
|
+
inlineContent?: ContentItem[]
|
|
52
|
+
items?: ContentItem[]
|
|
53
|
+
|
|
54
|
+
// Code content
|
|
55
|
+
code?: string | string[]
|
|
56
|
+
syntax?: string
|
|
57
|
+
|
|
58
|
+
// Structural properties
|
|
59
|
+
level?: number
|
|
60
|
+
style?: string
|
|
61
|
+
identifier?: string
|
|
62
|
+
identifiers?: string[]
|
|
63
|
+
url?: string
|
|
64
|
+
|
|
65
|
+
// Abstract content
|
|
66
|
+
abstract?: TextFragment[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// DECLARATION & PARAMETER TYPES
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Represents a code declaration with tokenized content
|
|
75
|
+
*/
|
|
76
|
+
export interface Declaration {
|
|
77
|
+
tokens?: Token[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Represents a function or method parameter
|
|
82
|
+
*/
|
|
83
|
+
export interface Parameter {
|
|
84
|
+
name: string
|
|
85
|
+
content?: ContentItem[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// SECTION TYPES
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Represents a topic section in the documentation
|
|
94
|
+
*/
|
|
95
|
+
export interface TopicSection {
|
|
96
|
+
title: string
|
|
97
|
+
identifiers?: string[]
|
|
98
|
+
children?: TopicSection[]
|
|
99
|
+
abstract?: ContentItem[]
|
|
100
|
+
anchor?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Represents a "see also" section with related identifiers
|
|
105
|
+
*/
|
|
106
|
+
export interface SeeAlsoSection {
|
|
107
|
+
title: string
|
|
108
|
+
identifiers?: string[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Represents a primary content section with specific kind and content
|
|
113
|
+
*/
|
|
114
|
+
export interface PrimaryContentSection {
|
|
115
|
+
kind: string
|
|
116
|
+
content?: ContentItem[]
|
|
117
|
+
declarations?: Declaration[]
|
|
118
|
+
parameters?: Parameter[]
|
|
119
|
+
items?: PropertyItem[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Represents a property item used in data dictionary pages.
|
|
124
|
+
*/
|
|
125
|
+
export interface PropertyItem {
|
|
126
|
+
name: string
|
|
127
|
+
required?: boolean
|
|
128
|
+
content?: ContentItem[]
|
|
129
|
+
type?: Array<{
|
|
130
|
+
text?: string
|
|
131
|
+
kind?: string
|
|
132
|
+
identifier?: string
|
|
133
|
+
}>
|
|
134
|
+
attributes?: Array<{
|
|
135
|
+
kind?: string
|
|
136
|
+
values?: string[]
|
|
137
|
+
}>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// VARIANT TYPES
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Common properties shared across all variant types
|
|
146
|
+
*/
|
|
147
|
+
interface BaseVariant {
|
|
148
|
+
title?: string
|
|
149
|
+
abstract?: TextFragment[]
|
|
150
|
+
identifier?: string
|
|
151
|
+
type?: string
|
|
152
|
+
role?: string
|
|
153
|
+
kind?: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Represents a language-specific variant of documentation
|
|
158
|
+
*/
|
|
159
|
+
export interface LanguageVariant extends BaseVariant {
|
|
160
|
+
traits: Array<{ interfaceLanguage: string }>
|
|
161
|
+
paths: string[]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Represents an image variant with URL and traits
|
|
166
|
+
*/
|
|
167
|
+
export interface ImageVariant extends BaseVariant {
|
|
168
|
+
url: string
|
|
169
|
+
traits: string[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Represents a symbol variant with detailed metadata
|
|
174
|
+
*/
|
|
175
|
+
export interface SymbolVariant extends BaseVariant {
|
|
176
|
+
url?: string
|
|
177
|
+
fragments?: Array<{
|
|
178
|
+
kind: string
|
|
179
|
+
text?: string
|
|
180
|
+
preciseIdentifier?: string
|
|
181
|
+
}>
|
|
182
|
+
conformance?: {
|
|
183
|
+
constraints?: Array<{
|
|
184
|
+
code?: string
|
|
185
|
+
type: string
|
|
186
|
+
text?: string
|
|
187
|
+
}>
|
|
188
|
+
conformancePrefix?: Array<{
|
|
189
|
+
text: string
|
|
190
|
+
type: string
|
|
191
|
+
}>
|
|
192
|
+
availabilityPrefix?: Array<{
|
|
193
|
+
type: string
|
|
194
|
+
text: string
|
|
195
|
+
}>
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Union type representing any variant type
|
|
201
|
+
*/
|
|
202
|
+
export type Variant = LanguageVariant | ImageVariant | SymbolVariant
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// INTERFACE & INDEX TYPES
|
|
206
|
+
// ============================================================================
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Represents a Swift interface item in the documentation index
|
|
210
|
+
*/
|
|
211
|
+
export interface SwiftInterfaceItem {
|
|
212
|
+
path: string
|
|
213
|
+
title: string
|
|
214
|
+
type: string
|
|
215
|
+
children?: SwiftInterfaceItem[]
|
|
216
|
+
external?: boolean
|
|
217
|
+
beta?: boolean
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Represents an item in the documentation index
|
|
222
|
+
*/
|
|
223
|
+
export interface IndexContentItem {
|
|
224
|
+
type?: string
|
|
225
|
+
title?: string
|
|
226
|
+
path?: string
|
|
227
|
+
beta?: boolean
|
|
228
|
+
children?: IndexContentItem[]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// METADATA TYPES
|
|
233
|
+
// ============================================================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Platform information for documentation
|
|
237
|
+
*/
|
|
238
|
+
export interface Platform {
|
|
239
|
+
name: string
|
|
240
|
+
introducedAt: string
|
|
241
|
+
beta?: boolean
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Documentation metadata
|
|
246
|
+
*/
|
|
247
|
+
export interface DocumentationMetadata {
|
|
248
|
+
title?: string
|
|
249
|
+
platforms?: Platform[]
|
|
250
|
+
roleHeading?: string
|
|
251
|
+
symbolKind?: string
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Documentation identifier with URL and language
|
|
256
|
+
*/
|
|
257
|
+
export interface DocumentationIdentifier {
|
|
258
|
+
url: string
|
|
259
|
+
interfaceLanguage?: string
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// MAIN DOCUMENTATION STRUCTURE
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The main Apple documentation JSON structure.
|
|
268
|
+
* This is the root type for all Apple documentation responses.
|
|
269
|
+
*/
|
|
270
|
+
export interface AppleDocJSON {
|
|
271
|
+
// Metadata
|
|
272
|
+
metadata?: DocumentationMetadata
|
|
273
|
+
kind?: string
|
|
274
|
+
identifier?: DocumentationIdentifier
|
|
275
|
+
|
|
276
|
+
// Content
|
|
277
|
+
abstract?: Array<{ text: string; type: string }>
|
|
278
|
+
sections?: ContentItem[]
|
|
279
|
+
|
|
280
|
+
// Primary content sections
|
|
281
|
+
primaryContentSections?: PrimaryContentSection[]
|
|
282
|
+
|
|
283
|
+
// Topic sections
|
|
284
|
+
topicSections?: TopicSection[]
|
|
285
|
+
seeAlsoSections?: SeeAlsoSection[]
|
|
286
|
+
|
|
287
|
+
// Variants and relationships
|
|
288
|
+
variants?: Variant[]
|
|
289
|
+
relationshipsSections?: ContentItem[]
|
|
290
|
+
|
|
291
|
+
// References
|
|
292
|
+
references?: Record<string, ContentItem>
|
|
293
|
+
|
|
294
|
+
// Index-specific fields
|
|
295
|
+
interfaceLanguages?: {
|
|
296
|
+
swift?: SwiftInterfaceItem[]
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// UTILITY TYPES
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Type guard to check if a variant is a language variant
|
|
306
|
+
*/
|
|
307
|
+
export function isLanguageVariant(variant: Variant): variant is LanguageVariant {
|
|
308
|
+
return (
|
|
309
|
+
"traits" in variant &&
|
|
310
|
+
Array.isArray(variant.traits) &&
|
|
311
|
+
variant.traits.length > 0 &&
|
|
312
|
+
typeof variant.traits[0] === "object" &&
|
|
313
|
+
"interfaceLanguage" in variant.traits[0]
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Type guard to check if a variant is an image variant
|
|
319
|
+
*/
|
|
320
|
+
export function isImageVariant(variant: Variant): variant is ImageVariant {
|
|
321
|
+
return (
|
|
322
|
+
"url" in variant &&
|
|
323
|
+
"traits" in variant &&
|
|
324
|
+
Array.isArray(variant.traits) &&
|
|
325
|
+
typeof variant.traits[0] === "string"
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Type guard to check if a variant is a symbol variant
|
|
331
|
+
*/
|
|
332
|
+
export function isSymbolVariant(variant: Variant): variant is SymbolVariant {
|
|
333
|
+
return "fragments" in variant || "conformance" in variant
|
|
334
|
+
}
|
package/src/lib/url.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Constants for Apple Developer documentation URLs
|
|
2
|
+
const APPLE_DOC_BASE_URL = "https://developer.apple.com/documentation/"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes documentation paths by removing leading slashes, whitespace, and documentation prefixes.
|
|
6
|
+
*
|
|
7
|
+
* This function handles various input formats:
|
|
8
|
+
* - `/swift/array` → `swift/array`
|
|
9
|
+
* - `documentation/swift/array` → `swift/array`
|
|
10
|
+
* - ` swift/array ` → `swift/array`
|
|
11
|
+
* - `/documentation/swift/array` → `swift/array`
|
|
12
|
+
*
|
|
13
|
+
* @param path - The documentation path to normalize
|
|
14
|
+
* @returns The normalized path without leading slashes or documentation prefixes
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeDocumentationPath(path: string): string {
|
|
17
|
+
if (!path || typeof path !== "string") {
|
|
18
|
+
return ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
path
|
|
23
|
+
.trim()
|
|
24
|
+
// Remove leading slashes and documentation prefixes
|
|
25
|
+
.replace(/^\/?(?:documentation\/?)?/, "")
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a complete Apple Developer documentation URL from a normalized path.
|
|
31
|
+
*
|
|
32
|
+
* @param normalizedPath - The normalized documentation path (e.g., "swift/array")
|
|
33
|
+
* @returns The complete Apple Developer documentation URL
|
|
34
|
+
*/
|
|
35
|
+
export function generateAppleDocUrl(normalizedPath: string): string {
|
|
36
|
+
if (!normalizedPath || typeof normalizedPath !== "string") {
|
|
37
|
+
return APPLE_DOC_BASE_URL
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `${APPLE_DOC_BASE_URL}${normalizedPath}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validates if a URL is a proper Apple Developer documentation URL.
|
|
45
|
+
*
|
|
46
|
+
* @param url - The URL to validate
|
|
47
|
+
* @returns True if the URL is a valid Apple Developer documentation URL
|
|
48
|
+
*/
|
|
49
|
+
export function isValidAppleDocUrl(url: string): boolean {
|
|
50
|
+
if (!url || typeof url !== "string") {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return url.startsWith(APPLE_DOC_BASE_URL)
|
|
55
|
+
}
|