@marlinjai/clearify 1.9.0 → 1.10.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.
@@ -6,7 +6,7 @@ import {
6
6
  resolveConfig,
7
7
  resolveSections,
8
8
  scanHubProjects
9
- } from "./chunk-PQMKRFBQ.js";
9
+ } from "./chunk-PVTUVUBK.js";
10
10
  import {
11
11
  buildSectionData
12
12
  } from "./chunk-QNC5QRIO.js";
@@ -222,7 +222,7 @@ ${routeEntries.join(",\n")}
222
222
  const userConfig = await loadUserConfig(userRoot);
223
223
  config = resolveConfig(userConfig, userRoot);
224
224
  config = await scanHubProjects(config, userRoot);
225
- const allSections = resolveSections(config, userRoot);
225
+ const allSections = await resolveSections(config, userRoot);
226
226
  const isDev = resolvedViteConfig.command === "serve";
227
227
  visibleSections = isDev ? allSections : allSections.filter((s) => !s.draft);
228
228
  await refreshDocs();
@@ -0,0 +1,105 @@
1
+ // src/core/remote.ts
2
+ import { execSync } from "child_process";
3
+ import { existsSync, mkdirSync } from "fs";
4
+ import { resolve } from "path";
5
+ function repoSlug(repo, ref) {
6
+ return repo.replace(/^(https?:\/\/|git@)/, "").replace(/\.git$/, "").replace(/[/:@]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") + "--" + ref;
7
+ }
8
+ function isPinnedRef(ref) {
9
+ return /^[0-9a-f]{40}$/i.test(ref) || /^v?\d+\.\d+/.test(ref);
10
+ }
11
+ async function resolveRemoteSource(source, cacheDir) {
12
+ const ref = source.ref ?? "main";
13
+ const slug = repoSlug(source.repo, ref);
14
+ const localPath = resolve(cacheDir, slug);
15
+ const sparse = source.sparse !== false;
16
+ mkdirSync(cacheDir, { recursive: true });
17
+ if (existsSync(resolve(localPath, ".git"))) {
18
+ if (!isPinnedRef(ref)) {
19
+ try {
20
+ execSync(`git -C ${JSON.stringify(localPath)} fetch origin ${ref} --depth 1`, {
21
+ stdio: "pipe",
22
+ timeout: 3e4
23
+ });
24
+ execSync(`git -C ${JSON.stringify(localPath)} checkout FETCH_HEAD`, {
25
+ stdio: "pipe"
26
+ });
27
+ } catch (err) {
28
+ console.warn(
29
+ ` \u26A0 Failed to update remote "${source.repo}" (ref: ${ref}), using stale cache.`,
30
+ err instanceof Error ? err.message : ""
31
+ );
32
+ }
33
+ }
34
+ const commit2 = getHeadCommit(localPath);
35
+ return { localPath, resolvedCommit: commit2, cached: true };
36
+ }
37
+ try {
38
+ if (sparse && source.path) {
39
+ execSync(
40
+ `git clone --no-checkout --depth 1 --branch ${ref} ${source.repo} ${JSON.stringify(localPath)}`,
41
+ { stdio: "pipe", timeout: 6e4 }
42
+ );
43
+ execSync(`git -C ${JSON.stringify(localPath)} sparse-checkout init --cone`, {
44
+ stdio: "pipe"
45
+ });
46
+ execSync(`git -C ${JSON.stringify(localPath)} sparse-checkout set ${source.path}`, {
47
+ stdio: "pipe"
48
+ });
49
+ execSync(`git -C ${JSON.stringify(localPath)} checkout`, { stdio: "pipe" });
50
+ } else {
51
+ execSync(
52
+ `git clone --depth 1 --branch ${ref} ${source.repo} ${JSON.stringify(localPath)}`,
53
+ { stdio: "pipe", timeout: 6e4 }
54
+ );
55
+ }
56
+ } catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ throw new Error(
59
+ `Failed to clone remote repo "${source.repo}" (ref: ${ref}): ${msg}`
60
+ );
61
+ }
62
+ const commit = getHeadCommit(localPath);
63
+ return { localPath, resolvedCommit: commit, cached: false };
64
+ }
65
+ function getHeadCommit(repoPath) {
66
+ try {
67
+ return execSync(`git -C ${JSON.stringify(repoPath)} rev-parse HEAD`, {
68
+ encoding: "utf-8",
69
+ stdio: "pipe"
70
+ }).trim();
71
+ } catch {
72
+ return "unknown";
73
+ }
74
+ }
75
+ async function resolveRemoteSections(sections, cacheDir) {
76
+ const results = [];
77
+ for (const section of sections) {
78
+ if (!section.git) {
79
+ results.push(section);
80
+ continue;
81
+ }
82
+ const { localPath, cached } = await resolveRemoteSource(section.git, cacheDir);
83
+ const absoluteDocsDir = resolve(localPath, section.docsDir);
84
+ if (!existsSync(absoluteDocsDir)) {
85
+ throw new Error(
86
+ `Remote section "${section.label}": docs directory "${section.docsDir}" not found in cloned repo "${section.git.repo}".`
87
+ );
88
+ }
89
+ console.log(
90
+ ` Remote section "${section.label}": ${cached ? "cached" : "cloned"} \u2192 ${absoluteDocsDir}`
91
+ );
92
+ results.push({
93
+ ...section,
94
+ docsDir: absoluteDocsDir,
95
+ git: void 0
96
+ // Already resolved — docsDir is now absolute
97
+ });
98
+ }
99
+ return results;
100
+ }
101
+
102
+ export {
103
+ resolveRemoteSource,
104
+ resolveRemoteSections
105
+ };
@@ -2,7 +2,7 @@ import {
2
2
  loadUserConfig,
3
3
  resolveConfig,
4
4
  resolveSections
5
- } from "./chunk-PQMKRFBQ.js";
5
+ } from "./chunk-PVTUVUBK.js";
6
6
  import {
7
7
  scanDocs
8
8
  } from "./chunk-QNC5QRIO.js";
