sourcey 2.0.2

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.
Files changed (191) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +254 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +197 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/client/copy.js +21 -0
  8. package/dist/client/index.d.ts +7 -0
  9. package/dist/client/index.d.ts.map +1 -0
  10. package/dist/client/index.js +8 -0
  11. package/dist/client/index.js.map +1 -0
  12. package/dist/client/scroll-tracker.js +88 -0
  13. package/dist/client/search.js +164 -0
  14. package/dist/client/sidebar.js +54 -0
  15. package/dist/client/tabs.js +51 -0
  16. package/dist/client/theme-toggle.js +36 -0
  17. package/dist/components/App.d.ts +15 -0
  18. package/dist/components/App.d.ts.map +1 -0
  19. package/dist/components/App.js +13 -0
  20. package/dist/components/App.js.map +1 -0
  21. package/dist/components/layout/Head.d.ts +2 -0
  22. package/dist/components/layout/Head.d.ts.map +1 -0
  23. package/dist/components/layout/Head.js +29 -0
  24. package/dist/components/layout/Head.js.map +1 -0
  25. package/dist/components/layout/Header.d.ts +13 -0
  26. package/dist/components/layout/Header.d.ts.map +1 -0
  27. package/dist/components/layout/Header.js +38 -0
  28. package/dist/components/layout/Header.js.map +1 -0
  29. package/dist/components/layout/Page.d.ts +2 -0
  30. package/dist/components/layout/Page.d.ts.map +1 -0
  31. package/dist/components/layout/Page.js +29 -0
  32. package/dist/components/layout/Page.js.map +1 -0
  33. package/dist/components/layout/Sidebar.d.ts +9 -0
  34. package/dist/components/layout/Sidebar.d.ts.map +1 -0
  35. package/dist/components/layout/Sidebar.js +41 -0
  36. package/dist/components/layout/Sidebar.js.map +1 -0
  37. package/dist/components/layout/TableOfContents.d.ts +10 -0
  38. package/dist/components/layout/TableOfContents.d.ts.map +1 -0
  39. package/dist/components/layout/TableOfContents.js +12 -0
  40. package/dist/components/layout/TableOfContents.js.map +1 -0
  41. package/dist/components/openapi/CodeSamples.d.ts +11 -0
  42. package/dist/components/openapi/CodeSamples.d.ts.map +1 -0
  43. package/dist/components/openapi/CodeSamples.js +16 -0
  44. package/dist/components/openapi/CodeSamples.js.map +1 -0
  45. package/dist/components/openapi/Definition.d.ts +11 -0
  46. package/dist/components/openapi/Definition.d.ts.map +1 -0
  47. package/dist/components/openapi/Definition.js +12 -0
  48. package/dist/components/openapi/Definition.js.map +1 -0
  49. package/dist/components/openapi/EndpointBar.d.ts +12 -0
  50. package/dist/components/openapi/EndpointBar.d.ts.map +1 -0
  51. package/dist/components/openapi/EndpointBar.js +22 -0
  52. package/dist/components/openapi/EndpointBar.js.map +1 -0
  53. package/dist/components/openapi/Introduction.d.ts +6 -0
  54. package/dist/components/openapi/Introduction.d.ts.map +1 -0
  55. package/dist/components/openapi/Introduction.js +14 -0
  56. package/dist/components/openapi/Introduction.js.map +1 -0
  57. package/dist/components/openapi/Operation.d.ts +13 -0
  58. package/dist/components/openapi/Operation.d.ts.map +1 -0
  59. package/dist/components/openapi/Operation.js +23 -0
  60. package/dist/components/openapi/Operation.js.map +1 -0
  61. package/dist/components/openapi/Parameters.d.ts +7 -0
  62. package/dist/components/openapi/Parameters.d.ts.map +1 -0
  63. package/dist/components/openapi/Parameters.js +10 -0
  64. package/dist/components/openapi/Parameters.js.map +1 -0
  65. package/dist/components/openapi/RequestBody.d.ts +17 -0
  66. package/dist/components/openapi/RequestBody.d.ts.map +1 -0
  67. package/dist/components/openapi/RequestBody.js +27 -0
  68. package/dist/components/openapi/RequestBody.js.map +1 -0
  69. package/dist/components/openapi/Responses.d.ts +14 -0
  70. package/dist/components/openapi/Responses.d.ts.map +1 -0
  71. package/dist/components/openapi/Responses.js +60 -0
  72. package/dist/components/openapi/Responses.js.map +1 -0
  73. package/dist/components/openapi/Security.d.ts +14 -0
  74. package/dist/components/openapi/Security.d.ts.map +1 -0
  75. package/dist/components/openapi/Security.js +32 -0
  76. package/dist/components/openapi/Security.js.map +1 -0
  77. package/dist/components/openapi/Tags.d.ts +8 -0
  78. package/dist/components/openapi/Tags.d.ts.map +1 -0
  79. package/dist/components/openapi/Tags.js +10 -0
  80. package/dist/components/openapi/Tags.js.map +1 -0
  81. package/dist/components/schema/ExampleView.d.ts +11 -0
  82. package/dist/components/schema/ExampleView.d.ts.map +1 -0
  83. package/dist/components/schema/ExampleView.js +15 -0
  84. package/dist/components/schema/ExampleView.js.map +1 -0
  85. package/dist/components/schema/SchemaDatatype.d.ts +11 -0
  86. package/dist/components/schema/SchemaDatatype.d.ts.map +1 -0
  87. package/dist/components/schema/SchemaDatatype.js +36 -0
  88. package/dist/components/schema/SchemaDatatype.js.map +1 -0
  89. package/dist/components/schema/SchemaView.d.ts +14 -0
  90. package/dist/components/schema/SchemaView.d.ts.map +1 -0
  91. package/dist/components/schema/SchemaView.js +44 -0
  92. package/dist/components/schema/SchemaView.js.map +1 -0
  93. package/dist/components/ui/Badge.d.ts +11 -0
  94. package/dist/components/ui/Badge.d.ts.map +1 -0
  95. package/dist/components/ui/Badge.js +14 -0
  96. package/dist/components/ui/Badge.js.map +1 -0
  97. package/dist/components/ui/Markdown.d.ts +8 -0
  98. package/dist/components/ui/Markdown.d.ts.map +1 -0
  99. package/dist/components/ui/Markdown.js +13 -0
  100. package/dist/components/ui/Markdown.js.map +1 -0
  101. package/dist/components/ui/SectionLabel.d.ts +10 -0
  102. package/dist/components/ui/SectionLabel.d.ts.map +1 -0
  103. package/dist/components/ui/SectionLabel.js +9 -0
  104. package/dist/components/ui/SectionLabel.js.map +1 -0
  105. package/dist/config.d.ts +46 -0
  106. package/dist/config.d.ts.map +1 -0
  107. package/dist/config.js +102 -0
  108. package/dist/config.js.map +1 -0
  109. package/dist/core/converter.d.ts +9 -0
  110. package/dist/core/converter.d.ts.map +1 -0
  111. package/dist/core/converter.js +29 -0
  112. package/dist/core/converter.js.map +1 -0
  113. package/dist/core/loader.d.ts +7 -0
  114. package/dist/core/loader.d.ts.map +1 -0
  115. package/dist/core/loader.js +92 -0
  116. package/dist/core/loader.js.map +1 -0
  117. package/dist/core/markdown-loader.d.ts +29 -0
  118. package/dist/core/markdown-loader.d.ts.map +1 -0
  119. package/dist/core/markdown-loader.js +65 -0
  120. package/dist/core/markdown-loader.js.map +1 -0
  121. package/dist/core/navigation.d.ts +51 -0
  122. package/dist/core/navigation.d.ts.map +1 -0
  123. package/dist/core/navigation.js +108 -0
  124. package/dist/core/navigation.js.map +1 -0
  125. package/dist/core/normalizer.d.ts +7 -0
  126. package/dist/core/normalizer.d.ts.map +1 -0
  127. package/dist/core/normalizer.js +472 -0
  128. package/dist/core/normalizer.js.map +1 -0
  129. package/dist/core/parser.d.ts +10 -0
  130. package/dist/core/parser.d.ts.map +1 -0
  131. package/dist/core/parser.js +34 -0
  132. package/dist/core/parser.js.map +1 -0
  133. package/dist/core/search-indexer.d.ts +25 -0
  134. package/dist/core/search-indexer.d.ts.map +1 -0
  135. package/dist/core/search-indexer.js +72 -0
  136. package/dist/core/search-indexer.js.map +1 -0
  137. package/dist/core/types.d.ts +236 -0
  138. package/dist/core/types.d.ts.map +1 -0
  139. package/dist/core/types.js +7 -0
  140. package/dist/core/types.js.map +1 -0
  141. package/dist/dev-server.d.ts +17 -0
  142. package/dist/dev-server.d.ts.map +1 -0
  143. package/dist/dev-server.js +202 -0
  144. package/dist/dev-server.js.map +1 -0
  145. package/dist/index.d.ts +62 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +166 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/renderer/context.d.ts +46 -0
  150. package/dist/renderer/context.d.ts.map +1 -0
  151. package/dist/renderer/context.js +22 -0
  152. package/dist/renderer/context.js.map +1 -0
  153. package/dist/renderer/html-builder.d.ts +37 -0
  154. package/dist/renderer/html-builder.d.ts.map +1 -0
  155. package/dist/renderer/html-builder.js +87 -0
  156. package/dist/renderer/html-builder.js.map +1 -0
  157. package/dist/renderer/static-renderer.d.ts +10 -0
  158. package/dist/renderer/static-renderer.d.ts.map +1 -0
  159. package/dist/renderer/static-renderer.js +17 -0
  160. package/dist/renderer/static-renderer.js.map +1 -0
  161. package/dist/themes/default/main.css +61 -0
  162. package/dist/themes/default/sourcey.css +425 -0
  163. package/dist/utils/code-samples.d.ts +8 -0
  164. package/dist/utils/code-samples.d.ts.map +1 -0
  165. package/dist/utils/code-samples.js +84 -0
  166. package/dist/utils/code-samples.js.map +1 -0
  167. package/dist/utils/example-generator.d.ts +12 -0
  168. package/dist/utils/example-generator.d.ts.map +1 -0
  169. package/dist/utils/example-generator.js +123 -0
  170. package/dist/utils/example-generator.js.map +1 -0
  171. package/dist/utils/highlighter.d.ts +10 -0
  172. package/dist/utils/highlighter.d.ts.map +1 -0
  173. package/dist/utils/highlighter.js +51 -0
  174. package/dist/utils/highlighter.js.map +1 -0
  175. package/dist/utils/html-id.d.ts +6 -0
  176. package/dist/utils/html-id.d.ts.map +1 -0
  177. package/dist/utils/html-id.js +11 -0
  178. package/dist/utils/html-id.js.map +1 -0
  179. package/dist/utils/http.d.ts +14 -0
  180. package/dist/utils/http.d.ts.map +1 -0
  181. package/dist/utils/http.js +97 -0
  182. package/dist/utils/http.js.map +1 -0
  183. package/dist/utils/markdown.d.ts +9 -0
  184. package/dist/utils/markdown.d.ts.map +1 -0
  185. package/dist/utils/markdown.js +18 -0
  186. package/dist/utils/markdown.js.map +1 -0
  187. package/dist/vite-plugin.d.ts +16 -0
  188. package/dist/vite-plugin.d.ts.map +1 -0
  189. package/dist/vite-plugin.js +92 -0
  190. package/dist/vite-plugin.js.map +1 -0
  191. package/package.json +78 -0
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2015-2017 Kam Low
2
+ Code from bootprint-openapi and bootprint-json-schema Copyright (c) 2015 Nils Knappmeier
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # Sourcey
2
+
3
+ > Beautiful API documentation from OpenAPI specs
4
+
5
+ [![CI](https://github.com/sourcey/sourcey/actions/workflows/ci.yml/badge.svg)](https://github.com/sourcey/sourcey/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/sourcey)](https://www.npmjs.com/package/sourcey)
7
+ [![Node](https://img.shields.io/node/v/sourcey)](https://nodejs.org)
8
+ [![License](https://img.shields.io/npm/l/sourcey)](https://github.com/sourcey/sourcey/blob/master/LICENSE)
9
+
10
+ Sourcey generates beautiful static HTML documentation from [OpenAPI](https://openapis.org) / [Swagger](http://swagger.io) specifications.
11
+
12
+ Supports **OpenAPI 3.0**, **OpenAPI 3.1**, and **Swagger 2.0** (auto-converted).
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - **OpenAPI 3.x + Swagger 2.0** — full support, with automatic Swagger-to-OpenAPI conversion
19
+ - **Beautiful layout** — sidebar navigation, docs, and code examples
20
+ - **Dark mode** — toggle with a button, respects system preference, persists with localStorage
21
+ - **Client-side search** — `/` or `Ctrl+K` to search endpoints and models instantly
22
+ - **Auto-generated code samples** — cURL, JavaScript (fetch), and Python (requests) for every operation
23
+ - **Synced language tabs** — switch language in one example, all others follow
24
+ - **Shiki syntax highlighting** — VS Code-quality highlighting at build time
25
+ - **Markdown descriptions** — full markdown rendering in all description fields
26
+ - **Dev server with live reload** — `sourcey dev` watches your spec and reloads the browser
27
+ - **Theming** — override any CSS variable via `sourcey.json` — colors, fonts, spacing
28
+ - **Custom branding** — logo and favicon via config
29
+ - **Embeddable output** — generate partial HTML for embedding into your own site
30
+ - **Zero client-side dependencies** — no frameworks shipped to the browser
31
+
32
+ ## Quick Start
33
+
34
+ Install Sourcey:
35
+
36
+ ```bash
37
+ npm install -g sourcey
38
+ ```
39
+
40
+ Generate docs from your spec:
41
+
42
+ ```bash
43
+ sourcey build your_api.yaml -o docs/
44
+ ```
45
+
46
+ That's it. Open `docs/index.html` in your browser.
47
+
48
+ ## CLI
49
+
50
+ ```
51
+ sourcey build <spec> [options] Build static documentation
52
+ sourcey dev <spec> [options] Start dev server with live reload
53
+ sourcey validate <spec> Validate a spec file
54
+ sourcey <spec> Shorthand for 'sourcey build'
55
+ ```
56
+
57
+ ### Build options
58
+
59
+ | Flag | Alias | Description | Default |
60
+ |------|-------|-------------|---------|
61
+ | `--output <dir>` | `-o`, `-t` | Output directory | `dist` |
62
+ | `--logo <file>` | `-l` | Custom logo image | — |
63
+ | `--single-file` | `-1` | Embed all assets into a single HTML file | `false` |
64
+ | `--embed` | `-e` | Omit `<html>`/`<body>` tags for embedding | `false` |
65
+ | `--quiet` | `-q` | Suppress output | `false` |
66
+
67
+ ### Dev server options
68
+
69
+ | Flag | Alias | Description | Default |
70
+ |------|-------|-------------|---------|
71
+ | `--port <port>` | `-p` | Port to listen on | `4400` |
72
+ | `--output <dir>` | `-o` | Build output directory | `.preview` |
73
+ | `--logo <file>` | `-l` | Custom logo image | — |
74
+
75
+ ### Examples
76
+
77
+ ```bash
78
+ # Build docs to a custom directory
79
+ sourcey build api.yaml -o public/docs
80
+
81
+ # Start dev server with live reload
82
+ sourcey dev api.yaml
83
+
84
+ # Validate a spec without building
85
+ sourcey validate api.yaml
86
+
87
+ # Build with a custom logo
88
+ sourcey build api.yaml --logo ./logo.png -o docs/
89
+ ```
90
+
91
+ ## Configuration
92
+
93
+ Create a `sourcey.json` in your project root to configure branding and theming. CLI flags take precedence over config values.
94
+
95
+ ```json
96
+ {
97
+ "logo": "./logo.png",
98
+ "favicon": "./favicon.ico",
99
+ "theme": {
100
+ "--color-primary": "99 102 241",
101
+ "--font-sans": "'IBM Plex Sans', sans-serif",
102
+ "--sidebar-width": "280px"
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Theme variables
108
+
109
+ The `theme` object accepts any CSS custom property defined in the design token system. All color tokens use RGB triplets (`R G B`) consumed via `rgb(var(--token))` with alpha support.
110
+
111
+ #### Layout
112
+
113
+ | Variable | Default | Description |
114
+ |----------|---------|-------------|
115
+ | `--sidebar-width` | `18rem` | Sidebar width |
116
+ | `--header-height` | `7rem` | Header height |
117
+ | `--toc-width` | `19rem` | Table of contents width |
118
+ | `--content-padding` | `2.5rem` | Content area padding |
119
+ | `--content-max-width` | `44rem` | Content max width |
120
+
121
+ #### Colors (RGB triplets)
122
+
123
+ | Variable | Default | Description |
124
+ |----------|---------|-------------|
125
+ | `--color-primary` | `99 102 241` | Primary accent (indigo) |
126
+ | `--color-primary-light` | `129 140 248` | Primary light variant |
127
+ | `--color-primary-dark` | `79 70 229` | Primary dark variant |
128
+ | `--color-background-light` | `255 255 255` | Page background (light) |
129
+ | `--color-background-dark` | `11 12 16` | Page background (dark) |
130
+ | `--color-gray-50` through `--color-gray-950` | — | Full gray scale |
131
+ | `--color-success` | `34 197 94` | Success states |
132
+ | `--color-overlay` | `0 0 0` | Overlay backdrop |
133
+
134
+ #### Method colors
135
+
136
+ | Variable | Default | Description |
137
+ |----------|---------|-------------|
138
+ | `--method-get` | `#16a34a` | GET badge |
139
+ | `--method-post` | `#2563eb` | POST badge |
140
+ | `--method-put` | `#d97706` | PUT badge |
141
+ | `--method-delete` | `#dc2626` | DELETE badge |
142
+ | `--method-patch` | `#9333ea` | PATCH badge |
143
+
144
+ #### Typography
145
+
146
+ | Variable | Default | Description |
147
+ |----------|---------|-------------|
148
+ | `--font-sans` | `'Inter', system-ui, sans-serif` | Body font |
149
+ | `--font-mono` | `'JetBrains Mono', monospace` | Code font |
150
+
151
+ ## Programmatic API
152
+
153
+ ```typescript
154
+ import { buildDocs } from 'sourcey';
155
+
156
+ const result = await buildDocs({
157
+ specSource: './api.yaml',
158
+ outputDir: './docs',
159
+ logo: './logo.png',
160
+ favicon: './favicon.ico',
161
+ themeOverrides: {
162
+ '--color-primary': '225 29 72',
163
+ },
164
+ });
165
+
166
+ console.log(result.spec.info.title); // "My API"
167
+ console.log(result.spec.operations.length); // 42
168
+ ```
169
+
170
+ ### Options
171
+
172
+ | Option | Type | Description |
173
+ |--------|------|-------------|
174
+ | `specSource` | `string` | Path or URL to the spec file (required) |
175
+ | `outputDir` | `string` | Output directory (default: `"dist"`) |
176
+ | `logo` | `string` | Path to a custom logo |
177
+ | `favicon` | `string` | Path to a custom favicon |
178
+ | `singleFile` | `boolean` | Embed assets into one HTML file |
179
+ | `embeddable` | `boolean` | Omit `<html>`/`<body>` tags |
180
+ | `skipWrite` | `boolean` | Parse and normalize without writing files |
181
+ | `themeOverrides` | `Record<string, string>` | CSS custom property overrides |
182
+
183
+ ## Keyboard Shortcuts
184
+
185
+ | Key | Action |
186
+ |-----|--------|
187
+ | `/` or `Ctrl+K` | Open search |
188
+ | `Escape` | Close search or sidebar |
189
+ | `↑` / `↓` | Navigate search results |
190
+ | `Enter` | Go to selected result |
191
+
192
+ ## OpenAPI Extensions
193
+
194
+ | Extension | Description |
195
+ |-----------|-------------|
196
+ | `x-sourcey-hide` | Hide a tag or operation from the docs |
197
+ | `x-sourcey-code-samples` | Custom code samples for an operation |
198
+
199
+ ## Development
200
+
201
+ ```bash
202
+ git clone https://github.com/sourcey/sourcey.git
203
+ cd sourcey
204
+ npm install
205
+ npm run build
206
+ npm test
207
+ ```
208
+
209
+ ### Project structure
210
+
211
+ ```
212
+ src/
213
+ cli.ts CLI entry point (citty)
214
+ index.ts Programmatic API
215
+ config.ts sourcey.json loader
216
+ dev-server.ts Dev server with live reload
217
+ core/ Spec processing pipeline
218
+ loader.ts Load from file/URL
219
+ parser.ts Validate and dereference $refs
220
+ converter.ts Swagger 2.0 → OpenAPI 3.x
221
+ normalizer.ts OpenAPI → internal model
222
+ types.ts Internal types
223
+ components/ Preact components (SSG)
224
+ layout/ Page, Sidebar, Head
225
+ openapi/ Operation, Parameters, Responses, etc.
226
+ schema/ SchemaView, ExampleView
227
+ ui/ Badge, Markdown, SectionLabel
228
+ renderer/ HTML generation
229
+ static-renderer.ts Preact → HTML string
230
+ html-builder.ts Assemble HTML + CSS + JS
231
+ context.ts Render context
232
+ client/ Browser JavaScript (vanilla JS)
233
+ sidebar.js Drawer toggle, close on outside click/Escape
234
+ scroll-tracker.js IntersectionObserver nav highlighting
235
+ tabs.js Synced language tab switching
236
+ copy.js Clipboard copy with feedback
237
+ theme-toggle.js Dark/light mode toggle
238
+ search.js Client-side search dialog
239
+ themes/
240
+ default/ Default theme CSS
241
+ ```
242
+
243
+ ### Testing
244
+
245
+ ```bash
246
+ npm test # Run all tests
247
+ npm run test:watch # Watch mode
248
+ npm run typecheck # TypeScript type checking
249
+ npm run lint # ESLint
250
+ ```
251
+
252
+ ## License
253
+
254
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+ import { buildDocs, buildSiteDocs } from "./index.js";
4
+ import { loadConfig, hasNavigation, configFromSpec } from "./config.js";
5
+ const build = defineCommand({
6
+ meta: {
7
+ name: "build",
8
+ description: "Build documentation from an OpenAPI spec or sourcey.json config",
9
+ },
10
+ args: {
11
+ spec: {
12
+ type: "positional",
13
+ description: "Path or URL to the OpenAPI/Swagger spec file (optional if sourcey.json exists)",
14
+ required: false,
15
+ },
16
+ output: {
17
+ type: "string",
18
+ alias: ["o", "t"],
19
+ description: "Output directory",
20
+ default: "dist",
21
+ },
22
+ logo: {
23
+ type: "string",
24
+ alias: ["l"],
25
+ description: "Path to a custom logo file",
26
+ },
27
+ embed: {
28
+ type: "boolean",
29
+ alias: ["e"],
30
+ description: "Generate embeddable output (no <html>/<body> tags)",
31
+ default: false,
32
+ },
33
+ quiet: {
34
+ type: "boolean",
35
+ alias: ["q"],
36
+ description: "Suppress output",
37
+ default: false,
38
+ },
39
+ },
40
+ async run({ args }) {
41
+ const startTime = Date.now();
42
+ const config = await loadConfig();
43
+ try {
44
+ if (args.spec) {
45
+ // Spec file provided: wrap in single-tab site
46
+ if (!args.quiet) {
47
+ console.log(`\nSourcey — generating docs from ${args.spec}\n`);
48
+ }
49
+ const result = await buildDocs({
50
+ specSource: args.spec,
51
+ outputDir: args.output,
52
+ logo: args.logo || config.logo,
53
+ favicon: config.favicon,
54
+ embeddable: args.embed,
55
+ themeOverrides: config.theme,
56
+ });
57
+ if (!args.quiet) {
58
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
59
+ console.log(` Spec: ${result.spec.info.title} v${result.spec.info.version}`);
60
+ console.log(` Operations: ${result.spec.operations.length}`);
61
+ console.log(` Schemas: ${Object.keys(result.spec.schemas).length}`);
62
+ console.log(` Time: ${elapsed}s\n`);
63
+ }
64
+ }
65
+ else if (hasNavigation(config)) {
66
+ // sourcey.json with navigation
67
+ if (!args.quiet) {
68
+ console.log(`\nSourcey — building documentation site\n`);
69
+ }
70
+ const result = await buildSiteDocs({
71
+ outputDir: args.output,
72
+ });
73
+ if (!args.quiet) {
74
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
75
+ console.log(` Pages: ${result.pageCount}`);
76
+ console.log(` Output: ${result.outputDir}`);
77
+ console.log(` Time: ${elapsed}s\n`);
78
+ }
79
+ }
80
+ else {
81
+ console.error("\nError: No spec file provided and no sourcey.json with navigation found.");
82
+ console.error("Usage: sourcey build <spec-file> or create a sourcey.json with navigation config.\n");
83
+ process.exit(1);
84
+ }
85
+ }
86
+ catch (error) {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ console.error(`\nError: ${message}\n`);
89
+ process.exit(1);
90
+ }
91
+ },
92
+ });
93
+ const dev = defineCommand({
94
+ meta: {
95
+ name: "dev",
96
+ description: "Start a dev server with live reload",
97
+ },
98
+ args: {
99
+ spec: {
100
+ type: "positional",
101
+ description: "Path to the OpenAPI/Swagger spec file (optional if sourcey.json exists)",
102
+ required: false,
103
+ },
104
+ port: {
105
+ type: "string",
106
+ alias: ["p"],
107
+ description: "Port to listen on",
108
+ default: "4400",
109
+ },
110
+ output: {
111
+ type: "string",
112
+ alias: ["o"],
113
+ description: "Output directory for built files",
114
+ default: ".preview",
115
+ },
116
+ logo: {
117
+ type: "string",
118
+ alias: ["l"],
119
+ description: "Path to a custom logo file",
120
+ },
121
+ },
122
+ async run({ args }) {
123
+ const config = await loadConfig();
124
+ const { startDevServer } = await import("./dev-server.js");
125
+ await startDevServer({
126
+ specSource: args.spec || undefined,
127
+ outputDir: args.output,
128
+ port: parseInt(args.port, 10),
129
+ logo: args.logo || config.logo,
130
+ favicon: config.favicon,
131
+ themeOverrides: config.theme,
132
+ });
133
+ },
134
+ });
135
+ const validate = defineCommand({
136
+ meta: {
137
+ name: "validate",
138
+ description: "Validate an OpenAPI/Swagger spec file",
139
+ },
140
+ args: {
141
+ spec: {
142
+ type: "positional",
143
+ description: "Path or URL to the OpenAPI/Swagger spec file",
144
+ required: true,
145
+ },
146
+ },
147
+ async run({ args }) {
148
+ try {
149
+ const result = await buildDocs({
150
+ specSource: args.spec,
151
+ skipWrite: true,
152
+ });
153
+ console.log(`\n ✓ Valid: ${result.spec.info.title} v${result.spec.info.version}`);
154
+ console.log(` Operations: ${result.spec.operations.length}`);
155
+ console.log(` Schemas: ${Object.keys(result.spec.schemas).length}\n`);
156
+ }
157
+ catch (error) {
158
+ const message = error instanceof Error ? error.message : String(error);
159
+ console.error(`\n ✗ Invalid: ${message}\n`);
160
+ process.exit(1);
161
+ }
162
+ },
163
+ });
164
+ const main = defineCommand({
165
+ meta: {
166
+ name: "sourcey",
167
+ version: "2.0.0-alpha.1",
168
+ description: "Generate beautiful static API documentation from OpenAPI/Swagger specifications",
169
+ },
170
+ subCommands: {
171
+ build,
172
+ dev,
173
+ validate,
174
+ },
175
+ args: {
176
+ spec: {
177
+ type: "positional",
178
+ description: "Path or URL to the OpenAPI/Swagger spec file (shorthand for 'sourcey build')",
179
+ required: false,
180
+ },
181
+ },
182
+ async run({ args, rawArgs }) {
183
+ // Skip if a subcommand was matched (citty still calls parent run)
184
+ if (rawArgs.includes("build") || rawArgs.includes("dev") || rawArgs.includes("validate"))
185
+ return;
186
+ if (args.spec) {
187
+ // `sourcey <specfile>` → `sourcey build <specfile>`
188
+ await build.run({
189
+ args: { _: [], spec: args.spec, output: "dist", logo: "", embed: false, quiet: false },
190
+ rawArgs: [],
191
+ cmd: build,
192
+ });
193
+ }
194
+ },
195
+ });
196
+ runMain(main);
197
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ // Copy button — clipboard copy with "Copied!" feedback
2
+ (function () {
3
+ document.addEventListener('click', function (e) {
4
+ var btn = e.target.closest('.copy-btn');
5
+ if (!btn) return;
6
+ var wrapper = btn.closest('.code-block-wrapper');
7
+ if (!wrapper) return;
8
+ var code = wrapper.querySelector('code');
9
+ if (!code) return;
10
+
11
+ navigator.clipboard.writeText(code.textContent || '').then(function () {
12
+ var span = btn.querySelector('span');
13
+ if (span) span.textContent = 'Copied!';
14
+ btn.classList.add('copied');
15
+ setTimeout(function () {
16
+ if (span) span.textContent = 'Copy';
17
+ btn.classList.remove('copied');
18
+ }, 2000);
19
+ });
20
+ });
21
+ })();
@@ -0,0 +1,7 @@
1
+ import "./sidebar.js";
2
+ import "./scroll-tracker.js";
3
+ import "./tabs.js";
4
+ import "./copy.js";
5
+ import "./theme-toggle.js";
6
+ import "./search.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAC;AACtB,OAAO,qBAAqB,CAAC;AAC7B,OAAO,WAAW,CAAC;AACnB,OAAO,WAAW,CAAC;AACnB,OAAO,mBAAmB,CAAC;AAC3B,OAAO,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Sourcey client entry — bundled by Vite
2
+ import "./sidebar.js";
3
+ import "./scroll-tracker.js";
4
+ import "./tabs.js";
5
+ import "./copy.js";
6
+ import "./theme-toggle.js";
7
+ import "./search.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,cAAc,CAAC;AACtB,OAAO,qBAAqB,CAAC;AAC7B,OAAO,WAAW,CAAC;AACnB,OAAO,WAAW,CAAC;AACnB,OAAO,mBAAmB,CAAC;AAC3B,OAAO,aAAa,CAAC"}
@@ -0,0 +1,88 @@
1
+ // Scroll tracker — highlights the active sidebar nav link as user scrolls.
2
+ //
3
+ // Observes [data-traverse-target] section anchors and toggles .active
4
+ // on the matching #nav .nav-link. All visual styling is in sourcey.css;
5
+ // this file only manages the class.
6
+ (function () {
7
+ var navLinks = document.querySelectorAll('#nav .nav-link');
8
+ var targets = document.querySelectorAll('[data-traverse-target]');
9
+ if (!targets.length || !navLinks.length) return;
10
+
11
+ var currentId = null;
12
+
13
+ // Map traverse-target id → nav link element (by matching href fragment)
14
+ var linkMap = {};
15
+ navLinks.forEach(function (link) {
16
+ var href = link.getAttribute('href');
17
+ if (href && href.indexOf('#') !== -1) {
18
+ linkMap[href.split('#')[1]] = link;
19
+ }
20
+ });
21
+
22
+ // If no fragment-based links found, bail (e.g. markdown pages)
23
+ if (!Object.keys(linkMap).length) return;
24
+
25
+ function activate(id) {
26
+ if (id === currentId) return;
27
+ currentId = id;
28
+
29
+ navLinks.forEach(function (link) {
30
+ link.classList.remove('active');
31
+ });
32
+
33
+ var active = linkMap[id];
34
+ if (active) {
35
+ active.classList.add('active');
36
+ // Scroll the sidebar so the active link stays visible
37
+ active.scrollIntoView({ block: 'nearest', behavior: 'auto' });
38
+ }
39
+ }
40
+
41
+ // Track which sections are visible; pick the topmost one
42
+ var visibleSections = new Map();
43
+
44
+ var observer = new IntersectionObserver(function (entries) {
45
+ entries.forEach(function (entry) {
46
+ var id = entry.target.getAttribute('data-traverse-target');
47
+ if (entry.isIntersecting) {
48
+ visibleSections.set(id, entry.target);
49
+ } else {
50
+ visibleSections.delete(id);
51
+ }
52
+ });
53
+
54
+ // Pick the topmost visible section (smallest boundingClientRect.top)
55
+ var best = null;
56
+ var bestTop = Infinity;
57
+ visibleSections.forEach(function (el, id) {
58
+ var top = el.getBoundingClientRect().top;
59
+ if (top < bestTop) { bestTop = top; best = id; }
60
+ });
61
+
62
+ // Fallback: if nothing visible, find the last section above viewport
63
+ if (!best) {
64
+ for (var i = targets.length - 1; i >= 0; i--) {
65
+ if (targets[i].getBoundingClientRect().top < 10) {
66
+ best = targets[i].getAttribute('data-traverse-target');
67
+ break;
68
+ }
69
+ }
70
+ }
71
+
72
+ if (best) activate(best);
73
+ }, {
74
+ // Trigger when section header enters top 20% of viewport
75
+ rootMargin: '0px 0px -80% 0px',
76
+ threshold: 0
77
+ });
78
+
79
+ targets.forEach(function (el) { observer.observe(el); });
80
+
81
+ // Activate on initial load based on URL hash or first section
82
+ var hash = window.location.hash.slice(1);
83
+ if (hash && linkMap[hash]) {
84
+ activate(hash);
85
+ } else if (targets.length) {
86
+ activate(targets[0].getAttribute('data-traverse-target'));
87
+ }
88
+ })();