Skip to content

Fix child process spawning on Windows - #189

Open
franbarbalopez wants to merge 1 commit into
laravel:mainfrom
franbarbalopez:fix/windows-child-process-spawning
Open

Fix child process spawning on Windows#189
franbarbalopez wants to merge 1 commit into
laravel:mainfrom
franbarbalopez:fix/windows-child-process-spawning

Conversation

@franbarbalopez

Copy link
Copy Markdown

Summary

Use cross-spawn for synchronous and asynchronous child processes invoked by the orchestrator scripts.

This fixes command resolution on Windows for executables provided through .bat and .cmd wrappers, including Composer, npm, and npx.

Problem

The orchestrator used Node.js child_process.spawn() and spawnSync() directly:

spawn('composer', ['setup']);

On Windows, tools such as Composer, npm, and npx are commonly exposed through wrapper scripts:

  • composer.bat
  • npm.cmd
  • npx.cmd

These scripts cannot be executed directly by modern Node.js versions without a command shell. As a result, running:

composer kit:run

failed while attempting to execute the generated kit setup:

Running composer setup...
spawn composer ENOENT
Script node scripts/run.js handling the kit:run event returned with error code 1

Composer was installed and available on PATH, but Node.js could not execute its Windows wrapper using the default spawn() behavior.

Using shell: true made the command work, but it also produced the following deprecation warning:

[DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.

Therefore, enabling the shell was not an appropriate permanent solution.

Solution

Add cross-spawn as a development dependency and use it for both asynchronous and synchronous process execution:

import { spawn, sync as spawnSync } from 'cross-spawn';

cross-spawn handles platform-specific executable resolution, including Windows .bat and .cmd wrappers, while preserving the existing spawn() API and avoiding shell: true.

This fixes command execution for:

  • Composer
  • npm
  • npx
  • Other commands exposed through Windows command wrappers

Verification

The change was verified on Windows with Node.js 26 by executing Composer through both APIs:

  • spawn('composer', ['--version'])
  • spawnSync('composer', ['--version'])

Both commands completed successfully without ENOENT, EINVAL, or the DEP0190 warning.

Alternatives considered

Another possible approach would be to detect Windows and resolve each command to its platform-specific wrapper:

const composerCommand = process.platform === 'win32'
    ? 'composer.bat'
    : 'composer';

const npmCommand = process.platform === 'win32'
    ? 'npm.cmd'
    : 'npm';

spawn(composerCommand, ['setup']);
spawn(npmCommand, ['install']);

However, this approach has several drawbacks:

  • The correct extension must be known for every command.
  • Composer commonly uses .bat, while npm and npx commonly use .cmd.
  • It requires platform-specific handling at every call site.
  • Modern Node.js versions may reject direct .bat and .cmd execution with EINVAL unless the command is launched through cmd.exe.
  • Using shell: true to execute these wrappers triggers the DEP0190 warning when arguments are passed separately.
  • Manually invoking cmd.exe would require carefully implementing Windows command-line quoting and escaping.

For example, a complete Windows-specific implementation would need to resemble:

const command = process.platform === 'win32'
    ? process.env.ComSpec ?? 'cmd.exe'
    : 'composer';

const args = process.platform === 'win32'
    ? ['/d', '/s', '/c', 'composer.bat', 'setup']
    : ['setup'];

spawn(command, args);

This still requires special handling for argument escaping and shell metacharacters.

Using cross-spawn centralizes this platform-specific behavior, preserves the existing spawn(command, args, options) API, and avoids maintaining a custom command resolution and escaping implementation.

@franbarbalopez

Copy link
Copy Markdown
Author

Can't test this on Mac fyi @WendellAdriel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant