@marlinjai/clearify 1.5.3 → 1.6.1

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/README.md CHANGED
@@ -11,7 +11,7 @@ An open-source documentation site generator. Turn markdown into beautiful docs.
11
11
  - **Dark mode** — automatic light/dark theme switching
12
12
  - **Syntax highlighting** — beautiful code blocks with Shiki (dual light/dark themes)
13
13
  - **SSG & SEO** — pre-rendered HTML, Open Graph, Twitter Cards, JSON-LD, sitemap, robots.txt
14
- - **OpenAPI / Scalar** — render API reference docs from an OpenAPI spec, with theme sync and dark mode
14
+ - **OpenAPI API Reference** — custom-built renderer for OpenAPI specs with code examples, schema viewer, and full design system integration
15
15
  - **Multi-section support** — pill-based section switcher (e.g. public docs + internal docs)
16
16
  - **Auto changelog** — drop a `CHANGELOG.md` in your project root, it appears as `/changelog`
17
17
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-SCZZB7OE.js";
9
9
  import {
10
10
  clearifyPlugin
11
- } from "./chunk-UOVBAJMR.js";
11
+ } from "./chunk-7VYQMIW2.js";
12
12
  import {
13
13
  loadUserConfig,
14
14
  resolveConfig,
@@ -13,6 +13,7 @@ import {
13
13
  // src/vite-plugin/index.ts
14
14
  import { resolve } from "path";
15
15
  import { existsSync, readFileSync } from "fs";
16
+ import { dereference } from "@scalar/openapi-parser";
16
17
  var VIRTUAL_CONFIG = "virtual:clearify/config";
17
18
  var RESOLVED_VIRTUAL_CONFIG = "\0" + VIRTUAL_CONFIG;
18
19
  var VIRTUAL_ROUTES = "virtual:clearify/routes";
@@ -35,7 +36,7 @@ function clearifyPlugin(options = {}) {
35
36
  let allSectionNavigations;
36
37
  let allSearchEntries;
37
38
  let mermaidSvgData = options.mermaidSvgs ?? {};
38
- function refreshDocs() {
39
+ async function refreshDocs() {
39
40
  const changelogPath = resolve(userRoot, "CHANGELOG.md");
40
41
  sectionDataList = visibleSections.map(
41
42
  (section) => buildSectionData(section, changelogPath, config.navigation)
@@ -48,7 +49,7 @@ function clearifyPlugin(options = {}) {
48
49
  );
49
50
  if (config.openapi?.spec && config.openapi.generatePages !== false) {
50
51
  const basePath = config.openapi.basePath ?? "/api";
51
- const specData = loadOpenAPISpecData();
52
+ const specData = await loadOpenAPISpecData();
52
53
  if (specData) {
53
54
  let opAnchor2 = function(tag, method, opPath) {
54
55
  const slug = `${method.toLowerCase()}${opPath.replace(/[{}]/g, "")}`;
@@ -132,7 +133,7 @@ ${routeEntries.join(",\n")}
132
133
  }
133
134
  let cachedSpecData = null;
134
135
  let specDataInitialized = false;
135
- function loadOpenAPISpecData() {
136
+ async function loadOpenAPISpecData() {
136
137
  if (specDataInitialized) return cachedSpecData;
137
138
  specDataInitialized = true;
138
139
  const specPath = config.openapi?.spec;
@@ -148,45 +149,32 @@ ${routeEntries.join(",\n")}
148
149
  try {
149
150
  const content = readFileSync(resolvedPath, "utf-8");
150
151
  const isYaml = /\.ya?ml$/i.test(resolvedPath);
152
+ let parsed;
151
153
  if (isYaml) {
152
154
  try {
153
- cachedSpecData = JSON.parse(content);
154
- return cachedSpecData;
155
+ parsed = JSON.parse(content);
155
156
  } catch {
156
157
  console.warn(" YAML OpenAPI specs require js-yaml for page generation. Install it or use a JSON spec.");
157
158
  cachedSpecData = null;
158
159
  return null;
159
160
  }
161
+ } else {
162
+ parsed = JSON.parse(content);
160
163
  }
161
- cachedSpecData = JSON.parse(content);
164
+ const { schema } = await dereference(parsed);
165
+ cachedSpecData = schema;
162
166
  return cachedSpecData;
163
167
  } catch {
164
168
  cachedSpecData = null;
165
169
  return null;
166
170
  }
167
171
  }
168
- function loadOpenAPISpec() {
169
- const specPath = config.openapi?.spec;
170
- if (!specPath) {
171
- return "export default null;";
172
- }
173
- const resolvedPath = resolve(userRoot, specPath);
174
- if (!existsSync(resolvedPath)) {
175
- console.warn(` OpenAPI spec not found: ${resolvedPath}`);
176
- return "export default null;";
177
- }
178
- try {
179
- const content = readFileSync(resolvedPath, "utf-8");
180
- const isYaml = /\.ya?ml$/i.test(resolvedPath);
181
- if (isYaml) {
182
- return `export default ${JSON.stringify(content)};`;
183
- }
184
- JSON.parse(content);
185
- return `export default ${content};`;
186
- } catch (err) {
187
- console.warn(` Failed to load OpenAPI spec: ${resolvedPath}`, err instanceof Error ? err.message : err);
172
+ async function loadOpenAPISpec() {
173
+ const specData = await loadOpenAPISpecData();
174
+ if (!specData) {
188
175
  return "export default null;";
189
176
  }
177
+ return `export default ${JSON.stringify(specData)};`;
190
178
  }
191
179
  const mainPlugin = {
192
180
  name: "clearify",
@@ -199,7 +187,7 @@ ${routeEntries.join(",\n")}
199
187
  const allSections = resolveSections(config, userRoot);
200
188
  const isDev = resolvedViteConfig.command === "serve";
201
189
  visibleSections = isDev ? allSections : allSections.filter((s) => !s.draft);
202
- refreshDocs();
190
+ await refreshDocs();
203
191
  },
204
192
  resolveId(id) {
205
193
  if (id === VIRTUAL_CONFIG) return RESOLVED_VIRTUAL_CONFIG;
@@ -210,7 +198,7 @@ ${routeEntries.join(",\n")}
210
198
  if (id === VIRTUAL_OPENAPI_SPEC) return RESOLVED_VIRTUAL_OPENAPI_SPEC;
211
199
  return null;
212
200
  },
213
- load(id) {
201
+ async load(id) {
214
202
  if (id === RESOLVED_VIRTUAL_CONFIG) {
215
203
  return `export default ${JSON.stringify(config)};`;
216
204
  }
@@ -227,7 +215,7 @@ ${routeEntries.join(",\n")}
227
215
  return `export default ${JSON.stringify(mermaidSvgData)};`;
228
216
  }
229
217
  if (id === RESOLVED_VIRTUAL_OPENAPI_SPEC) {
230
- return loadOpenAPISpec();
218
+ return await loadOpenAPISpec();
231
219
  }
232
220
  return null;
233
221
  },
@@ -248,16 +236,17 @@ ${routeEntries.join(",\n")}
248
236
  if (changedPath === openapiPath) {
249
237
  specDataInitialized = false;
250
238
  cachedSpecData = null;
251
- refreshDocs();
252
- const specMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_OPENAPI_SPEC);
253
- if (specMod) server.moduleGraph.invalidateModule(specMod);
254
- const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
255
- if (routesMod) server.moduleGraph.invalidateModule(routesMod);
256
- const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
257
- if (navMod) server.moduleGraph.invalidateModule(navMod);
258
- const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
259
- if (searchMod) server.moduleGraph.invalidateModule(searchMod);
260
- server.ws.send({ type: "full-reload" });
239
+ refreshDocs().then(() => {
240
+ const specMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_OPENAPI_SPEC);
241
+ if (specMod) server.moduleGraph.invalidateModule(specMod);
242
+ const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
243
+ if (routesMod) server.moduleGraph.invalidateModule(routesMod);
244
+ const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
245
+ if (navMod) server.moduleGraph.invalidateModule(navMod);
246
+ const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
247
+ if (searchMod) server.moduleGraph.invalidateModule(searchMod);
248
+ server.ws.send({ type: "full-reload" });
249
+ });
261
250
  }
262
251
  });
263
252
  }
@@ -358,14 +347,15 @@ ${routeEntries.join(",\n")}
358
347
  if (!isChangelog && !isInSection) return;
359
348
  if (!path.endsWith(".md") && !path.endsWith(".mdx")) return;
360
349
  if (event === "add" || event === "unlink" || event === "change") {
361
- refreshDocs();
362
- const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
363
- const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
364
- const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
365
- if (routesMod) server.moduleGraph.invalidateModule(routesMod);
366
- if (navMod) server.moduleGraph.invalidateModule(navMod);
367
- if (searchMod) server.moduleGraph.invalidateModule(searchMod);
368
- server.ws.send({ type: "full-reload" });
350
+ refreshDocs().then(() => {
351
+ const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
352
+ const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
353
+ const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
354
+ if (routesMod) server.moduleGraph.invalidateModule(routesMod);
355
+ if (navMod) server.moduleGraph.invalidateModule(navMod);
356
+ if (searchMod) server.moduleGraph.invalidateModule(searchMod);
357
+ server.ws.send({ type: "full-reload" });
358
+ });
369
359
  }
370
360
  });
371
361
  return () => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-NY3VVUQ3.js";
3
+ } from "../chunk-7IXPTIHW.js";
4
4
  import "../chunk-PRTER35L.js";
5
5
  import "../chunk-SCZZB7OE.js";
6
- import "../chunk-UOVBAJMR.js";
6
+ import "../chunk-7VYQMIW2.js";
7
7
  import "../chunk-B2Q23JW3.js";
8
8
  import "../chunk-IBK35HZR.js";
9
9
  import "../chunk-V7LLYIRO.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-NY3VVUQ3.js";
3
+ } from "../chunk-7IXPTIHW.js";
4
4
  import {
5
5
  remarkMermaidToComponent
6
6
  } from "../chunk-PRTER35L.js";
7
7
  import "../chunk-SCZZB7OE.js";
8
8
  import {
9
9
  clearifyPlugin
10
- } from "../chunk-UOVBAJMR.js";
10
+ } from "../chunk-7VYQMIW2.js";
11
11
  import "../chunk-B2Q23JW3.js";
12
12
  import {
13
13
  init
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  clearifyPlugin
3
- } from "../chunk-UOVBAJMR.js";
3
+ } from "../chunk-7VYQMIW2.js";
4
4
  import "../chunk-B2Q23JW3.js";