@@ -14,7 +14,7 @@ async function checkLinks() {
14
14
  const userRoot = process.cwd();
15
15
  const userConfig = await loadUserConfig(userRoot);
16
16
  const config = resolveConfig(userConfig, userRoot);
17
- const sections = resolveSections(config, userRoot);
17
+ const sections = await resolveSections(config, userRoot);
18
18
  const validRoutes = /* @__PURE__ */ new Set();
19
19
  const allDocs = [];
20
20
  for (const section of sections) {
@@ -1,3 +1,7 @@
1
+ import {
2
+ resolveRemoteSections
3
+ } from "./chunk-AJBCHKBP.js";
4
+
1
5
  // src/core/config.ts
2
6
  import { z } from "zod";
3
7
  import { resolve, basename, dirname } from "path";
@@ -15,13 +19,20 @@ var NavigationItemSchema = z.lazy(
15
19
  children: z.array(NavigationItemSchema).optional()
16
20
  })
17
21
  );
22
+ var RemoteGitSourceSchema = z.object({
23
+ repo: z.string(),
24
+ ref: z.string().optional(),
25
+ path: z.string().optional(),
26
+ sparse: z.boolean().optional()
27
+ });
18
28
  var SectionConfigSchema = z.object({
19
29
  label: z.string(),
20
30
  docsDir: z.string(),
21
31
  basePath: z.string().optional(),
22
32
  draft: z.boolean().optional(),
23
33
  sitemap: z.boolean().optional(),
24
- exclude: z.array(z.string()).optional()
34
+ exclude: z.array(z.string()).optional(),
35
+ git: RemoteGitSourceSchema.optional()
25
36
  });
