Skip to content

Commit a25d29f

Browse files
committed
feat: force run env finalizer during runCommand phase
1 parent 64e6f6e commit a25d29f

File tree

1 file changed

+174
-166
lines changed

1 file changed

+174
-166
lines changed

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

Lines changed: 174 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -75,90 +75,113 @@ export class AppCommandRunner {
7575
env.variables.set('execHandlerKind', executionMode);
7676
env.variables.set('customHandler', options?.handler ?? null);
7777

78-
const middlewareCtx = new MiddlewareContext(commandkit, {
79-
command: prepared.command,
80-
environment: env,
81-
executionMode,
82-
interaction: !(source instanceof Message)
83-
? (source as ChatInputCommandInteraction)
84-
: (null as never),
85-
message: source instanceof Message ? source : (null as never),
86-
forwarded: false,
87-
customArgs: {
88-
setCommandRunner: (fn: RunCommand) => {
89-
runCommand = fn;
78+
try {
79+
const middlewareCtx = new MiddlewareContext(commandkit, {
80+
command: prepared.command,
81+
environment: env,
82+
executionMode,
83+
interaction: !(source instanceof Message)
84+
? (source as ChatInputCommandInteraction)
85+
: (null as never),
86+
message: source instanceof Message ? source : (null as never),
87+
forwarded: false,
88+
customArgs: {
89+
setCommandRunner: (fn: RunCommand) => {
90+
runCommand = fn;
91+
},
9092
},
91-
},
92-
messageCommandParser: prepared.messageCommandParser,
93-
});
94-
95-
const beforeMiddlewares = prepared.middlewares.filter(
96-
(m) => m.data.beforeExecute,
97-
);
98-
99-
let beforeMiddlewaresStopped = false;
93+
messageCommandParser: prepared.messageCommandParser,
94+
});
10095

101-
// Run middleware before command execution
102-
if (beforeMiddlewares.length) {
103-
await provideContext(env, async () => {
104-
for (const middleware of beforeMiddlewares) {
105-
try {
106-
await middleware.data.beforeExecute(middlewareCtx);
107-
} catch (e) {
108-
if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) {
109-
beforeMiddlewaresStopped = true;
110-
Logger.debug(
111-
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside a beforeExecute function at "${middleware.middleware.relativePath}"`,
112-
);
113-
break; // Stop the middleware loop if `stopMiddlewares()` is called.
114-
}
96+
const beforeMiddlewares = prepared.middlewares.filter(
97+
(m) => m.data.beforeExecute,
98+
);
99+
100+
let beforeMiddlewaresStopped = false;
101+
102+
// Run middleware before command execution
103+
if (beforeMiddlewares.length) {
104+
await provideContext(env, async () => {
105+
for (const middleware of beforeMiddlewares) {
106+
try {
107+
await middleware.data.beforeExecute(middlewareCtx);
108+
} catch (e) {
109+
if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) {
110+
beforeMiddlewaresStopped = true;
111+
Logger.debug(
112+
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside a beforeExecute function at "${middleware.middleware.relativePath}"`,
113+
);
114+
break; // Stop the middleware loop if `stopMiddlewares()` is called.
115+
}
116+
117+
if (
118+
isErrorType(e, [
119+
CommandKitErrorCodes.ForwardedCommand,
120+
CommandKitErrorCodes.InvalidCommandPrefix,
121+
])
122+
) {
123+
continue;
124+
}
115125

116-
if (
117-
isErrorType(e, [
118-
CommandKitErrorCodes.ForwardedCommand,
119-
CommandKitErrorCodes.InvalidCommandPrefix,
120-
])
121-
) {
122-
continue;
126+
throw e;
123127
}
124-
125-
throw e;
126128
}
127-
}
128-
});
129-
}
129+
});
130+
}
130131

131-
let result: any;
132+
let result: any;
132133

133-
let stopMiddlewaresCalledInCmd = false;
134+
let stopMiddlewaresCalledInCmd = false;
134135

