feat(stepper): add common-stepper and runner-stepper plugin packages#20
feat(stepper): add common-stepper and runner-stepper plugin packages#20Shrey5132 wants to merge 17 commits into
Conversation
|
There was a problem hiding this comment.
Code Review
This pull request establishes a monorepo workspace structure for Source Academy plugins, introducing a language-agnostic Stepper plugin system split into a shared protocol, a runner base class, and a React-based web visualizer. The review feedback focuses on enhancing robustness and error handling, specifically recommending that the spawnPromise helper in the build script handle the child process 'error' event, and that the custom AST renderer in SubstVisualizer.tsx include defensive null/undefined guards for nodes and argument lists to prevent runtime crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export async function spawnPromise(...args: Parameters<typeof spawn>) { | ||
| return new Promise<void>((resolve, reject) => { | ||
| const child = spawn(...args); | ||
| child.on("close", code => { | ||
| if (code === 0) { | ||
| resolve(); | ||
| } else { | ||
| reject(new Error(`Process exited with code ${code}`)); | ||
| } | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The spawnPromise helper does not listen to the 'error' event on the spawned child process. If the executable fails to start (e.g., due to a missing command or system-level issue), Node.js will emit an unhandled 'error' event, causing the entire process to crash immediately instead of rejecting the promise. Adding an 'error' event listener that rejects the promise ensures robust error handling.
export async function spawnPromise(...args: Parameters<typeof spawn>) {
return new Promise<void>((resolve, reject) => {
const child = spawn(...args);
child.on("error", reject);
child.on("close", code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Process exited with code ${code}`));
}
});
});
}| function renderNode(currentNode: StepperNode, renderContext: RenderContext): React.ReactNode { | ||
| const styleWrapper = renderContext.styleWrapper; |
There was a problem hiding this comment.
To make the recursive AST renderer highly robust and defensive against malformed, incomplete, or language-specific AST nodes (e.g., from custom parsers), add a guard at the top of renderNode to safely handle null or undefined nodes. This prevents runtime crashes when rendering optional or missing child nodes.
function renderNode(currentNode: StepperNode | null | undefined, renderContext: RenderContext): React.ReactNode {
if (!currentNode) {
return null;
}
const styleWrapper = renderContext.styleWrapper;
| const renderFunctionArguments = ( | ||
| nodes: StepperNode[], | ||
| renderNodeFn: typeof renderNode, | ||
| styleWrapper: StyleWrapper | undefined, | ||
| popoverDepth: number, | ||
| ) => { | ||
| const args: React.ReactNode[] = nodes.map(arg => |
There was a problem hiding this comment.
If nodes (representing params or arguments) is undefined or null due to an unexpected AST structure, calling nodes.map will throw a TypeError and crash the entire React render tree. Adding a defensive check at the beginning of renderFunctionArguments ensures the component fails gracefully.
const renderFunctionArguments = (
nodes: StepperNode[] | undefined,
renderNodeFn: typeof renderNode,
styleWrapper: StyleWrapper | undefined,
popoverDepth: number,
) => {
if (!nodes) {
return null;
}
const args: React.ReactNode[] = nodes.map(arg =>
| const renderArguments = (nodes: StepperNode[]) => { | ||
| const args: React.ReactNode[] = nodes.map(arg => |
There was a problem hiding this comment.
Similar to renderFunctionArguments, renderArguments should defensively guard against nodes being undefined or null to prevent runtime crashes if a CallExpression or FunctionDeclaration has missing arguments.
| const renderArguments = (nodes: StepperNode[]) => { | |
| const args: React.ReactNode[] = nodes.map(arg => | |
| const renderArguments = (nodes: StepperNode[] | undefined) => { | |
| if (!nodes) { | |
| return "()"; | |
| } | |
| const args: React.ReactNode[] = nodes.map(arg => |
This pull request introduces the
common-stepper,runner-stepper, andweb-stepperpackages to plugins. It forms the core plugin infrastructure needed for the new Conductor-based Stepper framework.