26
37
  var MermaidConfigSchema = z.object({
27
38
  strategy: z.enum(["client", "build"]).default("client")
@@ -39,7 +50,14 @@ var HubProjectSchema = z.object({
39
50
  status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
40
51
  icon: z.string().optional(),
41
52
  tags: z.array(z.string()).optional(),
42
- group: z.string().optional()
53
+ group: z.string().optional(),
54
+ mode: z.enum(["link", "embed"]).optional(),
55
+ git: RemoteGitSourceSchema.optional(),
56
+ embedSections: z.union([
57
+ z.literal("all"),
58
+ z.literal("public"),
59
+ z.array(z.string())
60
+ ]).optional()
43
61
  });
44
62
  var HubProjectPartialSchema = z.object({
45
63
  description: z.string(),
@@ -54,7 +72,8 @@ var HubProjectPartialSchema = z.object({
54
72
  }).optional();
55
73
  var HubConfigSchema = z.object({
56
74
  projects: z.array(HubProjectSchema).default([]),
57
- scan: z.string().optional()
75
+ scan: z.string().optional(),
76
+ cacheDir: z.string().optional()
58
77
  }).optional();
59
78
  var ClearifyConfigSchema = z.object({
60
79
  name: z.string().default("Documentation"),
@@ -182,18 +201,25 @@ function resolveConfig(userConfig = {}, root) {
182
201
  function slugify(str) {
183
202
  return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
184
203
  }
185
- function resolveSections(config, root) {
204
+ async function resolveSections(config, root) {
186
205
  if (config.sections && config.sections.length > 0) {
187
- const sections = config.sections.map((s, i) => {
206
+ const hasRemote = config.sections.some((s) => s.git);
207
+ let resolvedInputs = config.sections;
208
+ if (hasRemote) {
209
+ const cacheDir = resolve(root, "node_modules/.cache/clearify-remote");
210
+ resolvedInputs = await resolveRemoteSections(config.sections, cacheDir);
211
+ }
212
+ const sections = resolvedInputs.map((s, i) => {
188
213
  const id = slugify(s.label);
189
214
  const draft = s.draft ?? false;
190
215
  const basePath = s.basePath ?? (i === 0 ? "/" : "/" + id);
191
216
  const sitemap = s.sitemap ?? !draft;
192
217
  const exclude = [...config.exclude ?? [], ...s.exclude ?? []];
218
+ const docsDir = s.docsDir.startsWith("/") ? s.docsDir : resolve(root, s.docsDir);
193
219
  return {
194
220
  id,
195
221
  label: s.label,
196
- docsDir: resolve(root, s.docsDir),
222
+ docsDir,
197
223
  basePath,
198
224
  draft,
199
225
  sitemap,
@@ -222,37 +248,82 @@ function resolveSections(config, root) {
222
248
  ];
223
249
  }
224
250
  async function scanHubProjects(config, root) {
225
- if (!config.hub?.scan) return config;
226
- const { globbySync } = await import("globby");
227
- const pattern = config.hub.scan;
228
- const configPaths = globbySync(pattern, { cwd: root, absolute: true });
251
+ if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed")) {
252
+ return config;
253
+ }
229
254
  const scannedProjects = [];
230
- for (const configPath of configPaths) {
231
- try {
232
- const childConfig = await loadUserConfig(dirname(configPath));
233
- if (!childConfig.hubProject) continue;
234
- const name = childConfig.name ?? basename(dirname(configPath));
235
- const hp = childConfig.hubProject;
236
- scannedProjects.push({
237
- name,
238
- description: hp.description,
239
- href: hp.href ?? childConfig.siteUrl,
240
- repo: hp.repo,
241
- status: hp.status,
242
- icon: hp.icon,
243
- tags: hp.tags,
244
- group: hp.group
245
- });
246
- } catch {
255
+ if (config.hub?.scan) {
256
+ const { globbySync } = await import("globby");
257
+ const pattern = config.hub.scan;
258
+ const configPaths = globbySync(pattern, { cwd: root, absolute: true });
259
+ for (const configPath of configPaths) {
260
+ try {
261
+ const childConfig = await loadUserConfig(dirname(configPath));
262
+ if (!childConfig.hubProject) continue;
263
+ const name = childConfig.name ?? basename(dirname(configPath));
264
+ const hp = childConfig.hubProject;
265
+ scannedProjects.push({
266
+ name,
267
+ description: hp.description,
268
+ href: hp.href ?? childConfig.siteUrl,
269
+ repo: hp.repo,
270
+ status: hp.status,
271
+ icon: hp.icon,
272
+ tags: hp.tags,
273
+ group: hp.group
274
+ });
275
+ } catch {
276
+ }
247
277
  }
248
278
  }
249
- const manualNames = new Set((config.hub.projects ?? []).map((p) => p.name));
279
+ const manualNames = new Set((config.hub?.projects ?? []).map((p) => p.name));
250
280
  const merged = [
251
281
  ...scannedProjects.filter((p) => !manualNames.has(p.name)),
252
- ...config.hub.projects ?? []
282
+ ...config.hub?.projects ?? []
253
283
  ];
284
+ const embedSections = [];
285
+ for (const project of merged) {
286
+ if (project.mode !== "embed" || !project.git) continue;
287
+ const cacheDir = config.hub?.cacheDir ? resolve(root, config.hub.cacheDir) : resolve(root, "node_modules/.cache/clearify-remote");
288
+ try {
289
+ const { resolveRemoteSource } = await import("./core/remote.js");
290
+ const { localPath } = await resolveRemoteSource(project.git, cacheDir);
291
+ const remoteConfig = await loadUserConfig(localPath);
292
+ const remoteSections = remoteConfig.sections ?? [
293
+ { label: remoteConfig.name ?? project.name, docsDir: remoteConfig.docsDir ?? "./docs" }
294
+ ];
295
+ const filter = project.embedSections ?? "public";
296
+ const filtered = remoteSections.filter((s) => {
297
+ if (filter === "all") return true;
298
+ if (filter === "public") return !s.draft;
299
+ if (Array.isArray(filter)) return filter.includes(s.label);
300
+ return true;
301
+ });
302
+ for (const s of filtered) {
303
+ const docsDir = s.docsDir.startsWith("/") ? s.docsDir : resolve(localPath, s.docsDir);
304
+ const label = s.label === "Documentation" ? project.name : s.label;
305
+ const id = slugify(label);
306
+ embedSections.push({
307
+ label,
308
+ docsDir,
309
+ basePath: s.basePath ?? "/" + id,
310
+ draft: s.draft,
311
+ sitemap: s.sitemap,
312
+ exclude: s.exclude
313
+ });
314
+ }
315
+ console.log(` Hub embed "${project.name}": ${filtered.length} sections pulled`);
316
+ } catch (err) {
317
+ console.warn(
318
+ ` \u26A0 Hub embed "${project.name}" failed:`,
319
+ err instanceof Error ? err.message : err
320
+ );
321
+ }
322
+ }
323
+ const finalSections = [...config.sections ?? [], ...embedSections];
254
324
  return {
255
325
  ...config,
326
+ ...embedSections.length > 0 ? { sections: finalSections } : {},
256
327
  hub: {
257
328
  ...config.hub,
258
329
  projects: merged
@@ -8,12 +8,12 @@ import {
8
8
  } from "./chunk-SCZZB7OE.js";
9
9
  import {
10
10
  clearifyPlugin
11
- } from "./chunk-IF6GI4WU.js";
11
+ } from "./chunk-2RAA6477.js";
12
12
  import {
13
13
  loadUserConfig,
14
14
  resolveConfig,
15
15
  resolveSections
16
- } from "./chunk-PQMKRFBQ.js";
16
+ } from "./chunk-PVTUVUBK.js";
17
17
  import {
18
18
  buildSectionData
19
19
  } from "./chunk-QNC5QRIO.js";
@@ -140,7 +140,7 @@ async function buildSite() {
140
140
  const config = resolveConfig(userConfig);
141
141
  const outDir = resolve(userRoot, config.outDir);
142
142
  const ssrOutDir = resolve(outDir, ".ssr");
143
- const allSections = resolveSections(config, userRoot);
143
+ const allSections = await resolveSections(config, userRoot);
144
144
  const productionSections = allSections.filter((s) => !s.draft);
145
145
  const changelogPath = resolve(userRoot, "CHANGELOG.md");
146
146
  const sectionDataList = productionSections.map(
@@ -4,7 +4,7 @@ export { defineConfig } from '../types/index.js';
4
4
  declare const defaultConfig: ClearifyConfig;
5
5
  declare function loadUserConfig(root: string): Promise<Partial<ClearifyConfig>>;
6
6
  declare function resolveConfig(userConfig?: Partial<ClearifyConfig>, root?: string): ClearifyConfig;
7
- declare function resolveSections(config: ClearifyConfig, root: string): ResolvedSection[];
7
+ declare function resolveSections(config: ClearifyConfig, root: string): Promise<ResolvedSection[]>;
8
8
  declare function scanHubProjects(config: ClearifyConfig, root: string): Promise<ClearifyConfig>;
9
9
 
10
10
  export { defaultConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects };
@@ -4,7 +4,8 @@ import {
4
4
  resolveConfig,
5
5
  resolveSections,
6
6
  scanHubProjects
7
- } from "../chunk-PQMKRFBQ.js";
7
+ } from "../chunk-PVTUVUBK.js";
8
+ import "../chunk-AJBCHKBP.js";
8
9
  import {
9
10
  defineConfig
10
11
  } from "../chunk-V7LLYIRO.js";
@@ -0,0 +1,18 @@
1
+ import { SectionConfig, RemoteGitSource } from '../types/index.js';
2
+
3
+ interface ResolvedRemote {
4
+ localPath: string;
5
+ resolvedCommit: string;
6
+ cached: boolean;
7
+ }
8
+ /**
9
+ * Clone or update a git repo into the cache directory.
10
+ */
11
+ declare function resolveRemoteSource(source: RemoteGitSource, cacheDir: string): Promise<ResolvedRemote>;
12
+ /**
13
+ * For each section with a `git` source, clone/update the repo and rewrite
14
+ * `docsDir` to the absolute path inside the cached clone.
15
+ */
16
+ declare function resolveRemoteSections(sections: SectionConfig[], cacheDir: string): Promise<SectionConfig[]>;
17
+
18
+ export { resolveRemoteSections, resolveRemoteSource };
@@ -0,0 +1,8 @@
1
+ import {
2
+ resolveRemoteSections,
3
+ resolveRemoteSource
4
+ } from "../chunk-AJBCHKBP.js";
5
+ export {
6
+ resolveRemoteSections,
7
+ resolveRemoteSource
8
+ };
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-GGUDZD4K.js";
3
+ } from "../chunk-ZNURJXMI.js";
4
4
  import "../chunk-PRTER35L.js";
5
5
  import "../chunk-SCZZB7OE.js";
6
- import "../chunk-IF6GI4WU.js";
6
+ import "../chunk-2RAA6477.js";
7
7
  import "../chunk-B2Q23JW3.js";
8
- import "../chunk-PQMKRFBQ.js";
8
+ import "../chunk-PVTUVUBK.js";
9
+ import "../chunk-AJBCHKBP.js";
9
10
  import "../chunk-V7LLYIRO.js";
10
11
  import "../chunk-QNC5QRIO.js";
11
12
  import "../chunk-5TD7NQIW.js";
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  checkLinks
3
- } from "../chunk-77VHQV7W.js";
4
- import "../chunk-PQMKRFBQ.js";
3
+ } from "../chunk-ELRDUSZS.js";
4
+ import "../chunk-PVTUVUBK.js";
5
+ import "../chunk-AJBCHKBP.js";
5
6
  import "../chunk-V7LLYIRO.js";
6
7
  import "../chunk-QNC5QRIO.js";
7
8
  import "../chunk-5TD7NQIW.js";
@@ -1,24 +1,25 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-GGUDZD4K.js";
3
+ } from "../chunk-ZNURJXMI.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-IF6GI4WU.js";
10
+ } from "../chunk-2RAA6477.js";
11
11
  import "../chunk-B2Q23JW3.js";
12
12
  import {
13
13
  init
14
14
  } from "../chunk-QIAMD2Q6.js";
15
15
  import {
16
16
  checkLinks
17
- } from "../chunk-77VHQV7W.js";
17
+ } from "../chunk-ELRDUSZS.js";
18
18
  import {
19
19
  loadUserConfig,
20
20
  resolveConfig
21
- } from "../chunk-PQMKRFBQ.js";
21
+ } from "../chunk-PVTUVUBK.js";
22
+ import "../chunk-AJBCHKBP.js";
22
23
  import "../chunk-V7LLYIRO.js";
23
24
  import "../chunk-QNC5QRIO.js";
24
25
  import "../chunk-5TD7NQIW.js";
@@ -1,3 +1,9 @@
1
+ interface RemoteGitSource {
2
+ repo: string;
3
+ ref?: string;
4
+ path?: string;
5
+ sparse?: boolean;
6
+ }
1
7
  interface SectionConfig {
2
8
  label: string;
3
9
  docsDir: string;
@@ -5,6 +11,7 @@ interface SectionConfig {
5
11
  draft?: boolean;
6
12
  sitemap?: boolean;
7
13
  exclude?: string[];
14
+ git?: RemoteGitSource;
8
15
  }
9
16
  interface ResolvedSection {
10
17
  id: string;
@@ -88,11 +95,15 @@ interface HubProject {
88
95
  group?: string;
89
96
  hubUrl?: string;
90
97
  hubName?: string;
98
+ mode?: 'link' | 'embed';
99
+ git?: RemoteGitSource;
100
+ embedSections?: 'all' | 'public' | string[];
91
101
  }
92
102
  interface HubConfig {
93
103
  projects: HubProject[];
94
104
  scan?: string;
105
+ cacheDir?: string;
95
106
  }
96
107
  declare function defineConfig(config: Partial<ClearifyConfig>): Partial<ClearifyConfig>;
97
108
 
98
- export { type ClearifyConfig, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
109
+ export { type ClearifyConfig, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  clearifyPlugin
3
- } from "../chunk-IF6GI4WU.js";
3
+ } from "../chunk-2RAA6477.js";
4
4
  import "../chunk-B2Q23JW3.js";
5
- import "../chunk-PQMKRFBQ.js";
5
+ import "../chunk-PVTUVUBK.js";
6
+ import "../chunk-AJBCHKBP.js";
6
7
  import "../chunk-V7LLYIRO.js";
7
8
  import "../chunk-QNC5QRIO.js";
8
9
  import "../chunk-5TD7NQIW.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlinjai/clearify",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
5
5
  "type": "module",
6
6
  "bin": {