|
1 | 1 | import fs from "fs";
|
2 | 2 | import path from "path";
|
3 |
| -import * as readline from "readline/promises"; |
4 |
| -import { stdin, stdout } from "process"; |
5 |
| -import axios from "axios"; |
6 |
| -import { promisify } from "util"; |
7 |
| -import url from "url"; |
8 |
| -import Editors from "./helpers/editors"; |
9 | 3 | import { checkGitInstallation } from "./helpers/git";
|
| 4 | +import { askQuestion, slugify, closeRL } from "./helpers/cmd"; |
10 | 5 |
|
| 6 | +const Creators = [ |
| 7 | + { |
| 8 | + name: "Vtuber Entry", |
| 9 | + path: path.resolve(__dirname, "create", "vtubers.ts"), |
| 10 | + }, |
| 11 | + // { |
| 12 | + // name: "Vtuber Agency", |
| 13 | + // path: path.resolve(__dirname, "create", "agencies.ts"), |
| 14 | + // }, |
| 15 | + // { |
| 16 | + // name: "Guide Entry", |
| 17 | + // path: path.resolve(__dirname, "create", "guides.ts"), |
| 18 | + // }, |
| 19 | + // { |
| 20 | + // name: "Software Entry", |
| 21 | + // path: path.resolve(__dirname, "create", "software.ts"), |
| 22 | + // } |
| 23 | +]; |
11 | 24 |
|
12 |
| -const rl = readline.createInterface({ input: stdin, output: stdout }); |
| 25 | +async function main() { |
| 26 | + const gitUser = await checkGitInstallation(); |
13 | 27 |
|
14 |
| -const config = { |
15 |
| - name: "n/a", |
16 |
| - description: "n/a", |
17 |
| - avatarUrl: |
18 |
| - "https://via.placeholder.com/512x512.png?text=Avatar+Image+Placeholder", |
19 |
| - bannerUrl: |
20 |
| - "https://via.placeholder.com/1920x1080.png?text=Banner+Image+Placeholder", |
21 |
| - borderColor: "#000000", |
22 |
| - author: "n/a", |
23 |
| - links: [], |
24 |
| - slug: "default", |
25 |
| - graduated: false, |
26 |
| - is_draft: true, |
27 |
| -}; |
| 28 | + console.log("Welcome to the VtuberWiki Create CLI tool."); |
| 29 | + console.log(`Logged in as ${gitUser}`); |
28 | 30 |
|
29 |
| -async function checkIfImageExists(url: string): Promise<boolean> { |
30 |
| - try { |
31 |
| - const response = await axios.get(url); |
32 |
| - return response.status === 200; |
33 |
| - } catch (error) { |
34 |
| - return false; |
35 |
| - } |
36 |
| -} |
37 |
| - |
38 |
| -const paths = { |
39 |
| - vtuberMarkdown: path.resolve( |
40 |
| - __dirname, |
41 |
| - "..", |
42 |
| - "..", |
43 |
| - "src", |
44 |
| - "content", |
45 |
| - "vtubers" |
46 |
| - ), |
47 |
| - vtuberStatic: path.resolve( |
48 |
| - __dirname, |
49 |
| - "..", |
50 |
| - "..", |
51 |
| - "public", |
52 |
| - "static", |
53 |
| - "vtubers" |
54 |
| - ), |
55 |
| -}; |
56 |
| - |
57 |
| -async function validateInput(input: string, type: any): Promise<boolean> { |
58 |
| - switch (type) { |
59 |
| - case "url": |
60 |
| - const hostname = new URL(input).hostname; |
61 |
| - const firstLayer = |
62 |
| - input.startsWith("http://") || input.startsWith("https://"); |
63 |
| - return firstLayer; |
64 |
| - break; |
65 |
| - case "hex": |
66 |
| - return /^#[0-9A-F]{6}$/i.test(input); |
67 |
| - case "string": |
68 |
| - return typeof input === "string"; |
69 |
| - case "int": |
70 |
| - return Number.isInteger(Number(input)); |
71 |
| - case "array": |
72 |
| - return Array.isArray(input); |
73 |
| - case "obj": |
74 |
| - return ( |
75 |
| - typeof input === "object" && input !== null && !Array.isArray(input) |
76 |
| - ); |
77 |
| - default: |
78 |
| - throw new TypeError("Invalid type"); |
79 |
| - } |
80 |
| -} |
81 |
| - |
82 |
| -async function validateLinks(links: string[]): Promise<boolean> { |
83 |
| - for (let link of links) { |
84 |
| - if (!link.startsWith("http://") && !link.startsWith("https://")) { |
85 |
| - return false; |
86 |
| - } |
87 |
| - } |
88 |
| - return true; |
89 |
| -} |
90 |
| - |
91 |
| -function slugify(text: string): string { |
92 |
| - return text |
93 |
| - .toLowerCase() |
94 |
| - .replace(/ /g, "-") |
95 |
| - .replace(/[^\w-]+/g, ""); |
96 |
| -} |
97 |
| - |
98 |
| -async function askQuestion(questionText: string, type: string): Promise<any> { |
99 |
| - let userInput; |
100 |
| - do { |
101 |
| - userInput = await rl.question(questionText); |
102 |
| - userInput = userInput.trim(); // Trim leading and trailing spaces |
103 |
| - if (!(await validateInput(userInput, type))) { |
104 |
| - console.log(`Invalid input. Please enter a valid ${type}.`); |
105 |
| - } |
106 |
| - } while (!(await validateInput(userInput, type))); |
107 |
| - return userInput; |
108 |
| -} |
109 |
| - |
110 |
| -async function isEditorInstalled(editorCommand: string) { |
111 |
| - const { exec } = require("child_process"); |
112 |
| - return new Promise((resolve, reject) => { |
113 |
| - const command = |
114 |
| - process.platform === "win32" |
115 |
| - ? `where ${editorCommand}` |
116 |
| - : `which ${editorCommand}`; |
117 |
| - exec(command, (error: any, stdout: any, stderr: any) => { |
118 |
| - if (error || stderr) { |
119 |
| - resolve(false); // Editor is not installed |
120 |
| - } else { |
121 |
| - resolve(true); // Editor is installed |
122 |
| - } |
123 |
| - }); |
124 |
| - }); |
125 |
| -} |
126 |
| - |
127 |
| -async function downloadImage(url: string, dest: string) { |
128 |
| - try { |
129 |
| - const writer = fs.createWriteStream(dest); |
130 |
| - const response = await axios({ |
131 |
| - url, |
132 |
| - method: "GET", |
133 |
| - responseType: "stream", |
134 |
| - }); |
135 |
| - |
136 |
| - response.data.pipe(writer); |
137 |
| - |
138 |
| - return new Promise((resolve, reject) => { |
139 |
| - writer.on("finish", resolve); |
140 |
| - writer.on("error", reject); |
141 |
| - }); |
142 |
| - } catch (error) { |
143 |
| - console.error("Error downloading image:", error); |
144 |
| - throw error; |
145 |
| - } |
146 |
| -} |
147 |
| - |
148 |
| -const createvt = async () => { |
149 |
| - let author = await checkGitInstallation(); |
150 |
| - |
151 |
| - |
152 |
| - |
153 |
| - console.log("Welcome to the createvt CLI tool."); |
154 |
| - console.log("This tool will help you create a new vtuber entry."); |
155 |
| - console.log("Please answer the following questions to get started."); |
156 |
| - |
157 |
| - |
158 |
| - config.name = await askQuestion("Name: ", "string"); |
159 |
| - config.description = await askQuestion("Description: ", "string"); |
160 |
| - config.avatarUrl = await askQuestion("Avatar Url: ", "url"); |
161 |
| - config.bannerUrl = await askQuestion("Banner Url: ", "url"); |
162 |
| - config.borderColor = await askQuestion("Border Color: ", "hex"); |
163 |
| - let links = await askQuestion("Links (comma separated): ", "string"); |
164 |
| - config.links = links.split(",").map((link: string) => link.trim()); |
165 |
| - let graduated = await askQuestion("Graduated? (Y/N): ", "string"); |
166 |
| - config.slug = slugify(config.name); |
167 |
| - config.author = author as string; |
168 |
| - |
169 |
| - if (graduated.toLowerCase() === "y") { |
170 |
| - config.graduated = true; |
171 |
| - } else { |
172 |
| - config.graduated = false; |
173 |
| - } |
174 |
| - |
175 |
| - console.clear(); |
176 |
| - console.log(`Does this look correct?`); |
177 |
| - console.log(""); |
178 |
| - console.log(`Vtuber ${config.name} with description ${config.description}`); |
179 |
| - console.log(""); |
180 |
| - console.log(`With avatar ${config.avatarUrl} and banner ${config.bannerUrl}`); |
181 |
| - console.log(""); |
182 |
| - console.log( |
183 |
| - `With border color ${config.borderColor} and links ${config.links}?` |
184 |
| - ); |
185 |
| - console.log(""); |
186 |
| - |
187 |
| - let confirmation = await askQuestion("Y/N: ", "string"); |
| 31 | + let creatorType = ""; |
188 | 32 |
|
189 |
| - if (confirmation.toLowerCase() !== "y") { |
190 |
| - console.log("Exiting..."); |
191 |
| - process.exit(0); |
192 |
| - } |
193 |
| - |
194 |
| - console.clear(); |
195 |
| - |
196 |
| - console.log(`Creating BASE entry for ${config.name}...`); |
197 |
| - |
198 |
| - |
199 |
| - const $links = config.links.map((link: string) => { |
200 |
| - if (link.startsWith("http://")) { |
201 |
| - link = link.replace("http://", "https://"); |
202 |
| - } |
203 |
| - |
204 |
| - // Remove the subdomain from the url |
205 |
| - const parsedUrl = new URL(link); |
206 |
| - parsedUrl.hostname = parsedUrl.hostname.replace(/^www\./, ''); |
207 |
| - return parsedUrl.toString(); |
208 |
| -}); |
209 |
| - |
210 |
| - |
211 |
| - const vtuberMarkdownPath = path.resolve( |
212 |
| - paths.vtuberMarkdown, |
213 |
| - `${config.slug}.md` |
| 33 | + const creatorTypes = Creators.map((creator) => creator.name); |
| 34 | + creatorType = await askQuestion( |
| 35 | + `What would you like to create? (${creatorTypes.join(", ")})`, |
| 36 | + "string" |
214 | 37 | );
|
215 |
| - const vtuberStaticPath = path.resolve(paths.vtuberStatic, config.slug); |
216 | 38 |
|
217 |
| - fs.mkdirSync(vtuberStaticPath); |
218 |
| - await downloadImage( |
219 |
| - config.avatarUrl, |
220 |
| - path.resolve(vtuberStaticPath, "photo.jpg") |
| 39 | + const creator = Creators.find( |
| 40 | + (creator) => creator.name.toLowerCase() === creatorType.toLowerCase() |
221 | 41 | );
|
222 |
| - await downloadImage( |
223 |
| - config.bannerUrl, |
224 |
| - path.resolve(vtuberStaticPath, "banner.jpg") |
225 |
| - ); |
226 |
| - |
227 |
| - fs.writeFileSync( |
228 |
| - vtuberMarkdownPath, |
229 |
| - `--- |
230 |
| -name: ${config.name} |
231 |
| -pubDate: ${new Date().toISOString()} |
232 |
| -banner: "/static/vtubers/${config.slug}/banner.jpg" |
233 |
| -category: Unknown |
234 |
| -description: "${config.description}" |
235 |
| -author: ${config.author} |
236 |
| -image: "/static/vtubers/${config.slug}/photo.jpg" |
237 |
| -border_color: "${config.borderColor}" |
238 |
| -graduated: ${config.graduated} |
239 |
| -is_draft: ${config.is_draft} |
240 |
| -links: ${$links |
241 |
| - .map( |
242 |
| - (link) => ` |
243 |
| - - "${link}" |
244 |
| -` |
245 |
| - ) |
246 |
| - .join("\n")} |
247 |
| ---- |
248 |
| - ` |
249 |
| - ); |
250 |
| - |
251 |
| - console.log(`Entry created!`); |
252 |
| - |
253 |
| - console.log(`Please choose an editor to open the file with:`); |
254 |
| - let num = 0; // Start from 0 |
255 |
| - for (let [key, value] of Object.entries(Editors)) { |
256 |
| - const installed = await isEditorInstalled(value.split(" ")[0]); |
257 |
| - if (installed) { |
258 |
| - console.log(`${num}. ${key}`); |
259 |
| - num++; |
260 |
| - } |
261 |
| - } |
262 |
| - |
263 |
| - if (num === 0) { |
264 |
| - console.log("No editors installed. Exiting..."); |
265 |
| - process.exit(0); |
266 |
| - } |
267 |
| - |
268 |
| - let editorChoice = await askQuestion("Choice: ", "int"); |
269 |
| - editorChoice = Number(editorChoice); // Convert user input to a number |
270 |
| - |
271 |
| - // Check if the choice is within the valid range |
272 |
| - if (editorChoice < 0 || editorChoice >= Object.keys(Editors).length) { |
273 |
| - console.log("Invalid choice. Exiting..."); |
274 |
| - process.exit(0); |
275 |
| - } |
276 | 42 |
|
277 |
| - let selectedEditor; |
278 |
| - let count = 0; |
279 |
| - for (let [key, value] of Object.entries(Editors)) { |
280 |
| - const installed = await isEditorInstalled(value.split(" ")[0]); |
281 |
| - if (installed) { |
282 |
| - if (count === editorChoice) { |
283 |
| - selectedEditor = key; |
284 |
| - break; |
285 |
| - } |
286 |
| - count++; |
287 |
| - } |
| 43 | + if (!creator) { |
| 44 | + console.error("Invalid creator type."); |
| 45 | + process.exit(1); |
288 | 46 | }
|
289 | 47 |
|
290 |
| - console.log(`Opening file with ${selectedEditor}...`); |
291 |
| - |
292 |
| - const editorCommand = Editors[selectedEditor as keyof typeof Editors]; |
293 |
| - const editorPath = vtuberMarkdownPath; |
294 |
| - const editorArgs = [editorPath]; |
295 |
| - |
296 |
| - const { exec } = require("child_process"); |
297 |
| - |
298 |
| - exec( |
299 |
| - `${editorCommand} ${editorArgs.join(" ")}`, |
300 |
| - (err: any, stdout: any, stderr: any) => { |
301 |
| - if (err) { |
302 |
| - console.error(err); |
303 |
| - return; |
304 |
| - } |
305 |
| - } |
306 |
| - ); |
307 |
| - |
308 |
| - console.log( |
309 |
| - `If the file did not open, please open the file manually: ${vtuberMarkdownPath}` |
310 |
| - ); |
311 |
| - |
312 |
| - console.log( |
313 |
| - `To lean more about formatting, please visit: https://vtubers.wiki/wiki/guides/using-createcli` |
314 |
| - ); |
315 |
| - |
316 |
| - rl.close(); |
317 |
| - process.exit(0); |
318 |
| -}; |
319 |
| - |
| 48 | + const creatorModule = await import(creator.path); |
| 49 | + await creatorModule.create({ author: gitUser }); |
| 50 | + await closeRL(); |
| 51 | +} |
320 | 52 |
|
321 |
| -createvt(); |
| 53 | +main(); |
0 commit comments