doccupine 0.0.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/dist/index.d.ts +1 -0
- package/dist/index.js +526 -0
- package/dist/templates/click-outside.d.ts +1 -0
- package/dist/templates/click-outside.js +28 -0
- package/dist/templates/code.d.ts +1 -0
- package/dist/templates/code.js +192 -0
- package/dist/templates/components/ClickOutside.d.ts +1 -0
- package/dist/templates/components/ClickOutside.js +27 -0
- package/dist/templates/components/Docs.d.ts +1 -0
- package/dist/templates/components/Docs.js +52 -0
- package/dist/templates/components/SideBar.d.ts +1 -0
- package/dist/templates/components/SideBar.js +80 -0
- package/dist/templates/components/layout/Code.d.ts +1 -0
- package/dist/templates/components/layout/Code.js +192 -0
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -0
- package/dist/templates/components/layout/DocsComponents.js +444 -0
- package/dist/templates/components/layout/Footer.d.ts +1 -0
- package/dist/templates/components/layout/Footer.js +8 -0
- package/dist/templates/components/layout/Header.d.ts +1 -0
- package/dist/templates/components/layout/Header.js +280 -0
- package/dist/templates/components/layout/Icon.d.ts +1 -0
- package/dist/templates/components/layout/Icon.js +19 -0
- package/dist/templates/components/layout/Pictograms.d.ts +1 -0
- package/dist/templates/components/layout/Pictograms.js +66 -0
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -0
- package/dist/templates/components/layout/SharedStyles.js +791 -0
- package/dist/templates/components/layout/ThemeToggle.d.ts +1 -0
- package/dist/templates/components/layout/ThemeToggle.js +123 -0
- package/dist/templates/components/layout/Typography.d.ts +1 -0
- package/dist/templates/components/layout/Typography.js +51 -0
- package/dist/templates/docs-components.d.ts +1 -0
- package/dist/templates/docs-components.js +441 -0
- package/dist/templates/docs.d.ts +1 -0
- package/dist/templates/docs.js +48 -0
- package/dist/templates/footer.d.ts +1 -0
- package/dist/templates/footer.js +9 -0
- package/dist/templates/header.d.ts +1 -0
- package/dist/templates/header.js +275 -0
- package/dist/templates/home.d.ts +1 -0
- package/dist/templates/home.js +80 -0
- package/dist/templates/icon.d.ts +1 -0
- package/dist/templates/icon.js +20 -0
- package/dist/templates/layout.d.ts +1 -0
- package/dist/templates/layout.js +66 -0
- package/dist/templates/not-found.d.ts +1 -0
- package/dist/templates/not-found.js +22 -0
- package/dist/templates/pictograms.d.ts +1 -0
- package/dist/templates/pictograms.js +67 -0
- package/dist/templates/shared-styles.d.ts +1 -0
- package/dist/templates/shared-styles.js +792 -0
- package/dist/templates/theme-toggle.d.ts +1 -0
- package/dist/templates/theme-toggle.js +111 -0
- package/dist/templates/theme.d.ts +1 -0
- package/dist/templates/theme.js +291 -0
- package/dist/templates/typography.d.ts +1 -0
- package/dist/templates/typography.js +52 -0
- package/dist/templates/utils/orderNavItems.d.ts +1 -0
- package/dist/templates/utils/orderNavItems.js +45 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import chokidar from "chokidar";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import matter from "gray-matter";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import { homeTemplate } from "./templates/home.js";
|
|
9
|
+
import { notFoundTemplate } from "./templates/not-found.js";
|
|
10
|
+
import { layoutTemplate } from "./templates/layout.js";
|
|
11
|
+
import { themeTemplate } from "./templates/theme.js";
|
|
12
|
+
import { iconTemplate } from "./templates/components/layout/Icon.js";
|
|
13
|
+
import { pictogramsTemplate } from "./templates/components/layout/Pictograms.js";
|
|
14
|
+
import { typographyTemplate } from "./templates/components/layout/Typography.js";
|
|
15
|
+
import { headerTemplate } from "./templates/components/layout/Header.js";
|
|
16
|
+
import { footerTemplate } from "./templates/components/layout/Footer.js";
|
|
17
|
+
import { themeToggleTemplate } from "./templates/components/layout/ThemeToggle.js";
|
|
18
|
+
import { sharedStyledTemplate } from "./templates/components/layout/SharedStyles.js";
|
|
19
|
+
import { codeTemplate } from "./templates/components/layout/Code.js";
|
|
20
|
+
import { docsComponentsTemplate } from "./templates/components/layout/DocsComponents.js";
|
|
21
|
+
import { clickOutsideTemplate } from "./templates/components/ClickOutside.js";
|
|
22
|
+
import { docsTemplate } from "./templates/components/Docs.js";
|
|
23
|
+
import { orderNavItemsTemplate } from "./templates/utils/orderNavItems.js";
|
|
24
|
+
import { sideBarTemplate } from "./templates/components/SideBar.js";
|
|
25
|
+
class MDXToNextJSGenerator {
|
|
26
|
+
watchDir;
|
|
27
|
+
outputDir;
|
|
28
|
+
watcher = null;
|
|
29
|
+
constructor(watchDir, outputDir) {
|
|
30
|
+
this.watchDir = path.resolve(watchDir);
|
|
31
|
+
this.outputDir = path.resolve(outputDir);
|
|
32
|
+
}
|
|
33
|
+
async init() {
|
|
34
|
+
console.log(chalk.blue("š Initializing MDX to Next.js generator..."));
|
|
35
|
+
// Ensure directories exist
|
|
36
|
+
await fs.ensureDir(this.watchDir);
|
|
37
|
+
await fs.ensureDir(this.outputDir);
|
|
38
|
+
// Create initial Next.js structure
|
|
39
|
+
await this.createNextJSStructure();
|
|
40
|
+
// Process existing files
|
|
41
|
+
await this.processAllMDXFiles();
|
|
42
|
+
console.log(chalk.green("ā
Initial setup complete!"));
|
|
43
|
+
console.log(chalk.cyan("š” To start the Next.js dev server:"));
|
|
44
|
+
console.log(chalk.white(` cd ${path.relative(process.cwd(), this.outputDir)}`));
|
|
45
|
+
console.log(chalk.white(" npm install && npm run dev"));
|
|
46
|
+
}
|
|
47
|
+
async createNextJSStructure() {
|
|
48
|
+
const structure = {
|
|
49
|
+
"package.json": this.generatePackageJson(),
|
|
50
|
+
"next.config.ts": this.generateNextConfig(),
|
|
51
|
+
"tsconfig.json": this.generateTSConfig(),
|
|
52
|
+
"app/layout.tsx": await this.generateRootLayout(),
|
|
53
|
+
"app/page.tsx": this.generateHomePage(),
|
|
54
|
+
"app/not-found.tsx": this.generateNotFoundPage(),
|
|
55
|
+
"app/theme.ts": this.generateTheme(),
|
|
56
|
+
"components/layout/Icon.tsx": this.generateIcon(),
|
|
57
|
+
"components/layout/Pictograms.tsx": this.generatePictograms(),
|
|
58
|
+
"components/layout/Typography.ts": this.generateTypography(),
|
|
59
|
+
"components/layout/Header.tsx": this.generateHeader(),
|
|
60
|
+
"components/layout/Footer.tsx": this.generateFooter(),
|
|
61
|
+
"components/layout/ThemeToggle.tsx": this.generateThemeToggle(),
|
|
62
|
+
"components/layout/SharedStyled.ts": this.generateSharedStyled(),
|
|
63
|
+
"components/layout/Code.tsx": this.generateCode(),
|
|
64
|
+
"components/layout/DocsComponents.tsx": this.generateDocsComponents(),
|
|
65
|
+
"components/ClickOutside.ts": this.generateClickOutside(),
|
|
66
|
+
"components/Docs.tsx": this.generateDocs(),
|
|
67
|
+
"components/SideBar.tsx": this.generateSideBar(),
|
|
68
|
+
"utils/orderNavItems.ts": this.generateOrderNavItems(),
|
|
69
|
+
};
|
|
70
|
+
for (const [filePath, content] of Object.entries(structure)) {
|
|
71
|
+
const fullPath = path.join(this.outputDir, filePath);
|
|
72
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
73
|
+
await fs.writeFile(fullPath, String(content), "utf8");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async startWatching() {
|
|
77
|
+
console.log(chalk.yellow(`š Watching for changes in: ${this.watchDir}`));
|
|
78
|
+
this.watcher = chokidar.watch("**/*.mdx", {
|
|
79
|
+
cwd: this.watchDir,
|
|
80
|
+
persistent: true,
|
|
81
|
+
ignoreInitial: false,
|
|
82
|
+
});
|
|
83
|
+
this.watcher
|
|
84
|
+
.on("add", (filePath) => this.handleFileChange("added", filePath))
|
|
85
|
+
.on("change", (filePath) => this.handleFileChange("changed", filePath))
|
|
86
|
+
.on("unlink", (filePath) => this.handleFileDelete(filePath));
|
|
87
|
+
}
|
|
88
|
+
async handleFileChange(action, filePath) {
|
|
89
|
+
console.log(chalk.cyan(`š File ${action}: ${filePath}`));
|
|
90
|
+
const fullPath = path.join(this.watchDir, filePath);
|
|
91
|
+
try {
|
|
92
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
93
|
+
const { data: frontmatter, content: mdxContent } = matter(content);
|
|
94
|
+
if (filePath === "index.mdx" || filePath === "./index.mdx") {
|
|
95
|
+
console.log(chalk.blue("š Updating homepage with index.mdx content"));
|
|
96
|
+
await this.updatePagesIndex();
|
|
97
|
+
await this.updateRootLayout();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const mdxFile = {
|
|
101
|
+
path: filePath,
|
|
102
|
+
content: mdxContent,
|
|
103
|
+
frontmatter,
|
|
104
|
+
slug: this.generateSlug(filePath),
|
|
105
|
+
};
|
|
106
|
+
await this.generatePageFromMDX(mdxFile);
|
|
107
|
+
await this.updatePagesIndex();
|
|
108
|
+
await this.updateRootLayout();
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.green(`ā
Generated page for: ${filePath}`));
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error(chalk.red(`ā Error processing ${filePath}:`), error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async handleFileDelete(filePath) {
|
|
117
|
+
console.log(chalk.red(`šļø File deleted: ${filePath}`));
|
|
118
|
+
try {
|
|
119
|
+
if (filePath === "index.mdx" || filePath === "./index.mdx") {
|
|
120
|
+
console.log(chalk.blue("š Updating homepage - index.mdx deleted"));
|
|
121
|
+
await this.updatePagesIndex();
|
|
122
|
+
await this.updateRootLayout();
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const slug = this.generateSlug(filePath);
|
|
126
|
+
const pagePath = path.join(this.outputDir, "app", slug);
|
|
127
|
+
await fs.remove(pagePath);
|
|
128
|
+
await this.updatePagesIndex();
|
|
129
|
+
await this.updateRootLayout();
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk.green(`ā
Removed page for: ${filePath}`));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(chalk.red(`ā Error removing page for ${filePath}:`), error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async processAllMDXFiles() {
|
|
138
|
+
const files = await this.getAllMDXFiles();
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
await this.handleFileChange("processed", file);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async getAllMDXFiles() {
|
|
144
|
+
const files = [];
|
|
145
|
+
async function scanDir(dir, relativePath = "") {
|
|
146
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
const fullPath = path.join(dir, entry.name);
|
|
149
|
+
const relPath = path.join(relativePath, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
await scanDir(fullPath, relPath);
|
|
152
|
+
}
|
|
153
|
+
else if (entry.name.endsWith(".mdx")) {
|
|
154
|
+
files.push(relPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
await scanDir(this.watchDir);
|
|
159
|
+
return files;
|
|
160
|
+
}
|
|
161
|
+
generateSlug(filePath) {
|
|
162
|
+
if (filePath === "index.mdx" || filePath === "./index.mdx") {
|
|
163
|
+
return "";
|
|
164
|
+
}
|
|
165
|
+
return filePath
|
|
166
|
+
.replace(/\.mdx$/, "")
|
|
167
|
+
.replace(/\\/g, "/")
|
|
168
|
+
.replace(/[^a-zA-Z0-9\/\-_]/g, "-")
|
|
169
|
+
.toLowerCase();
|
|
170
|
+
}
|
|
171
|
+
async generatePageFromMDX(mdxFile) {
|
|
172
|
+
const files = await this.getAllMDXFiles();
|
|
173
|
+
const pages = [];
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
const fullPath = path.join(this.watchDir, file);
|
|
176
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
177
|
+
const { data: frontmatter } = matter(content);
|
|
178
|
+
pages.push({
|
|
179
|
+
slug: this.generateSlug(file),
|
|
180
|
+
title: frontmatter.title || "Untitled",
|
|
181
|
+
description: frontmatter.description || "",
|
|
182
|
+
date: frontmatter.date || null,
|
|
183
|
+
category: frontmatter.category || "",
|
|
184
|
+
path: file,
|
|
185
|
+
categoryOrder: frontmatter.categoryOrder || 0,
|
|
186
|
+
order: frontmatter.order || 0,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const pageContent = `import { Metadata } from "next";
|
|
190
|
+
import { Docs } from "@/components/Docs";
|
|
191
|
+
|
|
192
|
+
const content = \`${mdxFile.content.replace(/`/g, "\\`")}\`;
|
|
193
|
+
|
|
194
|
+
export const metadata: Metadata = {
|
|
195
|
+
title: '${mdxFile.frontmatter.title || "Generated with Doccupine"}',
|
|
196
|
+
description: '${mdxFile.frontmatter.description || "Automatically generated from MDX files using Doccupine"}',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export default function Page() {
|
|
200
|
+
return (
|
|
201
|
+
<Docs content={content} />
|
|
202
|
+
);
|
|
203
|
+
}`;
|
|
204
|
+
const pagePath = path.join(this.outputDir, "app", mdxFile.slug, "page.tsx");
|
|
205
|
+
await fs.ensureDir(path.dirname(pagePath));
|
|
206
|
+
await fs.writeFile(pagePath, pageContent, "utf8");
|
|
207
|
+
}
|
|
208
|
+
async updatePagesIndex() {
|
|
209
|
+
const files = await this.getAllMDXFiles();
|
|
210
|
+
let indexMDX = null;
|
|
211
|
+
for (const file of files) {
|
|
212
|
+
const fullPath = path.join(this.watchDir, file);
|
|
213
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
214
|
+
const { data: frontmatter, content: mdxContent } = matter(content);
|
|
215
|
+
if (file === "index.mdx" || file === "./index.mdx") {
|
|
216
|
+
indexMDX = {
|
|
217
|
+
content: mdxContent,
|
|
218
|
+
frontmatter,
|
|
219
|
+
title: frontmatter.title || "Welcome",
|
|
220
|
+
category: frontmatter.category || "",
|
|
221
|
+
description: frontmatter.description || "",
|
|
222
|
+
categoryOrder: frontmatter.categoryOrder || 0,
|
|
223
|
+
order: frontmatter.order || 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const indexContent = `import { Metadata } from "next";
|
|
228
|
+
import { Docs } from "@/components/Docs";
|
|
229
|
+
|
|
230
|
+
${indexMDX ? `const indexContent = \`${indexMDX.content.replace(/`/g, "\\`")}\`;` : `const indexContent = null;`}
|
|
231
|
+
|
|
232
|
+
${indexMDX
|
|
233
|
+
? `export const metadata: Metadata = {
|
|
234
|
+
title: '${indexMDX.title}',
|
|
235
|
+
description: '${indexMDX.description}',
|
|
236
|
+
};`
|
|
237
|
+
: `export const metadata: Metadata = {
|
|
238
|
+
title: 'Generated with Doccupine',
|
|
239
|
+
description: 'Automatically generated from MDX files using Doccupine',
|
|
240
|
+
};`}
|
|
241
|
+
|
|
242
|
+
export default function Home() {
|
|
243
|
+
return (
|
|
244
|
+
<Docs content={indexContent} />
|
|
245
|
+
);
|
|
246
|
+
}`;
|
|
247
|
+
await fs.writeFile(path.join(this.outputDir, "app", "page.tsx"), indexContent, "utf8");
|
|
248
|
+
}
|
|
249
|
+
// Configuration file generators
|
|
250
|
+
generatePackageJson() {
|
|
251
|
+
return JSON.stringify({
|
|
252
|
+
name: "doccupine",
|
|
253
|
+
version: "0.1.0",
|
|
254
|
+
private: true,
|
|
255
|
+
scripts: {
|
|
256
|
+
dev: "next dev",
|
|
257
|
+
build: "next build",
|
|
258
|
+
start: "next start",
|
|
259
|
+
lint: "next lint",
|
|
260
|
+
},
|
|
261
|
+
dependencies: {
|
|
262
|
+
next: "15.5.2",
|
|
263
|
+
react: "19.1.1",
|
|
264
|
+
"react-dom": "19.1.1",
|
|
265
|
+
},
|
|
266
|
+
devDependencies: {
|
|
267
|
+
"@types/node": "^24",
|
|
268
|
+
"@types/react": "^19",
|
|
269
|
+
"@types/react-dom": "^19",
|
|
270
|
+
"cherry-styled-components": "^0.1.0-43",
|
|
271
|
+
eslint: "^9",
|
|
272
|
+
"eslint-config-next": "15.5.2",
|
|
273
|
+
"lucide-react": "^0.542.0",
|
|
274
|
+
polished: "^4.3.1",
|
|
275
|
+
prettier: "^3.6.2",
|
|
276
|
+
"react-markdown": "^10.1.0",
|
|
277
|
+
"rehype-highlight": "^7.0.2",
|
|
278
|
+
"rehype-parse": "^9.0.1",
|
|
279
|
+
"rehype-stringify": "^10.0.1",
|
|
280
|
+
"remark-gfm": "^4.0.1",
|
|
281
|
+
"styled-components": "^6.1.19",
|
|
282
|
+
typescript: "^5",
|
|
283
|
+
},
|
|
284
|
+
}, null, 2);
|
|
285
|
+
}
|
|
286
|
+
generateNextConfig() {
|
|
287
|
+
return `import type { NextConfig } from "next";
|
|
288
|
+
|
|
289
|
+
const nextConfig: NextConfig = {
|
|
290
|
+
compiler: {
|
|
291
|
+
styledComponents: true,
|
|
292
|
+
},
|
|
293
|
+
transpilePackages: ["lucide-react"],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export default nextConfig;
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
generateTSConfig() {
|
|
300
|
+
return JSON.stringify({
|
|
301
|
+
compilerOptions: {
|
|
302
|
+
target: "es5",
|
|
303
|
+
lib: ["dom", "dom.iterable", "es6"],
|
|
304
|
+
allowJs: true,
|
|
305
|
+
skipLibCheck: true,
|
|
306
|
+
strict: true,
|
|
307
|
+
noEmit: true,
|
|
308
|
+
esModuleInterop: true,
|
|
309
|
+
module: "esnext",
|
|
310
|
+
moduleResolution: "bundler",
|
|
311
|
+
resolveJsonModule: true,
|
|
312
|
+
isolatedModules: true,
|
|
313
|
+
jsx: "preserve",
|
|
314
|
+
incremental: true,
|
|
315
|
+
plugins: [{ name: "next" }],
|
|
316
|
+
baseUrl: ".",
|
|
317
|
+
paths: {
|
|
318
|
+
"@/*": ["./*"],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
include: [
|
|
322
|
+
"next-env.d.ts",
|
|
323
|
+
"**/*.ts",
|
|
324
|
+
"**/*.tsx",
|
|
325
|
+
".next/types/**/*.ts",
|
|
326
|
+
],
|
|
327
|
+
exclude: ["node_modules"],
|
|
328
|
+
}, null, 2);
|
|
329
|
+
}
|
|
330
|
+
async generateRootLayout() {
|
|
331
|
+
const files = await this.getAllMDXFiles();
|
|
332
|
+
const pages = [];
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
const fullPath = path.join(this.watchDir, file);
|
|
335
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
336
|
+
const { data: frontmatter } = matter(content);
|
|
337
|
+
pages.push({
|
|
338
|
+
slug: this.generateSlug(file),
|
|
339
|
+
title: frontmatter.title || "Untitled",
|
|
340
|
+
description: frontmatter.description || "",
|
|
341
|
+
date: frontmatter.date || null,
|
|
342
|
+
category: frontmatter.category || "",
|
|
343
|
+
path: file,
|
|
344
|
+
categoryOrder: frontmatter.categoryOrder || 0,
|
|
345
|
+
order: frontmatter.order || 0,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return layoutTemplate(pages);
|
|
349
|
+
}
|
|
350
|
+
async updateRootLayout() {
|
|
351
|
+
const layoutContent = await this.generateRootLayout();
|
|
352
|
+
await fs.writeFile(path.join(this.outputDir, "app", "layout.tsx"), layoutContent, "utf8");
|
|
353
|
+
}
|
|
354
|
+
generateHomePage() {
|
|
355
|
+
return homeTemplate;
|
|
356
|
+
}
|
|
357
|
+
generateNotFoundPage() {
|
|
358
|
+
return notFoundTemplate;
|
|
359
|
+
}
|
|
360
|
+
generateTheme() {
|
|
361
|
+
return themeTemplate;
|
|
362
|
+
}
|
|
363
|
+
generateIcon() {
|
|
364
|
+
return iconTemplate;
|
|
365
|
+
}
|
|
366
|
+
generatePictograms() {
|
|
367
|
+
return pictogramsTemplate;
|
|
368
|
+
}
|
|
369
|
+
generateClickOutside() {
|
|
370
|
+
return clickOutsideTemplate;
|
|
371
|
+
}
|
|
372
|
+
generateTypography() {
|
|
373
|
+
return typographyTemplate;
|
|
374
|
+
}
|
|
375
|
+
generateHeader() {
|
|
376
|
+
return headerTemplate;
|
|
377
|
+
}
|
|
378
|
+
generateFooter() {
|
|
379
|
+
return footerTemplate;
|
|
380
|
+
}
|
|
381
|
+
generateThemeToggle() {
|
|
382
|
+
return themeToggleTemplate;
|
|
383
|
+
}
|
|
384
|
+
generateSharedStyled() {
|
|
385
|
+
return sharedStyledTemplate;
|
|
386
|
+
}
|
|
387
|
+
generateCode() {
|
|
388
|
+
return codeTemplate;
|
|
389
|
+
}
|
|
390
|
+
generateDocsComponents() {
|
|
391
|
+
return docsComponentsTemplate;
|
|
392
|
+
}
|
|
393
|
+
generateDocs() {
|
|
394
|
+
return docsTemplate;
|
|
395
|
+
}
|
|
396
|
+
generateSideBar() {
|
|
397
|
+
return sideBarTemplate;
|
|
398
|
+
}
|
|
399
|
+
generateOrderNavItems() {
|
|
400
|
+
return orderNavItemsTemplate;
|
|
401
|
+
}
|
|
402
|
+
stop() {
|
|
403
|
+
if (this.watcher) {
|
|
404
|
+
this.watcher.close();
|
|
405
|
+
console.log(chalk.yellow("š Stopped watching for changes"));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// CLI Commands
|
|
410
|
+
program
|
|
411
|
+
.name("doccupine")
|
|
412
|
+
.description("Watch MDX files and generate Next.js documentation pages automatically")
|
|
413
|
+
.version("0.0.1");
|
|
414
|
+
program
|
|
415
|
+
.command("watch", { isDefault: true })
|
|
416
|
+
.description("Watch a directory for MDX changes and generate Next.js app")
|
|
417
|
+
.option("--port <port>", "Port for Next.js dev server", "3000")
|
|
418
|
+
.option("--verbose", "Show verbose output")
|
|
419
|
+
.action(async (options) => {
|
|
420
|
+
const questions = [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
name: "watchDir",
|
|
424
|
+
message: "Enter directory to watch for MDX files:",
|
|
425
|
+
initial: "docs",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
type: "text",
|
|
429
|
+
name: "outputDir",
|
|
430
|
+
message: "Enter output directory for Next.js app:",
|
|
431
|
+
initial: "nextjs-app",
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
const { watchDir: watchDirInput, outputDir: outputDirInput } = (await prompts(questions));
|
|
435
|
+
const watchDir = path.resolve(process.cwd(), watchDirInput);
|
|
436
|
+
const outputDir = path.resolve(process.cwd(), outputDirInput);
|
|
437
|
+
const generator = new MDXToNextJSGenerator(watchDir, outputDir);
|
|
438
|
+
await generator.init();
|
|
439
|
+
let devServer = null;
|
|
440
|
+
console.log(chalk.blue("š¦ Installing dependencies..."));
|
|
441
|
+
const { spawn } = await import("child_process");
|
|
442
|
+
// Install dependencies first
|
|
443
|
+
const install = spawn("npm", ["install"], {
|
|
444
|
+
cwd: outputDir,
|
|
445
|
+
stdio: "pipe",
|
|
446
|
+
});
|
|
447
|
+
await new Promise((resolve, reject) => {
|
|
448
|
+
install.on("close", (code) => {
|
|
449
|
+
if (code === 0) {
|
|
450
|
+
console.log(chalk.green("ā
Dependencies installed"));
|
|
451
|
+
resolve(void 0);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
install.on("error", reject);
|
|
458
|
+
});
|
|
459
|
+
// Start dev server
|
|
460
|
+
console.log(chalk.blue(`š Starting Next.js dev server on port ${options.port}...`));
|
|
461
|
+
devServer = spawn("npm", ["run", "dev", "--", "--port", options.port], {
|
|
462
|
+
cwd: outputDir,
|
|
463
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
464
|
+
});
|
|
465
|
+
devServer.stdout.on("data", (data) => {
|
|
466
|
+
const output = data.toString();
|
|
467
|
+
if (output.includes("Ready") || output.includes("started")) {
|
|
468
|
+
console.log(chalk.green(`š Next.js ready at http://localhost:${options.port}`));
|
|
469
|
+
}
|
|
470
|
+
// Filter out noisy Next.js logs, show important ones
|
|
471
|
+
if (output.includes("compiled") ||
|
|
472
|
+
output.includes("error") ||
|
|
473
|
+
output.includes("Ready")) {
|
|
474
|
+
process.stdout.write(chalk.gray("[Next.js] ") + output);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
if (options.verbose) {
|
|
478
|
+
devServer.stderr.on("data", (data) => {
|
|
479
|
+
process.stderr.write(chalk.red("[Next.js Error] ") + data.toString());
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
devServer.on("error", (error) => {
|
|
483
|
+
console.error(chalk.red("ā Error starting dev server:"), error);
|
|
484
|
+
});
|
|
485
|
+
await generator.startWatching();
|
|
486
|
+
// Handle graceful shutdown
|
|
487
|
+
process.on("SIGINT", () => {
|
|
488
|
+
console.log(chalk.yellow("\nš Shutting down..."));
|
|
489
|
+
generator.stop();
|
|
490
|
+
if (devServer) {
|
|
491
|
+
devServer.kill();
|
|
492
|
+
}
|
|
493
|
+
process.exit(0);
|
|
494
|
+
});
|
|
495
|
+
console.log(chalk.green("š Generator is running! Press Ctrl+C to stop."));
|
|
496
|
+
if (options.dev) {
|
|
497
|
+
console.log(chalk.cyan(`š Edit your MDX files in: ${watchDir}`));
|
|
498
|
+
console.log(chalk.cyan(`š View changes at: http://localhost:${options.port}`));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
program
|
|
502
|
+
.command("build")
|
|
503
|
+
.description("One-time build of Next.js app from MDX files")
|
|
504
|
+
.action(async () => {
|
|
505
|
+
const questions = [
|
|
506
|
+
{
|
|
507
|
+
type: "text",
|
|
508
|
+
name: "watchDir",
|
|
509
|
+
message: "Enter directory to watch for MDX files:",
|
|
510
|
+
initial: "docs",
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
type: "text",
|
|
514
|
+
name: "outputDir",
|
|
515
|
+
message: "Enter output directory for Next.js app:",
|
|
516
|
+
initial: "nextjs-app",
|
|
517
|
+
},
|
|
518
|
+
];
|
|
519
|
+
const { watchDir: watchDirInput, outputDir: outputDirInput } = (await prompts(questions));
|
|
520
|
+
const watchDir = path.resolve(process.cwd(), watchDirInput);
|
|
521
|
+
const outputDir = path.resolve(process.cwd(), outputDirInput);
|
|
522
|
+
const generator = new MDXToNextJSGenerator(watchDir, outputDir);
|
|
523
|
+
await generator.init();
|
|
524
|
+
console.log(chalk.green("š Build complete!"));
|
|
525
|
+
});
|
|
526
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const clickOutsideTemplate = "\nimport { RefObject, useEffect } from \"react\";\n\nexport function useOnClickOutside(\n refs: RefObject<HTMLElement | null>[],\n cb: () => void,\n) {\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (\n refs &&\n refs\n .map(\n (ref) =>\n ref && ref.current && ref.current.contains(event.target as Node),\n )\n .every((i) => i === false)\n ) {\n cb();\n }\n }\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [refs, cb]);\n}\n";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const clickOutsideTemplate = `
|
|
2
|
+
import { RefObject, useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export function useOnClickOutside(
|
|
5
|
+
refs: RefObject<HTMLElement | null>[],
|
|
6
|
+
cb: () => void,
|
|
7
|
+
) {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
function handleClickOutside(event: MouseEvent) {
|
|
10
|
+
if (
|
|
11
|
+
refs &&
|
|
12
|
+
refs
|
|
13
|
+
.map(
|
|
14
|
+
(ref) =>
|
|
15
|
+
ref && ref.current && ref.current.contains(event.target as Node),
|
|
16
|
+
)
|
|
17
|
+
.every((i) => i === false)
|
|
18
|
+
) {
|
|
19
|
+
cb();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
23
|
+
return () => {
|
|
24
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
25
|
+
};
|
|
26
|
+
}, [refs, cb]);
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const codeTemplate = "\"use client\";\n\"use client\";\nimport styled from \"styled-components\";\nimport { Theme, styledCode } from \"cherry-styled-components/src/lib\";\nimport { rgba } from \"polished\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { editableContent } from \"@/app/components/layout/SharedStyled\";\n\ninterface CodeProps extends React.HTMLAttributes<HTMLDivElement> {\n code: string;\n language?: string;\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px\n ${({ theme }) =>\n theme.isDark ? rgba(theme.colors.dark, 0.2) : rgba(theme.colors.dark, 0)};\n`;\n\nconst TopBar = styled.div<{ theme: Theme }>`\n background: #0d1117;\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px ${rgba(\"#ffffff\", 0.1)};\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: flex-start;\n gap: 5px;\n padding: 10px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n margin: auto 0;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: ${rgba(\"#ffffff\", 0.1)};\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #0d1117;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #ffffff;\n padding: 20px;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(100svh - 400px);\n ${({ theme }) => styledCode(theme)};\n\n &[contenteditable=\"true\"] {\n ${editableContent};\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\n }\n\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #ff7b72;\n }\n\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\n }\n\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #79c0ff;\n }\n\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n\n & .hljs-subst {\n color: #c9d1d9;\n }\n\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n\n & .hljs-bullet {\n color: #f2cc60;\n }\n\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n`;\n\nconst highlightCode = (code: string, language: string): string => {\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `<pre><code class=\"language-${language}\">${code}</code></pre>`,\n );\n\n return String(result);\n};\n\nfunction Code({ code, language = \"javascript\", ...props }: CodeProps) {\n const highlightedCode = highlightCode(code, language);\n return (\n <CodeWrapper>\n <TopBar>\n <Dot />\n <Dot />\n <Dot />\n </TopBar>\n <Body dangerouslySetInnerHTML={{ __html: highlightedCode }} {...props} />\n </CodeWrapper>\n );\n}\n\nexport { Code };\n";
|