Skip to content

Commit 8366588

Browse files
committed
fix: cli issues
1 parent f5e3b5d commit 8366588

File tree

19 files changed

+413
-273
lines changed

19 files changed

+413
-273
lines changed

apps/test-bot/src/commands/misc/random.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const random = cache(
99
async () => {
1010
return Math.random();
1111
},
12-
{ tag: 'random', ttl: 60_000 },
12+
{ name: 'random', ttl: 60_000 },
1313
);
1414

1515
export async function run({ interaction }: SlashCommandProps) {

apps/test-bot/src/commands/misc/xp.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@ async function getUserXP(guildId: string, userId: string) {
1010
'use cache';
1111

1212
const key = `xp:${guildId}:${userId}`;
13-
14-
cacheTag(key);
13+
console.log('[getUserXP] Before database.get', {
14+
key,
15+
timestamp: Date.now(),
16+
});
1517

1618
const xp: number = (await database.get(key)) ?? 0;
1719

18-
console.log(`Cached XP: ${xp} for ${key}`);
20+
console.log('[getUserXP] After database.get', {
21+
key,
22+
xp,
23+
timestamp: Date.now(),
24+
});
25+
26+
cacheTag(key);
27+
28+
console.log(`Cached XP: ${xp} for ${key} = ${xp}`);
1929

2030
return xp;
2131
}

apps/test-bot/src/database/store.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
import { setTimeout } from 'node:timers/promises';
22

33
// Simulate a random latency between 30ms to 1.5s
4-
const randomLatency = () => setTimeout(Math.floor(Math.random() * 1500) + 30);
4+
const randomLatency = () => setTimeout(Math.floor(Math.random() * 1500) + 100);
55

66
class DataStore {
77
private store = new Map<string, any>();
88

99
async get(key: string) {
1010
await randomLatency();
11-
return this.store.get(key);
11+
const value = this.store.get(key);
12+
13+
return value;
1214
}
1315

1416
async set(key: string, value: any) {
15-
await randomLatency();
1617
this.store.set(key, value);
1718
}
1819

1920
async delete(key: string) {
20-
await randomLatency();
2121
this.store.delete(key);
2222
}
2323

2424
async clear() {
25-
await randomLatency();
2625
this.store.clear();
2726
}
2827
}

apps/test-bot/src/events/messageCreate/give-xp.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ export default async function (message: Message) {
66
if (message.author.bot || !message.inGuild()) return;
77

88
const key = `xp:${message.guildId}:${message.author.id}`;
9+
910
const oldXp = (await database.get(key)) ?? 0;
1011
const xp = Math.floor(Math.random() * 10) + 1;
12+
const newXp = oldXp + xp;
1113

12-
await database.set(key, oldXp + xp);
13-
14-
// invalidate the cache
14+
await database.set(key, newXp);
1515
await invalidate(key);
1616
}

apps/test-bot/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CommandKit } from 'commandkit';
22
import { Client } from 'discord.js';
3+
34
process.loadEnvFile();
45

56
const client = new Client({

packages/commandkit/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,4 @@
6262
"engines": {
6363
"node": ">=22"
6464
}
65-
}
65+
}

packages/commandkit/spec/cache.test.ts

-12
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,6 @@ describe('Cache', () => {
179179
expect(await fn()).not.toBe(result1);
180180
});
181181

