Skip to content

Commit 3ecdb40

Browse files
author
Stella
committed
feat: Scripts
Added Git Script Added better create script
1 parent 03b56e5 commit 3ecdb40

File tree

10 files changed

+882
-334
lines changed

10 files changed

+882
-334
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ Meet the contributors who have made this project possible:
6262
<tbody>
6363
<tr>
6464
<td align="center">
65-
<a href="https://github.com/withervt">
65+
<a href="https://github.com/0xyami">
6666
<img src="https://avatars.githubusercontent.com/u/93791569" width="100px;" alt="withervt"/>
6767
<br />
68-
<sub><b>withervt</b></sub>
68+
<sub><b>0xyami</b></sub>
6969
</a>
7070
<br />
7171
<a href="https://https://github.com/vtuberwiki/wiki" title="Programming">⌨️</a><a href="https://https://github.com/vtuberwiki/wiki" title="Writing Documentation">🖋️</a>

Scripts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@types/node": "^20.11.29",
1515
"@types/passport": "^1.0.16",
1616
"@types/passport-github": "^1.1.12",
17+
"chalk": "^4.1.2",
1718
"express": "^4.18.3",
1819
"express-session": "^1.18.0",
1920
"open": "^10.1.0",

Scripts/src/create.ts

Lines changed: 38 additions & 306 deletions
Original file line numberDiff line numberDiff line change
@@ -1,321 +1,53 @@
11
import fs from "fs";
22
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";
93
import { checkGitInstallation } from "./helpers/git";
4+
import { askQuestion, slugify, closeRL } from "./helpers/cmd";
105

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+
];
1124

12-
const rl = readline.createInterface({ input: stdin, output: stdout });
25+
async function main() {
26+
const gitUser = await checkGitInstallation();
1327

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}`);
2830

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 = "";
18832

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"
21437
);
215-
const vtuberStaticPath = path.resolve(paths.vtuberStatic, config.slug);
21638

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()
22141
);
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-
}
27642

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);
28846
}
28947

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+
}
32052

321-
createvt();
53+
main();

0 commit comments

Comments
 (0)