create-discordjs-nextgen 0.1.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/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # create-discordjs-nextgen
2
+
3
+ CLI generator for `discordjs-nextgen`.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-discordjs-nextgen my-bot
9
+ ```
10
+
11
+ The CLI asks for:
12
+
13
+ - Language: `JavaScript` or `TypeScript`
14
+ - Plugins: `Voice`
15
+ - Template: `Starter Kit`, `Basic`, `Advanced`
16
+
17
+ ## Templates
18
+
19
+ ### Basic
20
+
21
+ Creates the smallest possible bot:
22
+
23
+ - entry file
24
+ - `.env`
25
+ - `package.json`
26
+
27
+ ### Starter Kit
28
+
29
+ Creates a practical starter project based on the examples:
30
+
31
+ - `commands/prefix`
32
+ - `commands/slash`
33
+ - `commands/hybrid`
34
+ - `events`
35
+
36
+ ### Advanced
37
+
38
+ Creates a fuller example structure:
39
+
40
+ - `commands`
41
+ - `events`
42
+ - `buttons`
43
+ - `modals`
44
+ - `selects`
45
+ - `config`
46
+ - `plugins`
47
+ - `middleware`
48
+
49
+ ## Notes
50
+
51
+ - `Voice` adds `discordjs-nextgen-voice`
52
+ - TypeScript uses `src/` and adds `ts-node`, `typescript`, `@types/node`
53
+ - Generated code follows the current `discordjs-nextgen` API
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import fsp from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import * as p from "@clack/prompts";
9
+ import pc from "picocolors";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const templatesRoot = path.resolve(__dirname, "..", "templates");
14
+
15
+ async function main() {
16
+ console.log("");
17
+ p.intro(`${pc.bgBlue(pc.white(" NEXTGEN-CLI "))} ${pc.dim("Modern Discord.js Framework")}`);
18
+
19
+ const projectName = await p.text({
20
+ message: "Proje adını ne koyalım?",
21
+ placeholder: "my-nextgen-bot",
22
+ validate(value) {
23
+ if (value.length === 0) return "Lütfen bir isim girin!";
24
+ if (value.includes(" ")) return "Proje isminde boşluk olamaz.";
25
+ },
26
+ });
27
+
28
+ if (p.isCancel(projectName)) {
29
+ p.cancel("İşlem iptal edildi.");
30
+ process.exit(0);
31
+ }
32
+
33
+ const language = await p.select({
34
+ message: "Hangi dili tercih edersiniz?",
35
+ options: [
36
+ { value: "js", label: "JavaScript", hint: "Esnek ve hızlı" },
37
+ { value: "ts", label: "TypeScript", hint: "Tip güvenliği (Önerilen)" },
38
+ ],
39
+ });
40
+
41
+ if (p.isCancel(language)) {
42
+ p.cancel("İşlem iptal edildi.");
43
+ process.exit(0);
44
+ }
45
+
46
+ const plugins = await p.multiselect({
47
+ message: "Eklemek istediğiniz pluginleri seçin (Boşluk ile işaretle, Enter ile onayla):",
48
+ options: [
49
+ { value: "voice", label: "Voice Support", hint: "Müzik/Ses sistemleri için" },
50
+ ],
51
+ required: false,
52
+ });
53
+
54
+ if (p.isCancel(plugins)) {
55
+ p.cancel("İşlem iptal edildi.");
56
+ process.exit(0);
57
+ }
58
+
59
+ const template = await p.select({
60
+ message: "Hangi şablonla başlamak istersiniz?",
61
+ options: [
62
+ { value: "starter", label: "Starter Kit", hint: "Sadece ana dosya (Minimum)" },
63
+ { value: "basic", label: "Basic", hint: "Klasör yapısı & Basit komutlar" },
64
+ { value: "advanced", label: "Advanced", hint: "Tam teşekküllü profesyonel yapı" },
65
+ ],
66
+ });
67
+
68
+ if (p.isCancel(template)) {
69
+ p.cancel("İşlem iptal edildi.");
70
+ process.exit(0);
71
+ }
72
+
73
+ const s = p.spinner();
74
+ s.start(pc.cyan("Dosyalar hazirlaniyor..."));
75
+
76
+ const targetDir = path.resolve(process.cwd(), projectName);
77
+
78
+ try {
79
+ if (fs.existsSync(targetDir) && (await fsp.readdir(targetDir)).length > 0) {
80
+ s.stop(pc.red("Hata!"));
81
+ p.log.error(`Hata: '${projectName}' klasörü zaten var ve boş değil.`);
82
+ process.exit(1);
83
+ }
84
+
85
+ if (!fs.existsSync(targetDir)) {
86
+ await fsp.mkdir(targetDir, { recursive: true });
87
+ }
88
+
89
+ await generateProject(targetDir, { projectName, language, template, plugins });
90
+
91
+ s.stop(pc.green("Proje başarıyla oluşturuldu!"));
92
+
93
+ const shouldInstall = await p.confirm({
94
+ message: "Bağımlılıkları şimdi kurmak ister misiniz?",
95
+ initialValue: true,
96
+ });
97
+
98
+ if (shouldInstall) {
99
+ const installSpinner = p.spinner();
100
+ installSpinner.start(pc.yellow("Paketler yükleniyor..."));
101
+ const success = await runInstall(targetDir);
102
+ if (success) {
103
+ installSpinner.stop(pc.green("Kurulum tamamlandı!"));
104
+ } else {
105
+ installSpinner.stop(pc.red("Kurulum başarısız oldu."));
106
+ }
107
+ }
108
+
109
+ p.note(
110
+ `cd ${projectName}\n${!shouldInstall ? "npm install\n" : ""}npm run dev`,
111
+ "Sıradaki Adımlar"
112
+ );
113
+
114
+ p.outro(pc.blue("İyi kodlamalar! 🚀"));
115
+
116
+ } catch (err) {
117
+ s.stop(pc.red("Bir hata oluştu!"));
118
+ console.error(err);
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ async function generateProject(targetDir, config) {
124
+ const extension = config.language === "ts" ? "ts" : "js";
125
+ const templateDir = path.join(templatesRoot, config.template);
126
+
127
+ await copyTemplateDirectory(templateDir, targetDir, {
128
+ extension,
129
+ language: config.language,
130
+ });
131
+
132
+ const pkg = {
133
+ name: config.projectName,
134
+ version: "1.0.0",
135
+ private: true,
136
+ type: "module",
137
+ scripts: config.language === "ts" ? {
138
+ dev: "ts-node index.ts",
139
+ start: "ts-node index.ts",
140
+ build: "tsc"
141
+ } : {
142
+ dev: "node index.js",
143
+ start: "node index.js"
144
+ },
145
+ dependencies: {
146
+ "discordjs-nextgen": "latest",
147
+ "dotenv": "^17.3.1"
148
+ }
149
+ };
150
+
151
+ if (config.language === "ts") {
152
+ pkg.devDependencies = {
153
+ "@types/node": "^20.12.0",
154
+ "ts-node": "^10.9.2",
155
+ "typescript": "^5.4.5"
156
+ };
157
+ await writeFile(targetDir, "tsconfig.json", JSON.stringify({
158
+ compilerOptions: {
159
+ target: "ESNext",
160
+ module: "ESNext",
161
+ moduleResolution: "node",
162
+ esModuleInterop: true,
163
+ strict: true,
164
+ skipLibCheck: true,
165
+ outDir: "dist"
166
+ }
167
+ }, null, 2));
168
+ }
169
+
170
+ if (config.plugins.includes("voice")) {
171
+ pkg.dependencies["discordjs-nextgen-voice"] = "latest";
172
+ }
173
+
174
+ await writeFile(targetDir, "package.json", JSON.stringify(pkg, null, 2));
175
+ await writeFile(targetDir, ".gitignore", "node_modules\n.env\ndist\n");
176
+ await writeFile(targetDir, ".env", "TOKEN=YOUR_BOT_TOKEN_HERE");
177
+
178
+ if (config.plugins.includes("voice")) {
179
+ await injectVoicePlugin(targetDir, extension, config.template);
180
+ }
181
+ }
182
+
183
+ async function injectVoicePlugin(targetDir, extension, template) {
184
+ const entryPath = path.join(targetDir, `index.${extension}`);
185
+ let content = await fsp.readFile(entryPath, "utf8");
186
+
187
+ const importLine = `import { VoicePlugin } from "discordjs-nextgen-voice";`;
188
+ const useLine = `\napp.use(new VoicePlugin());`;
189
+
190
+ if (!content.includes(importLine)) {
191
+ content = `${importLine}\n${content}`;
192
+ }
193
+
194
+ if (!content.includes("new VoicePlugin()")) {
195
+ content = content.replace(/const app = new App\((\{[\s\S]*?\}|)\);/, (match) => `${match}\n${useLine}`);
196
+ }
197
+
198
+ await fsp.writeFile(entryPath, content, "utf8");
199
+
200
+ if (template !== "starter") {
201
+ const cmdCode = `export default {\n name: "join",\n description: "Sese girer.",\n run: async (ctx) => {\n await ctx.voice.join({ channelId: ctx.member.voice.channelId });\n }\n};`;
202
+ await writeFile(targetDir, "commands/prefix/join." + extension, cmdCode);
203
+ }
204
+ }
205
+
206
+ async function copyTemplateDirectory(sourceDir, targetDir, context) {
207
+ if (!fs.existsSync(sourceDir)) return;
208
+ const entries = await fsp.readdir(sourceDir, { withFileTypes: true });
209
+ for (const entry of entries) {
210
+ const src = path.join(sourceDir, entry.name);
211
+ const destName = entry.name.replace(/__EXT__/g, context.extension);
212
+ const dest = path.join(targetDir, destName);
213
+
214
+ if (entry.isDirectory()) {
215
+ await fsp.mkdir(dest, { recursive: true });
216
+ await copyTemplateDirectory(src, dest, context);
217
+ } else {
218
+ let content = await fsp.readFile(src, "utf8");
219
+
220
+ if (context.language === "ts") {
221
+ content = content.replace(/\/\/ \[JS\][\s\S]*?\/\/ \[\/JS\]/g, "");
222
+ content = content.replace(/\/\/ \[TS\]/g, "").replace(/\/\/ \[\/TS\]/g, "");
223
+ } else {
224
+ content = content.replace(/\/\/ \[TS\][\s\S]*?\/\/ \[\/TS\]/g, "");
225
+ content = content.replace(/\/\/ \[JS\]/g, "").replace(/\/\/ \[\/JS\]/g, "");
226
+ }
227
+
228
+ content = content.replaceAll("__EXT__", context.extension);
229
+ await fsp.writeFile(dest, content, "utf8");
230
+ }
231
+ }
232
+ }
233
+
234
+ async function writeFile(baseDir, relPath, content) {
235
+ const fullPath = path.join(baseDir, relPath);
236
+ await fsp.mkdir(path.dirname(fullPath), { recursive: true });
237
+ await fsp.writeFile(fullPath, content, "utf8");
238
+ }
239
+
240
+ async function runInstall(targetDir) {
241
+ const result = spawnSync("npm", ["install"], { cwd: targetDir, stdio: "inherit", shell: true });
242
+ return result.status === 0;
243
+ }
244
+
245
+ main();
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "create-discordjs-nextgen",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Discord bot powered by discordjs-nextgen",
5
+ "bin": {
6
+ "create-discordjs-nextgen": "./bin/create-discordjs-nextgen.js"
7
+ },
8
+ "type": "module",
9
+ "files": [
10
+ "bin",
11
+ "README.md",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "start": "node ./bin/create-discordjs-nextgen.js",
16
+ "test:smoke": "node ./bin/create-discordjs-nextgen.js test-bot --yes --no-install"
17
+ },
18
+ "keywords": [
19
+ "discord",
20
+ "discordjs",
21
+ "discordjs-nextgen",
22
+ "create",
23
+ "cli",
24
+ "nextgen"
25
+ ],
26
+ "author": "Burak <@burakbehlull>",
27
+ "license": "Apache-2.0",
28
+ "engines": {
29
+ "node": ">=18.17.0"
30
+ },
31
+ "dependencies": {
32
+ "@clack/prompts": "^0.10.0",
33
+ "picocolors": "^1.1.1"
34
+ }
35
+ }
@@ -0,0 +1,12 @@
1
+ // [TS]
2
+ import type { ButtonInteraction } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const myButton = {
6
+ customId: 'myButton',
7
+ run: async (ctx // [TS]: ButtonInteraction // [/TS]) => {
8
+ await ctx.showModal("myModal");
9
+ },
10
+ };
11
+
12
+ export default myButton;
@@ -0,0 +1,14 @@
1
+ // [TS]
2
+ import { HybridCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const pingHybrid // [TS]: HybridCommand // [/TS] = {
6
+ name: 'ping',
7
+ description: 'Hem prefix hem slash olarak çalışır!',
8
+ run: async (ctx) => {
9
+ const delay = Date.now() - ctx.createdAt.getTime();
10
+ await ctx.reply(`Pong! Gecikme: **${delay}ms**`);
11
+ },
12
+ };
13
+
14
+ export default pingHybrid;
@@ -0,0 +1,23 @@
1
+ // [TS]
2
+ import type { PrefixCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+ import { ActionRow } from 'discordjs-nextgen';
5
+
6
+ const colorsCommand // [TS]: PrefixCommand // [/TS] = {
7
+ name: 'colors',
8
+ description: 'Color choice',
9
+ run: async (ctx) => {
10
+ const selectMenu = ctx.app.selects.get('mySelect');
11
+
12
+ if (!selectMenu) return ctx.reply('Menü bulunamadı!');
13
+
14
+ const row = ActionRow.create(selectMenu);
15
+
16
+ await ctx.reply({
17
+ content: 'Lütfen aşağıdan bir renk seç:',
18
+ components: [row]
19
+ });
20
+ }
21
+ };
22
+
23
+ export default colorsCommand;
@@ -0,0 +1,15 @@
1
+ // [TS]
2
+ import type { SlashCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+ import { SlashCommandBuilder } from 'discordjs-nextgen';
5
+
6
+ const sayHello // [TS]: SlashCommand // [/TS] = {
7
+ data: new SlashCommandBuilder()
8
+ .setName('hello')
9
+ .setDescription('hello'),
10
+ run: async (ctx) => {
11
+ await ctx.reply("hi");
12
+ }
13
+ };
14
+
15
+ export default sayHello;
@@ -0,0 +1,12 @@
1
+ // [TS]
2
+ import type { AppEvent } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const readyEvent // [TS]: AppEvent<'ready'> // [/TS] = {
6
+ name: 'ready',
7
+ run: (user) => {
8
+ console.log(`${user.tag} hazır!`);
9
+ }
10
+ };
11
+
12
+ export default readyEvent;
@@ -0,0 +1,50 @@
1
+ import { App, Intents, Logger, cooldown } from 'discordjs-nextgen';
2
+ import "dotenv/config"
3
+
4
+ const app = new App({
5
+ intents: Intents.ALL
6
+ });
7
+
8
+ app.use(Logger({
9
+ colors: {
10
+ info: 'cyan',
11
+ warn: 'yellow',
12
+ error: 'red',
13
+ },
14
+ }));
15
+
16
+ app.use(cooldown(10));
17
+
18
+ app
19
+ .events('events')
20
+ .prefix({
21
+ folder: 'commands/prefix',
22
+ prefix: '.',
23
+ })
24
+ .slash({
25
+ folder: 'commands/slash',
26
+ })
27
+ .command({
28
+ folder: 'commands/hybrid',
29
+ })
30
+ .button({
31
+ folder: 'buttons',
32
+ })
33
+ .modal({
34
+ folder: 'modals',
35
+ })
36
+ .select({
37
+ folder: 'selects',
38
+ });
39
+
40
+ app.setPresence({
41
+ status: 'idle',
42
+ activities: [
43
+ {
44
+ name: 'Nextgen Advanced',
45
+ type: 0
46
+ },
47
+ ],
48
+ });
49
+
50
+ app.run(process.env.TOKEN);
@@ -0,0 +1,18 @@
1
+ import { Modal } from 'discordjs-nextgen';
2
+
3
+ // [TS]
4
+ import type { ModalInteraction } from 'discordjs-nextgen';
5
+ // [/TS]
6
+
7
+ const myModal = Modal.create('myModal')
8
+ .title('Geri Bildirim')
9
+ .short('name', { label: 'Adınız', placeholder: 'Buraya yazın...' })
10
+ .paragraph('comment', { label: 'Yorumunuz', min: 10, max: 1000 })
11
+ .onSubmit(async (ctx // [TS]: ModalInteraction // [/TS]) => {
12
+ const name = ctx.values.name;
13
+ const comment = ctx.values.comment;
14
+
15
+ await ctx.reply({ content: `Teşekkürler ${name}! Yorumun alındı.`, ephemeral: true });
16
+ });
17
+
18
+ export default myModal;
@@ -0,0 +1,19 @@
1
+ import { Select } from 'discordjs-nextgen';
2
+
3
+ // [TS]
4
+ import type { SelectInteraction } from 'discordjs-nextgen';
5
+ // [/TS]
6
+
7
+ const mySelect = Select.create('mySelect')
8
+ .placeholder('Favori rengini seç...')
9
+ .options([
10
+ { label: 'Kırmızı', value: 'red', description: 'Enerjinin rengi' },
11
+ { label: 'Mavi', value: 'blue', description: 'Huzurun rengi' },
12
+ { label: 'Yeşil', value: 'green', description: 'Doğanın rengi' }
13
+ ])
14
+ .onSelect(async (ctx // [TS]: SelectInteraction // [/TS]) => {
15
+ const secilen = ctx.values.mySelect;
16
+ await ctx.reply(`Harika seçim! Demek favori rengin: **${secilen}**`);
17
+ });
18
+
19
+ export default mySelect;
@@ -0,0 +1,14 @@
1
+ // [TS]
2
+ import { HybridCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const pingHybrid // [TS]: HybridCommand // [/TS] = {
6
+ name: 'ping',
7
+ description: 'Hem prefix hem slash olarak çalışır!',
8
+ run: async (ctx) => {
9
+ const delay = Date.now() - ctx.createdAt.getTime();
10
+ await ctx.reply(`Pong! Gecikme: **${delay}ms**`);
11
+ },
12
+ };
13
+
14
+ export default pingHybrid;
@@ -0,0 +1,13 @@
1
+ // [TS]
2
+ import type { PrefixCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const happyCommand // [TS]: PrefixCommand // [/TS] = {
6
+ name: 'happy',
7
+ description: 'Happy emoji',
8
+ run: async (ctx) => {
9
+ await ctx.reply("😊");
10
+ }
11
+ };
12
+
13
+ export default happyCommand;
@@ -0,0 +1,15 @@
1
+ // [TS]
2
+ import type { SlashCommand } from 'discordjs-nextgen';
3
+ // [/TS]
4
+ import { SlashCommandBuilder } from 'discordjs-nextgen';
5
+
6
+ const sayHello // [TS]: SlashCommand // [/TS] = {
7
+ data: new SlashCommandBuilder()
8
+ .setName('hello')
9
+ .setDescription('hello'),
10
+ run: async (ctx) => {
11
+ await ctx.reply("hi");
12
+ }
13
+ };
14
+
15
+ export default sayHello;
@@ -0,0 +1,12 @@
1
+ // [TS]
2
+ import type { AppEvent } from 'discordjs-nextgen';
3
+ // [/TS]
4
+
5
+ const readyEvent // [TS]: AppEvent<'ready'> // [/TS] = {
6
+ name: 'ready',
7
+ run: (user) => {
8
+ console.log(`${user.tag} hazır!`);
9
+ }
10
+ };
11
+
12
+ export default readyEvent;
@@ -0,0 +1,31 @@
1
+ import { App, Intents } from 'discordjs-nextgen';
2
+ import "dotenv/config";
3
+
4
+ const app = new App({
5
+ intents: Intents.ALL
6
+ });
7
+
8
+ app
9
+ .events('events')
10
+ .prefix({
11
+ folder: 'commands/prefix',
12
+ prefix: '.',
13
+ })
14
+ .slash({
15
+ folder: 'commands/slash',
16
+ })
17
+ .command({
18
+ folder: 'commands/hybrid',
19
+ });
20
+
21
+ app.setPresence({
22
+ status: 'idle',
23
+ activities: [
24
+ {
25
+ name: 'NextGen Basic',
26
+ type: 0
27
+ },
28
+ ],
29
+ });
30
+
31
+ app.run(process.env.TOKEN);
@@ -0,0 +1,19 @@
1
+ import { App, Intents } from 'discordjs-nextgen';
2
+ import "dotenv/config";
3
+
4
+ const app = new App({
5
+ intents: Intents.ALL
6
+ });
7
+
8
+ app.setPresence({
9
+ status: 'idle',
10
+ activities: [
11
+ {
12
+ name: 'NextGen',
13
+ type: 0,
14
+ url: null,
15
+ },
16
+ ],
17
+ });
18
+
19
+ app.run(process.env.TOKEN);