182-
test('Should throw when invalidating non-existent cache', async () => {
183-
await expect(invalidate('non-existent')).rejects.toThrow(
184-
'Cache key non-existent was not found',
185-
);
186-
});
187-
188182
test('Should revalidate cache using tag', async () => {
189183
async function fn(multiplier: number) {
190184
'use cache';
@@ -200,12 +194,6 @@ describe('Cache', () => {
200194
expect(await fn(2)).toBe(fresh);
201195
});
202196

203-
test('Should throw when revalidating non-existent cache', async () => {
204-
await expect(revalidate('non-existent')).rejects.toThrow(
205-
'Cache key non-existent was not found',
206-
);
207-
});
208-
209197
test('Should cache with multiple arguments', async () => {
210198
const fn = cache(async (a: number, b: string) => {
211199
return Math.random() + a + b;

packages/commandkit/src/app/commands/Context.ts

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Interaction,
88
UserContextMenuCommandInteraction,
99
Client,
10+
Awaitable,
1011
} from 'discord.js';
1112
import { CommandKit } from '../../CommandKit';
1213
import { Localization } from '../i18n/Localization';
@@ -85,6 +86,18 @@ export type CommandContextOptions<T extends CommandExecutionMode> =
8586
? UserContextMenuCommandInteraction['options']
8687
: never;
8788

89+
export type AnyCommandExecute<ContextType extends Context = Context> = (
90+
ctx: ContextType,
91+
) => Awaitable<unknown>;
92+
93+
export type SlashCommand = AnyCommandExecute<SlashCommandContext>;
94+
export type AutocompleteCommand = AnyCommandExecute<AutocompleteCommandContext>;
95+
export type MessageContextMenuCommand =
96+
AnyCommandExecute<MessageContextMenuCommandContext>;
97+
export type UserContextMenuCommand =
98+
AnyCommandExecute<UserContextMenuCommandContext>;
99+
export type MessageCommand = AnyCommandExecute<MessageCommandContext>;
100+
88101
export class Context<
89102
ExecutionMode extends CommandExecutionMode = CommandExecutionMode,
90103
> {

packages/commandkit/src/cache/cache.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ function getCacheProvider() {
7979
* Internal cache implementation
8080
* @internal
8181
* @private
82+
* This is used directly by the compiler if the function is annotated with `"use cache"` directive.
83+
* @example
84+
* ```ts
85+
* async function myCachedFunction() {
86+
* "use cache";
87+
* // can use cacheTag() and cacheLife() here to customize cache behavior
88+
* return await db.query('SELECT * FROM users');
89+
* }
90+
* ```
8291
*/
8392
function useCache<R extends any[], F extends AsyncFunction<R>>(
8493
fn: F,
@@ -125,26 +134,29 @@ function useCache<R extends any[], F extends AsyncFunction<R>>(
125134

126135
// Try to get cached value using effective key
127136
const cached = await provider.get(effectiveKey);
128-
if (cached) return cached.value;
137+
if (cached && cached.value != null) return cached.value;
129138

130139
// If we reach here, we need to cache the value
131140
const result = await fn(...args);
132141

133-
// Get the final key name (might have been modified by cacheTag)
134-
const finalKey = context.params.name!;
135-
const ttl = context.params.ttl ?? DEFAULT_TTL;
136-
137-
// Store the result
138-
await provider.set(finalKey, result, ttl);
139-
140-
// Update function store
141-
fnStore.set(keyHash, {
142-
key: finalKey,
143-
hash: keyHash,
144-
ttl,
145-
original: fn,
146-
memo,
147-
});
142+
// Only cache if result is not null/undefined
143+
if (result != null) {
144+
// Get the final key name (might have been modified by cacheTag)
145+
const finalKey = context.params.name!;
146+
const ttl = context.params.ttl ?? DEFAULT_TTL;
147+
148+
// Store the result
149+
await provider.set(finalKey, result, ttl);
150+
151+
// Update function store
152+
fnStore.set(keyHash, {
153+
key: finalKey,
154+
hash: keyHash,
155+
ttl,
156+
original: fn,
157+
memo,
158+
});
159+
}
148160

149161
return result;
150162
},
@@ -252,10 +264,6 @@ export async function invalidate(tag: string): Promise<void> {
252264
(v) => v.key === tag || v.hash === tag,
253265
);
254266

255-
// if (!entry) {
256-
// throw new Error(`Cache key ${tag} was not found.`);
257-
// }
258-
259267
if (!entry) return;
260268

261269
await provider.delete(entry.key);
@@ -282,10 +290,6 @@ export async function revalidate<T = unknown>(
282290
(v) => v.key === tag || v.hash === tag,
283291
);
284292

285-
// if (!entry) {
286-
// throw new Error(`Cache key ${tag} was not found.`);
287-
// }
288-
289293
if (!entry) return undefined as T;
290294

291295
await provider.delete(entry.key);

packages/commandkit/src/cli/build.ts

+61-24
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,55 @@ import {
1010
panic,
1111
write,
1212
} from './common.js';
13-
import ora from 'ora';
1413
import { commandkitPlugin } from './esbuild-plugins/plugin';
1514
import colors from '../utils/colors.js';
15+
import { createSpinner } from './utils';
16+
import { BuildOptions } from './types';
1617

17-
export async function bootstrapProductionBuild(config: any) {
18+
export async function bootstrapProductionBuild(configPath: string) {
19+
const config = await findCommandKitConfig(configPath);
20+
const spinner = createSpinner('Creating optimized production build...');
21+
const start = performance.now();
22+
23+
try {
24+
await buildProject(config);
25+
spinner.succeed(
26+
colors.green(
27+
`Build completed in ${(performance.now() - start).toFixed(2)}ms!`,
28+
),
29+
);
30+
} catch (e) {
31+
spinner.fail('Build failed');
32+
panic(e instanceof Error ? e.stack : e);
33+
}
34+
}
35+
36+
export async function bootstrapDevelopmentBuild(configPath: string) {
37+
const config = await findCommandKitConfig(configPath);
38+
39+
try {
40+
await buildProject({
41+
...config,
42+
outDir: '.commandkit',
43+
isDevelopment: true,
44+
});
45+
} catch (e) {
46+
console.error(e instanceof Error ? e.stack : e);
47+
console.error(
48+
colors.red('Failed to build the project. Waiting for changes...'),
49+
);
50+
}
51+
}
52+
53+
async function buildProject(options: BuildOptions) {
1854
const {
1955
sourcemap = false,
2056
minify = false,
2157
outDir = 'dist',
2258
antiCrash = true,
23-
src,
2459
main,
2560
requirePolyfill: polyfillRequire,
26-
} = await findCommandKitConfig(config);
27-
28-
const status = ora('Creating optimized production build...\n').start();
29-
const start = performance.now();
61+
} = options;
3062

3163
erase(outDir);
3264

@@ -38,32 +70,40 @@ export async function bootstrapProductionBuild(config: any) {
3870
skipNodeModulesBundle: true,
3971
minify,
4072
shims: true,
41-
banner: {
42-
js: '/* Optimized production build generated by CommandKit */',
43-
},
73+
banner: options.isDevelopment
74+
? {}
75+
: {
76+
js: '/* Optimized production build generated by CommandKit */',
77+
},
4478
sourcemap,
4579
keepNames: true,
4680
outDir,
4781
silent: true,
48-
watch: false,
82+
watch: !!options.isDevelopment && !!options.watch,
4983
cjsInterop: true,
50-
splitting: false,
51-
entry: [src, '!dist', '!.commandkit', `!${outDir}`],
52-
esbuildPlugins: [commandkitPlugin()],
84+
splitting: true,
85+
entry: ['src', '!dist', '!.commandkit', `!${outDir}`],
86+
esbuildPlugins: [
87+
commandkitPlugin({
88+
'jsx-importsource': false,
89+
'use-cache': true,
90+
'use-macro': !!options.isDevelopment,
91+
}),
92+
],
5393
jsxFactory: 'CommandKit.createElement',
5494
jsxFragment: 'CommandKit.Fragment',
5595
async onSuccess() {
56-
await copyLocaleFiles(src, '.commandkit');
96+
await copyLocaleFiles('src', outDir);
5797
},
5898
});
5999

60-
await injectShims(outDir, main, antiCrash, polyfillRequire);
61-
62-
status.succeed(
63-
colors.green(
64-
`Build completed in ${(performance.now() - start).toFixed(2)}ms!`,
65-
),
100+
await injectShims(
101+
outDir,
102+
main,
103+
!options.isDevelopment && antiCrash,
104+
!!polyfillRequire,
66105
);
106+
67107
write(
68108
colors.green(
69109
`\nRun ${colors.magenta(`commandkit start`)} ${colors.green(
@@ -72,9 +112,6 @@ export async function bootstrapProductionBuild(config: any) {
72112
),
73113
);
74114
} catch (e) {
75-
status.fail(
76-
`Build failed after ${(performance.now() - start).toFixed(2)}ms!`,
77-
);
78115
panic(e);
79116
}
80117
}

packages/commandkit/src/cli/common.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export async function copyLocaleFiles(_from: string, _to: string) {
130130
const resolvedFrom = join(process.cwd(), _from);
131131
const resolvedTo = join(process.cwd(), _to);
132132

133-
const localePaths = ['app/locales', 'src/app/locales'];
133+
const localePaths = ['app/locales'];
134134
const srcLocalePaths = localePaths.map((path) => join(resolvedFrom, path));
135135
const destLocalePaths = localePaths.map((path) => join(resolvedTo, path));
136136

0 commit comments

Comments
 (0)