5
5
  import "../chunk-IBK35HZR.js";
6
6
  import "../chunk-V7LLYIRO.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlinjai/clearify",
3
- "version": "1.5.3",
3
+ "version": "1.6.1",
4
4
  "description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "dependencies": {
30
30
  "@mdx-js/react": "^3.1.1",
31
31
  "@mdx-js/rollup": "^3.1.1",
32
- "@scalar/api-reference-react": "^0.8.52",
32
+ "@scalar/openapi-parser": "^0.10.7",
33
33
  "@shikijs/rehype": "^3.22.0",
34
34
  "@tailwindcss/typography": "^0.5.19",
35
35
  "@tailwindcss/vite": "^4.1.18",
@@ -109,6 +109,43 @@ export function Sidebar({ sections, open, onClose }: SidebarProps) {
109
109
  }, [location.pathname, sections, isSectionData]);
110
110
 
111
111
  const sidebarRef = useRef<HTMLElement>(null);
112
+ const [isResizing, setIsResizing] = useState(false);
113
+
114
+ // Restore saved sidebar width on mount
115
+ useEffect(() => {
116
+ const saved = localStorage.getItem('clearify-sidebar-width');
117
+ if (saved) {
118
+ document.documentElement.style.setProperty('--clearify-sidebar-width', saved);
119
+ }
120
+ }, []);
121
+
122
+ // Resize drag logic
123
+ useEffect(() => {
124
+ if (!isResizing) return;
125
+
126
+ const handleMouseMove = (e: MouseEvent) => {
127
+ const newWidth = Math.max(200, Math.min(500, e.clientX));
128
+ document.documentElement.style.setProperty('--clearify-sidebar-width', `${newWidth}px`);
129
+ };
130
+
131
+ const handleMouseUp = () => {
132
+ setIsResizing(false);
133
+ document.body.style.cursor = '';
134
+ document.body.style.userSelect = '';
135
+ const width = getComputedStyle(document.documentElement).getPropertyValue('--clearify-sidebar-width').trim();
136
+ localStorage.setItem('clearify-sidebar-width', width);
137
+ };
138
+
139
+ document.body.style.cursor = 'col-resize';
140
+ document.body.style.userSelect = 'none';
141
+ window.addEventListener('mousemove', handleMouseMove);
142
+ window.addEventListener('mouseup', handleMouseUp);
143
+
144
+ return () => {
145
+ window.removeEventListener('mousemove', handleMouseMove);
146
+ window.removeEventListener('mouseup', handleMouseUp);
147
+ };
148
+ }, [isResizing]);
112
149
 
