mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 08:33:30 +01:00
feat(create-discord-bot): bun/deno templates (#9795)
This commit is contained in:
@@ -1,27 +1,26 @@
|
||||
import type { ExecException } from 'node:child_process';
|
||||
import { cp, stat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { URL } from 'node:url';
|
||||
import chalk from 'chalk';
|
||||
import validateProjectName from 'validate-npm-package-name';
|
||||
import { install, resolvePackageManager } from './helpers/packageManager.js';
|
||||
import glob from 'fast-glob';
|
||||
import { red, yellow, green, cyan } from 'picocolors';
|
||||
import type { PackageManager } from './helpers/packageManager.js';
|
||||
import { install } from './helpers/packageManager.js';
|
||||
import { GUIDE_URL } from './util/constants.js';
|
||||
|
||||
interface Options {
|
||||
directory: string;
|
||||
javascript?: boolean;
|
||||
packageManager: PackageManager;
|
||||
typescript?: boolean;
|
||||
}
|
||||
|
||||
export async function createDiscordBot({ typescript, javascript, directory }: Options) {
|
||||
if (!directory) {
|
||||
console.error(chalk.red('Please specify the project directory.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export async function createDiscordBot({ directory, typescript, packageManager }: Options) {
|
||||
const root = path.resolve(directory);
|
||||
const directoryName = path.basename(root);
|
||||
|
||||
console.log();
|
||||
|
||||
const directoryStats = await stat(root).catch(async (error) => {
|
||||
// Create a new directory if the specified one does not exist.
|
||||
if (error.code === 'ENOENT') {
|
||||
@@ -34,47 +33,75 @@ export async function createDiscordBot({ typescript, javascript, directory }: Op
|
||||
|
||||
// If the directory is actually a file or if it's not empty, throw an error.
|
||||
if (!directoryStats.isDirectory() || (await readdir(root)).length > 0) {
|
||||
console.error(
|
||||
chalk.red(`The directory ${chalk.yellow(`"${directoryName}"`)} is either not a directory or is not empty.`),
|
||||
);
|
||||
console.error(chalk.red(`Please specify an empty directory.`));
|
||||
console.error(red(`The directory ${yellow(`"${directoryName}"`)} is either not a directory or is not empty.`));
|
||||
console.error(red(`Please specify an empty directory.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// We'll use the directory name as the project name. Check npm name validity.
|
||||
const validationResult = validateProjectName(directoryName);
|
||||
|
||||
if (!validationResult.validForNewPackages) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Cannot create a project named ${chalk.yellow(
|
||||
`"${directoryName}"`,
|
||||
)} due to npm naming restrictions.\n\nErrors:`,
|
||||
),
|
||||
);
|
||||
|
||||
for (const error of [...(validationResult.errors ?? []), ...(validationResult.warnings ?? [])]) {
|
||||
console.error(chalk.red(`- ${error}`));
|
||||
}
|
||||
|
||||
console.error(chalk.red('\nSee https://docs.npmjs.com/cli/configuring-npm/package-json for more details.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Creating ${directoryName} in ${chalk.green(root)}.`);
|
||||
await cp(new URL(`../template/${typescript ? 'TypeScript' : 'JavaScript'}`, import.meta.url), root, {
|
||||
console.log(`Creating ${directoryName} in ${green(root)}.`);
|
||||
const deno = packageManager === 'deno';
|
||||
await cp(new URL(`../template/${deno ? 'Deno' : typescript ? 'TypeScript' : 'JavaScript'}`, import.meta.url), root, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
const bun = packageManager === 'bun';
|
||||
if (bun) {
|
||||
await cp(
|
||||
new URL(`../template/Bun/${typescript ? 'TypeScript' : 'JavaScript'}/package.json`, import.meta.url),
|
||||
`${root}/package.json`,
|
||||
);
|
||||
|
||||
if (typescript) {
|
||||
await cp(
|
||||
new URL('../template/Bun/Typescript/tsconfig.eslint.json', import.meta.url),
|
||||
`${root}/tsconfig.eslint.json`,
|
||||
);
|
||||
await cp(new URL('../template/Bun/Typescript/tsconfig.json', import.meta.url), `${root}/tsconfig.json`);
|
||||
}
|
||||
}
|
||||
|
||||
process.chdir(root);
|
||||
|
||||
const newPackageJSON = await readFile('./package.json', { encoding: 'utf8' }).then((str) =>
|
||||
str.replace('[REPLACE-NAME]', directoryName),
|
||||
);
|
||||
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 globStream = glob.stream('./src/**/*.ts');
|
||||
for await (const file of globStream) {
|
||||
const newData = await readFile(file, { encoding: 'utf8' }).then((str) =>
|
||||
str.replaceAll('[REPLACE_IMPORT_EXT]', typescript ? 'ts' : 'js'),
|
||||
);
|
||||
await writeFile(file, newData);
|
||||
}
|
||||
|
||||
const newPackageJSON = await readFile('./package.json', { encoding: 'utf8' }).then((str) => {
|
||||
let newStr = str.replace('[REPLACE_ME]', directoryName);
|
||||
newStr = newStr.replaceAll('[REPLACE_IMPORT_EXT]', typescript ? 'ts' : 'js');
|
||||
return newStr;
|
||||
});
|
||||
await writeFile('./package.json', newPackageJSON);
|
||||
|
||||
const packageManager = resolvePackageManager();
|
||||
install(packageManager);
|
||||
console.log(chalk.green('All done! Be sure to read through the discord.js guide for help on your journey.'));
|
||||
console.log(`Link: ${chalk.cyan(GUIDE_URL)}`);
|
||||
try {
|
||||
install(packageManager);
|
||||
} catch (error) {
|
||||
console.log();
|
||||
const err = error as ExecException;
|
||||
if (err.signal === 'SIGINT') {
|
||||
console.log(red('Installation aborted.'));
|
||||
} else {
|
||||
console.error(red('Installation failed.'));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(green('All done! Be sure to read through the discord.js guide for help on your journey.'));
|
||||
console.log(`Link: ${cyan(GUIDE_URL)}`);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import process from 'node:process';
|
||||
import chalk from 'chalk';
|
||||
import { yellow } from 'picocolors';
|
||||
import { DEFAULT_PACKAGE_MANAGER } from '../util/constants.js';
|
||||
|
||||
/**
|
||||
* A union of supported package managers.
|
||||
*/
|
||||
export type PackageManager = 'npm' | 'pnpm' | 'yarn';
|
||||
export type PackageManager = 'bun' | 'deno' | 'npm' | 'pnpm' | 'yarn';
|
||||
|
||||
/**
|
||||
* Resolves the package manager from `npm_config_user_agent`.
|
||||
@@ -32,7 +32,7 @@ export function resolvePackageManager(): PackageManager {
|
||||
}
|
||||
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
yellow(
|
||||
`Detected an unsupported package manager (${npmConfigUserAgent}). Falling back to ${DEFAULT_PACKAGE_MANAGER}.`,
|
||||
),
|
||||
);
|
||||
@@ -47,21 +47,62 @@ export function resolvePackageManager(): PackageManager {
|
||||
* @param packageManager - The package manager to use
|
||||
*/
|
||||
export function install(packageManager: PackageManager) {
|
||||
let installCommand;
|
||||
|
||||
switch (packageManager) {
|
||||
case 'npm':
|
||||
case 'pnpm':
|
||||
installCommand = `${packageManager} install`;
|
||||
break;
|
||||
case 'yarn':
|
||||
installCommand = packageManager;
|
||||
break;
|
||||
}
|
||||
let installCommand: string[] | string = `${packageManager} install`;
|
||||
|
||||
console.log(`Installing dependencies with ${packageManager}...`);
|
||||
|
||||
switch (packageManager) {
|
||||
case 'yarn':
|
||||
console.log();
|
||||
installCommand = [
|
||||
`${packageManager} set version stable`,
|
||||
`${packageManager} config set nodeLinker node-modules`,
|
||||
`${packageManager} config set logFilters --json '[{ "code": "YN0002", "level": "discard" }, { "code": "YN0013", "level": "discard" }, { "code": "YN0032", "level": "discard" }, { "code": "YN0060", "level": "discard" }]'`,
|
||||
`${packageManager} plugin import interactive-tools`,
|
||||
`${packageManager} plugin import workspace-tools`,
|
||||
installCommand,
|
||||
];
|
||||
break;
|
||||
case 'deno':
|
||||
installCommand = `${packageManager} cache --reload src/index.ts`;
|
||||
break;
|
||||
case 'pnpm':
|
||||
case 'bun':
|
||||
console.log();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
ADBLOCK: '1',
|
||||
NODE_ENV: 'development',
|
||||
DISABLE_OPENCOLLECTIVE: '1',
|
||||
};
|
||||
|
||||
if (Array.isArray(installCommand)) {
|
||||
for (const [index, command] of installCommand.entries()) {
|
||||
if (index === installCommand.length - 1) {
|
||||
execSync(command, {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
execSync(command, {
|
||||
stdio: 'ignore',
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
execSync(installCommand, {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,32 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// eslint-disable-next-line n/shebang
|
||||
import { program } from 'commander';
|
||||
import process from 'node:process';
|
||||
import { Option, program } from 'commander';
|
||||
import { red, yellow, green } from 'picocolors';
|
||||
import prompts from 'prompts';
|
||||
import validateProjectName from 'validate-npm-package-name';
|
||||
import packageJSON from '../package.json' assert { type: 'json' };
|
||||
import { createDiscordBot } from './create-discord-bot.js';
|
||||
import { resolvePackageManager } from './helpers/packageManager.js';
|
||||
import { DEFAULT_PROJECT_NAME, PACKAGE_MANAGERS } from './util/constants.js';
|
||||
|
||||
let projectDirectory = '';
|
||||
|
||||
const handleSigTerm = () => process.exit(0);
|
||||
|
||||
process.on('SIGINT', handleSigTerm);
|
||||
process.on('SIGTERM', handleSigTerm);
|
||||
|
||||
// https://github.com/vercel/next.js/blob/canary/packages/create-next-app/index.ts#L24-L32
|
||||
const onPromptState = (state: any) => {
|
||||
if (state.aborted) {
|
||||
// If we don't re-enable the terminal cursor before exiting
|
||||
// the program, the cursor will remain hidden
|
||||
process.stdout.write('\u001B[?25h');
|
||||
process.stdout.write('\n');
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
program
|
||||
.name(packageJSON.name)
|
||||
.version(packageJSON.version)
|
||||
.description('Create a basic discord.js bot.')
|
||||
.option('--directory', 'The directory where this will be created.')
|
||||
.argument('[directory]', 'What is the name of the directory you want to create this project in?')
|
||||
.usage(`${green('<directory>')}`)
|
||||
.action((directory) => {
|
||||
projectDirectory = directory;
|
||||
})
|
||||
.option('--typescript', 'Whether to use the TypeScript template.')
|
||||
.option('--javascript', 'Whether to use the JavaScript template.')
|
||||
.addOption(
|
||||
new Option('--packageManager <packageManager>', 'The package manager to use.')
|
||||
.choices(PACKAGE_MANAGERS)
|
||||
.default(resolvePackageManager()),
|
||||
)
|
||||
.allowUnknownOption()
|
||||
.parse();
|
||||
|
||||
let { typescript, javascript, directory } = program.opts();
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { typescript, javascript, packageManager } = program.opts();
|
||||
|
||||
if (!directory) {
|
||||
directory = (
|
||||
if (!projectDirectory) {
|
||||
projectDirectory = (
|
||||
await prompts({
|
||||
onState: onPromptState,
|
||||
type: 'text',
|
||||
name: 'directory',
|
||||
initial: 'my-bot',
|
||||
initial: DEFAULT_PROJECT_NAME,
|
||||
message: 'What is the name of the directory you want to create this project in?',
|
||||
validate: (directory) => {
|
||||
// We'll use the directory name as the project name. Check npm name validity.
|
||||
const validationResult = validateProjectName(directory);
|
||||
|
||||
if (!validationResult.validForNewPackages) {
|
||||
const errors = [];
|
||||
|
||||
for (const error of [...(validationResult.errors ?? []), ...(validationResult.warnings ?? [])]) {
|
||||
errors.push(red(`- ${error}`));
|
||||
}
|
||||
|
||||
return red(
|
||||
`Cannot create a project named ${yellow(
|
||||
`"${directory}"`,
|
||||
)} due to npm naming restrictions.\n\nErrors:\n${errors.join('\n')}\n\n${red(
|
||||
'\nSee https://docs.npmjs.com/cli/configuring-npm/package-json for more details.',
|
||||
)}}`,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
})
|
||||
).directory;
|
||||
}
|
||||
|
||||
if (typescript === undefined && javascript === undefined) {
|
||||
const deno = packageManager === 'deno';
|
||||
if (!deno && typescript === undefined && javascript === undefined) {
|
||||
const { useTypescript } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: 'toggle',
|
||||
name: 'useTypescript',
|
||||
message: 'Do you want to use TypeScript?',
|
||||
@@ -36,7 +98,6 @@ if (typescript === undefined && javascript === undefined) {
|
||||
});
|
||||
|
||||
typescript = useTypescript;
|
||||
javascript = !useTypescript;
|
||||
}
|
||||
|
||||
await createDiscordBot({ typescript, javascript, directory });
|
||||
await createDiscordBot({ typescript, directory: projectDirectory, packageManager });
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
*/
|
||||
export const DEFAULT_PACKAGE_MANAGER = 'npm' as const;
|
||||
|
||||
/**
|
||||
* The default project name.
|
||||
*/
|
||||
export const DEFAULT_PROJECT_NAME = 'my-bot' as const;
|
||||
|
||||
/**
|
||||
* The supported package managers.
|
||||
*/
|
||||
export const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const;
|
||||
|
||||
/**
|
||||
* The URL to the guide.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user