0

A stateless file system commands allows you to mutate the file system even when a command is run in the current directory.

One advantage is that is does not corrupt the system as the scripts are abstracted away. One implementation is the use of decorator.

const { spawn } = require('child_process');

// Define the wrapper function
function wrapCommand(command, directoryState) {
  return function(args) {
    // Merge the current directory state with any new state passed in
    const newState = Object.assign({}, directoryState, args.state);

    // Create a new object to use as the WeakMap key
    const keyObject = { cwd: process.cwd() };

    // Create a new WeakMap to store the directory state for this command
    const stateMap = new WeakMap();
    stateMap.set(keyObject, newState);

    // Spawn the child process with the new directory state
    const childProcess = spawn(command, args.args, {
      stdio: 'pipe',
      env: Object.assign({}, process.env, { directoryState: stateMap }),
      cwd: process.cwd()
    });

    // Listen for the child process to exit and update the directory state
    childProcess.on('exit', (code, signal) => {
      directoryState = stateMap.get(keyObject);
    });

    // Log the output of the child process to the console
    childProcess.stdout.pipe(process.stdout);
  };
}

// Define the initial directory state
let directoryState = {};

// Wrap the 'ls' command
const ls = wrapCommand('ls', directoryState);

// Call the 'ls' command with some initial state
ls({ state: { foo: 'bar' } })

// Call the 'ls' command with some additional state
ls({ state: { baz: 'qux' } });


The following documented code wraps a command,but throws an internal error:

node:events:491
      throw er; // Unhandled 'error' event
      ^

Error: spawn ls ENOENT
    at ChildProcess._handle.onexit (node:internal/child_process:283:19)
    at onErrorNT (node:internal/child_process:476:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
Emitted 'error' event on ChildProcess instance at:
    at ChildProcess._handle.onexit (node:internal/child_process:289:12)
    at onErrorNT (node:internal/child_process:476:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'spawn ls',
  path: 'ls',
  spawnargs: []
}

Since this is an internal error,and not knowing how to debug using **node --inspect ** ,i have decieded to post it here.

I am new to debug my code.

nnamdi
  • 1
  • 1
  • `ls` is a Linux command, are you running this on windows? – Keith Mar 28 '23 at 19:17
  • Yes,it is a linux command,but I am using windows Powershell 7 which has ls as an alias – nnamdi Mar 28 '23 at 19:45
  • Well the error is telling you `Error: spawn ls ENOENT`. The ls command is not found. You might need to set the `shell` option on the `spawn` to use powershell. Eg. https://stackoverflow.com/questions/10179114/execute-powershell-script-from-node-js – Keith Mar 28 '23 at 21:06
  • I have tried the code on wsl(Windows Subsystem for linux) It works like a charm.The problem now is to get it working on windows powershell.I will rewrite it to work on powershell as well. – nnamdi Mar 28 '23 at 21:37

1 Answers1

0

Here is a sample implementation that is both compatable on windows ,and linux systems.

const { spawn } = require('child_process');

function wrapCommand(command, directoryState, debug) {
    return function (args) {
        const newState = Object.assign({}, directoryState, args.state);
        const keyObject = { cwd: process.cwd() };
        const stateMap = new WeakMap();
        stateMap.set(keyObject, newState);

        let childProcess;
        if (process.platform === 'win32') {
            // On Windows, use "powershell.exe" instead of a command
            childProcess = spawn('powershell.exe', ['-Command', command, ...args.args], {
                stdio: 'pipe',
                env: Object.assign({}, process.env, { directoryState: stateMap }),
                cwd: process.cwd()
            });
        } else {
            // On Linux and other platforms, spawn a shell with the command
            childProcess = spawn('sh', ['-c', `${command} ${args.args.join(' ')}`], {
                stdio: 'pipe',
                env: Object.assign({}, process.env, { directoryState: stateMap }),
                cwd: process.cwd()
            });
        }

        childProcess.on('exit', (code, signal) => {
            directoryState = stateMap.get(keyObject);
        });
        if (debug) {
            const changes = [];
            for (const [key, value] of Object.entries(args.state)) {
                const oldValue = directoryState[key];
                changes.push(`${key}: ${oldValue} -> ${value}`);
            }
            console.log(`Changes to directory state: ${changes.join(', ')}`);

            childProcess.stdout.pipe(process.stdout);
        }

    };
}

let directoryState = {};

const ls = wrapCommand('ls', directoryState); // Use'ls' on Linux and 'Get-ChildItem' on Windows

// Call the 'ls' command with some initial state
ls({ args: ['-al'], state: { foo: 'bar' } })
// Call the 'ls' command with some additional state
ls({ args: ['-l'], state: { baz: 'qux' } });

const gci = wrapCommand('gci', directoryState);
gci({ args: [], state: { foo: 'baz' } });
nnamdi
  • 1
  • 1