create-unmint 1.2.0 → 1.3.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.js +643 -6
- package/package.json +1 -1
- package/template/lib/theme-config.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk5 from "chalk";
|
|
6
6
|
import figlet from "figlet";
|
|
7
7
|
import gradient from "gradient-string";
|
|
8
|
+
import fs7 from "fs-extra";
|
|
9
|
+
import path7 from "path";
|
|
8
10
|
|
|
9
11
|
// src/commands/init.ts
|
|
10
12
|
import fs2 from "fs-extra";
|
|
@@ -109,6 +111,66 @@ function getDefaultConfig(projectName) {
|
|
|
109
111
|
installDeps: true
|
|
110
112
|
};
|
|
111
113
|
}
|
|
114
|
+
async function promptAddConfig(hasExistingDocs, customPath) {
|
|
115
|
+
const answers = await inquirer.prompt([
|
|
116
|
+
{
|
|
117
|
+
type: "input",
|
|
118
|
+
name: "docsRoute",
|
|
119
|
+
message: "Add Unmint docs at which route?",
|
|
120
|
+
default: customPath || (hasExistingDocs ? "/documentation" : "/docs"),
|
|
121
|
+
validate: (input) => {
|
|
122
|
+
if (!input.startsWith("/")) return "Route must start with /";
|
|
123
|
+
if (!/^\/[a-z0-9-/]*$/i.test(input)) {
|
|
124
|
+
return "Route can only contain letters, numbers, hyphens, and slashes";
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: "input",
|
|
131
|
+
name: "title",
|
|
132
|
+
message: "Docs title:",
|
|
133
|
+
default: "Documentation"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "input",
|
|
137
|
+
name: "description",
|
|
138
|
+
message: "Docs description:",
|
|
139
|
+
default: "Documentation for your project"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: "list",
|
|
143
|
+
name: "accentColor",
|
|
144
|
+
message: "Accent color:",
|
|
145
|
+
choices: accentColors.map((c) => ({
|
|
146
|
+
name: c.name === "Cyan" ? `${c.name} ${chalk.dim("(default)")}` : c.name,
|
|
147
|
+
value: c.value
|
|
148
|
+
})),
|
|
149
|
+
default: "#0891b2"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: "input",
|
|
153
|
+
name: "customAccent",
|
|
154
|
+
message: "Custom accent color (hex):",
|
|
155
|
+
when: (answers2) => answers2.accentColor === "custom",
|
|
156
|
+
validate: (input) => {
|
|
157
|
+
if (!/^#[0-9a-f]{6}$/i.test(input)) {
|
|
158
|
+
return "Please enter a valid hex color (e.g., #ff5733)";
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
]);
|
|
164
|
+
if (answers.accentColor === "custom" && answers.customAccent) {
|
|
165
|
+
answers.accentColor = answers.customAccent;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
docsRoute: answers.docsRoute,
|
|
169
|
+
title: answers.title,
|
|
170
|
+
description: answers.description,
|
|
171
|
+
accentColor: answers.accentColor
|
|
172
|
+
};
|
|
173
|
+
}
|
|
112
174
|
|
|
113
175
|
// src/scaffold.ts
|
|
114
176
|
import fs from "fs-extra";
|
|
@@ -488,6 +550,567 @@ async function applyUpdates(projectDir, changes) {
|
|
|
488
550
|
return results;
|
|
489
551
|
}
|
|
490
552
|
|
|
553
|
+
// src/commands/add.ts
|
|
554
|
+
import chalk4 from "chalk";
|
|
555
|
+
import ora3 from "ora";
|
|
556
|
+
import path6 from "path";
|
|
557
|
+
import fs6 from "fs-extra";
|
|
558
|
+
import { execa as execa2 } from "execa";
|
|
559
|
+
|
|
560
|
+
// src/utils/detect-project.ts
|
|
561
|
+
import fs4 from "fs-extra";
|
|
562
|
+
import path4 from "path";
|
|
563
|
+
async function detectProject(cwd) {
|
|
564
|
+
const result = {
|
|
565
|
+
isExistingProject: false,
|
|
566
|
+
framework: "unknown",
|
|
567
|
+
useSrcDir: false,
|
|
568
|
+
appDir: "app",
|
|
569
|
+
hasExistingDocs: false,
|
|
570
|
+
hasFumadocs: false,
|
|
571
|
+
packageManager: "npm",
|
|
572
|
+
nextConfigPath: null,
|
|
573
|
+
globalsCssPath: null,
|
|
574
|
+
tailwindConfigPath: null
|
|
575
|
+
};
|
|
576
|
+
const packageJsonPath = path4.join(cwd, "package.json");
|
|
577
|
+
if (!await fs4.pathExists(packageJsonPath)) {
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
result.isExistingProject = true;
|
|
581
|
+
const nextConfigExtensions = ["js", "ts", "mjs"];
|
|
582
|
+
for (const ext of nextConfigExtensions) {
|
|
583
|
+
const configPath = path4.join(cwd, `next.config.${ext}`);
|
|
584
|
+
if (await fs4.pathExists(configPath)) {
|
|
585
|
+
result.nextConfigPath = configPath;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (!result.nextConfigPath) {
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
const srcAppPath = path4.join(cwd, "src/app");
|
|
593
|
+
const appPath = path4.join(cwd, "app");
|
|
594
|
+
if (await fs4.pathExists(srcAppPath)) {
|
|
595
|
+
result.useSrcDir = true;
|
|
596
|
+
result.appDir = "src/app";
|
|
597
|
+
result.framework = "next-app";
|
|
598
|
+
} else if (await fs4.pathExists(appPath)) {
|
|
599
|
+
result.useSrcDir = false;
|
|
600
|
+
result.appDir = "app";
|
|
601
|
+
result.framework = "next-app";
|
|
602
|
+
} else {
|
|
603
|
+
const pagesPath = path4.join(cwd, "pages");
|
|
604
|
+
const srcPagesPath = path4.join(cwd, "src/pages");
|
|
605
|
+
if (await fs4.pathExists(pagesPath) || await fs4.pathExists(srcPagesPath)) {
|
|
606
|
+
result.framework = "next-pages";
|
|
607
|
+
}
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
const docsPath = path4.join(cwd, result.appDir, "docs");
|
|
611
|
+
result.hasExistingDocs = await fs4.pathExists(docsPath);
|
|
612
|
+
try {
|
|
613
|
+
const pkg = await fs4.readJson(packageJsonPath);
|
|
614
|
+
const allDeps = {
|
|
615
|
+
...pkg.dependencies,
|
|
616
|
+
...pkg.devDependencies
|
|
617
|
+
};
|
|
618
|
+
result.hasFumadocs = "fumadocs-core" in allDeps || "fumadocs-mdx" in allDeps;
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
result.packageManager = await detectPackageManager2(cwd);
|
|
622
|
+
const globalsCssPaths = [
|
|
623
|
+
path4.join(cwd, result.appDir, "globals.css"),
|
|
624
|
+
path4.join(cwd, "src/styles/globals.css"),
|
|
625
|
+
path4.join(cwd, "styles/globals.css")
|
|
626
|
+
];
|
|
627
|
+
for (const cssPath of globalsCssPaths) {
|
|
628
|
+
if (await fs4.pathExists(cssPath)) {
|
|
629
|
+
result.globalsCssPath = cssPath;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const tailwindConfigExtensions = ["ts", "js", "mjs"];
|
|
634
|
+
for (const ext of tailwindConfigExtensions) {
|
|
635
|
+
const configPath = path4.join(cwd, `tailwind.config.${ext}`);
|
|
636
|
+
if (await fs4.pathExists(configPath)) {
|
|
637
|
+
result.tailwindConfigPath = configPath;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
async function detectPackageManager2(cwd) {
|
|
644
|
+
if (await fs4.pathExists(path4.join(cwd, "pnpm-lock.yaml"))) {
|
|
645
|
+
return "pnpm";
|
|
646
|
+
}
|
|
647
|
+
if (await fs4.pathExists(path4.join(cwd, "yarn.lock"))) {
|
|
648
|
+
return "yarn";
|
|
649
|
+
}
|
|
650
|
+
if (await fs4.pathExists(path4.join(cwd, "bun.lockb"))) {
|
|
651
|
+
return "bun";
|
|
652
|
+
}
|
|
653
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
654
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
655
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
656
|
+
if (userAgent.includes("bun")) return "bun";
|
|
657
|
+
return "npm";
|
|
658
|
+
}
|
|
659
|
+
function validateProjectForAdd(info) {
|
|
660
|
+
if (!info.isExistingProject) {
|
|
661
|
+
return {
|
|
662
|
+
valid: false,
|
|
663
|
+
error: 'No package.json found. Use "npx create-unmint my-docs" to create a new project.'
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
if (!info.nextConfigPath) {
|
|
667
|
+
return {
|
|
668
|
+
valid: false,
|
|
669
|
+
error: "No Next.js config found. This directory does not appear to be a Next.js project."
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (info.framework === "next-pages") {
|
|
673
|
+
return {
|
|
674
|
+
valid: false,
|
|
675
|
+
error: "Unmint requires App Router. Your project appears to use Pages Router."
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (info.framework === "unknown") {
|
|
679
|
+
return {
|
|
680
|
+
valid: false,
|
|
681
|
+
error: "Could not detect app directory. Make sure your project uses the Next.js App Router."
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
return { valid: true };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/utils/merge.ts
|
|
688
|
+
import fs5 from "fs-extra";
|
|
689
|
+
import path5 from "path";
|
|
690
|
+
var UNMINT_DEPENDENCIES = {
|
|
691
|
+
"fumadocs-core": "^16.4.7",
|
|
692
|
+
"fumadocs-mdx": "^14.2.5"
|
|
693
|
+
};
|
|
694
|
+
async function mergeDependencies(packageJsonPath, depsToAdd = UNMINT_DEPENDENCIES) {
|
|
695
|
+
const pkg = await fs5.readJson(packageJsonPath);
|
|
696
|
+
const added = [];
|
|
697
|
+
const skipped = [];
|
|
698
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
699
|
+
for (const [name, version] of Object.entries(depsToAdd)) {
|
|
700
|
+
if (pkg.dependencies[name] || pkg.devDependencies?.[name]) {
|
|
701
|
+
skipped.push(name);
|
|
702
|
+
} else {
|
|
703
|
+
pkg.dependencies[name] = version;
|
|
704
|
+
added.push(name);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
pkg.dependencies = Object.fromEntries(
|
|
708
|
+
Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
709
|
+
);
|
|
710
|
+
await fs5.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
|
711
|
+
return { added, skipped };
|
|
712
|
+
}
|
|
713
|
+
function getUnmintCssVariables(accentColor, darkAccentColor) {
|
|
714
|
+
return `
|
|
715
|
+
/* Unmint Docs - Scoped accent colors */
|
|
716
|
+
.unmint-docs {
|
|
717
|
+
--accent: ${accentColor};
|
|
718
|
+
--accent-foreground: #ffffff;
|
|
719
|
+
--accent-muted: ${hexToRgba(accentColor, 0.1)};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.dark .unmint-docs {
|
|
723
|
+
--accent: ${darkAccentColor};
|
|
724
|
+
--accent-foreground: #0f172a;
|
|
725
|
+
--accent-muted: ${hexToRgba(darkAccentColor, 0.1)};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/* Syntax highlighting - Shiki integration for docs */
|
|
729
|
+
.unmint-docs pre code span {
|
|
730
|
+
color: var(--shiki-light);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.dark .unmint-docs pre code span {
|
|
734
|
+
color: var(--shiki-dark);
|
|
735
|
+
}
|
|
736
|
+
`;
|
|
737
|
+
}
|
|
738
|
+
async function mergeGlobalsCss(globalsCssPath, accentColor, darkAccentColor) {
|
|
739
|
+
const existing = await fs5.readFile(globalsCssPath, "utf-8");
|
|
740
|
+
if (existing.includes(".unmint-docs")) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
const cssToAdd = getUnmintCssVariables(accentColor, darkAccentColor);
|
|
744
|
+
await fs5.appendFile(globalsCssPath, cssToAdd);
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
async function wrapNextConfig(nextConfigPath) {
|
|
748
|
+
const existing = await fs5.readFile(nextConfigPath, "utf-8");
|
|
749
|
+
if (existing.includes("fumadocs-mdx") || existing.includes("createMDX")) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
const ext = path5.extname(nextConfigPath);
|
|
753
|
+
let modified;
|
|
754
|
+
if (ext === ".ts") {
|
|
755
|
+
modified = wrapTypescriptConfig(existing);
|
|
756
|
+
} else {
|
|
757
|
+
modified = wrapJavascriptConfig(existing);
|
|
758
|
+
}
|
|
759
|
+
await fs5.writeFile(nextConfigPath, modified);
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
function wrapTypescriptConfig(existing) {
|
|
763
|
+
const importLine = "import { createMDX } from 'fumadocs-mdx/next'\n";
|
|
764
|
+
const exportMatch = existing.match(/export\s+default\s+(\w+)/);
|
|
765
|
+
if (exportMatch) {
|
|
766
|
+
const configName = exportMatch[1];
|
|
767
|
+
let modified = importLine + existing;
|
|
768
|
+
modified = modified.replace(
|
|
769
|
+
/export\s+default\s+\w+/,
|
|
770
|
+
`const withMDX = createMDX()
|
|
771
|
+
|
|
772
|
+
export default withMDX(${configName})`
|
|
773
|
+
);
|
|
774
|
+
return modified;
|
|
775
|
+
}
|
|
776
|
+
if (existing.includes("export default {")) {
|
|
777
|
+
let modified = importLine + existing.replace(
|
|
778
|
+
"export default {",
|
|
779
|
+
"const nextConfig = {"
|
|
780
|
+
);
|
|
781
|
+
modified = modified.trimEnd() + "\n\nconst withMDX = createMDX()\n\nexport default withMDX(nextConfig)\n";
|
|
782
|
+
return modified;
|
|
783
|
+
}
|
|
784
|
+
return importLine + existing;
|
|
785
|
+
}
|
|
786
|
+
function wrapJavascriptConfig(existing) {
|
|
787
|
+
const isESM = existing.includes("export default") || existing.includes("import ");
|
|
788
|
+
if (isESM) {
|
|
789
|
+
const importLine = "import { createMDX } from 'fumadocs-mdx/next'\n";
|
|
790
|
+
const exportMatch = existing.match(/export\s+default\s+(\w+)/);
|
|
791
|
+
if (exportMatch) {
|
|
792
|
+
const configName = exportMatch[1];
|
|
793
|
+
let modified = importLine + existing;
|
|
794
|
+
modified = modified.replace(
|
|
795
|
+
/export\s+default\s+\w+/,
|
|
796
|
+
`const withMDX = createMDX()
|
|
797
|
+
|
|
798
|
+
export default withMDX(${configName})`
|
|
799
|
+
);
|
|
800
|
+
return modified;
|
|
801
|
+
}
|
|
802
|
+
if (existing.includes("export default {")) {
|
|
803
|
+
let modified = importLine + existing.replace(
|
|
804
|
+
"export default {",
|
|
805
|
+
"const nextConfig = {"
|
|
806
|
+
);
|
|
807
|
+
modified = modified.trimEnd() + "\n\nconst withMDX = createMDX()\n\nexport default withMDX(nextConfig)\n";
|
|
808
|
+
return modified;
|
|
809
|
+
}
|
|
810
|
+
return importLine + existing;
|
|
811
|
+
}
|
|
812
|
+
const requireLine = "const { createMDX } = require('fumadocs-mdx/next')\n";
|
|
813
|
+
if (existing.includes("module.exports")) {
|
|
814
|
+
let modified = requireLine + existing.replace(
|
|
815
|
+
/module\.exports\s*=\s*(\w+)/,
|
|
816
|
+
(_, configName) => `const withMDX = createMDX()
|
|
817
|
+
|
|
818
|
+
module.exports = withMDX(${configName})`
|
|
819
|
+
);
|
|
820
|
+
return modified;
|
|
821
|
+
}
|
|
822
|
+
return requireLine + existing;
|
|
823
|
+
}
|
|
824
|
+
function hexToRgba(hex, alpha) {
|
|
825
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
826
|
+
if (!result) return `rgba(0, 0, 0, ${alpha})`;
|
|
827
|
+
const r = parseInt(result[1], 16);
|
|
828
|
+
const g = parseInt(result[2], 16);
|
|
829
|
+
const b = parseInt(result[3], 16);
|
|
830
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
831
|
+
}
|
|
832
|
+
function lightenColor2(hex, percent = 30) {
|
|
833
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
834
|
+
if (!result) return hex;
|
|
835
|
+
let r = parseInt(result[1], 16);
|
|
836
|
+
let g = parseInt(result[2], 16);
|
|
837
|
+
let b = parseInt(result[3], 16);
|
|
838
|
+
r = Math.min(255, Math.floor(r + (255 - r) * (percent / 100)));
|
|
839
|
+
g = Math.min(255, Math.floor(g + (255 - g) * (percent / 100)));
|
|
840
|
+
b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
|
|
841
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
842
|
+
}
|
|
843
|
+
async function createSourceConfig(targetDir) {
|
|
844
|
+
const sourceConfigPath = path5.join(targetDir, "source.config.ts");
|
|
845
|
+
if (await fs5.pathExists(sourceConfigPath)) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const content = `import { defineConfig, defineDocs } from 'fumadocs-mdx/config'
|
|
849
|
+
import { rehypeCode } from 'fumadocs-core/mdx-plugins'
|
|
850
|
+
|
|
851
|
+
export const docs = defineDocs({
|
|
852
|
+
dir: 'content/docs',
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
export default defineConfig({
|
|
856
|
+
mdxOptions: {
|
|
857
|
+
rehypePlugins: [
|
|
858
|
+
[
|
|
859
|
+
rehypeCode,
|
|
860
|
+
{
|
|
861
|
+
themes: {
|
|
862
|
+
light: 'github-light',
|
|
863
|
+
dark: 'github-dark',
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
],
|
|
868
|
+
},
|
|
869
|
+
})
|
|
870
|
+
`;
|
|
871
|
+
await fs5.writeFile(sourceConfigPath, content);
|
|
872
|
+
}
|
|
873
|
+
async function createMdxComponents(targetDir, appDir) {
|
|
874
|
+
const mdxComponentsPath = path5.join(targetDir, "mdx-components.tsx");
|
|
875
|
+
if (await fs5.pathExists(mdxComponentsPath)) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const componentsPath = appDir.includes("src") ? "@/app/components/docs/mdx" : "@/app/components/docs/mdx";
|
|
879
|
+
const content = `import type { MDXComponents } from 'mdx/types'
|
|
880
|
+
import { getMDXComponents } from '${componentsPath}'
|
|
881
|
+
|
|
882
|
+
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
|
883
|
+
return {
|
|
884
|
+
...getMDXComponents(),
|
|
885
|
+
...components,
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
`;
|
|
889
|
+
await fs5.writeFile(mdxComponentsPath, content);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/commands/add.ts
|
|
893
|
+
async function add(options = {}) {
|
|
894
|
+
const cwd = process.cwd();
|
|
895
|
+
const spinner = ora3("Detecting project...").start();
|
|
896
|
+
const projectInfo = await detectProject(cwd);
|
|
897
|
+
const validation = validateProjectForAdd(projectInfo);
|
|
898
|
+
if (!validation.valid) {
|
|
899
|
+
spinner.fail(validation.error);
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
spinner.succeed(`Detected Next.js project (App Router)`);
|
|
903
|
+
console.log(chalk4.dim(` Using ${projectInfo.useSrcDir ? "src/app" : "app"} directory structure`));
|
|
904
|
+
console.log();
|
|
905
|
+
if (projectInfo.hasExistingDocs && !options.path) {
|
|
906
|
+
console.log(chalk4.yellow("\u26A0 A /docs route already exists in this project."));
|
|
907
|
+
console.log(chalk4.dim(" Use --path to specify a different route (e.g., --path /documentation)"));
|
|
908
|
+
console.log();
|
|
909
|
+
}
|
|
910
|
+
const config = options.yes ? getDefaultAddConfig(options.path) : await promptAddConfig(projectInfo.hasExistingDocs, options.path);
|
|
911
|
+
const docsRoutePath = path6.join(cwd, projectInfo.appDir, config.docsRoute.replace(/^\//, ""));
|
|
912
|
+
if (await fs6.pathExists(docsRoutePath)) {
|
|
913
|
+
console.log(chalk4.red(`
|
|
914
|
+
\u2716 The route ${config.docsRoute} already exists at ${docsRoutePath}`));
|
|
915
|
+
console.log(chalk4.dim(" Choose a different route or manually merge the directories."));
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
console.log();
|
|
919
|
+
console.log(chalk4.cyan(" Adding Unmint to your project..."));
|
|
920
|
+
console.log();
|
|
921
|
+
await copyDocsFiles(cwd, projectInfo, config);
|
|
922
|
+
await mergeConfigurations(cwd, projectInfo, config);
|
|
923
|
+
await installDependencies(cwd, projectInfo.packageManager);
|
|
924
|
+
printSuccessMessage(config, projectInfo.packageManager);
|
|
925
|
+
}
|
|
926
|
+
function getDefaultAddConfig(customPath) {
|
|
927
|
+
return {
|
|
928
|
+
docsRoute: customPath || "/docs",
|
|
929
|
+
title: "Documentation",
|
|
930
|
+
description: "Documentation for your project",
|
|
931
|
+
accentColor: "#0891b2"
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
async function copyDocsFiles(cwd, projectInfo, config) {
|
|
935
|
+
const spinner = ora3("Copying docs files...").start();
|
|
936
|
+
const templateDir = await resolveTemplateDir();
|
|
937
|
+
const appDir = projectInfo.appDir;
|
|
938
|
+
const routeName = config.docsRoute.replace(/^\//, "");
|
|
939
|
+
const libDir = projectInfo.useSrcDir ? "src/lib" : "lib";
|
|
940
|
+
const copyOperations = [
|
|
941
|
+
// Docs route
|
|
942
|
+
{
|
|
943
|
+
from: path6.join(templateDir, "app/docs"),
|
|
944
|
+
to: path6.join(cwd, appDir, routeName)
|
|
945
|
+
},
|
|
946
|
+
// Docs components
|
|
947
|
+
{
|
|
948
|
+
from: path6.join(templateDir, "app/components/docs"),
|
|
949
|
+
to: path6.join(cwd, appDir, "components/docs")
|
|
950
|
+
},
|
|
951
|
+
// Providers (theme provider)
|
|
952
|
+
{
|
|
953
|
+
from: path6.join(templateDir, "app/providers"),
|
|
954
|
+
to: path6.join(cwd, appDir, "providers")
|
|
955
|
+
},
|
|
956
|
+
// API routes
|
|
957
|
+
{
|
|
958
|
+
from: path6.join(templateDir, "app/api/search"),
|
|
959
|
+
to: path6.join(cwd, appDir, "api/search")
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
from: path6.join(templateDir, "app/api/og"),
|
|
963
|
+
to: path6.join(cwd, appDir, "api/og")
|
|
964
|
+
},
|
|
965
|
+
// Content (always at root)
|
|
966
|
+
{
|
|
967
|
+
from: path6.join(templateDir, "content/docs"),
|
|
968
|
+
to: path6.join(cwd, "content/docs")
|
|
969
|
+
},
|
|
970
|
+
// Lib files (in src/lib if using src directory)
|
|
971
|
+
{
|
|
972
|
+
from: path6.join(templateDir, "lib/docs-source.ts"),
|
|
973
|
+
to: path6.join(cwd, libDir, "docs-source.ts")
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
from: path6.join(templateDir, "lib/theme-config.ts"),
|
|
977
|
+
to: path6.join(cwd, libDir, "unmint-config.ts")
|
|
978
|
+
},
|
|
979
|
+
// Logo files (copy to public directory if not present)
|
|
980
|
+
{
|
|
981
|
+
from: path6.join(templateDir, "public/logo.svg"),
|
|
982
|
+
to: path6.join(cwd, "public/logo.svg")
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
from: path6.join(templateDir, "public/logo.png"),
|
|
986
|
+
to: path6.join(cwd, "public/logo.png")
|
|
987
|
+
}
|
|
988
|
+
];
|
|
989
|
+
await fs6.ensureDir(path6.join(cwd, "public"));
|
|
990
|
+
await fs6.ensureDir(path6.join(cwd, libDir));
|
|
991
|
+
for (const op of copyOperations) {
|
|
992
|
+
if (await fs6.pathExists(op.from)) {
|
|
993
|
+
await fs6.copy(op.from, op.to, { overwrite: false });
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const utilsPath = path6.join(cwd, libDir, "utils.ts");
|
|
997
|
+
if (!await fs6.pathExists(utilsPath)) {
|
|
998
|
+
await fs6.copy(
|
|
999
|
+
path6.join(templateDir, "lib/utils.ts"),
|
|
1000
|
+
utilsPath
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
if (projectInfo.useSrcDir) {
|
|
1004
|
+
const docsSourcePath = path6.join(cwd, libDir, "docs-source.ts");
|
|
1005
|
+
if (await fs6.pathExists(docsSourcePath)) {
|
|
1006
|
+
let content = await fs6.readFile(docsSourcePath, "utf-8");
|
|
1007
|
+
content = content.replace(
|
|
1008
|
+
/from ['"]\.\.\/\.source\/server['"]/,
|
|
1009
|
+
"from '../../.source/server'"
|
|
1010
|
+
);
|
|
1011
|
+
await fs6.writeFile(docsSourcePath, content);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
await updateDocsImports(cwd, appDir, routeName);
|
|
1015
|
+
await updateThemeConfig(cwd, config);
|
|
1016
|
+
spinner.succeed(`Added ${appDir}/${routeName}/ route with layout`);
|
|
1017
|
+
ora3().succeed(`Added docs components to ${appDir}/components/docs/`);
|
|
1018
|
+
ora3().succeed("Added content/docs/ directory with sample content");
|
|
1019
|
+
}
|
|
1020
|
+
async function resolveTemplateDir() {
|
|
1021
|
+
const bundledPath = path6.join(import.meta.dirname, "../../template");
|
|
1022
|
+
if (await fs6.pathExists(bundledPath)) {
|
|
1023
|
+
return bundledPath;
|
|
1024
|
+
}
|
|
1025
|
+
const devPath = path6.join(import.meta.dirname, "../../../template");
|
|
1026
|
+
if (await fs6.pathExists(devPath)) {
|
|
1027
|
+
return devPath;
|
|
1028
|
+
}
|
|
1029
|
+
throw new Error("Could not find template directory");
|
|
1030
|
+
}
|
|
1031
|
+
async function updateDocsImports(cwd, appDir, routeName) {
|
|
1032
|
+
const filesToUpdate = [
|
|
1033
|
+
path6.join(cwd, appDir, routeName, "layout.tsx"),
|
|
1034
|
+
path6.join(cwd, appDir, routeName, "[[...slug]]", "page.tsx"),
|
|
1035
|
+
path6.join(cwd, appDir, "components/docs/docs-sidebar.tsx"),
|
|
1036
|
+
path6.join(cwd, appDir, "components/docs/docs-header.tsx"),
|
|
1037
|
+
path6.join(cwd, appDir, "components/docs/mobile-sidebar.tsx"),
|
|
1038
|
+
path6.join(cwd, appDir, "components/docs/search-dialog.tsx"),
|
|
1039
|
+
path6.join(cwd, appDir, "api/og/route.tsx")
|
|
1040
|
+
];
|
|
1041
|
+
for (const filePath of filesToUpdate) {
|
|
1042
|
+
if (await fs6.pathExists(filePath)) {
|
|
1043
|
+
let content = await fs6.readFile(filePath, "utf-8");
|
|
1044
|
+
if (content.includes("@/lib/theme-config")) {
|
|
1045
|
+
content = content.replace(
|
|
1046
|
+
/@\/lib\/theme-config/g,
|
|
1047
|
+
"@/lib/unmint-config"
|
|
1048
|
+
);
|
|
1049
|
+
await fs6.writeFile(filePath, content);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function updateThemeConfig(cwd, config) {
|
|
1055
|
+
const configPath = path6.join(cwd, "lib/unmint-config.ts");
|
|
1056
|
+
if (!await fs6.pathExists(configPath)) return;
|
|
1057
|
+
let content = await fs6.readFile(configPath, "utf-8");
|
|
1058
|
+
content = content.replace(
|
|
1059
|
+
/name:\s*['"][^'"]*['"]/,
|
|
1060
|
+
`name: '${config.title}'`
|
|
1061
|
+
);
|
|
1062
|
+
content = content.replace(
|
|
1063
|
+
/description:\s*['"][^'"]*['"]/,
|
|
1064
|
+
`description: '${config.description}'`
|
|
1065
|
+
);
|
|
1066
|
+
await fs6.writeFile(configPath, content);
|
|
1067
|
+
}
|
|
1068
|
+
async function mergeConfigurations(cwd, projectInfo, config) {
|
|
1069
|
+
const packageJsonPath = path6.join(cwd, "package.json");
|
|
1070
|
+
const { added } = await mergeDependencies(packageJsonPath);
|
|
1071
|
+
if (added.length > 0) {
|
|
1072
|
+
ora3().succeed(`Merged ${added.length} dependencies into package.json`);
|
|
1073
|
+
}
|
|
1074
|
+
if (projectInfo.globalsCssPath) {
|
|
1075
|
+
const darkAccent = lightenColor2(config.accentColor, 30);
|
|
1076
|
+
const merged = await mergeGlobalsCss(projectInfo.globalsCssPath, config.accentColor, darkAccent);
|
|
1077
|
+
if (merged) {
|
|
1078
|
+
ora3().succeed("Added CSS variables to globals.css");
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (projectInfo.nextConfigPath) {
|
|
1082
|
+
const wrapped = await wrapNextConfig(projectInfo.nextConfigPath);
|
|
1083
|
+
if (wrapped) {
|
|
1084
|
+
ora3().succeed("Updated next.config for MDX support");
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
await createSourceConfig(cwd);
|
|
1088
|
+
ora3().succeed("Created source.config.ts");
|
|
1089
|
+
await createMdxComponents(cwd, projectInfo.appDir);
|
|
1090
|
+
ora3().succeed("Created mdx-components.tsx");
|
|
1091
|
+
}
|
|
1092
|
+
async function installDependencies(cwd, packageManager) {
|
|
1093
|
+
const spinner = ora3("Installing dependencies...").start();
|
|
1094
|
+
try {
|
|
1095
|
+
const installCmd = packageManager === "npm" ? "install" : "install";
|
|
1096
|
+
await execa2(packageManager, [installCmd], { cwd, stdio: "pipe" });
|
|
1097
|
+
spinner.succeed(`Installed ${Object.keys(UNMINT_DEPENDENCIES).join(", ")}`);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
spinner.warn("Could not install dependencies automatically");
|
|
1100
|
+
console.log(chalk4.dim(` Run "${packageManager} install" manually`));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function printSuccessMessage(config, packageManager) {
|
|
1104
|
+
console.log();
|
|
1105
|
+
console.log(chalk4.green(" Success! Unmint docs added to your project."));
|
|
1106
|
+
console.log();
|
|
1107
|
+
console.log(" Next steps:");
|
|
1108
|
+
console.log(chalk4.cyan(` ${packageManager} run dev`));
|
|
1109
|
+
console.log();
|
|
1110
|
+
console.log(` Your docs will be at ${chalk4.cyan(`http://localhost:3000${config.docsRoute}`)}`);
|
|
1111
|
+
console.log();
|
|
1112
|
+
}
|
|
1113
|
+
|
|
491
1114
|
// src/index.ts
|
|
492
1115
|
var cyanGradient = gradient([
|
|
493
1116
|
"#065f5f",
|
|
@@ -508,17 +1131,31 @@ function printBanner() {
|
|
|
508
1131
|
});
|
|
509
1132
|
console.log();
|
|
510
1133
|
console.log(cyanGradient.multiline(banner));
|
|
511
|
-
console.log(
|
|
1134
|
+
console.log(chalk5.dim(" Beautiful documentation, open source"));
|
|
512
1135
|
console.log();
|
|
513
1136
|
}
|
|
514
1137
|
var program = new Command();
|
|
515
|
-
program.name("create-unmint").description("Create and manage Unmint documentation projects").version("1.
|
|
516
|
-
program.argument("[project-name]",
|
|
1138
|
+
program.name("create-unmint").description("Create and manage Unmint documentation projects").version("1.2.0");
|
|
1139
|
+
program.argument("[project-name]", 'Name of the project to create (use "." for current directory)').option("-y, --yes", "Skip prompts and use defaults").option("--add", "Add Unmint docs to an existing Next.js project").option("--path <route>", "Custom route path for docs (e.g., /documentation)").option("--update", "Update an existing Unmint project").option("--dry-run", "Show what would be updated without making changes").action(async (projectName, options) => {
|
|
517
1140
|
printBanner();
|
|
518
1141
|
if (options.update) {
|
|
519
1142
|
await update(options);
|
|
520
|
-
|
|
521
|
-
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (options.add) {
|
|
1146
|
+
await add({ yes: options.yes, path: options.path });
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (projectName === ".") {
|
|
1150
|
+
const cwd = process.cwd();
|
|
1151
|
+
const hasNextConfig = await fs7.pathExists(path7.join(cwd, "next.config.ts")) || await fs7.pathExists(path7.join(cwd, "next.config.js")) || await fs7.pathExists(path7.join(cwd, "next.config.mjs"));
|
|
1152
|
+
if (hasNextConfig) {
|
|
1153
|
+
console.log(chalk5.dim(" Detected existing Next.js project, using add mode..."));
|
|
1154
|
+
console.log();
|
|
1155
|
+
await add({ yes: options.yes, path: options.path });
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
522
1158
|
}
|
|
1159
|
+
await init(projectName, options);
|
|
523
1160
|
});
|
|
524
1161
|
program.parse();
|
package/package.json
CHANGED