From b5eb961a6c07c04a61fd86768dcc06f7881acca9 Mon Sep 17 00:00:00 2001 From: twlite <46562212+twlite@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:10:01 +0545 Subject: [PATCH] feat(cli): expose compiler options --- .../command-kit-compiler-options.mdx | 54 ++++++++ .../interfaces/command-kit-config.mdx | 41 ++++-- .../types/resolved-command-kit-config.mdx | 7 +- packages/commandkit/src/cli/build.ts | 123 ++++++++++-------- packages/commandkit/src/config/config.ts | 25 +++- packages/commandkit/src/config/default.ts | 7 + packages/commandkit/src/config/types.ts | 57 ++++++-- packages/commandkit/src/config/utils.ts | 30 ++++- 8 files changed, 253 insertions(+), 91 deletions(-) create mode 100644 apps/website/docs/api-reference/commandkit/interfaces/command-kit-compiler-options.mdx diff --git a/apps/website/docs/api-reference/commandkit/interfaces/command-kit-compiler-options.mdx b/apps/website/docs/api-reference/commandkit/interfaces/command-kit-compiler-options.mdx new file mode 100644 index 00000000..eecc428b --- /dev/null +++ b/apps/website/docs/api-reference/commandkit/interfaces/command-kit-compiler-options.mdx @@ -0,0 +1,54 @@ +--- +title: "CommandKitCompilerOptions" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + + + +## CommandKitCompilerOptions + + + + + +```ts title="Signature" +interface CommandKitCompilerOptions { + macro?: { + /** + * Whether to enable macro function compilation in development mode. + * @default false + */ + development?: boolean; + }; + tsdown?: Partial; + disableChunking?: boolean; +} +``` + +
+ +### macro + + + +The macro compiler options to use with CommandKit. +### tsdown + + + +The tsdown compiler options to use with CommandKit. +**DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING** as it alters the behavior of the build process. +### disableChunking + + + +Disables chunking of the output (production only, development never chunks). + + +
diff --git a/apps/website/docs/api-reference/commandkit/interfaces/command-kit-config.mdx b/apps/website/docs/api-reference/commandkit/interfaces/command-kit-config.mdx index f151ab8f..f83db735 100644 --- a/apps/website/docs/api-reference/commandkit/interfaces/command-kit-config.mdx +++ b/apps/website/docs/api-reference/commandkit/interfaces/command-kit-config.mdx @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## CommandKitConfig - + @@ -21,18 +21,8 @@ import MemberDescription from '@site/src/components/MemberDescription'; interface CommandKitConfig { plugins?: MaybeArray[] | Array; rolldownPlugins?: any[]; - compilerOptions?: { - /** - * Macro compiler configuration. - */ - macro?: { - /** - * Whether to enable macro function compilation in development mode. - * @default false - */ - development?: boolean; - }; - }; + entrypoints?: string[]; + compilerOptions?: CommandKitCompilerOptions; typescript?: { /** * Whether to ignore type checking during builds. @@ -42,6 +32,18 @@ interface CommandKitConfig { static?: boolean; env?: Record; distDir?: string; + antiCrashScript?: { + /** + * Whether to enable the anti-crash script in development mode. + * @default true + */ + development?: boolean; + /** + * Whether to enable the anti-crash script in production mode. Usually, you should use other means to handle errors. + * @default false + */ + production?: boolean; + }; sourceMap?: { /** * Whether to enable source map generation in development mode. @@ -71,9 +73,15 @@ Can be a single plugin, an array of plugins, or a nested array of plugins. The rolldown plugins to use with CommandKit. +### entrypoints + + + +The list of additional entrypoints to compile. Eg, `dir` or `dir/index.ts` or `dir/*.ts`, etc. +Similarly, negative patterns can be used to exclude files. Eg, `!dir/index.ts` or `!dir/*.ts`, etc. ### compilerOptions - +CommandKitCompilerOptions`} /> The compiler options to use with CommandKit. ### typescript @@ -96,6 +104,11 @@ Statically define the environment variables to use. The custom build directory name to use. +### antiCrashScript + + + +The anti-crash script configuration. ### sourceMap diff --git a/apps/website/docs/api-reference/commandkit/types/resolved-command-kit-config.mdx b/apps/website/docs/api-reference/commandkit/types/resolved-command-kit-config.mdx index d6dd48a7..4cb91dfe 100644 --- a/apps/website/docs/api-reference/commandkit/types/resolved-command-kit-config.mdx +++ b/apps/website/docs/api-reference/commandkit/types/resolved-command-kit-config.mdx @@ -13,10 +13,13 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## ResolvedCommandKitConfig - + ```ts title="Signature" -type ResolvedCommandKitConfig = DeepRequired +type ResolvedCommandKitConfig = DeepRequired< + CommandKitConfig, + 'compilerOptions' +> ``` diff --git a/packages/commandkit/src/cli/build.ts b/packages/commandkit/src/cli/build.ts index ebb6bf48..de82d07e 100644 --- a/packages/commandkit/src/cli/build.ts +++ b/packages/commandkit/src/cli/build.ts @@ -1,4 +1,4 @@ -import { build } from 'tsdown'; +import { build, Options } from 'tsdown'; import { CompilerPlugin, CompilerPluginRuntime } from '../plugins'; import { loadConfigFile } from '../config/loader'; import { writeFile } from 'node:fs/promises'; @@ -9,6 +9,8 @@ import { performTypeCheck } from './type-checker'; import { copyLocaleFiles } from './common'; import { MaybeArray } from '../components'; import { COMMANDKIT_CWD } from '../utils/constants'; +import { mergeDeep } from '../config/utils'; +import { existsSync } from 'node:fs'; /** * @private @@ -84,60 +86,70 @@ export async function buildApplication({ await pluginRuntime.init(); - await build({ - watch: false, - dts: false, - clean: true, - format: ['esm'], - shims: true, - minify: false, - silent: !!isDev, - inputOptions: { - transform: { - jsx: { - runtime: 'automatic', - importSource: 'commandkit', + await build( + mergeDeep( + { + watch: false, + dts: false, + clean: true, + format: ['esm'], + shims: true, + minify: false, + silent: !!isDev, + inputOptions: { + transform: { + jsx: { + runtime: 'automatic', + importSource: 'commandkit', + }, + }, + checks: { + circularDependency: true, + }, + onwarn: (warning, defaultWarn) => { + if (warning?.message?.includes('compilerOptions.jsx')) return; + + return defaultWarn(warning); + }, + onLog: (level, log, defaultLog) => { + if (isDev) return; + + return defaultLog(level, log); + }, + moduleTypes: { + '.json': 'js', + '.node': 'binary', + }, }, - }, - checks: { - circularDependency: true, - }, - onwarn: (warning, defaultWarn) => { - if (warning?.message?.includes('compilerOptions.jsx')) return; - - return defaultWarn(warning); - }, - onLog: (level, log, defaultLog) => { - if (isDev) return; - - return defaultLog(level, log); - }, - moduleTypes: { - '.json': 'js', - '.node': 'binary', - }, - }, - plugins: rolldownPlugins, - platform: 'node', - skipNodeModulesBundle: true, - sourcemap: true, - target: 'node16', - outDir: dest, - env: mergeDefinitionsIfNeeded(config.env || {}, !!isDev), - entry: [ - 'src', - `!${config.distDir}`, - '!.commandkit', - '!**/*.test.*', - '!**/*.spec.*', - ], - unbundle: !!isDev, - }); + plugins: rolldownPlugins, + platform: 'node', + skipNodeModulesBundle: true, + sourcemap: + config.sourceMap?.[isDev ? 'development' : 'production'] ?? true, + target: 'node16', + outDir: dest, + env: mergeDefinitionsIfNeeded(config.env || {}, !!isDev), + entry: [ + 'src', + `!${config.distDir}`, + '!.commandkit', + '!**/*.test.*', + '!**/*.spec.*', + ], + unbundle: !!isDev, + } satisfies Options, + config.compilerOptions?.tsdown, + ), + ); await copyLocaleFiles('src', dest); await injectEntryFile( configPath || COMMANDKIT_CWD, !!isDev, + !!( + config.antiCrashScript?.[isDev ? 'development' : 'production'] ?? + (isDev ? true : false) + ), config.distDir, ); } catch (error) { @@ -185,10 +197,17 @@ const wrapInAsyncIIFE = (code: string[]) => async function injectEntryFile( configPath: string, isDev: boolean, + emitAntiCrashScript: boolean, distDir?: string, ) { + const dist = isDev ? '.commandkit' : distDir || 'dist'; + const entryFilePath = join(configPath, dist, 'index.js'); + + // skip if the entry file already exists + if (existsSync(entryFilePath)) return; + const code = `/* Entrypoint File Generated By CommandKit */ -${isDev ? `\n\n// Injected for development\n${wrapInAsyncIIFE([envScript(isDev), antiCrashScript])}\n\n` : wrapInAsyncIIFE([envScript(isDev)])} +${isDev ? `\n\n// Injected for development\n${wrapInAsyncIIFE([envScript(isDev), emitAntiCrashScript ? antiCrashScript : ''])}\n\n` : wrapInAsyncIIFE([envScript(isDev)])} import { commandkit } from 'commandkit'; import { Client } from 'discord.js'; @@ -210,7 +229,5 @@ await bootstrap().catch((e) => { }) `; - const dist = isDev ? '.commandkit' : distDir || 'dist'; - - await writeFile(join(configPath, dist, 'index.js'), code); + await writeFile(entryFilePath, code); } diff --git a/packages/commandkit/src/config/config.ts b/packages/commandkit/src/config/config.ts index 659c2b13..a3937a99 100644 --- a/packages/commandkit/src/config/config.ts +++ b/packages/commandkit/src/config/config.ts @@ -2,7 +2,7 @@ import { MaybeArray } from '../components'; import { CommandKitPlugin } from '../plugins'; import { defaultConfig } from './default'; import { CommandKitConfig } from './types'; -import { ResolvedCommandKitConfig } from './utils'; +import { mergeDeep, ResolvedCommandKitConfig } from './utils'; let defined: ResolvedCommandKitConfig = defaultConfig; @@ -20,10 +20,10 @@ export function getConfig(): ResolvedCommandKitConfig { export function defineConfig( config: Partial = {}, ): ResolvedCommandKitConfig { - // defined = mergeDeep( - // config as ResolvedCommandKitConfig, - // mergeDeep({} as ResolvedCommandKitConfig, defaultConfig), - // ); + defined = mergeDeep( + config as ResolvedCommandKitConfig, + mergeDeep({} as ResolvedCommandKitConfig, defaultConfig), + ); defined = { compilerOptions: { @@ -31,7 +31,18 @@ export function defineConfig( ...defaultConfig.compilerOptions.macro, ...config.compilerOptions?.macro, }, + tsdown: { + ...defaultConfig.compilerOptions.tsdown, + ...config.compilerOptions?.tsdown, + }, + disableChunking: + config.compilerOptions?.disableChunking ?? + defaultConfig.compilerOptions.disableChunking, }, + entrypoints: [ + ...(config.entrypoints ?? []), + ...(defaultConfig.entrypoints ?? []), + ], distDir: config.distDir ?? defaultConfig.distDir, env: { ...defaultConfig.env, @@ -59,6 +70,10 @@ export function defineConfig( showUnknownPrefixCommandsWarning: config.showUnknownPrefixCommandsWarning ?? defaultConfig.showUnknownPrefixCommandsWarning, + antiCrashScript: { + ...defaultConfig.antiCrashScript, + ...config.antiCrashScript, + }, }; return defined; diff --git a/packages/commandkit/src/config/default.ts b/packages/commandkit/src/config/default.ts index 40b888cd..a4b4ff3c 100644 --- a/packages/commandkit/src/config/default.ts +++ b/packages/commandkit/src/config/default.ts @@ -12,7 +12,10 @@ export const defaultConfig: ResolvedCommandKitConfig = { macro: { development: false, }, + tsdown: {}, + disableChunking: false, }, + entrypoints: [], static: true, typescript: { ignoreBuildErrors: false, @@ -26,4 +29,8 @@ export const defaultConfig: ResolvedCommandKitConfig = { typedCommands: true, disablePrefixCommands: false, showUnknownPrefixCommandsWarning: true, + antiCrashScript: { + development: true, + production: false, + }, }; diff --git a/packages/commandkit/src/config/types.ts b/packages/commandkit/src/config/types.ts index 0662adfe..156da75e 100644 --- a/packages/commandkit/src/config/types.ts +++ b/packages/commandkit/src/config/types.ts @@ -1,5 +1,29 @@ import { MaybeArray } from '../components'; import { CommandKitPlugin } from '../plugins'; +import type { Options as TsDownOptions } from 'tsdown'; + +export interface CommandKitCompilerOptions { + /** + * The macro compiler options to use with CommandKit. + */ + macro?: { + /** + * Whether to enable macro function compilation in development mode. + * @default false + */ + development?: boolean; + }; + /** + * The tsdown compiler options to use with CommandKit. + * **DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING** as it alters the behavior of the build process. + */ + tsdown?: Partial; + /** + * Disables chunking of the output (production only, development never chunks). + * @default false + */ + disableChunking?: boolean; +} export interface CommandKitConfig { /** @@ -11,21 +35,15 @@ export interface CommandKitConfig { * The rolldown plugins to use with CommandKit. */ rolldownPlugins?: any[]; + /** + * The list of additional entrypoints to compile. Eg, `dir` or `dir/index.ts` or `dir/*.ts`, etc. + * Similarly, negative patterns can be used to exclude files. Eg, `!dir/index.ts` or `!dir/*.ts`, etc. + */ + entrypoints?: string[]; /** * The compiler options to use with CommandKit. */ - compilerOptions?: { - /** - * Macro compiler configuration. - */ - macro?: { - /** - * Whether to enable macro function compilation in development mode. - * @default false - */ - development?: boolean; - }; - }; + compilerOptions?: CommandKitCompilerOptions; /** * The typescript configuration to use with CommandKit. */ @@ -48,6 +66,21 @@ export interface CommandKitConfig { * @default `dist` */ distDir?: string; + /** + * The anti-crash script configuration. + */ + antiCrashScript?: { + /** + * Whether to enable the anti-crash script in development mode. + * @default true + */ + development?: boolean; + /** + * Whether to enable the anti-crash script in production mode. Usually, you should use other means to handle errors. + * @default false + */ + production?: boolean; + }; /** * Whether or not to enable the source map generation. */ diff --git a/packages/commandkit/src/config/utils.ts b/packages/commandkit/src/config/utils.ts index a8e8cf0d..459d7276 100644 --- a/packages/commandkit/src/config/utils.ts +++ b/packages/commandkit/src/config/utils.ts @@ -3,8 +3,8 @@ import { CommandKitConfig } from './types'; /** * @private */ -export type DeepRequired = { - [P in keyof T]-?: DeepRequired; +export type DeepRequired = { + [P in keyof T]-?: P extends OptionalKeys ? Partial : DeepRequired; }; /** @@ -16,14 +16,31 @@ export type DeepPartial = { /** * @private + * @example + * ```ts + * const target = { a: 1, b: { c: 2 } }; + * const source = { b: { d: 3 } }; + * const result = mergeDeep(target, source); + * // result = { a: 1, b: { c: 2, d: 3 } } + * ``` */ export const mergeDeep = >( target: T, - source: T, + source?: T, + tracker = new WeakMap(), ): T => { + if (!source) return target; const isObject = (obj: unknown) => obj && typeof obj === 'object' && !Array.isArray(obj); + // Prevent infinite recursion + if (tracker.has(target)) { + return target; + } + + // Mark the target as visited + tracker.set(target, true); + const output: T = { ...target }; if (isObject(target) && isObject(source)) { Object.keys(source).forEach((key) => { @@ -31,7 +48,7 @@ export const mergeDeep = >( if (!(key in target)) { Object.assign(output, { [key]: source[key] }); } else { - output[key as keyof T] = mergeDeep(target[key], source[key]); + output[key as keyof T] = mergeDeep(target[key], source[key], tracker); } } else { Object.assign(output, { [key]: source[key] }); @@ -42,4 +59,7 @@ export const mergeDeep = >( return output as T; }; -export type ResolvedCommandKitConfig = DeepRequired; +export type ResolvedCommandKitConfig = DeepRequired< + CommandKitConfig, + 'compilerOptions' +>;