feat: use native node typescript (#11259)

* feat: use native node typescript

* fix: use basename

* Update packages/create-discord-bot/template/Bun/TypeScript/tsconfig.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Change module and moduleResolution to ESNext and Bundler

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Almeida
2025-12-09 02:22:16 +00:00
committed by GitHub
parent c9bc33c758
commit ec3ef7b1bd
34 changed files with 404 additions and 99 deletions

View File

@@ -1,11 +1,11 @@
import type { ExecException } from 'node:child_process';
import { cp, glob, mkdir, stat, readdir, readFile, writeFile } from 'node:fs/promises';
import { cp, mkdir, stat, readdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { URL } from 'node:url';
import { styleText } from 'node:util';
import type { PackageManager } from './helpers/packageManager.js';
import { install, isNodePackageManager } from './helpers/packageManager.js';
import { install } from './helpers/packageManager.js';
import { GUIDE_URL } from './util/constants.js';
interface Options {
@@ -67,39 +67,18 @@ export async function createDiscordBot({ directory, installPackages, typescript,
process.chdir(root);
const newVSCodeSettings = await readFile('./.vscode/settings.json', {
encoding: 'utf8',
}).then((str) => {
let newStr = str.replace('[REPLACE_ME]', deno || bun ? 'auto' : packageManager);
if (deno) {
// @ts-expect-error: This is fine
newStr = newStr.replaceAll('"[REPLACE_BOOL]"', true);
}
return newStr;
});
await writeFile('./.vscode/settings.json', newVSCodeSettings);
const globIterator = glob('./src/**/*.ts');
for await (const file of globIterator) {
const newData = await readFile(file, { encoding: 'utf8' }).then((str) =>
str.replaceAll('[REPLACE_IMPORT_EXT]', typescript && !isNodePackageManager(packageManager) ? 'ts' : 'js'),
);
await writeFile(file, newData);
}
const newVSCodeSettings = await readFile('./.vscode/settings.json', { encoding: 'utf8' });
await writeFile(
'./.vscode/settings.json',
newVSCodeSettings.replace(
/"npm\.packageManager":\s*"[^"]+"/,
`"npm.packageManager": "${deno || bun ? 'auto' : packageManager}"`,
),
);
if (!deno) {
const newPackageJSON = await readFile('./package.json', {
encoding: 'utf8',
}).then((str) => {
let newStr = str.replace('[REPLACE_ME]', directoryName);
newStr = newStr.replaceAll(
'[REPLACE_IMPORT_EXT]',
typescript && !isNodePackageManager(packageManager) ? 'ts' : 'js',
);
return newStr;
});
await writeFile('./package.json', newPackageJSON);
const newPackageJSON = await readFile('./package.json', { encoding: 'utf8' });
await writeFile('./package.json', newPackageJSON.replace(/"name":\s*"[^"]+"/, `"name": "${directoryName}"`));
}
if (installPackages) {

View File

@@ -1,12 +1,12 @@
import { execSync } from 'node:child_process';
import process from 'node:process';
import { styleText } from 'node:util';
import { DEFAULT_PACKAGE_MANAGER, NODE_PACKAGE_MANAGERS } from '../util/constants.js';
import { DEFAULT_PACKAGE_MANAGER, type PACKAGE_MANAGERS } from '../util/constants.js';
/**
* A union of supported package managers.
*/
export type PackageManager = 'bun' | 'deno' | 'npm' | 'pnpm' | 'yarn';
export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
/**
* Resolves the package manager from `npm_config_user_agent`.
@@ -117,12 +117,3 @@ export function install(packageManager: PackageManager) {
env,
});
}
/**
* Whether the provided package manager is a Node package manager.
*
* @param packageManager - The package manager to check
*/
export function isNodePackageManager(packageManager: PackageManager): packageManager is 'npm' | 'pnpm' | 'yarn' {
return NODE_PACKAGE_MANAGERS.includes(packageManager as any);
}

View File

@@ -13,11 +13,6 @@ export const DEFAULT_PROJECT_NAME = 'my-bot' as const;
*/
export const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const;
/**
* The supported Node.js package managers.
*/
export const NODE_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn'] as const;
/**
* The URL to the guide.
*/

View File

@@ -0,0 +1,21 @@
import common from 'eslint-config-neon/common';
import node from 'eslint-config-neon/node';
import prettier from 'eslint-config-neon/prettier';
const config = [
{
ignores: [],
},
...common,
...node,
...prettier,
{
rules: {
'jsdoc/check-tag-names': 0,
'jsdoc/no-undefined-types': 0,
'jsdoc/valid-types': 0,
},
},
];
export default config;

View File

@@ -1,14 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "[REPLACE_ME]",
"name": "@discordjs/template-bun-javascript",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"lint": "prettier --check . && eslint --ext .[REPLACE_IMPORT_EXT] --format=pretty src",
"deploy": "bun run src/util/deploy.[REPLACE_IMPORT_EXT]",
"format": "prettier --write . && eslint --ext .[REPLACE_IMPORT_EXT] --fix --format=pretty src",
"start": "bun run src/index.[REPLACE_IMPORT_EXT]"
"lint": "prettier --check . && eslint --ext .js --format=pretty src",
"deploy": "bun run src/util/deploy.js",
"format": "prettier --write . && eslint --ext .js --fix --format=pretty src",
"start": "bun run src/index.js"
},
"dependencies": {
"@discordjs/core": "^2.4.0",

View File

@@ -0,0 +1 @@
console.log();

View File

@@ -0,0 +1,26 @@
import common from 'eslint-config-neon/common';
import node from 'eslint-config-neon/node';
import prettier from 'eslint-config-neon/prettier';
import typescript from 'eslint-config-neon/typescript';
const config = [
{
ignores: [],
},
...common,
...node,
...typescript,
...prettier,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.eslint.json'],
},
},
rules: {
'import/extensions': 0,
},
},
];
export default config;

View File

@@ -1,14 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "[REPLACE_ME]",
"name": "@discordjs/template-bun-typescript",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"lint": "tsc && prettier --check . && eslint --ext .[REPLACE_IMPORT_EXT] --format=pretty src",
"deploy": "bun run src/util/deploy.[REPLACE_IMPORT_EXT]",
"format": "prettier --write . && eslint --ext .[REPLACE_IMPORT_EXT] --fix --format=pretty src",
"start": "bun run src/index.[REPLACE_IMPORT_EXT]"
"lint": "tsc && prettier --check . && eslint --ext .ts --format=pretty src",
"deploy": "bun run src/util/deploy.ts",
"format": "prettier --write . && eslint --ext .ts --fix --format=pretty src",
"start": "bun run src/index.ts"
},
"dependencies": {
"@discordjs/core": "^2.4.0",

View File

@@ -0,0 +1 @@
export {};

View File

@@ -2,14 +2,14 @@
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": ["@sapphire/ts-config", "@sapphire/ts-config/extra-strict"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"declaration": false,
"declarationMap": false,
"incremental": false,
"module": "ESNext",
"moduleResolution": "Bundler",
"target": "ESNext",
"outDir": "dist",
"noEmit": true,
"allowImportingTsExtensions": true,
"target": "ESNext",
"skipLibCheck": true
}
}

View File

@@ -8,5 +8,5 @@
"editor.trimAutoWhitespace": false,
"files.insertFinalNewline": true,
"files.eol": "\n",
"deno.enable": "[REPLACE_BOOL]"
"deno.enable": true
}