135-
// If no `stopMiddlewares()` was called in a `beforeExecute` middleware, try to run the command
136-
if (!beforeMiddlewaresStopped) {
137-
const targetData = prepared.command.data;
138-
const fn = targetData[options?.handler || executionMode];
136+
// If no `stopMiddlewares()` was called in a `beforeExecute` middleware, try to run the command
137+
if (!beforeMiddlewaresStopped) {
138+
const targetData = prepared.command.data;
139+
const fn = targetData[options?.handler || executionMode];
139140

140-
if (!fn) {
141-
Logger.warn(
142-
`Command ${prepared.command.command.name} has no handler for ${executionMode}`,
143-
);
144-
}
141+
if (!fn) {
142+
Logger.warn(
143+
`Command ${prepared.command.command.name} has no handler for ${executionMode}`,
144+
);
145+
}
146+
147+
const analytics = commandkit.analytics;
145148

146-
const analytics = commandkit.analytics;
147-
148-
if (fn) {
149-
try {
150-
const _executeCommand = makeContextAwareFunction(
151-
env,
152-
async () => {
153-
env.registerDeferredFunction(async (env) => {
154-
env.markEnd();
155-
const error = env.getExecutionError();
156-
const marker = env.getMarker();
157-
const time = `${env.getExecutionTime().toFixed(2)}ms`;
158-
159-
if (error) {
160-
Logger.error(
161-
`[${marker} - ${time}] Error executing command: ${error.stack || error}`,
149+
if (fn) {
150+
try {
151+
const _executeCommand = makeContextAwareFunction(
152+
env,
153+
async () => {
154+
env.registerDeferredFunction(async (env) => {
155+
env.markEnd();
156+
const error = env.getExecutionError();
157+
const marker = env.getMarker();
158+
const time = `${env.getExecutionTime().toFixed(2)}ms`;
159+
160+
if (error) {
161+
Logger.error(
162+
`[${marker} - ${time}] Error executing command: ${error.stack || error}`,
163+
);
164+
165+
const commandName =
166+
prepared.command?.data?.command?.name ??
167+
prepared.command.command.name;
168+
169+
await analytics.track({
170+
name: AnalyticsEvents.COMMAND_EXECUTION,
171+
id: commandName,
172+
data: {
173+
error: true,
174+
executionTime: env.getExecutionTime().toFixed(2),
175+
type: executionMode,
176+
command: commandName,
177+
},
178+
});
179+
180+
return;
181+
}
182+
183+
Logger.info(
184+
`[${marker} - ${time}] Command executed successfully`,
162185
);
163186

164187
const commandName =
@@ -169,130 +192,115 @@ export class AppCommandRunner {
169192
name: AnalyticsEvents.COMMAND_EXECUTION,
170193
id: commandName,
171194
data: {
172-
error: true,
195+
error: false,
173196
executionTime: env.getExecutionTime().toFixed(2),
174197
type: executionMode,
175198
command: commandName,
176199
},
177200
});
178-
179-
return;
180-
}
181-
182-
Logger.info(
183-
`[${marker} - ${time}] Command executed successfully`,
184-
);
185-
186-
const commandName =
187-
prepared.command?.data?.command?.name ??
188-
prepared.command.command.name;
189-
190-
await analytics.track({
191-
name: AnalyticsEvents.COMMAND_EXECUTION,
192-
id: commandName,
193-
data: {
194-
error: false,
195-
executionTime: env.getExecutionTime().toFixed(2),
196-
type: executionMode,
197-
command: commandName,
198-
},
199201
});
200-
});
201202

202-
return fn(middlewareCtx.clone());
203-
},
204-
this.#finalizer.bind(this),
205-
);
206-
207-
const executeCommand =
208-
runCommand != null
209-
? (runCommand as RunCommand)(_executeCommand)
210-
: _executeCommand;
211-
212-
env.markStart(prepared.command.data.command.name);
213-
214-
const res = await commandkit.plugins.execute(async (ctx, plugin) => {
215-
return plugin.executeCommand(
216-
ctx,
217-
env,
218-
source,
219-
prepared,
220-
executeCommand,
203+
return fn(middlewareCtx.clone());
204+
},
205+
this.#finalizer.bind(this),
221206
);
222-
});
223207

224-
if (!res) {
225-
result = await executeCommand();
226-
}
227-
} catch (e) {
228-
if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) {
229-
stopMiddlewaresCalledInCmd = true;
230-
Logger.debug(
231-
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called by the command itself`,
208+
const executeCommand =
209+
runCommand != null
210+
? (runCommand as RunCommand)(_executeCommand)
211+
: _executeCommand;
212+
213+
env.markStart(prepared.command.data.command.name);
214+
215+
const res = await commandkit.plugins.execute(
216+
async (ctx, plugin) => {
217+
return plugin.executeCommand(
218+
ctx,
219+
env,
220+
source,
221+
prepared,
222+
executeCommand,
223+
);
224+
},
232225
);
233-
} else if (!isErrorType(e, CommandKitErrorCodes.ForwardedCommand)) {
234-
if (shouldThrowOnError) {
235-
throw e;
236-
}
237-
Logger.error(e);
238-
}
239-
}
240-
}
241-
} else {
242-
result = {
243-
error: true,
244-
message:
245-
'Command execution was cancelled by a beforeExecute middleware.',
246-
};
247-
}
248226

249-
const afterMiddlewares = prepared.middlewares.filter(
250-
(m) => m.data.afterExecute,
251-
);
252-
253-
// Run middleware after command execution only if `stopMiddlewares()` wasn't
254-
// called in either `beforeExecute` middleware or in the command itself.
255-
if (
256-
!beforeMiddlewaresStopped &&
257-
!stopMiddlewaresCalledInCmd &&
258-
afterMiddlewares.length
259-
) {
260-
await provideContext(env, async () => {
261-
for (const middleware of afterMiddlewares) {
262-
try {
263-
await middleware.data.afterExecute(middlewareCtx);
227+
if (!res) {
228+
result = await executeCommand();
229+
}
264230
} catch (e) {
265231
if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) {
232+
stopMiddlewaresCalledInCmd = true;
266233
Logger.debug(
267-
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside an afterExecute function at "${middleware.middleware.relativePath}"`,
234+
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called by the command itself`,
268235
);
269-
break; // Stop the afterExecute middleware loop if `stopMiddlewares()` is called.
236+
} else if (!isErrorType(e, CommandKitErrorCodes.ForwardedCommand)) {
237+
if (shouldThrowOnError) {
238+
throw e;
239+
}
240+
Logger.error(e);
270241
}
271-
throw e;
272242
}
273243
}
274-
});
275-
}
244+
} else {
245+
result = {
246+
error: true,
247+
message:
248+
'Command execution was cancelled by a beforeExecute middleware.',
249+
};
250+
}
276251

277-
return result;
252+
const afterMiddlewares = prepared.middlewares.filter(
253+
(m) => m.data.afterExecute,
254+
);
255+
256+
// Run middleware after command execution only if `stopMiddlewares()` wasn't
257+
// called in either `beforeExecute` middleware or in the command itself.
258+
if (
259+
!beforeMiddlewaresStopped &&
260+
!stopMiddlewaresCalledInCmd &&
261+
afterMiddlewares.length
262+
) {
263+
await provideContext(env, async () => {
264+
for (const middleware of afterMiddlewares) {
265+
try {
266+
await middleware.data.afterExecute(middlewareCtx);
267+
} catch (e) {
268+
if (isErrorType(e, CommandKitErrorCodes.StopMiddlewares)) {
269+
Logger.debug(
270+
`Middleware propagation stopped for command "${middlewareCtx.commandName}". stopMiddlewares() was called inside an afterExecute function at "${middleware.middleware.relativePath}"`,
271+
);
272+
break; // Stop the afterExecute middleware loop if `stopMiddlewares()` is called.
273+
}
274+
throw e;
275+
}
276+
}
277+
});
278+
}
279+
280+
return result;
281+
} finally {
282+
await this.#finalizer(env, false);
283+
}
278284
}
279285

280286
/**
281287
* @private
282288
* @internal
283289
* Finalizes command execution by running deferred functions and plugin cleanup.
284290
*/
285-
async #finalizer() {
286-
const env = useEnvironment();
291+
async #finalizer(env?: CommandKitEnvironment, runPlugins = true) {
292+
env ??= useEnvironment();
287293

288294
await env.runDeferredFunctions();
289295

290296
env.clearAllDeferredFunctions();
291297

292298
// plugins may have their own deferred function, useful for cleanup or post-command analytics
293-
await this.handler.commandkit.plugins.execute(async (ctx, plugin) => {
294-
await plugin.onAfterCommand(ctx, env);
295-
});
299+
if (runPlugins) {
300+
await this.handler.commandkit.plugins.execute(async (ctx, plugin) => {
301+
await plugin.onAfterCommand(ctx, env);
302+
});
303+
}
296304
}
297305

298306
/**

0 commit comments

Comments
 (0)