@docubook/create 1.14.0 → 1.14.2
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 +3 -3
- package/src/cli/program.js +13 -12
- package/src/cli/promptHandler.js +40 -47
- package/src/dist/app/page.tsx +2 -2
- package/src/dist/components/SearchModal.tsx +198 -0
- package/src/dist/components/SearchTrigger.tsx +31 -0
- package/src/dist/components/search.tsx +27 -222
- package/src/dist/contents/docs/changelog/version-1/index.mdx +16 -0
- package/src/dist/package.json +1 -1
- package/src/installer/projectInstaller.js +43 -56
- package/src/utils/display.js +44 -43
- package/src/utils/logger.js +5 -6
- package/src/utils/packageManager.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docubook/create",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.2",
|
|
4
4
|
"description": "CLI to create DocuBook projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"chalk": "^5.3.0",
|
|
24
24
|
"cli-progress": "^3.12.0",
|
|
25
25
|
"commander": "^12.1.0",
|
|
26
|
-
"enquirer": "^2.4.1",
|
|
27
26
|
"figlet": "^1.8.0",
|
|
28
|
-
"ora": "^8.1.0"
|
|
27
|
+
"ora": "^8.1.0",
|
|
28
|
+
"prompts": "^2.4.2"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/src/cli/program.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { program } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import { displayAsciiArt } from "../utils/display.js";
|
|
2
|
+
import { displayIntro } from "../utils/display.js";
|
|
4
3
|
import { collectUserInput } from "./promptHandler.js";
|
|
5
4
|
import { createProject } from "../installer/projectInstaller.js";
|
|
5
|
+
import log from "../utils/logger.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Initializes the CLI program
|
|
@@ -11,19 +11,20 @@ import { createProject } from "../installer/projectInstaller.js";
|
|
|
11
11
|
export function initializeProgram(version) {
|
|
12
12
|
program
|
|
13
13
|
.version(version)
|
|
14
|
-
.description("CLI to create a new
|
|
15
|
-
.
|
|
14
|
+
.description("CLI to create a new DocuBook project")
|
|
15
|
+
.argument("[directory]", "The name of the project directory")
|
|
16
|
+
.action(async (directory) => {
|
|
16
17
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
displayIntro();
|
|
19
|
+
const userInput = await collectUserInput(directory);
|
|
19
20
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
// Pass all user input AND the DocuBook version to createProject
|
|
22
|
+
await createProject({
|
|
23
|
+
...userInput,
|
|
24
|
+
docubookVersion: version, // Add DocuBook version here
|
|
25
|
+
});
|
|
25
26
|
} catch (err) {
|
|
26
|
-
|
|
27
|
+
log.error(err.message || "An unexpected error occurred.");
|
|
27
28
|
process.exit(1);
|
|
28
29
|
}
|
|
29
30
|
});
|
package/src/cli/promptHandler.js
CHANGED
|
@@ -1,73 +1,66 @@
|
|
|
1
|
-
import
|
|
1
|
+
import prompts from "prompts";
|
|
2
2
|
import { detectDefaultPackageManager, getPackageManagerVersion } from "../utils/packageManager.js";
|
|
3
3
|
import log from "../utils/logger.js";
|
|
4
|
-
|
|
5
|
-
const { prompt } = enquirer;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Detects if the command was run with yarn or yarn dlx
|
|
9
|
-
* @returns {boolean}
|
|
10
|
-
*/
|
|
11
|
-
function isYarnCommand() {
|
|
12
|
-
const userAgent = process.env.npm_config_user_agent || '';
|
|
13
|
-
return userAgent.includes('yarn') || process.argv.some(arg => arg.includes('yarn'));
|
|
14
|
-
}
|
|
4
|
+
import chalk from "chalk";
|
|
15
5
|
|
|
16
6
|
/**
|
|
17
7
|
* Collects user input for project creation
|
|
8
|
+
* @param {string} [cliProvidedDir] - The directory name provided via CLI argument.
|
|
18
9
|
* @returns {Promise<Object>} User answers
|
|
19
10
|
*/
|
|
20
|
-
export async function collectUserInput() {
|
|
21
|
-
// Skip installation prompt if running with yarn/yarn dlx
|
|
22
|
-
const isYarn = isYarnCommand();
|
|
11
|
+
export async function collectUserInput(cliProvidedDir) {
|
|
23
12
|
const defaultPackageManager = detectDefaultPackageManager();
|
|
13
|
+
let answers = {
|
|
14
|
+
directoryName: cliProvidedDir
|
|
15
|
+
};
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
const { directoryName, packageManager } = await prompt([
|
|
17
|
+
const questions = [
|
|
27
18
|
{
|
|
28
|
-
type: "
|
|
19
|
+
type: cliProvidedDir ? null : "text",
|
|
29
20
|
name: "directoryName",
|
|
30
|
-
message: "
|
|
21
|
+
message: "What is your project named?",
|
|
31
22
|
initial: "docubook",
|
|
23
|
+
validate: (name) => name.trim().length > 0 ? true : "Project name cannot be empty.",
|
|
32
24
|
},
|
|
33
25
|
{
|
|
34
26
|
type: "select",
|
|
35
27
|
name: "packageManager",
|
|
36
|
-
message: "
|
|
37
|
-
choices: [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
type: "confirm",
|
|
47
|
-
name: "
|
|
48
|
-
message: "
|
|
28
|
+
message: "Select your package manager:",
|
|
29
|
+
choices: [
|
|
30
|
+
{ title: "npm", value: "npm" },
|
|
31
|
+
{ title: "pnpm", value: "pnpm" },
|
|
32
|
+
{ title: "yarn", value: "yarn" },
|
|
33
|
+
{ title: "bun", value: "bun" },
|
|
34
|
+
],
|
|
35
|
+
initial: ["npm", "pnpm", "yarn", "bun"].indexOf(defaultPackageManager),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: (prev) => (prev !== 'yarn' ? "confirm" : null),
|
|
39
|
+
name: "installNow",
|
|
40
|
+
message: "Would you like to install dependencies now?",
|
|
49
41
|
initial: true,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
}
|
|
42
|
+
},
|
|
43
|
+
];
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
45
|
+
const promptAnswers = await prompts(questions, {
|
|
46
|
+
onCancel: () => {
|
|
47
|
+
log.error("Scaffolding cancelled.");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
const version = getPackageManagerVersion(packageManager);
|
|
52
|
+
answers = { ...answers, ...promptAnswers };
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
// Validate the selected package manager
|
|
55
|
+
const pmVersion = getPackageManagerVersion(answers.packageManager);
|
|
56
|
+
if (!pmVersion) {
|
|
57
|
+
throw new Error(`${chalk.bold(answers.packageManager)} is not installed on your system. Please install it to continue.`);
|
|
67
58
|
}
|
|
68
59
|
|
|
60
|
+
// Return all answers, including the package manager version with a clear property name
|
|
69
61
|
return {
|
|
70
62
|
...answers,
|
|
71
|
-
|
|
63
|
+
directoryName: answers.directoryName.trim(),
|
|
64
|
+
pmVersion,
|
|
72
65
|
};
|
|
73
66
|
}
|
package/src/dist/app/page.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
|
|
|
6
6
|
import AnimatedShinyText from "@/components/ui/animated-shiny-text";
|
|
7
7
|
import { getMetadata } from "@/app/layout";
|
|
8
8
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
export const metadata = getMetadata({
|
|
11
11
|
title: "Home",
|
|
12
12
|
});
|
|
@@ -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.14.
|
|
28
|
+
<span>🚀 New Version - Release v1.14.2</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,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,52 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "@/components/ui/dialog";
|
|
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
|
+
|
|
8
|
+
// Define props for the Search component
|
|
9
|
+
interface SearchProps {
|
|
10
|
+
/**
|
|
11
|
+
* Specify the type of search engine to use.
|
|
12
|
+
* @default 'default'
|
|
13
|
+
*/
|
|
14
|
+
type?: "default" | "algolia";
|
|
15
|
+
}
|
|
37
16
|
|
|
38
|
-
export default function Search() {
|
|
39
|
-
const router = useRouter();
|
|
40
|
-
const [searchedInput, setSearchedInput] = useState("");
|
|
17
|
+
export default function Search({ type = "default" }: SearchProps) {
|
|
41
18
|
const [isOpen, setIsOpen] = useState(false);
|
|
42
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
43
|
-
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
44
19
|
|
|
20
|
+
// Effect to handle keyboard shortcut (Cmd/Ctrl + K)
|
|
45
21
|
useEffect(() => {
|
|
46
22
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
47
23
|
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
|
48
24
|
event.preventDefault();
|
|
49
|
-
setIsOpen(
|
|
25
|
+
setIsOpen((open) => !open);
|
|
50
26
|
}
|
|
51
27
|
};
|
|
52
28
|
|
|
@@ -56,191 +32,20 @@ export default function Search() {
|
|
|
56
32
|
};
|
|
57
33
|
}, []);
|
|
58
34
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
setSelectedIndex(0);
|
|
81
|
-
}, [filteredResults]);
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
const handleNavigation = (event: KeyboardEvent) => {
|
|
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);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
window.addEventListener("keydown", handleNavigation);
|
|
108
|
-
return () => {
|
|
109
|
-
window.removeEventListener("keydown", handleNavigation);
|
|
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
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}, [selectedIndex]);
|
|
35
|
+
// Here you can add logic for different search types if needed in the future
|
|
36
|
+
if (type === "algolia") {
|
|
37
|
+
// return <AlgoliaSearchComponent />; // Example for future implementation
|
|
38
|
+
console.warn("Tipe pencarian 'algolia' belum diimplementasikan.");
|
|
39
|
+
// For now, we will fall back to the default search implementation
|
|
40
|
+
}
|
|
121
41
|
|
|
42
|
+
// Render the default search components
|
|
122
43
|
return (
|
|
123
44
|
<div>
|
|
124
|
-
<Dialog
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!open) setSearchedInput("");
|
|
128
|
-
setIsOpen(open);
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
<DialogTrigger asChild>
|
|
132
|
-
<div className="relative flex-1 cursor-pointer max-w-[140px]">
|
|
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>
|
|
151
|
-
</DialogTrigger>
|
|
152
|
-
<DialogContent className="p-0 max-w-[650px] sm:top-[38%] top-[45%] !rounded-md">
|
|
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>
|
|
45
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
46
|
+
<SearchTrigger />
|
|
47
|
+
<SearchModal isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
237
48
|
</Dialog>
|
|
238
49
|
</div>
|
|
239
50
|
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const paddingMap = {
|
|
243
|
-
1: "pl-2",
|
|
244
|
-
2: "pl-4",
|
|
245
|
-
3: "pl-10",
|
|
246
|
-
} as const;
|
|
51
|
+
}
|
|
@@ -8,6 +8,22 @@ 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.14.2
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<Release version="1.14.2" date="2025-08-05" title="Refactor & Fix: Decouple Search Component and Resolve Type Errors">
|
|
16
|
+
<Changes type="added">
|
|
17
|
+
- Refactor Search component into three distinct components: Search, SearchTrigger, and SearchModal for better maintainability and scalability.
|
|
18
|
+
- New SearchTrigger components
|
|
19
|
+
- New SearchModal components
|
|
20
|
+
</Changes>
|
|
21
|
+
<Changes type="fixed">
|
|
22
|
+
- Resolve TypeScript error for missing 'noLink' property on the 'Page' type after refactoring.
|
|
23
|
+
- Fix TypeScript error for missing 'context' property by providing a complete inline type annotation in the SearchModal component.
|
|
24
|
+
</Changes>
|
|
25
|
+
</Release>
|
|
26
|
+
|
|
11
27
|
<div className="sr-only">
|
|
12
28
|
### v 1.14.0
|
|
13
29
|
</div>
|
package/src/dist/package.json
CHANGED
|
@@ -9,117 +9,104 @@ import { configurePackageManager } from "../utils/packageManager.js";
|
|
|
9
9
|
import { displayManualSteps, simulateInstallation, displayNextSteps } from "../utils/display.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Creates a new DocuBook project
|
|
13
|
-
* @param {Object} options - Installation options
|
|
14
|
-
* @param {string} options.directoryName - Project directory name
|
|
15
|
-
* @param {string} options.packageManager - Package manager to use
|
|
16
|
-
* @param {string} options.version - Package manager version
|
|
17
|
-
* @param {boolean} options.installNow - Whether to install dependencies immediately
|
|
18
|
-
* @returns {Promise<void>}
|
|
12
|
+
* Creates a new DocuBook project.
|
|
13
|
+
* @param {Object} options - Installation options.
|
|
19
14
|
*/
|
|
20
|
-
export async function createProject({ directoryName, packageManager,
|
|
15
|
+
export async function createProject({ directoryName, packageManager, pmVersion, docubookVersion, installNow }) {
|
|
21
16
|
const projectPath = path.resolve(process.cwd(), directoryName);
|
|
22
|
-
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(projectPath)) {
|
|
19
|
+
throw new Error(`Directory "${directoryName}" already exists.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
log.info(`Creating a new DocuBook project in ${chalk.green(projectPath)}...`);
|
|
23
|
+
|
|
24
|
+
const spinner = ora("Creating project files...").start();
|
|
23
25
|
|
|
24
26
|
try {
|
|
25
|
-
//
|
|
27
|
+
// 1. Create project directory and copy template files
|
|
26
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
29
|
const __dirname = path.dirname(__filename);
|
|
28
30
|
const templatePath = path.join(__dirname, "../dist");
|
|
29
|
-
|
|
30
|
-
// Create project directory if it doesn't exist
|
|
31
|
-
if (!fs.existsSync(projectPath)) {
|
|
32
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Copy template files to project directory
|
|
36
31
|
copyDirectoryRecursive(templatePath, projectPath);
|
|
37
32
|
|
|
38
|
-
// Configure package manager specific settings
|
|
33
|
+
// 2. Configure package manager specific settings
|
|
34
|
+
spinner.text = "Configuring package manager...";
|
|
39
35
|
configurePackageManager(packageManager, projectPath);
|
|
40
36
|
|
|
41
|
-
// Update package.json
|
|
37
|
+
// 3. Update package.json
|
|
38
|
+
spinner.text = "Updating package.json...";
|
|
42
39
|
const pkgPath = path.join(projectPath, "package.json");
|
|
43
|
-
let pkgVersion = "";
|
|
44
40
|
if (fs.existsSync(pkgPath)) {
|
|
45
41
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
46
|
-
pkg.
|
|
47
|
-
|
|
42
|
+
pkg.name = directoryName; // Set project name
|
|
43
|
+
// Use the package manager version here
|
|
44
|
+
pkg.packageManager = `${packageManager}@${pmVersion}`;
|
|
48
45
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
);
|
|
53
53
|
|
|
54
|
-
if (
|
|
54
|
+
if (installNow) {
|
|
55
|
+
await installDependencies(directoryName, packageManager, projectPath);
|
|
56
|
+
await simulateInstallation();
|
|
57
|
+
displayNextSteps(directoryName, packageManager);
|
|
58
|
+
} else {
|
|
55
59
|
displayManualSteps(directoryName, packageManager);
|
|
56
|
-
return;
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
await installDependencies(directoryName, packageManager, projectPath);
|
|
60
61
|
} catch (err) {
|
|
61
62
|
spinner.fail("Failed to create project.");
|
|
62
|
-
|
|
63
|
+
// Cleanup created directory on failure
|
|
64
|
+
if (fs.existsSync(projectPath)) {
|
|
65
|
+
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
63
67
|
throw err;
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
/**
|
|
68
|
-
* Recursively copies a directory
|
|
69
|
-
* @param {string} source - Source directory path
|
|
70
|
-
* @param {string} destination - Destination directory path
|
|
72
|
+
* Recursively copies a directory.
|
|
73
|
+
* @param {string} source - Source directory path.
|
|
74
|
+
* @param {string} destination - Destination directory path.
|
|
71
75
|
*/
|
|
72
76
|
function copyDirectoryRecursive(source, destination) {
|
|
73
|
-
// Create destination directory if it doesn't exist
|
|
74
77
|
if (!fs.existsSync(destination)) {
|
|
75
78
|
fs.mkdirSync(destination, { recursive: true });
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
// Read source directory contents
|
|
79
81
|
const entries = fs.readdirSync(source, { withFileTypes: true });
|
|
80
|
-
|
|
81
|
-
// Process each entry
|
|
82
82
|
for (const entry of entries) {
|
|
83
83
|
const srcPath = path.join(source, entry.name);
|
|
84
84
|
const destPath = path.join(destination, entry.name);
|
|
85
85
|
|
|
86
|
-
// If entry is a directory, recursively copy it
|
|
87
86
|
if (entry.isDirectory()) {
|
|
88
87
|
copyDirectoryRecursive(srcPath, destPath);
|
|
89
88
|
} else {
|
|
90
|
-
// Otherwise, copy the file
|
|
91
89
|
fs.copyFileSync(srcPath, destPath);
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
/**
|
|
97
|
-
* Installs project dependencies
|
|
98
|
-
* @param {string} directoryName - Project directory name
|
|
99
|
-
* @param {string} packageManager - Package manager to use
|
|
100
|
-
* @param {string} projectPath - Path to the project directory
|
|
101
|
-
* @returns {Promise<void>}
|
|
95
|
+
* Installs project dependencies.
|
|
96
|
+
* @param {string} directoryName - Project directory name.
|
|
97
|
+
* @param {string} packageManager - Package manager to use.
|
|
98
|
+
* @param {string} projectPath - Path to the project directory.
|
|
102
99
|
*/
|
|
103
100
|
async function installDependencies(directoryName, packageManager, projectPath) {
|
|
104
101
|
log.info("Installing dependencies...");
|
|
105
|
-
|
|
106
|
-
console.log(
|
|
107
|
-
chalk.white(
|
|
108
|
-
"You don't need to worry about this process not running, you just need the latest device for a faster installation process."
|
|
109
|
-
)
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const installSpinner = ora(`Using ${packageManager}...`).start();
|
|
102
|
+
const installSpinner = ora(`Running ${chalk.green(`${packageManager} install`)}...`).start();
|
|
113
103
|
|
|
114
104
|
try {
|
|
115
105
|
execSync(`${packageManager} install`, { cwd: projectPath, stdio: "ignore" });
|
|
116
|
-
installSpinner.succeed("Dependencies installed.");
|
|
117
|
-
|
|
118
|
-
await simulateInstallation();
|
|
119
|
-
displayNextSteps(directoryName, packageManager);
|
|
106
|
+
installSpinner.succeed("Dependencies installed successfully.");
|
|
120
107
|
} catch (error) {
|
|
121
108
|
installSpinner.fail("Failed to install dependencies.");
|
|
122
109
|
displayManualSteps(directoryName, packageManager);
|
|
123
|
-
throw new Error("
|
|
110
|
+
throw new Error("Dependency installation failed.");
|
|
124
111
|
}
|
|
125
112
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -1,83 +1,84 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import figlet from "figlet";
|
|
3
2
|
import boxen from "boxen";
|
|
4
3
|
import cliProgress from "cli-progress";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* Displays
|
|
8
|
-
* @returns {Promise} Promise that resolves when ASCII art is displayed
|
|
6
|
+
* Displays an introduction message for the CLI.
|
|
9
7
|
*/
|
|
10
|
-
export function
|
|
11
|
-
|
|
12
|
-
figlet.text("DocuBook", { horizontalLayout: "full" }, (err, data) => {
|
|
13
|
-
if (err) return reject(err);
|
|
14
|
-
console.log(chalk.green(data));
|
|
15
|
-
resolve();
|
|
16
|
-
});
|
|
17
|
-
});
|
|
8
|
+
export function displayIntro() {
|
|
9
|
+
console.log(`\n${chalk.bold.green("🚀 DocuBook Installer")}\n`);
|
|
18
10
|
}
|
|
19
11
|
|
|
20
12
|
/**
|
|
21
|
-
* Displays a progress bar to simulate final setup
|
|
22
|
-
* @returns {Promise} Promise that resolves when simulation completes
|
|
13
|
+
* Displays a progress bar to simulate final setup.
|
|
14
|
+
* @returns {Promise<void>} Promise that resolves when simulation completes.
|
|
23
15
|
*/
|
|
24
16
|
export async function simulateInstallation() {
|
|
25
17
|
const bar = new cliProgress.SingleBar(
|
|
26
18
|
{
|
|
27
|
-
format:
|
|
28
|
-
barCompleteChar:
|
|
29
|
-
barIncompleteChar:
|
|
19
|
+
format: `Finishing setup... ${chalk.greenBright("{bar}")} | {percentage}%`,
|
|
20
|
+
barCompleteChar: "\u2588",
|
|
21
|
+
barIncompleteChar: "\u2591",
|
|
22
|
+
hideCursor: true,
|
|
30
23
|
},
|
|
31
24
|
cliProgress.Presets.shades_classic
|
|
32
25
|
);
|
|
33
26
|
|
|
34
27
|
bar.start(100, 0);
|
|
35
28
|
for (let i = 0; i <= 100; i++) {
|
|
36
|
-
await new Promise((r) => setTimeout(r,
|
|
29
|
+
await new Promise((r) => setTimeout(r, 20)); // Faster simulation
|
|
37
30
|
bar.update(i);
|
|
38
31
|
}
|
|
39
32
|
bar.stop();
|
|
33
|
+
console.log("\n");
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
/**
|
|
43
|
-
* Displays manual installation steps if automatic installation fails
|
|
44
|
-
* @param {string} projectDirectory - Project directory name
|
|
45
|
-
* @param {string} packageManager - Package manager being used
|
|
37
|
+
* Displays manual installation steps if automatic installation fails.
|
|
38
|
+
* @param {string} projectDirectory - Project directory name.
|
|
39
|
+
* @param {string} packageManager - Package manager being used.
|
|
46
40
|
*/
|
|
47
41
|
export function displayManualSteps(projectDirectory, packageManager) {
|
|
48
|
-
const
|
|
49
|
-
|
|
42
|
+
const steps = `
|
|
43
|
+
${chalk.yellow("Automatic installation failed.")} Please finish setup manually:
|
|
50
44
|
|
|
51
|
-
1. ${chalk.
|
|
52
|
-
2. ${chalk.
|
|
53
|
-
3. ${chalk.
|
|
45
|
+
1. ${chalk.blueBright(`cd ${projectDirectory}`)}
|
|
46
|
+
2. ${chalk.blueBright(`${packageManager} install`)}
|
|
47
|
+
3. ${chalk.blueBright(`${packageManager} run dev`)}
|
|
54
48
|
`;
|
|
55
|
-
|
|
56
49
|
console.log(
|
|
57
|
-
boxen(
|
|
58
|
-
padding:
|
|
50
|
+
boxen(steps, {
|
|
51
|
+
padding: 1,
|
|
52
|
+
margin: 1,
|
|
59
53
|
borderStyle: "round",
|
|
60
|
-
borderColor: "
|
|
54
|
+
borderColor: "yellow",
|
|
55
|
+
title: "Manual Setup Required",
|
|
56
|
+
titleAlignment: "center",
|
|
61
57
|
})
|
|
62
58
|
);
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
/**
|
|
66
|
-
* Displays next steps after successful installation
|
|
67
|
-
* @param {string} directoryName - Project directory name
|
|
68
|
-
* @param {string} packageManager - Package manager being used
|
|
62
|
+
* Displays next steps after successful installation.
|
|
63
|
+
* @param {string} directoryName - Project directory name.
|
|
64
|
+
* @param {string} packageManager - Package manager being used.
|
|
69
65
|
*/
|
|
70
66
|
export function displayNextSteps(directoryName, packageManager) {
|
|
67
|
+
const steps = `
|
|
68
|
+
${chalk.bold("Next steps:")}
|
|
69
|
+
|
|
70
|
+
1. ${chalk.blueBright(`cd ${directoryName}`)}
|
|
71
|
+
2. ${chalk.blueBright(`${packageManager} run dev`)}
|
|
72
|
+
`;
|
|
73
|
+
|
|
71
74
|
console.log(
|
|
72
|
-
boxen(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
)
|
|
75
|
+
boxen(steps, {
|
|
76
|
+
padding: 1,
|
|
77
|
+
margin: 1,
|
|
78
|
+
borderStyle: "round",
|
|
79
|
+
borderColor: "green",
|
|
80
|
+
title: "Success!",
|
|
81
|
+
titleAlignment: "center",
|
|
82
|
+
})
|
|
82
83
|
);
|
|
83
|
-
}
|
|
84
|
+
}
|
package/src/utils/logger.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
|
|
3
|
-
// Logging helper with styles
|
|
4
3
|
const log = {
|
|
5
|
-
info: (msg) => console.log(chalk.
|
|
6
|
-
success: (msg) => console.log(chalk.green("✔
|
|
7
|
-
warn: (msg) => console.log(chalk.yellow("
|
|
8
|
-
error: (msg) => console.log(chalk.red("
|
|
4
|
+
info: (msg) => console.log(`${chalk.blue("ℹ")} ${msg}`),
|
|
5
|
+
success: (msg) => console.log(`${chalk.green("✔")} ${msg}`),
|
|
6
|
+
warn: (msg) => console.log(`${chalk.yellow("⚠")} ${msg}`),
|
|
7
|
+
error: (msg) => console.log(`\n${chalk.red("✖")} ${chalk.bold(msg)}\n`),
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
export default log;
|
|
10
|
+
export default log;
|