create-commandkit 1.2.0-dev.20251019020542 → 1.2.0-dev.20251019062347

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 CHANGED
@@ -14,6 +14,9 @@ create-commandkit is a CLI utility to quickly instantiate a Discord bot with Com
14
14
 
15
15
  - Interactive, beautiful command-line interface 🖥️
16
16
  - Supports CommonJS and ES Modules 📦
17
+ - Dynamic template system with examples from GitHub 🚀
18
+ - Support for all major package managers (npm, pnpm, yarn, bun, deno) 📦
19
+ - TypeScript and JavaScript support 🔧
17
20
 
18
21
  ## Documentation
19
22
 
@@ -21,12 +24,75 @@ You can find the full documentation [here](https://commandkit.dev).
21
24
 
22
25
  ## Usage
23
26
 
24
- Run the following command in your terminal:
27
+ ### Basic Usage
25
28
 
26
29
  ```sh
27
30
  npx create-commandkit@latest
28
31
  ```
29
32
 
33
+ ### With Project Name
34
+
35
+ ```sh
36
+ npx create-commandkit@latest my-bot
37
+ ```
38
+
39
+ ### Using Examples
40
+
41
+ ```sh
42
+ # Use a curated example
43
+ npx create-commandkit@latest --example with-database
44
+
45
+ # Use a custom GitHub repository
46
+ npx create-commandkit@latest --example "https://github.com/user/repo"
47
+
48
+ # Use a specific path within a repository
49
+ npx create-commandkit@latest --example "https://github.com/user/repo" --example-path "examples/bot"
50
+ ```
51
+
52
+ ### CLI Options
53
+
54
+ - `-h, --help` - Show all available options
55
+ - `-V, --version` - Output the version number
56
+ - `-e, --example <name-or-url>` - An example to bootstrap the app with
57
+ - `--example-path <path>` - Specify the path to the example separately
58
+ - `--use-npm` - Use npm as package manager
59
+ - `--use-pnpm` - Use pnpm as package manager
60
+ - `--use-yarn` - Use yarn as package manager
61
+ - `--use-bun` - Use bun as package manager
62
+ - `--use-deno` - Use deno as package manager
63
+ - `--skip-install` - Skip installing packages
64
+ - `--no-git` - Skip git initialization
65
+ - `--yes` - Use defaults for all options
66
+ - `--list-examples` - List all available examples from the official repository
67
+
68
+ ### Available Examples
69
+
70
+ <!-- BEGIN_AVAILABLE_EXAMPLES -->
71
+ - `basic-js` - [examples/basic-js](https://github.com/underctrl-io/commandkit/tree/main/examples/basic-js)
72
+ - `basic-ts` - [examples/basic-ts](https://github.com/underctrl-io/commandkit/tree/main/examples/basic-ts)
73
+ - `deno-ts` - [examples/deno-ts](https://github.com/underctrl-io/commandkit/tree/main/examples/deno-ts)
74
+ - `without-cli` - [examples/without-cli](https://github.com/underctrl-io/commandkit/tree/main/examples/without-cli)
75
+ <!-- END_AVAILABLE_EXAMPLES -->
76
+
77
+ ### Examples
78
+
79
+ ```sh
80
+ # Create a basic TypeScript bot, skip installation
81
+ npx create-commandkit@latest --example basic-ts --skip-install
82
+
83
+ # Create a bot with all defaults (no prompts)
84
+ npx create-commandkit@latest --yes
85
+
86
+ # Create a bot from custom repository
87
+ npx create-commandkit@latest --example "https://github.com/username/my-commandkit-template"
88
+
89
+ # Create a bot with pnpm
90
+ npx create-commandkit@latest --use-pnpm
91
+
92
+ # List all available examples
93
+ npx create-commandkit@latest --list-examples
94
+ ```
95
+
30
96
  ## Support and Suggestions
31
97
 
32
98
  Submit any queries or suggestions in our [Discord community](https://ctrl.lol/discord).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { CLIOptions } from './types.js';
2
+ export declare function parseCLI(): CLIOptions;
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ import { Command } from 'commander';
2
+ export function parseCLI() {
3
+ const program = new Command();
4
+ program
5
+ .name('create-commandkit')
6
+ .description('Effortlessly create a CommandKit project')
7
+ .version(process.env.npm_package_version || '1.0.0')
8
+ .argument('[project-directory]', 'Project directory name')
9
+ .option('-h, --help', 'Show all available options')
10
+ .option('-e, --example <name-or-url>', 'An example to bootstrap the app with')
11
+ .option('--example-path <path>', 'Specify the path to the example separately')
12
+ .option('--use-npm', 'Explicitly tell the CLI to bootstrap using npm')
13
+ .option('--use-pnpm', 'Explicitly tell the CLI to bootstrap using pnpm')
14
+ .option('--use-yarn', 'Explicitly tell the CLI to bootstrap using yarn')
15
+ .option('--use-bun', 'Explicitly tell the CLI to bootstrap using bun')
16
+ .option('--use-deno', 'Explicitly tell the CLI to bootstrap using deno')
17
+ .option('--skip-install', 'Explicitly tell the CLI to skip installing packages')
18
+ .option('--no-git', 'Explicitly tell the CLI to disable git initialization')
19
+ .option('--yes', 'Use previous preferences or defaults for all options')
20
+ .option('--list-examples', 'List all available examples from the official repository');
21
+ program.parse();
22
+ const options = program.opts();
23
+ const args = program.args;
24
+ return {
25
+ help: options.help,
26
+ example: options.example,
27
+ examplePath: options.examplePath,
28
+ useNpm: options.useNpm,
29
+ usePnpm: options.usePnpm,
30
+ useYarn: options.useYarn,
31
+ useBun: options.useBun,
32
+ useDeno: options.useDeno,
33
+ skipInstall: options.skipInstall,
34
+ noGit: options.noGit,
35
+ yes: options.yes,
36
+ listExamples: options.listExamples,
37
+ projectDirectory: args[0],
38
+ };
39
+ }
@@ -0,0 +1,6 @@
1
+ export interface FetchExampleOptions {
2
+ example: string;
3
+ examplePath?: string;
4
+ targetDir: string;
5
+ }
6
+ export declare function fetchExample({ example, examplePath, targetDir, }: FetchExampleOptions): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ // @ts-ignore
4
+ import tiged from 'tiged';
5
+ import { validateExampleName } from './validate.js';
6
+ export async function fetchExample({ example, examplePath, targetDir, }) {
7
+ const validation = validateExampleName(example);
8
+ if (!validation.valid) {
9
+ throw new Error(validation.error);
10
+ }
11
+ let sourceUrl;
12
+ // Check if it's a GitHub URL
13
+ if (example.startsWith('http://') || example.startsWith('https://')) {
14
+ sourceUrl = example;
15
+ }
16
+ else {
17
+ // Construct URL for curated examples
18
+ sourceUrl = `underctrl-io/commandkit/examples/${example}`;
19
+ }
20
+ // Create temporary directory for download
21
+ const tempDir = path.join(targetDir, '.temp-example');
22
+ try {
23
+ // Download the example
24
+ const emitter = tiged(sourceUrl, {
25
+ mode: 'tar',
26
+ disableCache: true,
27
+ });
28
+ await emitter.clone(tempDir);
29
+ // If examplePath is specified, navigate to that subdirectory
30
+ const sourceDir = examplePath ? path.join(tempDir, examplePath) : tempDir;
31
+ if (examplePath && !fs.existsSync(sourceDir)) {
32
+ throw new Error(`Example path '${examplePath}' not found in the repository`);
33
+ }
34
+ // Copy contents to target directory
35
+ const contents = fs.readdirSync(sourceDir);
36
+ for (const item of contents) {
37
+ const srcPath = path.join(sourceDir, item);
38
+ const destPath = path.join(targetDir, item);
39
+ if (fs.statSync(srcPath).isDirectory()) {
40
+ await fs.copy(srcPath, destPath);
41
+ }
42
+ else {
43
+ await fs.copy(srcPath, destPath);
44
+ }
45
+ }
46
+ // Clean up temporary directory
47
+ await fs.remove(tempDir);
48
+ }
49
+ catch (error) {
50
+ // Clean up on error
51
+ if (fs.existsSync(tempDir)) {
52
+ await fs.remove(tempDir);
53
+ }
54
+ if (error instanceof Error) {
55
+ if (error.message.includes('not found') ||
56
+ error.message.includes('404')) {
57
+ throw new Error(`Example '${example}' not found. Available examples: basic-ts, basic-js, deno-ts, without-cli`);
58
+ }
59
+ throw new Error(`Failed to fetch example: ${error.message}`);
60
+ }
61
+ throw new Error('Failed to fetch example due to an unknown error');
62
+ }
63
+ }
@@ -0,0 +1,12 @@
1
+ export declare function validateProjectName(name: string): {
2
+ valid: boolean;
3
+ error?: string;
4
+ };
5
+ export declare function validateDirectory(dir: string): {
6
+ valid: boolean;
7
+ error?: string;
8
+ };
9
+ export declare function validateExampleName(example: string): {
10
+ valid: boolean;
11
+ error?: string;
12
+ };
@@ -0,0 +1,70 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import validateNpmPackageName from 'validate-npm-package-name';
4
+ export function validateProjectName(name) {
5
+ const result = validateNpmPackageName(name);
6
+ if (!result.validForNewPackages) {
7
+ const errors = result.errors || [];
8
+ const warnings = result.warnings || [];
9
+ const allIssues = [...errors, ...warnings];
10
+ return {
11
+ valid: false,
12
+ error: allIssues.length > 0 ? allIssues[0] : 'Invalid project name',
13
+ };
14
+ }
15
+ return { valid: true };
16
+ }
17
+ export function validateDirectory(dir) {
18
+ const resolvedDir = path.resolve(process.cwd(), dir);
19
+ try {
20
+ const exists = fs.existsSync(resolvedDir);
21
+ if (!exists) {
22
+ return { valid: true };
23
+ }
24
+ const contents = fs.readdirSync(resolvedDir);
25
+ const isEmpty = contents.length === 0;
26
+ if (!isEmpty) {
27
+ return {
28
+ valid: false,
29
+ error: 'Directory is not empty!',
30
+ };
31
+ }
32
+ return { valid: true };
33
+ }
34
+ catch (error) {
35
+ return {
36
+ valid: false,
37
+ error: 'Unable to access directory',
38
+ };
39
+ }
40
+ }
41
+ export function validateExampleName(example) {
42
+ // Check if it's a GitHub URL
43
+ if (example.startsWith('http://') || example.startsWith('https://')) {
44
+ try {
45
+ const url = new URL(example);
46
+ if (url.hostname === 'github.com' || url.hostname === 'www.github.com') {
47
+ return { valid: true };
48
+ }
49
+ return {
50
+ valid: false,
51
+ error: 'Only GitHub URLs are supported',
52
+ };
53
+ }
54
+ catch {
55
+ return {
56
+ valid: false,
57
+ error: 'Invalid URL format',
58
+ };
59
+ }
60
+ }
61
+ // Check if it's a valid example name (alphanumeric, hyphens, underscores)
62
+ const exampleNameRegex = /^[a-zA-Z0-9-_]+$/;
63
+ if (!exampleNameRegex.test(example)) {
64
+ return {
65
+ valid: false,
66
+ error: 'Example name can only contain letters, numbers, hyphens, and underscores',
67
+ };
68
+ }
69
+ return { valid: true };
70
+ }
package/dist/index.js CHANGED
@@ -1,100 +1,197 @@
1
1
  #!/usr/bin/env node
2
- console.clear();
3
- import { confirm, intro, outro, password, select, text } from '@clack/prompts';
2
+ import { confirm, intro, outro, password, text } from '@clack/prompts';
4
3
  import fs from 'fs-extra';
5
4
  import gradient from 'gradient-string';
6
5
  import { execSync } from 'node:child_process';
7
- import path from 'node:path';
6
+ import path, { join } from 'node:path';
8
7
  import colors from 'picocolors';
9
- import { copyTemplates } from './functions/copyTemplates.js';
10
- import { installDeps } from './functions/installDeps.js';
11
- import { setup } from './functions/setup.js';
12
- import { detectPackageManager, textColors } from './utils.js';
13
- const commandkitGradient = gradient(textColors.commandkit)('CommandKit');
14
- intro(`Welcome to ${commandkitGradient}!`);
15
- const dir = path.resolve(process.cwd(), (await text({
16
- message: 'Enter a project directory:',
17
- placeholder: 'Leave blank for current directory',
18
- defaultValue: '.',
19
- validate: (value) => {
20
- value = path.resolve(process.cwd(), value);
21
- let isEmpty;
8
+ import { parseCLI } from './cli.js';
9
+ import { fetchExample } from './functions/fetchExample.js';
10
+ import { validateDirectory, validateProjectName, } from './functions/validate.js';
11
+ import { fetchAvailableExamples, getDefaultExample, getInstallCommand, isOfficialExample, resolvePackageManager, textColors, } from './utils.js';
12
+ import { readFile } from 'node:fs/promises';
13
+ async function main() {
14
+ const cliOptions = parseCLI();
15
+ // Handle help and version flags
16
+ if (cliOptions.help) {
17
+ console.log(`
18
+ Usage: create-commandkit [options] [project-directory]
19
+
20
+ Options:
21
+ -h, --help Show all available options
22
+ -V, --version Output the version number
23
+ -e, --example <name-or-url> An example to bootstrap the app with
24
+ --example-path <path> Specify the path to the example separately
25
+ --use-npm Explicitly tell the CLI to bootstrap using npm
26
+ --use-pnpm Explicitly tell the CLI to bootstrap using pnpm
27
+ --use-yarn Explicitly tell the CLI to bootstrap using yarn
28
+ --use-bun Explicitly tell the CLI to bootstrap using bun
29
+ --use-deno Explicitly tell the CLI to bootstrap using deno
30
+ --skip-install Explicitly tell the CLI to skip installing packages
31
+ --no-git Explicitly tell the CLI to disable git initialization
32
+ --yes Use previous preferences or defaults for all options
33
+ --list-examples List all available examples from the official repository
34
+
35
+ Examples:
36
+ npx create-commandkit@latest
37
+ npx create-commandkit@latest my-bot
38
+ npx create-commandkit@latest --example basic-ts
39
+ npx create-commandkit@latest --example "https://github.com/user/repo" --example-path "examples/bot"
40
+ npx create-commandkit@latest --use-pnpm --yes
41
+ npx create-commandkit@latest --list-examples
42
+ `);
43
+ process.exit(0);
44
+ }
45
+ // Handle list examples flag
46
+ if (cliOptions.listExamples) {
47
+ console.log(colors.cyan('Fetching available examples...'));
22
48
  try {
23
- const contents = fs.readdirSync(value);
24
- isEmpty = contents.length === 0;
49
+ const examples = await fetchAvailableExamples();
50
+ console.log(colors.green('\nAvailable examples:'));
51
+ console.log('');
52
+ for (const example of examples) {
53
+ console.log(` ${colors.magenta(example)}`);
54
+ }
55
+ console.log('');
56
+ console.log(colors.gray('Usage: npx create-commandkit@latest --example <example-name>'));
57
+ console.log(colors.gray('Example: npx create-commandkit@latest --example basic-ts'));
25
58
  }
26
- catch {
27
- isEmpty = true;
59
+ catch (error) {
60
+ console.error(colors.red('Failed to fetch examples list. Please check your internet connection.'));
61
+ process.exit(1);
28
62
  }
29
- return isEmpty ? undefined : 'Directory is not empty!';
30
- },
31
- })));
32
- const manager = (await select({
33
- message: 'Select a package manager:',
34
- initialValue: detectPackageManager(),
35
- options: [
36
- { label: 'npm', value: 'npm' },
37
- { label: 'pnpm', value: 'pnpm' },
38
- { label: 'yarn', value: 'yarn' },
39
- { label: 'bun', value: 'bun' },
40
- { label: 'deno', value: 'deno' },
41
- ],
42
- }));
43
- const lang = (await select({
44
- message: 'Select the language to use:',
45
- initialValue: 'ts',
46
- options: [
47
- { label: 'TypeScript', value: 'ts' },
48
- { label: 'JavaScript', value: 'js' },
49
- ],
50
- }));
51
- const token = (await password({
52
- message: 'Enter your Discord bot token (stored in .env, optional):',
53
- mask: colors.gray('*'),
54
- }));
55
- const gitInit = await confirm({
56
- message: 'Initialize a git repository?',
57
- initialValue: true,
58
- });
59
- outro(colors.cyan('Setup complete.'));
60
- await setup({
61
- manager,
62
- dir,
63
- token,
64
- });
65
- await copyTemplates({ dir, lang });
66
- if (gitInit) {
63
+ process.exit(0);
64
+ }
65
+ const commandkitGradient = gradient(textColors.commandkit)('CommandKit');
66
+ intro(`Welcome to ${commandkitGradient}!`);
67
+ // Determine project directory
68
+ let projectDir;
69
+ if (cliOptions.projectDirectory) {
70
+ projectDir = path.resolve(process.cwd(), cliOptions.projectDirectory);
71
+ // Validate project name if provided
72
+ const projectName = path.basename(projectDir);
73
+ const nameValidation = validateProjectName(projectName);
74
+ if (!nameValidation.valid) {
75
+ console.error(colors.red(`Error: ${nameValidation.error}`));
76
+ process.exit(1);
77
+ }
78
+ }
79
+ else if (cliOptions.yes) {
80
+ projectDir = path.resolve(process.cwd(), 'commandkit-project');
81
+ }
82
+ else {
83
+ projectDir = path.resolve(process.cwd(), (await text({
84
+ message: 'Enter a project directory:',
85
+ placeholder: 'Leave blank for current directory',
86
+ defaultValue: '.',
87
+ validate: (value) => {
88
+ value = path.resolve(process.cwd(), value);
89
+ const validation = validateDirectory(value);
90
+ return validation.valid ? undefined : validation.error;
91
+ },
92
+ })));
93
+ }
94
+ // Validate directory
95
+ const dirValidation = validateDirectory(projectDir);
96
+ if (!dirValidation.valid) {
97
+ console.error(colors.red(`Error: ${dirValidation.error}`));
98
+ process.exit(1);
99
+ }
100
+ // Determine package manager
101
+ const manager = resolvePackageManager(cliOptions);
102
+ // Get Discord token
103
+ let token;
104
+ if (cliOptions.yes) {
105
+ token = '';
106
+ }
107
+ else {
108
+ token = (await password({
109
+ message: 'Enter your Discord bot token (stored in .env, optional):',
110
+ mask: colors.gray('*'),
111
+ }));
112
+ }
113
+ // Determine git initialization
114
+ const gitInit = cliOptions.noGit
115
+ ? false
116
+ : cliOptions.yes
117
+ ? true
118
+ : await confirm({
119
+ message: 'Initialize a git repository?',
120
+ initialValue: true,
121
+ });
122
+ outro(colors.cyan('Setup complete.'));
123
+ // Fetch example from GitHub
67
124
  try {
68
- execSync('git init', { cwd: dir, stdio: 'pipe' });
125
+ const example = cliOptions.example || getDefaultExample(cliOptions);
126
+ await fetchExample({
127
+ example,
128
+ examplePath: cliOptions.examplePath,
129
+ targetDir: projectDir,
130
+ });
131
+ // Create .env file with token
132
+ await fs.writeFile(`${projectDir}/.env`, `DISCORD_TOKEN="${token || ''}"`);
133
+ // Install packages for official examples
134
+ if (isOfficialExample(example) && !cliOptions.skipInstall) {
135
+ console.log(colors.cyan('Installing dependencies for official example...'));
136
+ try {
137
+ const tagMap = [
138
+ ['-dev.', 'dev'],
139
+ ['-rc.', 'next'],
140
+ ];
141
+ const tag = await readFile(join(import.meta.dirname, '..', 'package.json'), 'utf-8')
142
+ .then((data) => {
143
+ const version = JSON.parse(data).version;
144
+ return (tagMap.find(([suffix]) => version.includes(suffix))?.[1] ||
145
+ 'latest');
146
+ })
147
+ .catch(() => 'latest');
148
+ // Install dependencies
149
+ const depsCommand = getInstallCommand(manager, [
150
+ `commandkit@${tag}`,
151
+ 'discord.js',
152
+ ]);
153
+ execSync(depsCommand, { cwd: projectDir, stdio: 'pipe' });
154
+ // Install dev dependencies
155
+ const devDepsCommand = getInstallCommand(manager, ['typescript', '@types/node'], true);
156
+ execSync(devDepsCommand, { cwd: projectDir, stdio: 'pipe' });
157
+ console.log(colors.green('Dependencies installed successfully!'));
158
+ }
159
+ catch (error) {
160
+ console.log(colors.yellow('Warning: Failed to install dependencies. You may need to install them manually.'));
161
+ }
162
+ }
69
163
  }
70
164
  catch (error) {
71
- console.log(colors.yellow('Warning: Git initialization failed. Make sure Git is installed on your system.'));
165
+ console.error(colors.red(`Error fetching example: ${error instanceof Error ? error.message : 'Unknown error'}`));
166
+ process.exit(1);
72
167
  }
73
- }
74
- installDeps({
75
- dir,
76
- manager,
77
- lang,
78
- stdio: 'pipe',
79
- });
80
- const command = (cmd) => {
81
- switch (manager) {
82
- case 'npm':
83
- // bun build runs bundler instead of the build script
84
- case 'bun':
85
- return `${manager} run ${cmd}`;
86
- case 'pnpm':
87
- case 'yarn':
88
- return `${manager} ${cmd}`;
89
- case 'deno':
90
- return `deno task ${cmd}`;
91
- default:
92
- return manager;
168
+ // Initialize git if requested
169
+ if (gitInit) {
170
+ try {
171
+ execSync('git init', { cwd: projectDir, stdio: 'pipe' });
172
+ }
173
+ catch (error) {
174
+ console.log(colors.yellow('Warning: Git initialization failed. Make sure Git is installed on your system.'));
175
+ }
93
176
  }
94
- };
95
- console.log(`${gradient(textColors.commandkit)('Thank you for choosing CommandKit!')}
177
+ const command = (cmd) => {
178
+ switch (manager) {
179
+ case 'npm':
180
+ // bun build runs bundler instead of the build script
181
+ case 'bun':
182
+ return `${manager} run ${cmd}`;
183
+ case 'pnpm':
184
+ case 'yarn':
185
+ return `${manager} ${cmd}`;
186
+ case 'deno':
187
+ return `deno task ${cmd}`;
188
+ default:
189
+ return manager;
190
+ }
191
+ };
192
+ console.log(`${gradient(textColors.commandkit)('Thank you for choosing CommandKit!')}
96
193
 
97
- To start your bot, use the following commands:
194
+ To start your bot${projectDir !== '.' ? `, ${colors.magenta(`cd ${projectDir}`)}` : ''}${projectDir !== '.' ? ' and' : ''} use the following commands:
98
195
  ${colors.magenta(command('dev'))} - Run your bot in development mode
99
196
  ${colors.magenta(command('build'))} - Build your bot for production
100
197
  ${colors.magenta(command('start'))} - Run your bot in production mode
@@ -105,3 +202,8 @@ To start your bot, use the following commands:
105
202
  • Discord community: ${colors.blue('https://ctrl.lol/discord')}
106
203
 
107
204
  Happy coding! 🚀`);
205
+ }
206
+ main().catch((error) => {
207
+ console.error(colors.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
208
+ process.exit(1);
209
+ });
package/dist/types.d.ts CHANGED
@@ -1,2 +1,16 @@
1
- export type Language = 'js' | 'ts';
2
1
  export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno';
2
+ export interface CLIOptions {
3
+ help?: boolean;
4
+ example?: string;
5
+ examplePath?: string;
6
+ useNpm?: boolean;
7
+ usePnpm?: boolean;
8
+ useYarn?: boolean;
9
+ useBun?: boolean;
10
+ useDeno?: boolean;
11
+ skipInstall?: boolean;
12
+ noGit?: boolean;
13
+ yes?: boolean;
14
+ listExamples?: boolean;
15
+ projectDirectory?: string;
16
+ }
package/dist/utils.d.ts CHANGED
@@ -1,13 +1,25 @@
1
- import type { PackageManager } from './types';
1
+ import type { CLIOptions, PackageManager } from './types';
2
2
  export declare const textColors: {
3
3
  commandkit: string[];
4
4
  js: string[];
5
5
  ts: string[];
6
6
  };
7
- export declare const commands: {
8
- init: {
9
- yarn: string;
10
- };
11
- };
12
7
  export declare function detectPackageManager(): PackageManager;
13
- export declare function getCommandKitVersion(): string;
8
+ export declare function getPackageManagerFromCLI(options: {
9
+ useNpm?: boolean;
10
+ usePnpm?: boolean;
11
+ useYarn?: boolean;
12
+ useBun?: boolean;
13
+ useDeno?: boolean;
14
+ }): PackageManager | null;
15
+ export declare function resolvePackageManager(cliOptions: {
16
+ useNpm?: boolean;
17
+ usePnpm?: boolean;
18
+ useYarn?: boolean;
19
+ useBun?: boolean;
20
+ useDeno?: boolean;
21
+ }): PackageManager;
22
+ export declare function getDefaultExample(cliOptions: CLIOptions): string;
23
+ export declare function isOfficialExample(example: string): boolean;
24
+ export declare function getInstallCommand(manager: PackageManager, deps: string[], dev?: boolean): string;
25
+ export declare function fetchAvailableExamples(): Promise<string[]>;
package/dist/utils.js CHANGED
@@ -1,16 +1,8 @@
1
- import fs from 'fs-extra';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
1
  export const textColors = {
5
2
  commandkit: ['#fdba74', '#e4a5a2', '#c288de', '#b27bf9'],
6
3
  js: ['#f7e01c', '#f7e01c'],
7
4
  ts: ['#2480c5', '#2480c5'],
8
5
  };
9
- export const commands = {
10
- init: {
11
- yarn: 'yarn set version stable; yarn config set nodeLinker node-modules;',
12
- },
13
- };
14
6
  export function detectPackageManager() {
15
7
  const packageManager = process.env.npm_config_user_agent;
16
8
  if (packageManager?.includes('pnpm'))
@@ -25,25 +17,92 @@ export function detectPackageManager() {
25
17
  return 'deno';
26
18
  return 'npm';
27
19
  }
28
- export function getCommandKitVersion() {
29
- try {
30
- const __filename = fileURLToPath(import.meta.url);
31
- const __dirname = path.dirname(__filename);
32
- const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
33
- const packageJson = fs.readJsonSync(packageJsonPath);
34
- const currentVersion = packageJson.version;
35
- if (currentVersion.includes('dev.')) {
36
- return '@dev';
20
+ export function getPackageManagerFromCLI(options) {
21
+ if (options.useNpm)
22
+ return 'npm';
23
+ if (options.usePnpm)
24
+ return 'pnpm';
25
+ if (options.useYarn)
26
+ return 'yarn';
27
+ if (options.useBun)
28
+ return 'bun';
29
+ if (options.useDeno)
30
+ return 'deno';
31
+ return null;
32
+ }
33
+ export function resolvePackageManager(cliOptions) {
34
+ const cliManager = getPackageManagerFromCLI(cliOptions);
35
+ return cliManager || detectPackageManager();
36
+ }
37
+ export function getDefaultExample(cliOptions) {
38
+ if (cliOptions.useDeno) {
39
+ return 'deno-ts';
40
+ }
41
+ return 'basic-ts';
42
+ }
43
+ export function isOfficialExample(example) {
44
+ // Check if it's a GitHub URL pointing to underctrl-io/commandkit
45
+ if (example.startsWith('http://') || example.startsWith('https://')) {
46
+ try {
47
+ const url = new URL(example);
48
+ return (url.hostname === 'github.com' &&
49
+ url.pathname.startsWith('/underctrl-io/commandkit'));
37
50
  }
38
- else if (currentVersion.includes('rc.')) {
39
- return '@next';
51
+ catch {
52
+ return false;
40
53
  }
41
- else {
42
- return '@latest';
54
+ }
55
+ // If it's just an example name, it's official
56
+ return true;
57
+ }
58
+ export function getInstallCommand(manager, deps, dev = false) {
59
+ switch (manager) {
60
+ case 'npm':
61
+ case 'pnpm':
62
+ case 'yarn':
63
+ case 'bun':
64
+ return `${manager} add ${dev ? '-D' : ''} ${deps.join(' ')}`;
65
+ case 'deno':
66
+ return `deno add ${dev ? '--dev' : ''} ${deps.map((d) => `npm:${d}`).join(' ')}`;
67
+ default:
68
+ return manager;
69
+ }
70
+ }
71
+ export async function fetchAvailableExamples() {
72
+ let controller = null;
73
+ let timeoutId = null;
74
+ try {
75
+ controller = new AbortController();
76
+ timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
77
+ const response = await fetch('https://api.github.com/repos/underctrl-io/commandkit/contents/examples', {
78
+ signal: controller.signal,
79
+ headers: {
80
+ 'User-Agent': 'create-commandkit',
81
+ },
82
+ });
83
+ if (timeoutId) {
84
+ clearTimeout(timeoutId);
85
+ timeoutId = null;
86
+ }
87
+ if (!response.ok) {
88
+ throw new Error(`GitHub API error: ${response.status}`);
43
89
  }
90
+ const data = (await response.json());
91
+ // Filter for directories only and return their names
92
+ return data
93
+ .filter((item) => item.type === 'dir')
94
+ .map((item) => item.name)
95
+ .sort();
44
96
  }
45
97
  catch (error) {
46
- console.warn('Could not determine create-commandkit version, defaulting to commandkit@latest');
47
- return '@latest';
98
+ // Clean up on error
99
+ if (timeoutId) {
100
+ clearTimeout(timeoutId);
101
+ }
102
+ if (controller) {
103
+ controller.abort();
104
+ }
105
+ // Fallback to few known examples if API fails
106
+ return ['basic-ts', 'basic-js', 'deno-ts', 'without-cli'];
48
107
  }
49
108
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-commandkit",
3
3
  "description": "Effortlessly create a CommandKit project",
4
- "version": "1.2.0-dev.20251019020542",
4
+ "version": "1.2.0-dev.20251019062347",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
7
7
  "bin": "./dist/index.js",
@@ -18,8 +18,7 @@
18
18
  "commands"
19
19
  ],
20
20
  "files": [
21
- "dist",
22
- "templates"
21
+ "dist"
23
22
  ],
24
23
  "repository": {
25
24
  "type": "git",
@@ -29,21 +28,26 @@
29
28
  "homepage": "https://commandkit.dev",
30
29
  "dependencies": {
31
30
  "@clack/prompts": "^0.11.0",
31
+ "commander": "^14.0.1",
32
32
  "fs-extra": "^11.1.1",
33
33
  "gradient-string": "^3.0.0",
34
34
  "ora": "^8.0.1",
35
- "picocolors": "^1.1.1"
35
+ "picocolors": "^1.1.1",
36
+ "tiged": "^2.12.7",
37
+ "validate-npm-package-name": "^6.0.2"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@types/fs-extra": "^11.0.4",
39
41
  "@types/gradient-string": "^1.1.5",
40
42
  "@types/node": "^22.0.0",
43
+ "@types/validate-npm-package-name": "^4.0.2",
44
+ "tsx": "^4.20.6",
41
45
  "typescript": "^5.8.3",
42
- "tsconfig": "0.0.0-dev.20251019020542"
46
+ "tsconfig": "0.0.0-dev.20251019062347"
43
47
  },
44
48
  "scripts": {
45
49
  "check-types": "tsc --noEmit",
46
50
  "dev": "tsc --watch",
47
- "build": "tsc"
51
+ "build": "tsc && tsx scripts/sync-available-examples.ts"
48
52
  }
49
53
  }
@@ -1,5 +0,0 @@
1
- import type { Language } from '../types';
2
- export declare function copyTemplates({ dir, lang, }: {
3
- lang: Language;
4
- dir: string;
5
- }): Promise<void>;
@@ -1,47 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'node:path';
3
- const templates = {
4
- js: path.join(import.meta.dirname, '..', '..', 'templates', 'JavaScript'),
5
- ts: path.join(import.meta.dirname, '..', '..', 'templates', 'TypeScript'),
6
- };
7
- const gitignore = `
8
- # dependencies
9
- node_modules
10
-
11
- # build output
12
- build
13
- out
14
- dist
15
-
16
- # commandkit
17
- .commandkit
18
- dist
19
- compiled-commandkit.config.mjs
20
-
21
- # env
22
- **/*.env*
23
- !**/*.env.example*
24
-
25
- # logging
26
- logs
27
- *.log
28
- npm-debug.log*
29
- yarn-debug.log*
30
- yarn-error.log*
31
- lerna-debug.log*
32
- .pnpm-debug.log*
33
-
34
- # yarn v2+
35
- .yarn/cache
36
- .yarn/unplugged
37
- .yarn/build-state.yml
38
- .yarn/install-state.gz
39
- .pnp.*
40
-
41
- # other
42
- **/*.DS_Store
43
- `;
44
- export async function copyTemplates({ dir, lang, }) {
45
- await fs.copy(templates[lang], dir);
46
- await fs.writeFile(path.join(dir, '.gitignore'), gitignore);
47
- }
@@ -1,10 +0,0 @@
1
- import { type IOType } from 'node:child_process';
2
- import type { Language, PackageManager } from '../types';
3
- interface InstallDepsProps {
4
- manager: PackageManager;
5
- dir: string;
6
- lang: Language;
7
- stdio: IOType;
8
- }
9
- export declare function installDeps({ manager, dir, lang, stdio, }: InstallDepsProps): void;
10
- export {};
@@ -1,49 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import ora from 'ora';
3
- import { getCommandKitVersion } from '../utils.js';
4
- const getBaseDependencies = () => [
5
- `commandkit${getCommandKitVersion()}`,
6
- 'discord.js',
7
- ];
8
- const getDependencies = () => ({
9
- js: {
10
- dependencies: getBaseDependencies(),
11
- devDependencies: ['@types/node', 'typescript', 'prettier'],
12
- },
13
- ts: {
14
- dependencies: getBaseDependencies(),
15
- devDependencies: ['@types/node', 'typescript', 'prettier'],
16
- },
17
- });
18
- function getInstallCommand(manager, deps, dev = false) {
19
- switch (manager) {
20
- case 'npm':
21
- case 'pnpm':
22
- case 'yarn':
23
- case 'bun':
24
- return `${manager} add ${dev ? '-D' : ''} ${deps.join(' ')}`;
25
- case 'deno':
26
- return `deno add ${dev ? '--dev' : ''} ${deps.map((d) => `npm:${d}`).join(' ')}`;
27
- default:
28
- return manager;
29
- }
30
- }
31
- export function installDeps({ manager, dir, lang, stdio = 'inherit', }) {
32
- const spinner = ora('Installing dependencies...').start();
33
- try {
34
- const dependencies = getDependencies();
35
- if (dependencies[lang].dependencies.length) {
36
- const depsCommand = getInstallCommand(manager, dependencies[lang].dependencies);
37
- execSync(depsCommand, { cwd: dir, stdio });
38
- }
39
- if (dependencies[lang].devDependencies.length) {
40
- const devDepsCommand = getInstallCommand(manager, dependencies[lang].devDependencies, true);
41
- execSync(devDepsCommand, { cwd: dir, stdio });
42
- }
43
- spinner.succeed('Dependencies installed successfully!');
44
- }
45
- catch (error) {
46
- spinner.fail('Failed to install dependencies');
47
- throw error;
48
- }
49
- }
@@ -1,10 +0,0 @@
1
- import { type IOType } from 'child_process';
2
- import type { PackageManager } from '../types';
3
- interface SetupProps {
4
- manager: PackageManager;
5
- token: string;
6
- dir: string;
7
- stdio?: IOType;
8
- }
9
- export declare function setup({ manager, token, dir, stdio, }: SetupProps): Promise<void>;
10
- export {};
@@ -1,68 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import fs from 'fs-extra';
3
- import path from 'node:path';
4
- import { commands } from '../utils.js';
5
- export async function setup({ manager, token, dir, stdio = 'pipe', }) {
6
- await fs.emptyDir(dir);
7
- if (manager === 'yarn') {
8
- execSync(commands.init.yarn, { cwd: dir, stdio });
9
- }
10
- if (manager === 'deno') {
11
- const denoJsonPath = path.join(dir, 'deno.json');
12
- const denoJson = {
13
- compilerOptions: {
14
- jsx: 'react-jsx',
15
- jsxImportSource: 'commandkit',
16
- },
17
- nodeModulesDir: 'auto',
18
- lock: true,
19
- lint: {
20
- include: ['src/'],
21
- exclude: ['node_modules/', 'dist/', '.commandkit/'],
22
- },
23
- fmt: {
24
- useTabs: false,
25
- lineWidth: 120,
26
- indentWidth: 2,
27
- endOfLine: 'lf',
28
- semiColons: true,
29
- singleQuote: true,
30
- include: ['src/'],
31
- exclude: ['node_modules/', 'dist/', '.commandkit/'],
32
- },
33
- };
34
- await fs.writeJSON(denoJsonPath, denoJson, { spaces: 2, EOL: '\n' });
35
- }
36
- const prettierrc = path.join(dir, '.prettierrc');
37
- const prettierConfig = {
38
- printWidth: 120,
39
- tabWidth: 2,
40
- useTabs: false,
41
- semi: true,
42
- endOfLine: 'lf',
43
- singleQuote: true,
44
- trailingComma: 'all',
45
- arrowParens: 'always',
46
- };
47
- await fs.writeJSON(prettierrc, prettierConfig, { spaces: 2, EOL: '\n' });
48
- const packageJsonPath = path.join(dir, 'package.json');
49
- const packageJson = {
50
- name: dir.replaceAll('\\', '/').split('/').pop()?.toLowerCase() ||
51
- 'commandkit-project',
52
- description: 'A CommandKit project',
53
- version: '0.1.0',
54
- type: 'module',
55
- private: true,
56
- main: 'dist/index.js',
57
- scripts: {
58
- dev: 'commandkit dev',
59
- build: 'commandkit build',
60
- start: 'commandkit start',
61
- format: 'prettier --write "src/**/*.{js,ts}"',
62
- },
63
- devDependencies: {},
64
- dependencies: {},
65
- };
66
- await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
67
- await fs.writeFile(`${dir}/.env`, `DISCORD_TOKEN="${token || ''}"`);
68
- }
@@ -1,16 +0,0 @@
1
- # Welcome to CommandKit
2
-
3
- > This project was generated by [create-commandkit](https://npmjs.com/package/create-commandkit).
4
-
5
- Thanks for choosing CommandKit to build your Discord bot!
6
-
7
- ## To run this project
8
-
9
- ```
10
- npx commandkit dev
11
- ```
12
-
13
- ## Useful links
14
-
15
- - [Documentation](https://commandkit.dev)
16
- - [Discord](https://ctrl.lol/discord)
@@ -1 +0,0 @@
1
- /// <reference path="node_modules/commandkit-types/index.d.ts" />
@@ -1,3 +0,0 @@
1
- import { defineConfig } from 'commandkit/config';
2
-
3
- export default defineConfig({});
@@ -1,30 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "lib": ["ESNext", "DOM"],
5
- "target": "ESNext",
6
- "moduleResolution": "Node",
7
- "module": "Preserve",
8
- "allowImportingTsExtensions": true,
9
- "esModuleInterop": true,
10
- "resolveJsonModule": true,
11
- "skipLibCheck": true,
12
- "skipDefaultLibCheck": true,
13
- "noUncheckedIndexedAccess": true,
14
- "removeComments": true,
15
- "allowJs": true,
16
- "checkJs": false,
17
- "strict": true,
18
- "alwaysStrict": true,
19
- "noEmit": true,
20
- "declaration": false,
21
- "jsx": "react-jsx",
22
- "jsxImportSource": "commandkit",
23
- "baseUrl": ".",
24
- "paths": {
25
- "@/*": ["./src/*"]
26
- }
27
- },
28
- "include": ["src", "commandkit.config.mjs", "commandkit-env.d.ts"],
29
- "exclude": ["dist", "node_modules", ".commandkit"]
30
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * @type {import('commandkit').CommandData}
3
- */
4
- export const command = {
5
- name: 'ping',
6
- description: "Ping the bot to check if it's online.",
7
- };
8
-
9
- /**
10
- * @param {import('commandkit').ChatInputCommandContext} ctx
11
- */
12
- export const chatInput = async (ctx) => {
13
- const latency = (ctx.client.ws.ping ?? -1).toString();
14
- const response = `Pong! Latency: ${latency}ms`;
15
-
16
- await ctx.interaction.reply(response);
17
- };
18
-
19
- /**
20
- * @param {import('commandkit').MessageCommandContext} ctx
21
- */
22
- export const message = async (ctx) => {
23
- const latency = (ctx.client.ws.ping ?? -1).toString();
24
- const response = `Pong! Latency: ${latency}ms`;
25
-
26
- await ctx.message.reply(response);
27
- };
@@ -1,10 +0,0 @@
1
- import { Logger } from 'commandkit/logger';
2
-
3
- /**
4
- * @type {import('commandkit').EventHandler<'clientReady'>}
5
- */
6
- const handler = async (client) => {
7
- Logger.info(`Logged in as ${client.user.username}!`);
8
- };
9
-
10
- export default handler;
@@ -1,7 +0,0 @@
1
- import { Client } from 'discord.js';
2
-
3
- const client = new Client({
4
- intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent'],
5
- });
6
-
7
- export default client;
@@ -1,16 +0,0 @@
1
- # Welcome to CommandKit
2
-
3
- > This project was generated by [create-commandkit](https://npmjs.com/package/create-commandkit).
4
-
5
- Thanks for choosing CommandKit to build your Discord bot!
6
-
7
- ## To run this project
8
-
9
- ```
10
- npx commandkit dev
11
- ```
12
-
13
- ## Useful links
14
-
15
- - [Documentation](https://commandkit.dev)
16
- - [Discord](https://ctrl.lol/discord)
@@ -1 +0,0 @@
1
- /// <reference path="node_modules/commandkit-types/index.d.ts" />
@@ -1,3 +0,0 @@
1
- import { defineConfig } from 'commandkit/config';
2
-
3
- export default defineConfig({});
@@ -1,20 +0,0 @@
1
- import type { ChatInputCommand, MessageCommand, CommandData } from 'commandkit';
2
-
3
- export const command: CommandData = {
4
- name: 'ping',
5
- description: "Ping the bot to check if it's online.",
6
- };
7
-
8
- export const chatInput: ChatInputCommand = async (ctx) => {
9
- const latency = (ctx.client.ws.ping ?? -1).toString();
10
- const response = `Pong! Latency: ${latency}ms`;
11
-
12
- await ctx.interaction.reply(response);
13
- };
14
-
15
- export const message: MessageCommand = async (ctx) => {
16
- const latency = (ctx.client.ws.ping ?? -1).toString();
17
- const response = `Pong! Latency: ${latency}ms`;
18
-
19
- await ctx.message.reply(response);
20
- };
@@ -1,8 +0,0 @@
1
- import type { EventHandler } from 'commandkit';
2
- import { Logger } from 'commandkit/logger';
3
-
4
- const handler: EventHandler<'clientReady'> = async (client) => {
5
- Logger.info(`Logged in as ${client.user.username}!`);
6
- };
7
-
8
- export default handler;
@@ -1,7 +0,0 @@
1
- import { Client } from 'discord.js';
2
-
3
- const client = new Client({
4
- intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent'],
5
- });
6
-
7
- export default client;
@@ -1,29 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "lib": ["ESNext", "DOM"],
5
- "target": "ESNext",
6
- "moduleResolution": "Node",
7
- "module": "Preserve",
8
- "allowImportingTsExtensions": true,
9
- "esModuleInterop": true,
10
- "resolveJsonModule": true,
11
- "skipLibCheck": true,
12
- "skipDefaultLibCheck": true,
13
- "noUncheckedIndexedAccess": true,
14
- "removeComments": true,
15
- "allowJs": true,
16
- "strict": true,
17
- "alwaysStrict": true,
18
- "noEmit": true,
19
- "declaration": false,
20
- "jsx": "react-jsx",
21
- "jsxImportSource": "commandkit",
22
- "baseUrl": ".",
23
- "paths": {
24
- "@/*": ["./src/*"]
25
- }
26
- },
27
- "include": ["src", "commandkit.config.ts", "commandkit-env.d.ts"],
28
- "exclude": ["dist", "node_modules", ".commandkit"]
29
- }