113
150
  useEffect(() => {
114
151
  const sidebar = sidebarRef.current;
@@ -279,6 +316,23 @@ export function Sidebar({ sections, open, onClose }: SidebarProps) {
279
316
  />
280
317
  ))}
281
318
  </nav>
319
+
320
+ {/* Resize handle */}
321
+ <div
322
+ onMouseDown={(e) => { e.preventDefault(); setIsResizing(true); }}
323
+ className="clearify-sidebar-resize"
324
+ style={{
325
+ position: 'absolute',
326
+ right: 0,
327
+ top: 0,
328
+ bottom: 0,
329
+ width: '5px',
330
+ cursor: 'col-resize',
331
+ background: isResizing ? 'var(--clearify-primary)' : 'transparent',
332
+ transition: isResizing ? 'none' : 'background 0.15s ease',
333
+ zIndex: 10,
334
+ }}
335
+ />
282
336
  </aside>
283
337
 
284
338
  <style>{`
@@ -298,6 +352,12 @@ export function Sidebar({ sections, open, onClose }: SidebarProps) {
298
352
  .clearify-menu-btn {
299
353
  display: flex !important;
300
354
  }
355
+ .clearify-sidebar-resize {
356
+ display: none !important;
357
+ }
358
+ }
359
+ .clearify-sidebar-resize:hover {
360
+ background: var(--clearify-border-strong) !important;
301
361
  }
302
362
  `}</style>
303
363
  </>