prev-cli 0.5.0 → 0.6.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/dist/cli.js CHANGED
@@ -71,13 +71,50 @@ async function ensureCacheDir(rootDir) {
71
71
  import fg from "fast-glob";
72
72
  import { readFile } from "fs/promises";
73
73
  import path2 from "path";
74
+ function parseFrontmatter(content) {
75
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
76
+ const match = content.match(frontmatterRegex);
77
+ if (!match) {
78
+ return { frontmatter: {}, content };
79
+ }
80
+ const frontmatterStr = match[1];
81
+ const restContent = content.slice(match[0].length);
82
+ const frontmatter = {};
83
+ for (const line of frontmatterStr.split(`
84
+ `)) {
85
+ const colonIndex = line.indexOf(":");
86
+ if (colonIndex === -1)
87
+ continue;
88
+ const key = line.slice(0, colonIndex).trim();
89
+ let value = line.slice(colonIndex + 1).trim();
90
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
91
+ value = value.slice(1, -1);
92
+ }
93
+ if (value === "true")
94
+ value = true;
95
+ else if (value === "false")
96
+ value = false;
97
+ else if (!isNaN(Number(value)) && value !== "")
98
+ value = Number(value);
99
+ if (key) {
100
+ frontmatter[key] = value;
101
+ }
102
+ }
103
+ return { frontmatter, content: restContent };
104
+ }
105
+ function isIndexFile(basename) {
106
+ const lower = basename.toLowerCase();
107
+ return lower === "index" || lower === "readme";
108
+ }
74
109
  function fileToRoute(file) {
75
110
  const withoutExt = file.replace(/\.mdx?$/, "");
76
- if (withoutExt === "index") {
77
- return "/";
78
- }
79
- if (withoutExt.endsWith("/index")) {
80
- return "/" + withoutExt.slice(0, -6);
111
+ const basename = path2.basename(withoutExt).toLowerCase();
112
+ if (basename === "index" || basename === "readme") {
113
+ const dir = path2.dirname(withoutExt);
114
+ if (dir === ".") {
115
+ return "/";
116
+ }
117
+ return "/" + dir;
81
118
  }
82
119
  return "/" + withoutExt;
83
120
  }
@@ -86,30 +123,51 @@ async function scanPages(rootDir) {
86
123
  cwd: rootDir,
87
124
  ignore: ["node_modules/**", "dist/**", ".cache/**"]
88
125
  });
89
- const pages = [];
126
+ const routeMap = new Map;
90
127
  for (const file of files) {
128
+ const route = fileToRoute(file);
129
+ const basename = path2.basename(file, path2.extname(file)).toLowerCase();
130
+ const priority = basename === "index" ? 1 : basename === "readme" ? 2 : 0;
131
+ const existing = routeMap.get(route);
132
+ if (!existing || priority > 0 && priority < existing.priority) {
133
+ routeMap.set(route, { file, priority });
134
+ }
135
+ }
136
+ const pages = [];
137
+ for (const { file } of routeMap.values()) {
91
138
  const fullPath = path2.join(rootDir, file);
92
- const content = await readFile(fullPath, "utf-8");
93
- const title = extractTitle(content, file);
94
- pages.push({
139
+ const rawContent = await readFile(fullPath, "utf-8");
140
+ const { frontmatter, content } = parseFrontmatter(rawContent);
141
+ const title = extractTitle(content, file, frontmatter);
142
+ const page = {
95
143
  route: fileToRoute(file),
96
144
  title,
97
145
  file
98
- });
146
+ };
147
+ if (frontmatter.description) {
148
+ page.description = frontmatter.description;
149
+ }
150
+ if (Object.keys(frontmatter).length > 0) {
151
+ page.frontmatter = frontmatter;
152
+ }
153
+ pages.push(page);
99
154
  }
100
155
  return pages.sort((a, b) => a.route.localeCompare(b.route));
101
156
  }
102
- function extractTitle(content, file) {
157
+ function extractTitle(content, file, frontmatter) {
158
+ if (frontmatter?.title && typeof frontmatter.title === "string") {
159
+ return frontmatter.title;
160
+ }
103
161
  const match = content.match(/^#\s+(.+)$/m);
104
162
  if (match) {
105
163
  return match[1].trim();
106
164
  }
107
- const basename = path2.basename(file, path2.extname(file));
108
- if (basename === "index") {
165
+ const basename = path2.basename(file, path2.extname(file)).toLowerCase();
166
+ if (isIndexFile(basename)) {
109
167
  const dirname = path2.dirname(file);
110
168
  return dirname === "." ? "Home" : capitalize(path2.basename(dirname));
111
169
  }
112
- return capitalize(basename);
170
+ return capitalize(path2.basename(file, path2.extname(file)));
113
171
  }
114
172
  function capitalize(str) {
115
173
  return str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
@@ -1,13 +1,27 @@
1
+ export interface Frontmatter {
2
+ title?: string;
3
+ description?: string;
4
+ [key: string]: unknown;
5
+ }
1
6
  export interface Page {
2
7
  route: string;
3
8
  title: string;
4
9
  file: string;
10
+ description?: string;
11
+ frontmatter?: Frontmatter;
5
12
  }
6
13
  export interface SidebarItem {
7
14
  title: string;
8
15
  route?: string;
9
16
  children?: SidebarItem[];
10
17
  }
18
+ /**
19
+ * Parse YAML frontmatter from markdown content
20
+ */
21
+ export declare function parseFrontmatter(content: string): {
22
+ frontmatter: Frontmatter;
23
+ content: string;
24
+ };
11
25
  export declare function fileToRoute(file: string): string;
12
26
  export declare function scanPages(rootDir: string): Promise<Page[]>;
13
27
  export declare function buildSidebarTree(pages: Page[]): SidebarItem[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",