View File

@@ -1,6 +1,6 @@
import type { PathLike } from 'node:fs';
import { glob, stat } from 'node:fs/promises';
import { resolve } from 'node:path';
import { basename, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Command } from '../commands/index.ts';
import { predicate as commandPredicate } from '../commands/index.ts';
@@ -43,7 +43,7 @@ export async function loadStructures<Structure>(
// 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')) {
if (basename(file) === 'index.ts') {
continue;
}

View File

@@ -0,0 +1 @@
pnpm-lock.yaml

View File

@@ -2,7 +2,6 @@
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"tamasfe.even-better-toml",
"codezombiech.gitignore",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense"

View File

@@ -9,5 +9,5 @@
"editor.trimAutoWhitespace": false,
"files.insertFinalNewline": true,
"files.eol": "\n",
"npm.packageManager": "[REPLACE_ME]"
"npm.packageManager": "[REPLACE_PACKAGE_MANAGER]"
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "[REPLACE_ME]",
"name": "@discordjs/template-javascript",
"version": "0.1.0",
"private": true,
"type": "module",

View File

@@ -1,5 +1,5 @@
import { glob, stat } from 'node:fs/promises';
import { resolve } from 'node:path';
import { basename, resolve } from 'node:path';
import { fileURLToPath, URL } from 'node:url';
import { predicate as commandPredicate } from '../commands/index.js';
import { predicate as eventPredicate } from '../events/index.js';
@@ -40,7 +40,7 @@ export async function loadStructures(dir, predicate, recursive = true) {
// 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')) {
if (basename(file) === 'index.js') {
continue;
}

View File

@@ -1 +1 @@
dist
pnpm-lock.yaml

View File

@@ -2,7 +2,6 @@
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"tamasfe.even-better-toml",
"codezombiech.gitignore",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense"

View File

@@ -9,5 +9,5 @@
"editor.trimAutoWhitespace": false,
"files.insertFinalNewline": true,
"files.eol": "\n",
"npm.packageManager": "[REPLACE_ME]"
"npm.packageManager": "[REPLACE_PACKAGE_MANAGER]"
}

View File

@@ -5,7 +5,7 @@ import typescript from 'eslint-config-neon/typescript';
const config = [
{
ignores: ['**/dist/*'],
ignores: [],
},
...common,
...node,

View File

@@ -1,15 +1,15 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "[REPLACE_ME]",
"name": "@discordjs/template-typescript",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"lint": "prettier --check . && eslint --ext .ts --format=pretty src",
"deploy": "node --env-file=.env dist/util/deploy.js",
"deploy": "node --env-file=.env src/util/deploy.ts",
"format": "prettier --write . && eslint --ext .ts --fix --format=pretty src",
"start": "node --env-file=.env dist/index.js"
"start": "node --env-file=.env src/index.ts"
},
"dependencies": {
"@discordjs/core": "^2.4.0",

View File

@@ -1,6 +1,6 @@
import type { RESTPostAPIApplicationCommandsJSONBody, CommandInteraction } from 'discord.js';
import { z } from 'zod';
import type { StructurePredicate } from '../util/loaders.[REPLACE_IMPORT_EXT]';
import type { StructurePredicate } from '../util/loaders.ts';
/**
* Defines the structure of a command

View File

@@ -1,4 +1,4 @@
import type { Command } from './index.[REPLACE_IMPORT_EXT]';
import type { Command } from './index.ts';
export default {
data: {

View File

@@ -1,4 +1,4 @@
import type { Command } from '../index.[REPLACE_IMPORT_EXT]';
import type { Command } from '../index.ts';
export default {
data: {

View File

@@ -1,6 +1,6 @@
import type { ClientEvents } from 'discord.js';
import { z } from 'zod';
import type { StructurePredicate } from '../util/loaders.[REPLACE_IMPORT_EXT]';
import type { StructurePredicate } from '../util/loaders.ts';
/**
* Defines the structure of an event.

View File

@@ -1,7 +1,7 @@
import { URL } from 'node:url';
import { Events } from 'discord.js';
import { loadCommands } from '../util/loaders.[REPLACE_IMPORT_EXT]';
import type { Event } from './index.[REPLACE_IMPORT_EXT]';
import { loadCommands } from '../util/loaders.ts';
import type { Event } from './index.ts';
const commands = await loadCommands(new URL('../commands/', import.meta.url));

View File

@@ -1,5 +1,5 @@
import { Events } from 'discord.js';
import type { Event } from './index.[REPLACE_IMPORT_EXT]';
import type { Event } from './index.ts';
export default {
name: Events.ClientReady,

View File

@@ -1,7 +1,7 @@
import process from 'node:process';
import { URL } from 'node:url';
import { Client, GatewayIntentBits } from 'discord.js';
import { loadEvents } from './util/loaders.[REPLACE_IMPORT_EXT]';
import { loadEvents } from './util/loaders.ts';
// Initialize the client
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

View File

@@ -2,7 +2,7 @@ import process from 'node:process';
import { URL } from 'node:url';
import { API } from '@discordjs/core/http-only';
import { REST } from 'discord.js';
import { loadCommands } from './loaders.[REPLACE_IMPORT_EXT]';
import { loadCommands } from './loaders.ts';
const commands = await loadCommands(new URL('../commands/', import.meta.url));
const commandData = [...commands.values()].map((command) => command.data);

View File

@@ -1,11 +1,9 @@
import type { PathLike } from 'node:fs';
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]';
import { predicate as eventPredicate } from '../events/index.[REPLACE_IMPORT_EXT]';
import { basename, resolve } from 'node:path';
import { fileURLToPath, URL } from 'node:url';
import { predicate as commandPredicate, type Command } from '../commands/index.ts';
import { predicate as eventPredicate, type Event } from '../events/index.ts';
/**
* A predicate to check if the structure is valid
@@ -36,14 +34,14 @@ export async function loadStructures<Structure>(
// Create an empty array to store the structures
const structures: Structure[] = [];
// Create a glob pattern to match the .[REPLACE_IMPORT_EXT] files
// Create a glob pattern to match the .ts files
const basePath = dir instanceof URL ? fileURLToPath(dir) : dir.toString();
const pattern = resolve(basePath, recursive ? '**/*.[REPLACE_IMPORT_EXT]' : '*.[REPLACE_IMPORT_EXT]');
const pattern = resolve(basePath, recursive ? '**/*.ts' : '*.ts');
// 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]')) {
// If the file is index.ts, skip the file
if (basename(file) === 'index.ts') {
continue;
}

View File

@@ -2,12 +2,15 @@
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": ["@sapphire/ts-config", "@sapphire/ts-config/extra-strict"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"erasableSyntaxOnly": true,
"declaration": false,
"declarationMap": false,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"incremental": false,
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"target": "ESNext",
"outDir": "dist",
"skipLibCheck": true
}
}