diff --git a/packages/create-discord-bot/bin/index.ts b/packages/create-discord-bot/bin/index.ts index 3b31d9836..6aa47fbf9 100644 --- a/packages/create-discord-bot/bin/index.ts +++ b/packages/create-discord-bot/bin/index.ts @@ -6,7 +6,7 @@ import { styleText } from 'node:util'; import { Option, program } from 'commander'; import prompts from 'prompts'; import validateProjectName from 'validate-npm-package-name'; -import packageJSON from '../package.json' assert { type: 'json' }; +import packageJSON from '../package.json' with { type: 'json' }; import { createDiscordBot } from '../src/create-discord-bot.js'; import { resolvePackageManager } from '../src/helpers/packageManager.js'; import { DEFAULT_PROJECT_NAME, PACKAGE_MANAGERS } from '../src/util/constants.js'; diff --git a/packages/create-discord-bot/src/helpers/packageManager.ts b/packages/create-discord-bot/src/helpers/packageManager.ts index c7f054bc0..4d02c679a 100644 --- a/packages/create-discord-bot/src/helpers/packageManager.ts +++ b/packages/create-discord-bot/src/helpers/packageManager.ts @@ -14,6 +14,11 @@ export type PackageManager = 'bun' | 'deno' | 'npm' | 'pnpm' | 'yarn'; export function resolvePackageManager(): PackageManager { const npmConfigUserAgent = process.env.npm_config_user_agent; + // @ts-expect-error: We're not using Deno's types, so its global is not declared + if (typeof Deno !== 'undefined') { + return 'deno'; + } + // If this is not present, return the default package manager. if (!npmConfigUserAgent) { return DEFAULT_PACKAGE_MANAGER; diff --git a/packages/create-discord-bot/template/Deno/.prettierrc.json b/packages/create-discord-bot/template/Deno/.prettierrc.json deleted file mode 100644 index 7920c42b8..000000000 --- a/packages/create-discord-bot/template/Deno/.prettierrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/prettierrc.json", - "printWidth": 120, - "useTabs": true, - "singleQuote": true, - "quoteProps": "as-needed", - "trailingComma": "all", - "endOfLine": "lf" -} diff --git a/packages/create-discord-bot/template/Deno/.vscode/extensions.json b/packages/create-discord-bot/template/Deno/.vscode/extensions.json index 9a3c0c3e0..35448591a 100644 --- a/packages/create-discord-bot/template/Deno/.vscode/extensions.json +++ b/packages/create-discord-bot/template/Deno/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["denoland.vscode-deno", "tamasfe.even-better-toml", "codezombiech.gitignore"] + "recommendations": ["denoland.vscode-deno"] } diff --git a/packages/create-discord-bot/template/Deno/.vscode/settings.json b/packages/create-discord-bot/template/Deno/.vscode/settings.json index 93c3e5071..6457249fe 100644 --- a/packages/create-discord-bot/template/Deno/.vscode/settings.json +++ b/packages/create-discord-bot/template/Deno/.vscode/settings.json @@ -2,14 +2,11 @@ "editor.defaultFormatter": "denoland.vscode-deno", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": false + "source.fixAll": "always", + "source.organizeImports": "always" }, "editor.trimAutoWhitespace": false, "files.insertFinalNewline": true, "files.eol": "\n", - "npm.packageManager": "[REPLACE_ME]", - "deno.enable": "[REPLACE_BOOL]", - "deno.lint": "[REPLACE_BOOL]", - "deno.unstable": false + "deno.enable": "[REPLACE_BOOL]" } diff --git a/packages/create-discord-bot/template/Deno/deno.jsonc b/packages/create-discord-bot/template/Deno/deno.jsonc index f67977ec9..7116c0e28 100644 --- a/packages/create-discord-bot/template/Deno/deno.jsonc +++ b/packages/create-discord-bot/template/Deno/deno.jsonc @@ -2,40 +2,25 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "tasks": { "lint": "deno lint", - "deploy": "deno run --allow-read --allow-env --allow-net src/util/deploy.ts", + "deploy": "deno run --env-file --allow-read --allow-env --allow-net src/util/deploy.ts", "format": "deno fmt", "fmt": "deno fmt", - "start": "deno run --allow-read --allow-env --allow-net src/index.ts", + "start": "deno run --env-file --allow-read --allow-env --allow-net src/index.ts", + }, + "fmt": { + "useTabs": true, + "lineWidth": 120, + "singleQuote": true, }, "lint": { - "include": ["src/"], "rules": { "tags": ["recommended"], "exclude": ["require-await", "no-await-in-sync-fn"], }, }, - "fmt": { - "useTabs": true, - "lineWidth": 120, - "semiColons": true, - "singleQuote": true, - "proseWrap": "preserve", - "include": ["src/"], - }, - "compilerOptions": { - "alwaysStrict": true, - "emitDecoratorMetadata": true, - "verbatimModuleSyntax": true, - "lib": ["deno.window"], - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "removeComments": false, - "strict": true, - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "exactOptionalPropertyTypes": false, - "noImplicitOverride": true, + "imports": { + "@discordjs/core": "npm:@discordjs/core@^2.2.1", + "discord.js": "npm:discord.js@^14.22.1", + "zod": "npm:zod@^3.25.76", }, } diff --git a/packages/create-discord-bot/template/Deno/src/commands/index.ts b/packages/create-discord-bot/template/Deno/src/commands/index.ts index 7057eca5a..4634540b0 100644 --- a/packages/create-discord-bot/template/Deno/src/commands/index.ts +++ b/packages/create-discord-bot/template/Deno/src/commands/index.ts @@ -1,5 +1,5 @@ -import type { RESTPostAPIApplicationCommandsJSONBody, CommandInteraction } from 'npm:discord.js@^14.22.0'; -import { z } from 'npm:zod@^3.25.76'; +import type { CommandInteraction, RESTPostAPIApplicationCommandsJSONBody } from 'discord.js'; +import { z } from 'zod'; import type { StructurePredicate } from '../util/loaders.ts'; /** diff --git a/packages/create-discord-bot/template/Deno/src/events/index.ts b/packages/create-discord-bot/template/Deno/src/events/index.ts index a14281264..c32d85db1 100644 --- a/packages/create-discord-bot/template/Deno/src/events/index.ts +++ b/packages/create-discord-bot/template/Deno/src/events/index.ts @@ -1,21 +1,21 @@ -import type { ClientEvents } from 'npm:discord.js@^14.22.0'; -import { z } from 'npm:zod@^3.25.76'; +import type { ClientEvents } from 'discord.js'; +import { z } from 'zod'; import type { StructurePredicate } from '../util/loaders.ts'; /** * Defines the structure of an event. */ -export type Event = { +export type Event = { /** * The function to execute when the event is emitted. * * @param parameters - The parameters of the event */ - execute(...parameters: ClientEvents[T]): Promise | void; + execute(...parameters: ClientEvents[EventName]): Promise | void; /** * The name of the event to listen to */ - name: T; + name: EventName; /** * Whether or not the event should only be listened to once * diff --git a/packages/create-discord-bot/template/Deno/src/events/interactionCreate.ts b/packages/create-discord-bot/template/Deno/src/events/interactionCreate.ts index 188fc4498..86b82641c 100644 --- a/packages/create-discord-bot/template/Deno/src/events/interactionCreate.ts +++ b/packages/create-discord-bot/template/Deno/src/events/interactionCreate.ts @@ -1,4 +1,4 @@ -import { Events } from 'npm:discord.js@^14.22.0'; +import { Events } from 'discord.js'; import type { Event } from './index.ts'; import { loadCommands } from '../util/loaders.ts'; diff --git a/packages/create-discord-bot/template/Deno/src/events/ready.ts b/packages/create-discord-bot/template/Deno/src/events/ready.ts index 1ec35c050..5fd6216f9 100644 --- a/packages/create-discord-bot/template/Deno/src/events/ready.ts +++ b/packages/create-discord-bot/template/Deno/src/events/ready.ts @@ -1,4 +1,4 @@ -import { Events } from 'npm:discord.js@^14.22.0'; +import { Events } from 'discord.js'; import type { Event } from './index.ts'; export default { diff --git a/packages/create-discord-bot/template/Deno/src/index.ts b/packages/create-discord-bot/template/Deno/src/index.ts index 02a5cfbc5..62d8fea97 100644 --- a/packages/create-discord-bot/template/Deno/src/index.ts +++ b/packages/create-discord-bot/template/Deno/src/index.ts @@ -1,6 +1,4 @@ -import 'https://deno.land/std@0.223.0/dotenv/load.ts'; -import { URL } from 'node:url'; -import { Client, GatewayIntentBits } from 'npm:discord.js@^14.22.0'; +import { Client, GatewayIntentBits } from 'discord.js'; import { loadEvents } from './util/loaders.ts'; // Initialize the client diff --git a/packages/create-discord-bot/template/Deno/src/util/deploy.ts b/packages/create-discord-bot/template/Deno/src/util/deploy.ts index 128c2c68a..fa3acb234 100644 --- a/packages/create-discord-bot/template/Deno/src/util/deploy.ts +++ b/packages/create-discord-bot/template/Deno/src/util/deploy.ts @@ -1,7 +1,5 @@ -import 'https://deno.land/std@0.223.0/dotenv/load.ts'; -import { URL } from 'node:url'; -import { API } from 'npm:@discordjs/core@^2.2.1/http-only'; -import { REST } from 'npm:discord.js@^14.22.0'; +import { API } from '@discordjs/core/http-only'; +import { REST } from 'discord.js'; import { loadCommands } from './loaders.ts'; const commands = await loadCommands(new URL('../commands/', import.meta.url)); diff --git a/packages/create-discord-bot/template/Deno/src/util/loaders.ts b/packages/create-discord-bot/template/Deno/src/util/loaders.ts index ee76051b3..9a1d9cbc6 100644 --- a/packages/create-discord-bot/template/Deno/src/util/loaders.ts +++ b/packages/create-discord-bot/template/Deno/src/util/loaders.ts @@ -1,6 +1,7 @@ import type { PathLike } from 'node:fs'; -import { readdir, stat } from 'node:fs/promises'; -import { URL } from 'node:url'; +import { glob, stat } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; import type { Command } from '../commands/index.ts'; import { predicate as commandPredicate } from '../commands/index.ts'; import type { Event } from '../events/index.ts'; @@ -9,7 +10,7 @@ import { predicate as eventPredicate } from '../events/index.ts'; /** * A predicate to check if the structure is valid */ -export type StructurePredicate = (structure: unknown) => structure is T; +export type StructurePredicate = (structure: unknown) => structure is Structure; /** * Loads all the structures in the provided directory @@ -19,11 +20,11 @@ export type StructurePredicate = (structure: unknown) => structure is T; * @param recursive - Whether to recursively load the structures in the directory * @returns */ -export async function loadStructures( +export async function loadStructures( dir: PathLike, - predicate: StructurePredicate, + predicate: StructurePredicate, recursive = true, -): Promise { +): Promise { // Get the stats of the directory const statDir = await stat(dir); @@ -32,34 +33,24 @@ export async function loadStructures( throw new Error(`The directory '${dir}' is not a directory.`); } - // Get all the files in the directory - const files = await readdir(dir); - // Create an empty array to store the structures - const structures: T[] = []; + const structures: Structure[] = []; - // Loop through all the files in the directory - for (const file of files) { - const fileUrl = new URL(`${dir}/${file}`, import.meta.url); + // Create a glob pattern to match the .ts files + const basePath = dir instanceof URL ? fileURLToPath(dir) : dir.toString(); + const pattern = resolve(basePath, recursive ? '**/*.ts' : '*.ts'); - // Get the stats of the file - const statFile = await stat(fileUrl); - - // If the file is a directory and recursive is true, recursively load the structures in the directory - if (statFile.isDirectory() && recursive) { - structures.push(...(await loadStructures(fileUrl, predicate, recursive))); - continue; - } - - // If the file is index.ts or the file does not end with .ts, skip the file - if (file === 'index.ts' || !file.endsWith('.ts')) { + // Loop through all the matching files in the directory + for await (const file of glob(pattern)) { + // If the file is index.ts, skip the file + if (file.endsWith('/index.ts')) { continue; } // Import the structure dynamically from the file - const structure = (await import(fileUrl.toString())).default; + const { default: structure } = await import(file); - // If the structure is a valid structure, add it + // If the default export is a valid structure, add it if (predicate(structure)) { structures.push(structure); } diff --git a/packages/create-discord-bot/template/JavaScript/src/events/index.js b/packages/create-discord-bot/template/JavaScript/src/events/index.js index dcac11a80..5d870899c 100644 --- a/packages/create-discord-bot/template/JavaScript/src/events/index.js +++ b/packages/create-discord-bot/template/JavaScript/src/events/index.js @@ -3,10 +3,10 @@ import { z } from 'zod'; /** * Defines the structure of an event. * - * @template {keyof import('discord.js').ClientEvents} [T=keyof import('discord.js').ClientEvents] + * @template {keyof import('discord.js').ClientEvents} [EventName=keyof import('discord.js').ClientEvents] * @typedef {object} Event - * @property {(...parameters: import('discord.js').ClientEvents[T]) => Promise | void} execute The function to execute the command - * @property {T} name The name of the event to listen to + * @property {(...parameters: import('discord.js').ClientEvents[EventName]) => Promise | void} execute The function to execute the command + * @property {EventName} name The name of the event to listen to * @property {boolean} [once] Whether or not the event should only be listened to once */ diff --git a/packages/create-discord-bot/template/JavaScript/src/util/loaders.js b/packages/create-discord-bot/template/JavaScript/src/util/loaders.js index 860ccb9bd..f23f4f36a 100644 --- a/packages/create-discord-bot/template/JavaScript/src/util/loaders.js +++ b/packages/create-discord-bot/template/JavaScript/src/util/loaders.js @@ -1,23 +1,23 @@ -import { readdir, stat } from 'node:fs/promises'; -import { URL } from 'node:url'; +import { glob, stat } from 'node:fs/promises'; +import { fileURLToPath, resolve, URL } from 'node:url'; import { predicate as commandPredicate } from '../commands/index.js'; import { predicate as eventPredicate } from '../events/index.js'; /** * A predicate to check if the structure is valid. * - * @template T - * @typedef {(structure: unknown) => structure is T} StructurePredicate + * @template Structure + * @typedef {(structure: unknown) => structure is Structure} StructurePredicate */ /** * Loads all the structures in the provided directory. * - * @template T + * @template Structure * @param {import('node:fs').PathLike} dir - The directory to load the structures from - * @param {StructurePredicate} predicate - The predicate to check if the structure is valid + * @param {StructurePredicate} predicate - The predicate to check if the structure is valid * @param {boolean} recursive - Whether to recursively load the structures in the directory - * @returns {Promise} + * @returns {Promise} */ export async function loadStructures(dir, predicate, recursive = true) { // Get the stats of the directory @@ -28,35 +28,25 @@ export async function loadStructures(dir, predicate, recursive = true) { throw new Error(`The directory '${dir}' is not a directory.`); } - // Get all the files in the directory - const files = await readdir(dir); - // Create an empty array to store the structures - /** @type {T[]} */ + /** @type {Structure[]} */ const structures = []; - // Loop through all the files in the directory - for (const file of files) { - const fileUrl = new URL(`${dir}/${file}`, import.meta.url); + // Create a glob pattern to match the .js files + const basePath = dir instanceof URL ? fileURLToPath(dir) : dir.toString(); + const pattern = resolve(basePath, recursive ? '**/*.js' : '*.js'); - // Get the stats of the file - const statFile = await stat(fileUrl); - - // If the file is a directory and recursive is true, recursively load the structures in the directory - if (statFile.isDirectory() && recursive) { - structures.push(...(await loadStructures(fileUrl, predicate, recursive))); - continue; - } - - // If the file is index.js or the file does not end with .js, skip the file - if (file === 'index.js' || !file.endsWith('.js')) { + // Loop through all the matching files in the directory + for await (const file of glob(pattern)) { + // If the file is index.js, skip the file + if (file.endsWith('/index.js')) { continue; } // Import the structure dynamically from the file - const structure = (await import(fileUrl.toString())).default; + const { default: structure } = await import(file); - // If the structure is a valid structure, add it + // If the default export is a valid structure, add it if (predicate(structure)) { structures.push(structure); } @@ -68,7 +58,7 @@ export async function loadStructures(dir, predicate, recursive = true) { /** * @param {import('node:fs').PathLike} dir * @param {boolean} [recursive] - * @returns {Promise>} + * @returns {Promise>} */ export async function loadCommands(dir, recursive = true) { return (await loadStructures(dir, commandPredicate, recursive)).reduce( diff --git a/packages/create-discord-bot/template/TypeScript/.vscode/settings.json b/packages/create-discord-bot/template/TypeScript/.vscode/settings.json index a3ff3551e..ae55b963b 100644 --- a/packages/create-discord-bot/template/TypeScript/.vscode/settings.json +++ b/packages/create-discord-bot/template/TypeScript/.vscode/settings.json @@ -3,8 +3,8 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": false + "source.fixAll": "explicit", + "source.organizeImports": "never" }, "editor.trimAutoWhitespace": false, "files.insertFinalNewline": true, diff --git a/packages/create-discord-bot/template/TypeScript/src/events/index.ts b/packages/create-discord-bot/template/TypeScript/src/events/index.ts index 2d14857fd..c716614b1 100644 --- a/packages/create-discord-bot/template/TypeScript/src/events/index.ts +++ b/packages/create-discord-bot/template/TypeScript/src/events/index.ts @@ -5,17 +5,17 @@ import type { StructurePredicate } from '../util/loaders.[REPLACE_IMPORT_EXT]'; /** * Defines the structure of an event. */ -export type Event = { +export type Event = { /** * The function to execute when the event is emitted. * * @param parameters - The parameters of the event */ - execute(...parameters: ClientEvents[T]): Promise | void; + execute(...parameters: ClientEvents[EventName]): Promise | void; /** * The name of the event to listen to */ - name: T; + name: EventName; /** * Whether or not the event should only be listened to once * diff --git a/packages/create-discord-bot/template/TypeScript/src/util/loaders.ts b/packages/create-discord-bot/template/TypeScript/src/util/loaders.ts index cd79a5327..09bad738c 100644 --- a/packages/create-discord-bot/template/TypeScript/src/util/loaders.ts +++ b/packages/create-discord-bot/template/TypeScript/src/util/loaders.ts @@ -1,6 +1,7 @@ import type { PathLike } from 'node:fs'; -import { readdir, stat } from 'node:fs/promises'; -import { URL } from 'node:url'; +import { glob, stat } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; import type { Command } from '../commands/index.[REPLACE_IMPORT_EXT]'; import { predicate as commandPredicate } from '../commands/index.[REPLACE_IMPORT_EXT]'; import type { Event } from '../events/index.[REPLACE_IMPORT_EXT]'; @@ -9,7 +10,7 @@ import { predicate as eventPredicate } from '../events/index.[REPLACE_IMPORT_EXT /** * A predicate to check if the structure is valid */ -export type StructurePredicate = (structure: unknown) => structure is T; +export type StructurePredicate = (structure: unknown) => structure is Structure; /** * Loads all the structures in the provided directory @@ -19,11 +20,11 @@ export type StructurePredicate = (structure: unknown) => structure is T; * @param recursive - Whether to recursively load the structures in the directory * @returns */ -export async function loadStructures( +export async function loadStructures( dir: PathLike, - predicate: StructurePredicate, + predicate: StructurePredicate, recursive = true, -): Promise { +): Promise { // Get the stats of the directory const statDir = await stat(dir); @@ -32,35 +33,27 @@ export async function loadStructures( throw new Error(`The directory '${dir}' is not a directory.`); } - // Get all the files in the directory - const files = await readdir(dir); - // Create an empty array to store the structures - const structures: T[] = []; + const structures: Structure[] = []; - // Loop through all the files in the directory - for (const file of files) { - const fileUrl = new URL(`${dir}/${file}`, import.meta.url); + // Create a glob pattern to match the .[REPLACE_IMPORT_EXT] files + const basePath = dir instanceof URL ? fileURLToPath(dir) : dir.toString(); + const pattern = resolve(basePath, recursive ? '**/*.[REPLACE_IMPORT_EXT]' : '*.[REPLACE_IMPORT_EXT]'); - // Get the stats of the file - const statFile = await stat(fileUrl); - - // If the file is a directory and recursive is true, recursively load the structures in the directory - if (statFile.isDirectory() && recursive) { - structures.push(...(await loadStructures(fileUrl, predicate, recursive))); - continue; - } - - // If the file is index.[REPLACE_IMPORT_EXT] or the file does not end with .[REPLACE_IMPORT_EXT], skip the file - if (file === 'index.[REPLACE_IMPORT_EXT]' || !file.endsWith('.[REPLACE_IMPORT_EXT]')) { + // Loop through all the matching files in the directory + for await (const file of glob(pattern)) { + // If the file is index.[REPLACE_IMPORT_EXT], skip the file + if (file.endsWith('/index.[REPLACE_IMPORT_EXT]')) { continue; } // Import the structure dynamically from the file - const structure = (await import(fileUrl.toString())).default; + const { default: structure } = await import(file); - // If the structure is a valid structure, add it - if (predicate(structure)) structures.push(structure); + // If the default export is a valid structure, add it + if (predicate(structure)) { + structures.push(structure); + } } return structures;