@docubook/create 1.14.1 → 1.15.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/package.json +1 -1
- package/src/cli/program.js +8 -6
- package/src/cli/promptHandler.js +5 -6
- package/src/dist/app/layout.tsx +1 -0
- package/src/dist/app/page.tsx +1 -1
- package/src/dist/components/DocSearch.tsx +33 -0
- package/src/dist/components/SearchModal.tsx +198 -0
- package/src/dist/components/SearchTrigger.tsx +31 -0
- package/src/dist/components/search.tsx +38 -229
- package/src/dist/contents/docs/changelog/version-1/index.mdx +39 -0
- package/src/dist/package.json +5 -1
- package/src/dist/styles/algolia.css +156 -0
- package/src/installer/projectInstaller.js +14 -12
- package/src/utils/display.js +6 -6
package/package.json
CHANGED
package/src/cli/program.js
CHANGED
|
@@ -12,15 +12,17 @@ export function initializeProgram(version) {
|
|
|
12
12
|
program
|
|
13
13
|
.version(version)
|
|
14
14
|
.description("CLI to create a new DocuBook project")
|
|
15
|
-
// 1. Define optional argument here (handled below with .argument)
|
|
16
15
|
.argument("[directory]", "The name of the project directory")
|
|
17
|
-
.action(async (directory) => {
|
|
16
|
+
.action(async (directory) => {
|
|
18
17
|
try {
|
|
19
18
|
displayIntro();
|
|
20
|
-
|
|
21
|
-
const options = await collectUserInput(directory);
|
|
19
|
+
const userInput = await collectUserInput(directory);
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
// Pass all user input AND the DocuBook version to createProject
|
|
22
|
+
await createProject({
|
|
23
|
+
...userInput,
|
|
24
|
+
docubookVersion: version, // Add DocuBook version here
|
|
25
|
+
});
|
|
24
26
|
} catch (err) {
|
|
25
27
|
log.error(err.message || "An unexpected error occurred.");
|
|
26
28
|
process.exit(1);
|
|
@@ -28,4 +30,4 @@ export function initializeProgram(version) {
|
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
return program;
|
|
31
|
-
}
|
|
33
|
+
}
|
package/src/cli/promptHandler.js
CHANGED
|
@@ -16,7 +16,6 @@ export async function collectUserInput(cliProvidedDir) {
|
|
|
16
16
|
|
|
17
17
|
const questions = [
|
|
18
18
|
{
|
|
19
|
-
// Skip this question if directory name is provided
|
|
20
19
|
type: cliProvidedDir ? null : "text",
|
|
21
20
|
name: "directoryName",
|
|
22
21
|
message: "What is your project named?",
|
|
@@ -50,18 +49,18 @@ export async function collectUserInput(cliProvidedDir) {
|
|
|
50
49
|
},
|
|
51
50
|
});
|
|
52
51
|
|
|
53
|
-
// Combine answers from CLI and from prompt
|
|
54
52
|
answers = { ...answers, ...promptAnswers };
|
|
55
53
|
|
|
56
54
|
// Validate the selected package manager
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
55
|
+
const pmVersion = getPackageManagerVersion(answers.packageManager);
|
|
56
|
+
if (!pmVersion) {
|
|
59
57
|
throw new Error(`${chalk.bold(answers.packageManager)} is not installed on your system. Please install it to continue.`);
|
|
60
58
|
}
|
|
61
59
|
|
|
60
|
+
// Return all answers, including the package manager version with a clear property name
|
|
62
61
|
return {
|
|
63
62
|
...answers,
|
|
64
63
|
directoryName: answers.directoryName.trim(),
|
|
65
|
-
|
|
64
|
+
pmVersion,
|
|
66
65
|
};
|
|
67
|
-
}
|
|
66
|
+
}
|
package/src/dist/app/layout.tsx
CHANGED
package/src/dist/app/page.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export default function Home() {
|
|
|
25
25
|
)}
|
|
26
26
|
>
|
|
27
27
|
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-100 hover:duration-300 hover:dark:text-neutral-200">
|
|
28
|
-
<span>🚀 New Version - Release v1.
|
|
28
|
+
<span>🚀 New Version - Release v1.15.0</span>
|
|
29
29
|
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
|
30
30
|
</AnimatedShinyText>
|
|
31
31
|
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { DocSearch } from "@docsearch/react";
|
|
5
|
+
import "@docsearch/css";
|
|
6
|
+
|
|
7
|
+
export default function DocSearchComponent() {
|
|
8
|
+
const appId = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_APP_ID;
|
|
9
|
+
const apiKey = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_API_KEY;
|
|
10
|
+
const indexName = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_INDEX_NAME;
|
|
11
|
+
|
|
12
|
+
if (!appId || !apiKey || !indexName) {
|
|
13
|
+
console.error(
|
|
14
|
+
"DocSearch credentials are not set in the environment variables."
|
|
15
|
+
);
|
|
16
|
+
return (
|
|
17
|
+
<button className="text-sm text-muted-foreground" disabled>
|
|
18
|
+
Search... (misconfigured)
|
|
19
|
+
</button>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="docsearch">
|
|
25
|
+
<DocSearch
|
|
26
|
+
appId={appId}
|
|
27
|
+
apiKey={apiKey}
|
|
28
|
+
indexName={indexName}
|
|
29
|
+
placeholder="Type something to search..."
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useEffect, useMemo, useState, useRef } from "react";
|
|
5
|
+
import { ArrowUpIcon, ArrowDownIcon, CornerDownLeftIcon, FileTextIcon } from "lucide-react";
|
|
6
|
+
import Anchor from "./anchor";
|
|
7
|
+
import { advanceSearch, cn } from "@/lib/utils";
|
|
8
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
9
|
+
import { page_routes } from "@/lib/routes-config";
|
|
10
|
+
import {
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogFooter,
|
|
14
|
+
DialogClose,
|
|
15
|
+
DialogTitle,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
} from "@/components/ui/dialog";
|
|
18
|
+
|
|
19
|
+
type ContextInfo = {
|
|
20
|
+
icon: string;
|
|
21
|
+
description: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type SearchResult = {
|
|
26
|
+
title: string;
|
|
27
|
+
href: string;
|
|
28
|
+
noLink?: boolean;
|
|
29
|
+
items?: undefined;
|
|
30
|
+
score?: number;
|
|
31
|
+
context?: ContextInfo;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const paddingMap = {
|
|
35
|
+
1: "pl-2",
|
|
36
|
+
2: "pl-4",
|
|
37
|
+
3: "pl-10",
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
interface SearchModalProps {
|
|
41
|
+
isOpen: boolean;
|
|
42
|
+
setIsOpen: (open: boolean) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const [searchedInput, setSearchedInput] = useState("");
|
|
48
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
49
|
+
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isOpen) {
|
|
53
|
+
setSearchedInput("");
|
|
54
|
+
}
|
|
55
|
+
}, [isOpen]);
|
|
56
|
+
|
|
57
|
+
const filteredResults = useMemo<SearchResult[]>(() => {
|
|
58
|
+
const trimmedInput = searchedInput.trim();
|
|
59
|
+
|
|
60
|
+
if (trimmedInput.length < 3) {
|
|
61
|
+
return page_routes
|
|
62
|
+
.filter((route) => !route.href.endsWith('/'))
|
|
63
|
+
.slice(0, 6)
|
|
64
|
+
.map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({
|
|
65
|
+
title: route.title,
|
|
66
|
+
href: route.href,
|
|
67
|
+
noLink: route.noLink,
|
|
68
|
+
context: route.context,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
return advanceSearch(trimmedInput) as unknown as SearchResult[];
|
|
72
|
+
}, [searchedInput]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
setSelectedIndex(0);
|
|
76
|
+
}, [filteredResults]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const handleNavigation = (event: KeyboardEvent) => {
|
|
80
|
+
if (!isOpen || filteredResults.length === 0) return;
|
|
81
|
+
|
|
82
|
+
if (event.key === "ArrowDown") {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
setSelectedIndex((prev) => (prev + 1) % filteredResults.length);
|
|
85
|
+
} else if (event.key === "ArrowUp") {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
setSelectedIndex((prev) => (prev - 1 + filteredResults.length) % filteredResults.length);
|
|
88
|
+
} else if (event.key === "Enter") {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
const selectedItem = filteredResults[selectedIndex];
|
|
91
|
+
if (selectedItem) {
|
|
92
|
+
router.push(`/docs${selectedItem.href}`);
|
|
93
|
+
setIsOpen(false);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
window.addEventListener("keydown", handleNavigation);
|
|
99
|
+
return () => window.removeEventListener("keydown", handleNavigation);
|
|
100
|
+
}, [isOpen, filteredResults, selectedIndex, router, setIsOpen]);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (itemRefs.current[selectedIndex]) {
|
|
104
|
+
itemRefs.current[selectedIndex]?.scrollIntoView({
|
|
105
|
+
behavior: "smooth",
|
|
106
|
+
block: "nearest",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}, [selectedIndex]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<DialogContent className="p-0 max-w-[650px] sm:top-[38%] top-[45%] !rounded-md">
|
|
113
|
+
<DialogHeader>
|
|
114
|
+
<DialogTitle className="sr-only">Search Documentation</DialogTitle>
|
|
115
|
+
<DialogDescription className="sr-only">Search through the documentation</DialogDescription>
|
|
116
|
+
</DialogHeader>
|
|
117
|
+
|
|
118
|
+
<input
|
|
119
|
+
value={searchedInput}
|
|
120
|
+
onChange={(e) => setSearchedInput(e.target.value)}
|
|
121
|
+
placeholder="Type something to search..."
|
|
122
|
+
autoFocus
|
|
123
|
+
className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full"
|
|
124
|
+
aria-label="Search documentation"
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
{filteredResults.length == 0 && searchedInput && (
|
|
128
|
+
<p className="text-muted-foreground mx-auto mt-2 text-sm">
|
|
129
|
+
No results found for{" "}
|
|
130
|
+
<span className="text-primary">{`"${searchedInput}"`}</span>
|
|
131
|
+
</p>
|
|
132
|
+
)}
|
|
133
|
+
<ScrollArea className="max-h-[400px] overflow-y-auto">
|
|
134
|
+
<div className="flex flex-col items-start overflow-y-auto sm:px-2 px-1 pb-4">
|
|
135
|
+
{filteredResults.map((item, index) => {
|
|
136
|
+
const level = (item.href.split("/").slice(1).length - 1) as keyof typeof paddingMap;
|
|
137
|
+
const paddingClass = paddingMap[level] || 'pl-2';
|
|
138
|
+
const isActive = index === selectedIndex;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<DialogClose key={item.href} asChild>
|
|
142
|
+
<Anchor
|
|
143
|
+
ref={(el) => {
|
|
144
|
+
itemRefs.current[index] = el as HTMLDivElement | null;
|
|
145
|
+
}}
|
|
146
|
+
className={cn(
|
|
147
|
+
"dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5",
|
|
148
|
+
isActive && "bg-primary/20 dark:bg-primary/30",
|
|
149
|
+
paddingClass
|
|
150
|
+
)}
|
|
151
|
+
href={`/docs${item.href}`}
|
|
152
|
+
tabIndex={-1}
|
|
153
|
+
>
|
|
154
|
+
<div
|
|
155
|
+
className={cn(
|
|
156
|
+
"flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
|
|
157
|
+
level > 1 && "border-l pl-4"
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
<div className="flex items-center">
|
|
161
|
+
<FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
|
|
162
|
+
<span>{item.title}</span>
|
|
163
|
+
</div>
|
|
164
|
+
{isActive && (
|
|
165
|
+
<div className="hidden md:flex items-center text-xs text-muted-foreground">
|
|
166
|
+
<span>Return</span>
|
|
167
|
+
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
</Anchor>
|
|
172
|
+
</DialogClose>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</div>
|
|
176
|
+
</ScrollArea>
|
|
177
|
+
<DialogFooter className="md:flex md:justify-start hidden h-14 px-6 bg-transparent border-t text-[14px] outline-none">
|
|
178
|
+
<div className="flex items-center gap-2">
|
|
179
|
+
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
180
|
+
<ArrowUpIcon className="w-3 h-3"/>
|
|
181
|
+
</span>
|
|
182
|
+
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
183
|
+
<ArrowDownIcon className="w-3 h-3"/>
|
|
184
|
+
</span>
|
|
185
|
+
<p className="text-muted-foreground">to navigate</p>
|
|
186
|
+
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
187
|
+
<CornerDownLeftIcon className="w-3 h-3"/>
|
|
188
|
+
</span>
|
|
189
|
+
<p className="text-muted-foreground">to select</p>
|
|
190
|
+
<span className="dark:bg-accent/15 bg-slate-200 border rounded px-2 py-1">
|
|
191
|
+
esc
|
|
192
|
+
</span>
|
|
193
|
+
<p className="text-muted-foreground">to close</p>
|
|
194
|
+
</div>
|
|
195
|
+
</DialogFooter>
|
|
196
|
+
</DialogContent>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CommandIcon, SearchIcon } from "lucide-react";
|
|
4
|
+
import { DialogTrigger } from "@/components/ui/dialog";
|
|
5
|
+
import { Input } from "@/components/ui/input";
|
|
6
|
+
|
|
7
|
+
export function SearchTrigger() {
|
|
8
|
+
return (
|
|
9
|
+
<DialogTrigger asChild>
|
|
10
|
+
<div className="relative flex-1 cursor-pointer max-w-[140px]">
|
|
11
|
+
<div className="flex items-center">
|
|
12
|
+
<div className="md:hidden p-2 -ml-2">
|
|
13
|
+
<SearchIcon className="h-5 w-5 text-muted-foreground" />
|
|
14
|
+
</div>
|
|
15
|
+
<div className="hidden md:block w-full">
|
|
16
|
+
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
17
|
+
<Input
|
|
18
|
+
className="w-full rounded-full dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
|
|
19
|
+
placeholder="Search"
|
|
20
|
+
readOnly // This input is for display only
|
|
21
|
+
/>
|
|
22
|
+
<div className="flex absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-accent bg-accent text-white px-2 py-0.5 rounded-full">
|
|
23
|
+
<CommandIcon className="w-3 h-3" />
|
|
24
|
+
<span>K</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</DialogTrigger>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -1,246 +1,55 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
import Anchor from "./anchor";
|
|
18
|
-
import { advanceSearch, cn } from "@/lib/utils";
|
|
19
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
20
|
-
import { page_routes } from "@/lib/routes-config";
|
|
21
|
-
|
|
22
|
-
// Define the ContextInfo type to match the one in routes-config
|
|
23
|
-
type ContextInfo = {
|
|
24
|
-
icon: string;
|
|
25
|
-
description: string;
|
|
26
|
-
title?: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type SearchResult = {
|
|
30
|
-
title: string;
|
|
31
|
-
href: string;
|
|
32
|
-
noLink?: boolean;
|
|
33
|
-
items?: undefined;
|
|
34
|
-
score?: number;
|
|
35
|
-
context?: ContextInfo;
|
|
36
|
-
};
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Dialog } from "@/components/ui/dialog";
|
|
5
|
+
import { SearchTrigger } from "@/components/SearchTrigger";
|
|
6
|
+
import { SearchModal } from "@/components/SearchModal";
|
|
7
|
+
import DocSearchComponent from "@/components/DocSearch";
|
|
8
|
+
import { DialogTrigger } from "@radix-ui/react-dialog";
|
|
9
|
+
|
|
10
|
+
interface SearchProps {
|
|
11
|
+
/**
|
|
12
|
+
* Specify which search engine to use.
|
|
13
|
+
* @default 'default'
|
|
14
|
+
*/
|
|
15
|
+
type?: "default" | "algolia";
|
|
16
|
+
}
|
|
37
17
|
|
|
38
|
-
export default function Search() {
|
|
39
|
-
const router = useRouter();
|
|
40
|
-
const [searchedInput, setSearchedInput] = useState("");
|
|
18
|
+
export default function Search({ type = "default" }: SearchProps) {
|
|
41
19
|
const [isOpen, setIsOpen] = useState(false);
|
|
42
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
43
|
-
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
47
|
-
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
|
48
|
-
event.preventDefault();
|
|
49
|
-
setIsOpen(true);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
54
|
-
return () => {
|
|
55
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
56
|
-
};
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
const filteredResults = useMemo<SearchResult[]>(() => {
|
|
60
|
-
const trimmedInput = searchedInput.trim();
|
|
61
|
-
|
|
62
|
-
// If search input is empty or less than 3 characters, show initial suggestions
|
|
63
|
-
if (trimmedInput.length < 3) {
|
|
64
|
-
return page_routes
|
|
65
|
-
.filter((route: { href: string }) => !route.href.endsWith('/')) // Filter out directory routes
|
|
66
|
-
.slice(0, 6) // Limit to 6 posts
|
|
67
|
-
.map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({
|
|
68
|
-
title: route.title,
|
|
69
|
-
href: route.href,
|
|
70
|
-
noLink: route.noLink,
|
|
71
|
-
context: route.context
|
|
72
|
-
}));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// For search with 3 or more characters, use the advance search
|
|
76
|
-
return advanceSearch(trimmedInput) as unknown as SearchResult[];
|
|
77
|
-
}, [searchedInput]);
|
|
78
20
|
|
|
21
|
+
// The useEffect below is ONLY for the 'default' type, which is correct.
|
|
22
|
+
// DocSearch handles its own keyboard shortcut.
|
|
79
23
|
useEffect(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!isOpen || filteredResults.length === 0) return;
|
|
86
|
-
|
|
87
|
-
if (event.key === "ArrowDown") {
|
|
88
|
-
event.preventDefault();
|
|
89
|
-
setSelectedIndex((prev) => (prev + 1) % filteredResults.length);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (event.key === "ArrowUp") {
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
setSelectedIndex((prev) => (prev - 1 + filteredResults.length) % filteredResults.length);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (event.key === "Enter") {
|
|
98
|
-
event.preventDefault();
|
|
99
|
-
const selectedItem = filteredResults[selectedIndex];
|
|
100
|
-
if (selectedItem) {
|
|
101
|
-
router.push(`/docs${selectedItem.href}`);
|
|
102
|
-
setIsOpen(false);
|
|
24
|
+
if (type === 'default') {
|
|
25
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
26
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
setIsOpen((open) => !open);
|
|
103
29
|
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
30
|
+
};
|
|
106
31
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}, [isOpen, filteredResults, selectedIndex, router]);
|
|
112
|
-
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (itemRefs.current[selectedIndex]) {
|
|
115
|
-
itemRefs.current[selectedIndex]?.scrollIntoView({
|
|
116
|
-
behavior: "smooth",
|
|
117
|
-
block: "nearest",
|
|
118
|
-
});
|
|
32
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
35
|
+
};
|
|
119
36
|
}
|
|
120
|
-
}, [
|
|
37
|
+
}, [type]);
|
|
38
|
+
|
|
39
|
+
if (type === "algolia") {
|
|
40
|
+
// Just render the component without passing any state props
|
|
41
|
+
return <DocSearchComponent />;
|
|
42
|
+
}
|
|
121
43
|
|
|
44
|
+
// Logic for 'default' search
|
|
122
45
|
return (
|
|
123
46
|
<div>
|
|
124
|
-
<Dialog
|
|
125
|
-
open={isOpen}
|
|
126
|
-
onOpenChange={(open) => {
|
|
127
|
-
if (!open) setSearchedInput("");
|
|
128
|
-
setIsOpen(open);
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
47
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
131
48
|
<DialogTrigger asChild>
|
|
132
|
-
<
|
|
133
|
-
<div className="flex items-center">
|
|
134
|
-
<div className="md:hidden p-2 -ml-2">
|
|
135
|
-
<SearchIcon className="h-5 w-5 text-muted-foreground" />
|
|
136
|
-
</div>
|
|
137
|
-
<div className="hidden md:block w-full">
|
|
138
|
-
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
139
|
-
<Input
|
|
140
|
-
className="w-full rounded-full dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
|
|
141
|
-
placeholder="Search"
|
|
142
|
-
type="search"
|
|
143
|
-
/>
|
|
144
|
-
<div className="flex absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-accent bg-accent text-white px-2 py-0.5 rounded-full">
|
|
145
|
-
<CommandIcon className="w-3 h-3" />
|
|
146
|
-
<span>K</span>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
49
|
+
<SearchTrigger />
|
|
151
50
|
</DialogTrigger>
|
|
152
|
-
<
|
|
153
|
-
<DialogHeader>
|
|
154
|
-
<DialogTitle className="sr-only">Search Documentation</DialogTitle>
|
|
155
|
-
</DialogHeader>
|
|
156
|
-
<DialogDescription className="sr-only">
|
|
157
|
-
Search through the documentation
|
|
158
|
-
</DialogDescription>
|
|
159
|
-
<input
|
|
160
|
-
value={searchedInput}
|
|
161
|
-
onChange={(e) => setSearchedInput(e.target.value)}
|
|
162
|
-
placeholder="Type something to search..."
|
|
163
|
-
autoFocus
|
|
164
|
-
className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full"
|
|
165
|
-
aria-label="Search documentation"
|
|
166
|
-
/>
|
|
167
|
-
{filteredResults.length == 0 && searchedInput && (
|
|
168
|
-
<p className="text-muted-foreground mx-auto mt-2 text-sm">
|
|
169
|
-
No results found for{" "}
|
|
170
|
-
<span className="text-primary">{`"${searchedInput}"`}</span>
|
|
171
|
-
</p>
|
|
172
|
-
)}
|
|
173
|
-
<ScrollArea className="max-h-[400px] overflow-y-auto">
|
|
174
|
-
<div className="flex flex-col items-start overflow-y-auto sm:px-2 px-1 pb-4">
|
|
175
|
-
{filteredResults.map((item, index) => {
|
|
176
|
-
const level = (item.href.split("/").slice(1).length - 1) as keyof typeof paddingMap;
|
|
177
|
-
const paddingClass = paddingMap[level];
|
|
178
|
-
const isActive = index === selectedIndex;
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<DialogClose key={item.href} asChild>
|
|
182
|
-
<Anchor
|
|
183
|
-
ref={(el) => {
|
|
184
|
-
itemRefs.current[index] = el as HTMLDivElement | null;
|
|
185
|
-
}}
|
|
186
|
-
className={cn(
|
|
187
|
-
"dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5",
|
|
188
|
-
isActive && "bg-primary/20 dark:bg-primary/30",
|
|
189
|
-
paddingClass
|
|
190
|
-
)}
|
|
191
|
-
href={`/docs${item.href}`}
|
|
192
|
-
tabIndex={0}
|
|
193
|
-
>
|
|
194
|
-
<div
|
|
195
|
-
className={cn(
|
|
196
|
-
"flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
|
|
197
|
-
level > 1 && "border-l pl-4"
|
|
198
|
-
)}
|
|
199
|
-
>
|
|
200
|
-
<div className="flex items-center">
|
|
201
|
-
<FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
|
|
202
|
-
<span>{item.title}</span>
|
|
203
|
-
</div>
|
|
204
|
-
{isActive && (
|
|
205
|
-
<div className="hidden md:flex items-center text-xs text-muted-foreground">
|
|
206
|
-
<span>Return</span>
|
|
207
|
-
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
|
|
208
|
-
</div>
|
|
209
|
-
)}
|
|
210
|
-
</div>
|
|
211
|
-
</Anchor>
|
|
212
|
-
</DialogClose>
|
|
213
|
-
);
|
|
214
|
-
})}
|
|
215
|
-
</div>
|
|
216
|
-
</ScrollArea>
|
|
217
|
-
<DialogFooter className="md:flex md:justify-start hidden h-14 px-6 bg-transparent border-t text-[14px] outline-none">
|
|
218
|
-
<div className="flex items-center gap-2">
|
|
219
|
-
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
220
|
-
<ArrowUpIcon className="w-3 h-3"/>
|
|
221
|
-
</span>
|
|
222
|
-
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
223
|
-
<ArrowDownIcon className="w-3 h-3"/>
|
|
224
|
-
</span>
|
|
225
|
-
<p className="text-muted-foreground">to navigate</p>
|
|
226
|
-
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
227
|
-
<CornerDownLeftIcon className="w-3 h-3"/>
|
|
228
|
-
</span>
|
|
229
|
-
<p className="text-muted-foreground">to select</p>
|
|
230
|
-
<span className="dark:bg-accent/15 bg-slate-200 border rounded px-2 py-1">
|
|
231
|
-
esc
|
|
232
|
-
</span>
|
|
233
|
-
<p className="text-muted-foreground">to close</p>
|
|
234
|
-
</div>
|
|
235
|
-
</DialogFooter>
|
|
236
|
-
</DialogContent>
|
|
51
|
+
<SearchModal isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
237
52
|
</Dialog>
|
|
238
53
|
</div>
|
|
239
54
|
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const paddingMap = {
|
|
243
|
-
1: "pl-2",
|
|
244
|
-
2: "pl-4",
|
|
245
|
-
3: "pl-10",
|
|
246
|
-
} as const;
|
|
55
|
+
}
|
|
@@ -8,6 +8,45 @@ date: 02-08-2025
|
|
|
8
8
|
This changelog contains a list of all the changes made to the DocuBook template. It will be updated with each new release and will include information about new features, bug fixes, and other improvements.
|
|
9
9
|
</Note>
|
|
10
10
|
|
|
11
|
+
<div className="sr-only">
|
|
12
|
+
### v 1.15.0
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<Release version="1.15.0" date="2025-08-06" title="Algolia DocSearch for better search result">
|
|
16
|
+
<Changes type="added">
|
|
17
|
+
- new DocSearch.tsx components
|
|
18
|
+
- add props type algolia
|
|
19
|
+
- add searchprops
|
|
20
|
+
- add algolia.css
|
|
21
|
+
</Changes>
|
|
22
|
+
</Release>
|
|
23
|
+
|
|
24
|
+
<Note type="warning" title="environment">
|
|
25
|
+
To use Algolia DocSearch, you need to configure the following environment variables:
|
|
26
|
+
|
|
27
|
+
```plaintext
|
|
28
|
+
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_APP_ID="your_app_id"
|
|
29
|
+
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_API_KEY="your_api_key"
|
|
30
|
+
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_INDEX_NAME="your_index_name"
|
|
31
|
+
```
|
|
32
|
+
</Note>
|
|
33
|
+
|
|
34
|
+
<div className="sr-only">
|
|
35
|
+
### v 1.14.2
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<Release version="1.14.2" date="2025-08-05" title="Refactor & Fix: Decouple Search Component and Resolve Type Errors">
|
|
39
|
+
<Changes type="added">
|
|
40
|
+
- Refactor Search component into three distinct components: Search, SearchTrigger, and SearchModal for better maintainability and scalability.
|
|
41
|
+
- New SearchTrigger components
|
|
42
|
+
- New SearchModal components
|
|
43
|
+
</Changes>
|
|
44
|
+
<Changes type="fixed">
|
|
45
|
+
- Resolve TypeScript error for missing 'noLink' property on the 'Page' type after refactoring.
|
|
46
|
+
- Fix TypeScript error for missing 'context' property by providing a complete inline type annotation in the SearchModal component.
|
|
47
|
+
</Changes>
|
|
48
|
+
</Release>
|
|
49
|
+
|
|
11
50
|
<div className="sr-only">
|
|
12
51
|
### v 1.14.0
|
|
13
52
|
</div>
|
package/src/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docubook",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "next dev",
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"lint": "next lint"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@docsearch/css": "3",
|
|
13
|
+
"@docsearch/react": "^3.9.0",
|
|
12
14
|
"@radix-ui/react-accordion": "^1.2.0",
|
|
13
15
|
"@radix-ui/react-avatar": "^1.1.0",
|
|
14
16
|
"@radix-ui/react-collapsible": "^1.1.0",
|
|
@@ -21,12 +23,14 @@
|
|
|
21
23
|
"@radix-ui/react-tabs": "^1.1.0",
|
|
22
24
|
"@radix-ui/react-toggle": "^1.1.2",
|
|
23
25
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
|
26
|
+
"algoliasearch": "^5.35.0",
|
|
24
27
|
"class-variance-authority": "^0.7.0",
|
|
25
28
|
"clsx": "^2.1.1",
|
|
26
29
|
"cmdk": "1.0.0",
|
|
27
30
|
"framer-motion": "^12.4.1",
|
|
28
31
|
"geist": "^1.3.1",
|
|
29
32
|
"gray-matter": "^4.0.3",
|
|
33
|
+
"install": "^0.13.0",
|
|
30
34
|
"lucide-react": "^0.511.0",
|
|
31
35
|
"next": "^14.2.6",
|
|
32
36
|
"next-mdx-remote": "^5.0.0",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
================================================================================
|
|
3
|
+
DocSearch Component Styling (Themed Version)
|
|
4
|
+
- This version uses the CSS variables defined in :root and .dark
|
|
5
|
+
to automatically adapt to your site's light and dark themes.
|
|
6
|
+
================================================================================
|
|
7
|
+
*/
|
|
8
|
+
.docsearch {
|
|
9
|
+
/* Map theme variables to DocSearch's internal variables */
|
|
10
|
+
--docsearch-primary-color: hsl(var(--primary));
|
|
11
|
+
--docsearch-text-color: hsl(var(--foreground));
|
|
12
|
+
--docsearch-container-background: hsla(var(--background) / 0.8); /* Use theme background with transparency */
|
|
13
|
+
--docsearch-modal-background: hsl(var(--card)); /* Modals should use card color */
|
|
14
|
+
--docsearch-modal-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
15
|
+
--docsearch-searchbox-background: hsl(var(--secondary));
|
|
16
|
+
--docsearch-searchbox-focus-background: hsl(var(--secondary));
|
|
17
|
+
--docsearch-hit-color: hsl(var(--foreground));
|
|
18
|
+
--docsearch-hit-background: hsl(var(--card));
|
|
19
|
+
--docsearch-hit-shadow: none;
|
|
20
|
+
--docsearch-hit-active-color: hsl(var(--primary-foreground));
|
|
21
|
+
--docsearch-selected-background: hsl(var(--secondary)); /* Use secondary for selection */
|
|
22
|
+
--docsearch-footer-background: hsl(var(--card));
|
|
23
|
+
--docsearch-footer-shadow: inset 0 1px 0 0 hsl(var(--border));
|
|
24
|
+
--docsearch-key-gradient: transparent;
|
|
25
|
+
--docsearch-key-shadow: none;
|
|
26
|
+
--docsearch-muted-color: hsl(var(--muted-foreground)); /* Use muted for less important text */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* =====================================
|
|
31
|
+
* 1. Initial Search Button Styling
|
|
32
|
+
* =====================================
|
|
33
|
+
*/
|
|
34
|
+
.docsearch .DocSearch-Button {
|
|
35
|
+
background-color: hsl(var(--secondary)); /* Use secondary for the button background */
|
|
36
|
+
border: 1px solid hsl(var(--border)); /* Use the standard border color */
|
|
37
|
+
border-radius: 9999px; /* Pill shape */
|
|
38
|
+
width: 160px; /* Lebar default untuk desktop */
|
|
39
|
+
height: 40px;
|
|
40
|
+
color: hsl(var(--muted-foreground)); /* Use muted text color for the placeholder */
|
|
41
|
+
transition: width 0.3s ease; /* Transisi untuk perubahan lebar */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.docsearch .DocSearch-Button:hover {
|
|
45
|
+
border-color: var(--docsearch-primary-color);
|
|
46
|
+
box-shadow: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Magnifying glass icon */
|
|
50
|
+
.docsearch .DocSearch-Search-Icon {
|
|
51
|
+
color: hsl(var(--muted-foreground));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The 'Search' placeholder text */
|
|
55
|
+
.docsearch .DocSearch-Button-Placeholder {
|
|
56
|
+
font-style: normal;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Styling for the '⌘K' keys */
|
|
60
|
+
.docsearch .DocSearch-Button-Key {
|
|
61
|
+
background: hsl(var(--primary)); /* Use primary color for the key background */
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
color: hsl(var(--primary-foreground)); /* Use primary-foreground for the key text */
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
height: 24px;
|
|
67
|
+
padding: 0 6px;
|
|
68
|
+
border: none;
|
|
69
|
+
box-shadow: none;
|
|
70
|
+
top: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* =====================================
|
|
76
|
+
* 2. Modal and Results Styling
|
|
77
|
+
* =====================================
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/* Main modal window */
|
|
81
|
+
.docsearch .DocSearch-Modal {
|
|
82
|
+
backdrop-filter: blur(8px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.docsearch .DocSearch-Container {
|
|
86
|
+
box-shadow: var(--docsearch-modal-shadow);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Search input form */
|
|
90
|
+
.docsearch .DocSearch-Form {
|
|
91
|
+
border: 1px solid hsl(var(--border));
|
|
92
|
+
box-shadow: none;
|
|
93
|
+
background-color: transparent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* The 'Return' and 'Esc' hints */
|
|
97
|
+
.docsearch .DocSearch-Reset-Icon,
|
|
98
|
+
.docsearch .DocSearch-Cancel {
|
|
99
|
+
color: hsl(var(--muted-foreground));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Style for each search result item */
|
|
103
|
+
.docsearch .DocSearch-Hit a {
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Selected search result */
|
|
108
|
+
.docsearch .DocSearch-Hit[aria-selected="true"] a {
|
|
109
|
+
background: var(--docsearch-selected-background); /* Highlight color for selected item */
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Hide category headers if not needed */
|
|
113
|
+
.docsearch .DocSearch-Hit-source {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Icon next to each result title */
|
|
118
|
+
.docsearch .DocSearch-Hit-icon {
|
|
119
|
+
color: hsl(var(--muted-foreground));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Footer instructions ('to navigate', 'to select', etc.) */
|
|
123
|
+
.docsearch .DocSearch-Footer {
|
|
124
|
+
border-top: 1px solid hsl(var(--border));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.docsearch .DocSearch-Footer--commands kbd {
|
|
128
|
+
background: hsl(var(--muted)); /* Use muted for the background of keyboard hints */
|
|
129
|
+
border: 1px solid hsl(var(--border));
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
color: hsl(var(--muted-foreground));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* =====================================
|
|
136
|
+
* 3. Responsive Styling (Mobile)
|
|
137
|
+
* =====================================
|
|
138
|
+
*/
|
|
139
|
+
@media (max-width: 768px) {
|
|
140
|
+
/* Aturan ini akan aktif pada layar 768px ke bawah */
|
|
141
|
+
|
|
142
|
+
.docsearch .DocSearch-Button {
|
|
143
|
+
width: 40px; /* Mengubah lebar tombol menjadi seukuran ikon */
|
|
144
|
+
height: 40px; /* Memastikan tinggi tetap sama */
|
|
145
|
+
padding: 0; /* Menghapus padding agar ikon bisa di tengah */
|
|
146
|
+
justify-content: center; /* Memusatkan ikon di dalam tombol */
|
|
147
|
+
background: none;
|
|
148
|
+
border: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Menyembunyikan teks "Search..." dan shortcut keyboard */
|
|
152
|
+
.docsearch .DocSearch-Button-Placeholder,
|
|
153
|
+
.docsearch .DocSearch-Button-Key {
|
|
154
|
+
display: none;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -12,16 +12,16 @@ import { displayManualSteps, simulateInstallation, displayNextSteps } from "../u
|
|
|
12
12
|
* Creates a new DocuBook project.
|
|
13
13
|
* @param {Object} options - Installation options.
|
|
14
14
|
*/
|
|
15
|
-
export async function createProject({ directoryName, packageManager,
|
|
15
|
+
export async function createProject({ directoryName, packageManager, pmVersion, docubookVersion, installNow }) {
|
|
16
16
|
const projectPath = path.resolve(process.cwd(), directoryName);
|
|
17
17
|
|
|
18
18
|
if (fs.existsSync(projectPath)) {
|
|
19
19
|
throw new Error(`Directory "${directoryName}" already exists.`);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
log.info(`Creating a new DocuBook project in ${chalk.
|
|
22
|
+
log.info(`Creating a new DocuBook project in ${chalk.green(projectPath)}...`);
|
|
23
23
|
|
|
24
|
-
const spinner = ora("
|
|
24
|
+
const spinner = ora("Creating project files...").start();
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
27
|
// 1. Create project directory and copy template files
|
|
@@ -29,25 +29,27 @@ export async function createProject({ directoryName, packageManager, version, in
|
|
|
29
29
|
const __dirname = path.dirname(__filename);
|
|
30
30
|
const templatePath = path.join(__dirname, "../dist");
|
|
31
31
|
copyDirectoryRecursive(templatePath, projectPath);
|
|
32
|
-
spinner.succeed("Project files created.");
|
|
33
32
|
|
|
34
33
|
// 2. Configure package manager specific settings
|
|
35
|
-
spinner.
|
|
34
|
+
spinner.text = "Configuring package manager...";
|
|
36
35
|
configurePackageManager(packageManager, projectPath);
|
|
37
|
-
spinner.succeed("Package manager configured.");
|
|
38
36
|
|
|
39
37
|
// 3. Update package.json
|
|
40
|
-
spinner.
|
|
38
|
+
spinner.text = "Updating package.json...";
|
|
41
39
|
const pkgPath = path.join(projectPath, "package.json");
|
|
42
40
|
if (fs.existsSync(pkgPath)) {
|
|
43
41
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
44
42
|
pkg.name = directoryName; // Set project name
|
|
45
|
-
|
|
43
|
+
// Use the package manager version here
|
|
44
|
+
pkg.packageManager = `${packageManager}@${pmVersion}`;
|
|
46
45
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
47
46
|
}
|
|
48
|
-
spinner.succeed("package.json updated.");
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
// Combine all success messages into one dynamic line
|
|
49
|
+
// Use the docubookVersion for the success message
|
|
50
|
+
spinner.succeed(
|
|
51
|
+
chalk.green(`Successfully installed DocuBook - v${docubookVersion} with ${packageManager}`)
|
|
52
|
+
);
|
|
51
53
|
|
|
52
54
|
if (installNow) {
|
|
53
55
|
await installDependencies(directoryName, packageManager, projectPath);
|
|
@@ -97,7 +99,7 @@ function copyDirectoryRecursive(source, destination) {
|
|
|
97
99
|
*/
|
|
98
100
|
async function installDependencies(directoryName, packageManager, projectPath) {
|
|
99
101
|
log.info("Installing dependencies...");
|
|
100
|
-
const installSpinner = ora(`Running ${chalk.
|
|
102
|
+
const installSpinner = ora(`Running ${chalk.green(`${packageManager} install`)}...`).start();
|
|
101
103
|
|
|
102
104
|
try {
|
|
103
105
|
execSync(`${packageManager} install`, { cwd: projectPath, stdio: "ignore" });
|
|
@@ -107,4 +109,4 @@ async function installDependencies(directoryName, packageManager, projectPath) {
|
|
|
107
109
|
displayManualSteps(directoryName, packageManager);
|
|
108
110
|
throw new Error("Dependency installation failed.");
|
|
109
111
|
}
|
|
110
|
-
}
|
|
112
|
+
}
|
package/src/utils/display.js
CHANGED
|
@@ -16,7 +16,7 @@ export function displayIntro() {
|
|
|
16
16
|
export async function simulateInstallation() {
|
|
17
17
|
const bar = new cliProgress.SingleBar(
|
|
18
18
|
{
|
|
19
|
-
format: `Finishing setup... ${chalk.
|
|
19
|
+
format: `Finishing setup... ${chalk.greenBright("{bar}")} | {percentage}%`,
|
|
20
20
|
barCompleteChar: "\u2588",
|
|
21
21
|
barIncompleteChar: "\u2591",
|
|
22
22
|
hideCursor: true,
|
|
@@ -42,9 +42,9 @@ export function displayManualSteps(projectDirectory, packageManager) {
|
|
|
42
42
|
const steps = `
|
|
43
43
|
${chalk.yellow("Automatic installation failed.")} Please finish setup manually:
|
|
44
44
|
|
|
45
|
-
1. ${chalk.
|
|
46
|
-
2. ${chalk.
|
|
47
|
-
3. ${chalk.
|
|
45
|
+
1. ${chalk.blueBright(`cd ${projectDirectory}`)}
|
|
46
|
+
2. ${chalk.blueBright(`${packageManager} install`)}
|
|
47
|
+
3. ${chalk.blueBright(`${packageManager} run dev`)}
|
|
48
48
|
`;
|
|
49
49
|
console.log(
|
|
50
50
|
boxen(steps, {
|
|
@@ -67,8 +67,8 @@ export function displayNextSteps(directoryName, packageManager) {
|
|
|
67
67
|
const steps = `
|
|
68
68
|
${chalk.bold("Next steps:")}
|
|
69
69
|
|
|
70
|
-
1. ${chalk.
|
|
71
|
-
2. ${chalk.
|
|
70
|
+
1. ${chalk.blueBright(`cd ${directoryName}`)}
|
|
71
|
+
2. ${chalk.blueBright(`${packageManager} run dev`)}
|
|
72
72
|
`;
|
|
73
73
|
|
|
74
74
|
console.log(
|