Skip to content

Commit 60531b8

Browse files
committed
fix: types package issue on fresh build
1 parent efab96d commit 60531b8

File tree

7 files changed

+152
-7
lines changed

7 files changed

+152
-7
lines changed

apps/test-bot/src/app.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Client } from 'discord.js';
2+
import { Logger, onApplicationBootstrap } from 'commandkit';
23

34
const client = new Client({
45
intents: [
@@ -10,4 +11,16 @@ const client = new Client({
1011
],
1112
});
1213

14+
onApplicationBootstrap((commandkit) => {
15+
Logger.log('Application bootstrapped successfully!');
16+
commandkit.setPrefixResolver((message) => {
17+
return [
18+
`<@${message.client.user.id}>`,
19+
`<@!${message.client.user.id}>`,
20+
'!',
21+
'?',
22+
];
23+
});
24+
});
25+
1326
export default client;

apps/test-bot/src/app/commands/(developer)/server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
export const command: CommandData = {
99
name: 'server',
1010
description: 'server command',
11-
guilds: [process.env.DEV_GUILD_ID],
11+
guilds: [process.env.DEV_GUILD_ID!],
1212
};
1313

1414
export const chatInput: SlashCommand = async (ctx) => {

packages/commandkit/src/CommandKit.ts

+78
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { CommandKitEventsChannel } from './events/CommandKitEventsChannel';
2020
import { isRuntimePlugin } from './plugins';
2121
import { generateTypesPackage } from './utils/types-package';
2222
import { Logger } from './logger/Logger';
23+
import { GenericFunction } from './context/async-context';
24+
import { AsyncFunction } from './cache';
2325

2426
export interface CommandKitConfiguration {
2527
defaultLocale: Locale;
@@ -29,6 +31,49 @@ export interface CommandKitConfiguration {
2931
// @ts-ignore
3032
export let commandkit: CommandKit;
3133

34+
export type BootstrapFunction =
35+
| GenericFunction<[CommandKit]>
36+
| AsyncFunction<[CommandKit]>;
37+
38+
const bootstrapHooks = new Set<BootstrapFunction>();
39+
const onApplicationBootstrapHooks = new Set<BootstrapFunction>();
40+
41+
/**
42+
* Registers a bootstrap hook that will be called when the CommandKit instance is created.
43+
* This is useful for plugins that need to run some code after the CommandKit instance is fully initialized.
44+
* Note that not all commandkit dependiencs are available at this point. It is recommended to use the `onApplicationBootstrap` hook instead,
45+
* if you need access to the commandkit dependencies.
46+
* @param fn The bootstrap function to register.
47+
* @example ```ts
48+
* import { onBootstrap } from 'commandkit';
49+
*
50+
* onBootstrap(async (commandkit) => {
51+
* // Do something with the commandkit instance
52+
* })
53+
* ```
54+
*/
55+
export function onBootstrap<F extends BootstrapFunction>(fn: F): void {
56+
bootstrapHooks.add(fn);
57+
}
58+
59+
/**
60+
* Registers a bootstrap hook that will be called when the CommandKit instance is created.
61+
* This is useful for plugins that need to run some code after the CommandKit instance is fully initialized.
62+
* @param fn The bootstrap function to register.
63+
* @example ```ts
64+
* import { onApplicationBootstrap } from 'commandkit';
65+
*
66+
* onApplicationBootstrap(async (commandkit) => {
67+
* // Do something with the commandkit instance
68+
* })
69+
* ```
70+
*/
71+
export function onApplicationBootstrap<F extends BootstrapFunction>(
72+
fn: F,
73+
): void {
74+
onApplicationBootstrapHooks.add(fn);
75+
}
76+
3277
export class CommandKit extends EventEmitter {
3378
#started = false;
3479
public eventInterceptor!: EventInterceptor;
@@ -90,6 +135,37 @@ export class CommandKit extends EventEmitter {
90135

91136
// @ts-ignore
92137
commandkit = CommandKit.instance;
138+
139+
this.#bootstrapHooks();
140+
}
141+
142+
async #bootstrapHooks() {
143+
for (const hook of bootstrapHooks) {
144+
try {
145+
await hook(this);
146+
} catch (e) {
147+
Logger.error('Error while executing bootstrap hook: ', e);
148+
} finally {
149+
bootstrapHooks.delete(hook);
150+
}
151+
}
152+
153+
// force clear just in case we missed something
154+
bootstrapHooks.clear();
155+
}
156+
157+
async #applicationBootstrapHooks() {
158+
for (const hook of onApplicationBootstrapHooks) {
159+
try {
160+
await hook(this);
161+
} catch (e) {
162+
Logger.error('Error while executing application bootstrap hook: ', e);
163+
} finally {
164+
onApplicationBootstrapHooks.delete(hook);
165+
}
166+
}
167+
// force clear just in case we missed something
168+
onApplicationBootstrapHooks.clear();
93169
}
94170

95171
/**
@@ -151,6 +227,8 @@ export class CommandKit extends EventEmitter {
151227
}
152228

153229
this.#started = true;
230+
231+
await this.#applicationBootstrapHooks();
154232
}
155233

156234
/**

packages/commandkit/src/cli/common.ts

+53-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fs from 'node:fs';
44
import colors from '../utils/colors';
55
import { ResolvedCommandKitConfig } from '../config/utils';
66
import { generateTypesPackage } from '../utils/types-package';
7+
import { execSync } from 'node:child_process';
78

89
let ts: typeof import('typescript') | undefined;
910

@@ -42,12 +43,63 @@ async function ensureTypeScript(target: string) {
4243
return true;
4344
}
4445

46+
let packageManager: string;
47+
48+
function detectPackageManager() {
49+
if (packageManager) return packageManager;
50+
51+
const lockfiles = {
52+
'yarn.lock': 'yarn',
53+
'pnpm-lock.yaml': 'pnpm',
54+
'package-lock.json': 'npm',
55+
'bun.lock': 'bun',
56+
'bun.lockb': 'bun',
57+
};
58+
59+
for (const [lockfile, manager] of Object.entries(lockfiles)) {
60+
if (fs.existsSync(join(process.cwd(), lockfile))) {
61+
packageManager = manager;
62+
break;
63+
}
64+
}
65+
66+
if (!packageManager) {
67+
packageManager = 'npm';
68+
}
69+
70+
return packageManager;
71+
}
72+
4573
export async function loadTypeScript(e?: string) {
4674
if (ts) return ts;
4775

4876
try {
4977
ts = await import('typescript');
50-
} catch {
78+
} catch (e) {
79+
if (e instanceof Error && 'code' in e && e.code === 'MODULE_NOT_FOUND') {
80+
try {
81+
const packageManager = detectPackageManager();
82+
83+
execSync(`${packageManager} add typescript`, {
84+
stdio: 'inherit',
85+
cwd: process.cwd(),
86+
});
87+
88+
console.log(
89+
colors.cyan(
90+
`TypeScript has been installed automatically, restarting...`,
91+
),
92+
);
93+
94+
ts = await import('typescript');
95+
96+
return ts;
97+
} catch {
98+
panic(
99+
'TypeScript is not installed and could not be installed automatically. Please install it manually.',
100+
);
101+
}
102+
}
51103
panic(e || 'TypeScript must be installed to use TypeScript config files.');
52104
}
53105

packages/commandkit/src/cli/init.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync } from 'node:fs';
22
import { mkdir } from 'node:fs/promises';
33
import { join } from 'node:path';
4+
import { generateTypesPackage } from '../utils/types-package';
45

56
/**
67
* Creates a command line interface for CommandKit.
@@ -84,11 +85,12 @@ export async function bootstrapCommandkitCLI(
8485
}
8586
});
8687

87-
await program.parseAsync(argv, options);
88-
8988
const types = join(process.cwd(), 'node_modules', 'commandkit-types');
9089

9190
if (!existsSync(types)) {
9291
await mkdir(types, { recursive: true }).catch(() => {});
92+
await generateTypesPackage(true).catch(() => {});
9393
}
94+
95+
await program.parseAsync(argv, options);
9496
}

packages/commandkit/src/utils/types-package.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { join } from 'node:path';
33
import { COMMANDKIT_IS_DEV } from './constants';
44
import { existsSync } from 'node:fs';
55

6-
export async function generateTypesPackage() {
6+
export async function generateTypesPackage(force = false) {
77
const location = join(process.cwd(), 'node_modules', 'commandkit-types');
8-
if (!COMMANDKIT_IS_DEV) return location;
8+
if (!COMMANDKIT_IS_DEV && !force) return location;
99
const packageJSON = join(location, 'package.json');
1010
const index = join(location, 'index.js');
1111
const types = join(location, 'index.d.ts');

packages/redis/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type DeserializeFunction = (value: string) => Awaitable<any>;
1818
* });
1919
*/
2020
export class RedisCache extends CacheProvider {
21-
protected redis: Redis;
21+
public redis: Redis;
2222

2323
/**
2424
* Serialize function used to serialize values before storing them in the cache.

0 commit comments

Comments
 (0)