( engine: MacroChoiceEngine, macroChoice: IMacroChoice, memberAccess: string[], )
| 202 | } |
| 203 | |
| 204 | private async tryExecuteExport( |
| 205 | engine: MacroChoiceEngine, |
| 206 | macroChoice: IMacroChoice, |
| 207 | memberAccess: string[], |
| 208 | ): Promise<{ executed: boolean; result?: unknown }> { |
| 209 | const originalCommands = macroChoice.macro?.commands; |
| 210 | if (!originalCommands?.length) { |
| 211 | return { executed: false }; |
| 212 | } |
| 213 | |
| 214 | const userScriptCommands = originalCommands |
| 215 | .map((command, index) => ({ command, index })) |
| 216 | .filter( |
| 217 | (entry): entry is { command: IUserScript; index: number } => |
| 218 | entry.command.type === CommandType.UserScript, |
| 219 | ); |
| 220 | |
| 221 | if (userScriptCommands.length === 0) { |
| 222 | return { executed: false }; |
| 223 | } |
| 224 | |
| 225 | const selection = await this.selectUserScriptCandidate( |
| 226 | macroChoice, |
| 227 | userScriptCommands, |
| 228 | memberAccess, |
| 229 | ); |
| 230 | const candidateId = selection.candidate.command.id; |
| 231 | const preCommands = originalCommands.slice(0, selection.candidate.index); |
| 232 | |
| 233 | try { |
| 234 | if (preCommands.length) { |
| 235 | await engine.runSubset(preCommands); |
| 236 | this.ensureNotAborted(); |
| 237 | } |
| 238 | |
| 239 | const updatedCommands = macroChoice.macro?.commands ?? originalCommands; |
| 240 | // Pre-commands may have mutated the commands array, so re-resolve the selected |
| 241 | // command by its stable id. Both the candidate and the post-command slice are |
| 242 | // derived from this refreshed index so they cannot drift apart. The original |
| 243 | // positional index is only a fallback for legacy commands that genuinely have |
| 244 | // no id; if the command HAD an id but it is now gone (removed mid-run), leave |
| 245 | // the index unresolved so the guard below aborts rather than silently routing |
| 246 | // to a neighbouring script. |
| 247 | let refreshedIndex: number; |
| 248 | if (candidateId !== undefined) { |
| 249 | refreshedIndex = updatedCommands.findIndex( |
| 250 | (command) => |
| 251 | command.id === candidateId && |
| 252 | command.type === CommandType.UserScript, |
| 253 | ); |
| 254 | } else { |
| 255 | refreshedIndex = selection.candidate.index; |
| 256 | } |
| 257 | const refreshedCandidate = |
| 258 | refreshedIndex >= 0 ? updatedCommands[refreshedIndex] : undefined; |
| 259 | if (!refreshedCandidate || refreshedCandidate.type !== CommandType.UserScript) { |
| 260 | throw new MacroAbortError( |
| 261 | `Could not resolve the member-access script for '${macroChoice.name}'.`, |
no test coverage detected